feat: add lock flags and refresh theme

This commit is contained in:
joe 2026-02-25 19:13:34 +09:00
parent 5426751978
commit 145f0d7cad
9 changed files with 217 additions and 164 deletions

View file

@ -22,9 +22,43 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: '販売アシスト1号', title: '販売アシスト1号',
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blueGrey, colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo.shade700).copyWith(
primary: Colors.indigo.shade700,
secondary: Colors.deepOrange.shade400,
surface: Colors.grey.shade50,
onSurface: Colors.blueGrey.shade900,
),
scaffoldBackgroundColor: Colors.grey.shade50,
appBarTheme: AppBarTheme(
backgroundColor: Colors.indigo.shade700,
foregroundColor: Colors.white,
elevation: 0,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
textStyle: const TextStyle(fontWeight: FontWeight.bold),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
side: BorderSide(color: Colors.indigo.shade700),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
textStyle: const TextStyle(fontWeight: FontWeight.bold),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.indigo.shade700, width: 1.4),
),
),
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
useMaterial3: true, useMaterial3: true,
fontFamily: 'IPAexGothic',
), ),
home: const InvoiceHistoryScreen(), home: const InvoiceHistoryScreen(),
); );
@ -54,54 +88,20 @@ class _InvoiceFlowScreenState extends State<InvoiceFlowScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( // Scaffold
appBar: AppBar( return InvoiceInputForm(
leading: const BackButton(), onInvoiceGenerated: (invoice, path) async {
title: const Text("販売アシスト1号 V1.5.02"), // GPSの記録を試みる
backgroundColor: Colors.blueGrey, final locationService = LocationService();
), final position = await locationService.getCurrentLocation();
drawer: Drawer( if (position != null) {
child: ListView( final customerRepo = CustomerRepository();
padding: EdgeInsets.zero, await customerRepo.addGpsHistory(invoice.customer.id, position.latitude, position.longitude);
children: [ debugPrint("GPS recorded for customer ${invoice.customer.id}");
const DrawerHeader( }
decoration: BoxDecoration(color: Colors.blueGrey), _handleInvoiceGenerated(invoice, path);
child: Text("メニュー", style: TextStyle(color: Colors.white, fontSize: 24)), if (widget.onComplete != null) widget.onComplete!();
), },
ListTile(
leading: const Icon(Icons.add_task),
title: const Text("新規伝票作成"),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.history),
title: const Text("伝票履歴"),
onTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const InvoiceHistoryScreen()),
);
},
),
],
),
),
//
body: InvoiceInputForm(
onInvoiceGenerated: (invoice, path) async {
// GPSの記録を試みる
final locationService = LocationService();
final position = await locationService.getCurrentLocation();
if (position != null) {
final customerRepo = CustomerRepository();
await customerRepo.addGpsHistory(invoice.customer.id, position.latitude, position.longitude);
debugPrint("GPS recorded for customer ${invoice.customer.id}");
}
_handleInvoiceGenerated(invoice, path);
if (widget.onComplete != null) widget.onComplete!();
},
),
); );
} }
} }

View file

@ -10,6 +10,7 @@ class Customer {
final String? odooId; // Odoo側のID final String? odooId; // Odoo側のID
final bool isSynced; // final bool isSynced; //
final DateTime updatedAt; // final DateTime updatedAt; //
final bool isLocked; //
Customer({ Customer({
required this.id, required this.id,
@ -22,6 +23,7 @@ class Customer {
this.odooId, this.odooId,
this.isSynced = false, this.isSynced = false,
DateTime? updatedAt, DateTime? updatedAt,
this.isLocked = false,
}) : updatedAt = updatedAt ?? DateTime.now(); }) : updatedAt = updatedAt ?? DateTime.now();
String get invoiceName { String get invoiceName {
@ -42,6 +44,7 @@ class Customer {
'address': address, 'address': address,
'tel': tel, 'tel': tel,
'odoo_id': odooId, 'odoo_id': odooId,
'is_locked': isLocked ? 1 : 0,
'is_synced': isSynced ? 1 : 0, 'is_synced': isSynced ? 1 : 0,
'updated_at': updatedAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(),
}; };
@ -57,6 +60,7 @@ class Customer {
address: map['address'], address: map['address'],
tel: map['tel'], tel: map['tel'],
odooId: map['odoo_id'], odooId: map['odoo_id'],
isLocked: (map['is_locked'] ?? 0) == 1,
isSynced: map['is_synced'] == 1, isSynced: map['is_synced'] == 1,
updatedAt: DateTime.parse(map['updated_at']), updatedAt: DateTime.parse(map['updated_at']),
); );
@ -73,6 +77,7 @@ class Customer {
String? odooId, String? odooId,
bool? isSynced, bool? isSynced,
DateTime? updatedAt, DateTime? updatedAt,
bool? isLocked,
}) { }) {
return Customer( return Customer(
id: id ?? this.id, id: id ?? this.id,
@ -85,6 +90,7 @@ class Customer {
odooId: odooId ?? this.odooId, odooId: odooId ?? this.odooId,
isSynced: isSynced ?? this.isSynced, isSynced: isSynced ?? this.isSynced,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
isLocked: isLocked ?? this.isLocked,
); );
} }
} }

View file

@ -83,6 +83,7 @@ class Invoice {
final String terminalId; // : final String terminalId; // :
final bool isDraft; // : final bool isDraft; // :
final String? subject; // : final String? subject; // :
final bool isLocked; // :
Invoice({ Invoice({
String? id, String? id,
@ -102,6 +103,7 @@ class Invoice {
String? terminalId, // String? terminalId, //
this.isDraft = false, // : this.isDraft = false, // :
this.subject, // : this.subject, // :
this.isLocked = false,
}) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(), }) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(),
terminalId = terminalId ?? "T1", // ID terminalId = terminalId ?? "T1", // ID
updatedAt = updatedAt ?? DateTime.now(); updatedAt = updatedAt ?? DateTime.now();
@ -160,6 +162,7 @@ class Invoice {
'content_hash': contentHash, // 'content_hash': contentHash, //
'is_draft': isDraft ? 1 : 0, // 'is_draft': isDraft ? 1 : 0, //
'subject': subject, // 'subject': subject, //
'is_locked': isLocked ? 1 : 0,
}; };
} }
@ -181,6 +184,7 @@ class Invoice {
String? terminalId, String? terminalId,
bool? isDraft, bool? isDraft,
String? subject, String? subject,
bool? isLocked,
}) { }) {
return Invoice( return Invoice(
id: id ?? this.id, id: id ?? this.id,
@ -200,6 +204,7 @@ class Invoice {
terminalId: terminalId ?? this.terminalId, terminalId: terminalId ?? this.terminalId,
isDraft: isDraft ?? this.isDraft, isDraft: isDraft ?? this.isDraft,
subject: subject ?? this.subject, subject: subject ?? this.subject,
isLocked: isLocked ?? this.isLocked,
); );
} }

View file

@ -6,6 +6,7 @@ class Product {
final String? category; final String? category;
final int stockQuantity; // final int stockQuantity; //
final String? odooId; final String? odooId;
final bool isLocked; //
Product({ Product({
required this.id, required this.id,
@ -15,6 +16,7 @@ class Product {
this.category, this.category,
this.stockQuantity = 0, // this.stockQuantity = 0, //
this.odooId, this.odooId,
this.isLocked = false,
}); });
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
@ -25,6 +27,7 @@ class Product {
'barcode': barcode, 'barcode': barcode,
'category': category, 'category': category,
'stock_quantity': stockQuantity, // 'stock_quantity': stockQuantity, //
'is_locked': isLocked ? 1 : 0,
'odoo_id': odooId, 'odoo_id': odooId,
}; };
} }
@ -37,6 +40,7 @@ class Product {
barcode: map['barcode'], barcode: map['barcode'],
category: map['category'], category: map['category'],
stockQuantity: map['stock_quantity'] ?? 0, // stockQuantity: map['stock_quantity'] ?? 0, //
isLocked: (map['is_locked'] ?? 0) == 1,
odooId: map['odoo_id'], odooId: map['odoo_id'],
); );
} }
@ -47,12 +51,14 @@ class Product {
int? defaultUnitPrice, int? defaultUnitPrice,
String? barcode, String? barcode,
String? odooId, String? odooId,
bool? isLocked,
}) { }) {
return Product( return Product(
id: id ?? this.id, id: id ?? this.id,
name: name ?? this.name, name: name ?? this.name,
defaultUnitPrice: defaultUnitPrice ?? this.defaultUnitPrice, defaultUnitPrice: defaultUnitPrice ?? this.defaultUnitPrice,
odooId: odooId ?? this.odooId, odooId: odooId ?? this.odooId,
isLocked: isLocked ?? this.isLocked,
); );
} }
} }

View file

@ -327,6 +327,7 @@ class _InvoiceHistoryScreenState extends State<InvoiceHistoryScreen> {
label: const Text("新規伝票作成"), label: const Text("新規伝票作成"),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
backgroundColor: Colors.indigo, backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
), ),
); );
} }

View file

@ -129,24 +129,31 @@ class _InvoiceInputFormState extends State<InvoiceInputForm> {
); );
setState(() => _isSaving = true); setState(() => _isSaving = true);
try {
// PDF生成有無に関わらず // PDF生成有無に関わらず
if (generatePdf) { if (generatePdf) {
setState(() => _status = "PDFを生成中..."); setState(() => _status = "PDFを生成中...");
final path = await generateInvoicePdf(invoice); final path = await generateInvoicePdf(invoice);
if (path != null) { if (path != null) {
final updatedInvoice = invoice.copyWith(filePath: path); final updatedInvoice = invoice.copyWith(filePath: path);
await _repository.saveInvoice(updatedInvoice); await _repository.saveInvoice(updatedInvoice);
if (mounted) widget.onInvoiceGenerated(updatedInvoice, path); if (mounted) widget.onInvoiceGenerated(updatedInvoice, path);
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("伝票を保存し、PDFを生成しました"))); if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("伝票を保存し、PDFを生成しました")));
} else {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("PDF生成に失敗しました")));
}
} else {
await _repository.saveInvoice(invoice);
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("伝票を保存しましたPDF未生成")));
if (mounted) Navigator.pop(context);
} }
} else { } catch (e) {
await _repository.saveInvoice(invoice); if (mounted) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("伝票を保存しましたPDF未生成"))); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('保存に失敗しました: $e')));
if (mounted) Navigator.pop(context); }
} finally {
if (mounted) setState(() => _isSaving = false);
} }
if (mounted) setState(() => _isSaving = false);
} }
void _showPreview() { void _showPreview() {
@ -202,7 +209,7 @@ class _InvoiceInputFormState extends State<InvoiceInputForm> {
backgroundColor: themeColor, backgroundColor: themeColor,
appBar: AppBar( appBar: AppBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(_isDraft ? "伝票作成 (下書き)" : "販売アシスト1号 V1.5.05"), title: Text(_isDraft ? "伝票作成 (下書き)" : "販売アシスト1号 V1.5.06"),
backgroundColor: _isDraft ? Colors.black87 : Colors.blueGrey, backgroundColor: _isDraft ? Colors.black87 : Colors.blueGrey,
), ),
body: Stack( body: Stack(
@ -374,102 +381,98 @@ class _InvoiceInputFormState extends State<InvoiceInputForm> {
child: Center(child: Text("商品が追加されていません", style: TextStyle(color: Colors.grey))), child: Center(child: Text("商品が追加されていません", style: TextStyle(color: Colors.grey))),
) )
else else
..._items.asMap().entries.map((entry) { ReorderableListView.builder(
final idx = entry.key; shrinkWrap: true,
final item = entry.value; physics: const NeverScrollableScrollPhysics(),
return Card( itemCount: _items.length,
margin: const EdgeInsets.only(bottom: 8), onReorder: (oldIndex, newIndex) {
child: ListTile( setState(() {
title: Text(item.description), if (newIndex > oldIndex) newIndex -= 1;
subtitle: Text("${fmt.format(item.unitPrice)} x ${item.quantity}"), final item = _items.removeAt(oldIndex);
trailing: Row( _items.insert(newIndex, item);
mainAxisSize: MainAxisSize.min, });
children: [ },
Text("${fmt.format(item.unitPrice * item.quantity)}", style: const TextStyle(fontWeight: FontWeight.bold)), buildDefaultDragHandles: false,
const SizedBox(width: 8), itemBuilder: (context, idx) {
if (idx > 0) final item = _items[idx];
IconButton( return ReorderableDelayedDragStartListener(
icon: const Icon(Icons.arrow_upward, size: 20), key: ValueKey('item_${idx}_${item.description}'),
onPressed: () => setState(() { index: idx,
final temp = _items[idx]; child: Card(
_items[idx] = _items[idx - 1]; margin: const EdgeInsets.only(bottom: 8),
_items[idx - 1] = temp; child: ListTile(
}), title: Text(item.description),
tooltip: "上へ", subtitle: Text("${fmt.format(item.unitPrice)} x ${item.quantity}"),
), trailing: Row(
if (idx < _items.length - 1) mainAxisSize: MainAxisSize.min,
IconButton( children: [
icon: const Icon(Icons.arrow_downward, size: 20), Text("${fmt.format(item.unitPrice * item.quantity)}", style: const TextStyle(fontWeight: FontWeight.bold)),
onPressed: () => setState(() { const SizedBox(width: 8),
final temp = _items[idx]; IconButton(
_items[idx] = _items[idx + 1]; icon: const Icon(Icons.remove_circle_outline, color: Colors.redAccent),
_items[idx + 1] = temp; onPressed: () => setState(() => _items.removeAt(idx)),
}), tooltip: "削除",
tooltip: "下へ",
),
IconButton(
icon: const Icon(Icons.remove_circle_outline, color: Colors.redAccent),
onPressed: () => setState(() => _items.removeAt(idx)),
tooltip: "削除",
),
],
),
onTap: () {
//
final descCtrl = TextEditingController(text: item.description);
final qtyCtrl = TextEditingController(text: item.quantity.toString());
final priceCtrl = TextEditingController(text: item.unitPrice.toString());
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("明細の編集"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: descCtrl, decoration: const InputDecoration(labelText: "品名 / 項目")),
TextField(controller: qtyCtrl, decoration: const InputDecoration(labelText: "数量"), keyboardType: TextInputType.number),
TextField(controller: priceCtrl, decoration: const InputDecoration(labelText: "単価"), keyboardType: TextInputType.number),
],
),
actions: [
TextButton.icon(
icon: const Icon(Icons.search, size: 18),
label: const Text("マスター参照"),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ProductPickerModal(
onItemSelected: (selected) {
descCtrl.text = selected.description;
priceCtrl.text = selected.unitPrice.toString();
Navigator.pop(context); // close picker
},
),
);
},
),
TextButton(onPressed: () => Navigator.pop(context), child: const Text("キャンセル")),
ElevatedButton(
onPressed: () {
setState(() {
_items[idx] = item.copyWith(
description: descCtrl.text,
quantity: int.tryParse(qtyCtrl.text) ?? item.quantity,
unitPrice: int.tryParse(priceCtrl.text) ?? item.unitPrice,
);
});
Navigator.pop(context);
},
child: const Text("更新"),
), ),
], ],
), ),
); onTap: () {
}, //
), final descCtrl = TextEditingController(text: item.description);
); final qtyCtrl = TextEditingController(text: item.quantity.toString());
}), final priceCtrl = TextEditingController(text: item.unitPrice.toString());
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("明細の編集"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: descCtrl, decoration: const InputDecoration(labelText: "品名 / 項目")),
TextField(controller: qtyCtrl, decoration: const InputDecoration(labelText: "数量"), keyboardType: TextInputType.number),
TextField(controller: priceCtrl, decoration: const InputDecoration(labelText: "単価"), keyboardType: TextInputType.number),
],
),
actions: [
TextButton.icon(
icon: const Icon(Icons.search, size: 18),
label: const Text("マスター参照"),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ProductPickerModal(
onItemSelected: (selected) {
descCtrl.text = selected.description;
priceCtrl.text = selected.unitPrice.toString();
Navigator.pop(context); // close picker
},
),
);
},
),
TextButton(onPressed: () => Navigator.pop(context), child: const Text("キャンセル")),
ElevatedButton(
onPressed: () {
setState(() {
_items[idx] = item.copyWith(
description: descCtrl.text,
quantity: int.tryParse(qtyCtrl.text) ?? item.quantity,
unitPrice: int.tryParse(priceCtrl.text) ?? item.unitPrice,
);
});
Navigator.pop(context);
},
child: const Text("更新"),
),
],
),
);
},
),
),
);
},
),
], ],
); );
} }

View file

@ -2,7 +2,7 @@ import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
class DatabaseHelper { class DatabaseHelper {
static const _databaseVersion = 13; static const _databaseVersion = 15;
static final DatabaseHelper _instance = DatabaseHelper._internal(); static final DatabaseHelper _instance = DatabaseHelper._internal();
static Database? _database; static Database? _database;
@ -97,6 +97,14 @@ class DatabaseHelper {
if (oldVersion < 13) { if (oldVersion < 13) {
await db.execute('ALTER TABLE company_info ADD COLUMN registration_number TEXT'); await db.execute('ALTER TABLE company_info ADD COLUMN registration_number TEXT');
} }
if (oldVersion < 14) {
await _safeAddColumn(db, 'invoices', 'subject TEXT');
}
if (oldVersion < 15) {
await _safeAddColumn(db, 'invoices', 'is_locked INTEGER DEFAULT 0');
await _safeAddColumn(db, 'customers', 'is_locked INTEGER DEFAULT 0');
await _safeAddColumn(db, 'products', 'is_locked INTEGER DEFAULT 0');
}
} }
Future<void> _onCreate(Database db, int version) async { Future<void> _onCreate(Database db, int version) async {
@ -110,6 +118,7 @@ class DatabaseHelper {
address TEXT, address TEXT,
tel TEXT, tel TEXT,
odoo_id TEXT, odoo_id TEXT,
is_locked INTEGER DEFAULT 0,
is_synced INTEGER DEFAULT 0, is_synced INTEGER DEFAULT 0,
updated_at TEXT NOT NULL updated_at TEXT NOT NULL
) )
@ -135,6 +144,7 @@ class DatabaseHelper {
barcode TEXT, barcode TEXT,
category TEXT, category TEXT,
stock_quantity INTEGER DEFAULT 0, stock_quantity INTEGER DEFAULT 0,
is_locked INTEGER DEFAULT 0,
odoo_id TEXT odoo_id TEXT
) )
'''); ''');
@ -148,6 +158,7 @@ class DatabaseHelper {
customer_id TEXT NOT NULL, customer_id TEXT NOT NULL,
date TEXT NOT NULL, date TEXT NOT NULL,
notes TEXT, notes TEXT,
subject TEXT,
file_path TEXT, file_path TEXT,
total_amount INTEGER, total_amount INTEGER,
tax_rate REAL DEFAULT 0.10, tax_rate REAL DEFAULT 0.10,
@ -161,6 +172,7 @@ class DatabaseHelper {
terminal_id TEXT DEFAULT "T1", terminal_id TEXT DEFAULT "T1",
content_hash TEXT, content_hash TEXT,
is_draft INTEGER DEFAULT 0, is_draft INTEGER DEFAULT 0,
is_locked INTEGER DEFAULT 0,
FOREIGN KEY (customer_id) REFERENCES customers (id) FOREIGN KEY (customer_id) REFERENCES customers (id)
) )
'''); ''');
@ -212,4 +224,12 @@ class DatabaseHelper {
) )
'''); ''');
} }
Future<void> _safeAddColumn(Database db, String table, String columnDef) async {
try {
await db.execute('ALTER TABLE ' + table + ' ADD COLUMN ' + columnDef);
} catch (_) {
// Ignore if the column already exists.
}
}
} }

View file

@ -13,6 +13,9 @@ class InvoiceRepository {
Future<void> saveInvoice(Invoice invoice) async { Future<void> saveInvoice(Invoice invoice) async {
final db = await _dbHelper.database; final db = await _dbHelper.database;
//
final Invoice toSave = invoice.isDraft ? invoice : invoice.copyWith(isLocked: true);
await db.transaction((txn) async { await db.transaction((txn) async {
// 調 // 調
final List<Map<String, dynamic>> oldItems = await txn.query( final List<Map<String, dynamic>> oldItems = await txn.query(
@ -34,7 +37,7 @@ class InvoiceRepository {
// //
await txn.insert( await txn.insert(
'invoices', 'invoices',
invoice.toMap(), toSave.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace, conflictAlgorithm: ConflictAlgorithm.replace,
); );
@ -53,8 +56,16 @@ class InvoiceRepository {
'UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?', 'UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?',
[item.quantity, item.productId], [item.quantity, item.productId],
); );
if (!invoice.isDraft) {
await txn.execute('UPDATE products SET is_locked = 1 WHERE id = ?', [item.productId]);
}
} }
} }
//
if (!invoice.isDraft) {
await txn.execute('UPDATE customers SET is_locked = 1 WHERE id = ?', [invoice.customer.id]);
}
}); });
await _logRepo.logAction( await _logRepo.logAction(
@ -114,6 +125,7 @@ class InvoiceRepository {
terminalId: iMap['terminal_id'] ?? "T1", terminalId: iMap['terminal_id'] ?? "T1",
isDraft: (iMap['is_draft'] ?? 0) == 1, isDraft: (iMap['is_draft'] ?? 0) == 1,
subject: iMap['subject'], subject: iMap['subject'],
isLocked: (iMap['is_locked'] ?? 0) == 1,
)); ));
} }
return invoices; return invoices;

View file

@ -18,7 +18,7 @@ class SlideToUnlock extends StatefulWidget {
class _SlideToUnlockState extends State<SlideToUnlock> { class _SlideToUnlockState extends State<SlideToUnlock> {
double _position = 0.0; double _position = 0.0;
final double _thumbSize = 50.0; final double _thumbSize = 56.0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,7 +27,7 @@ class _SlideToUnlockState extends State<SlideToUnlock> {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final double maxWidth = constraints.maxWidth; final double maxWidth = constraints.maxWidth;
final double trackWidth = maxWidth - _thumbSize - 8; // final double trackWidth = (maxWidth - _thumbSize - 12).clamp(0, maxWidth);
return Container( return Container(
height: 64, height: 64,
@ -81,7 +81,7 @@ class _SlideToUnlockState extends State<SlideToUnlock> {
} }
}, },
child: Container( child: Container(
width: maxWidth * 0.25, // width: _thumbSize,
height: 56, height: 56,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: const LinearGradient(