fix: formatting

This commit is contained in:
2026-07-29 01:42:34 +02:00
parent 521c8f8c4e
commit 50e6fca9d1
32 changed files with 501 additions and 575 deletions
-1
View File
@@ -13,7 +13,6 @@ class KoolTabApp extends StatefulWidget {
} }
class _KoolTabAppState extends State<KoolTabApp> { class _KoolTabAppState extends State<KoolTabApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp.router( return MaterialApp.router(
+1 -4
View File
@@ -7,10 +7,7 @@ import '../viewmodels/product_list_view_model.dart';
class AppBootstrap extends StatefulWidget { class AppBootstrap extends StatefulWidget {
final Widget child; final Widget child;
const AppBootstrap({ const AppBootstrap({super.key, required this.child});
super.key,
required this.child,
});
@override @override
State<AppBootstrap> createState() => _AppBootstrapState(); State<AppBootstrap> createState() => _AppBootstrapState();
+1 -4
View File
@@ -32,10 +32,7 @@ GoRouter createAppRouter(PinLockViewModel pinLockViewModel) {
return null; return null;
}, },
routes: [ routes: [
GoRoute( GoRoute(path: '/', redirect: (context, state) => '/bar'),
path: '/',
redirect: (context, state) => '/bar'
),
GoRoute( GoRoute(
path: '/lock', path: '/lock',
builder: (context, state) => PinEntryView( builder: (context, state) => PinEntryView(
+16 -12
View File
@@ -46,16 +46,10 @@ class BarTabs extends Table {
class TabItems extends Table { class TabItems extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get tabId => text().references( TextColumn get tabId =>
BarTabs, text().references(BarTabs, #id, onDelete: KeyAction.cascade)();
#id,
onDelete: KeyAction.cascade,
)();
TextColumn get productId => text().references( TextColumn get productId => text().references(Products, #id)();
Products,
#id,
)();
TextColumn get productName => text()(); TextColumn get productName => text()();
@@ -72,8 +66,11 @@ class TabItems extends Table {
@DataClassName('ClosedTabsRow') @DataClassName('ClosedTabsRow')
class ClosedTabs extends Table { class ClosedTabs extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get originalTabId => text()(); TextColumn get originalTabId => text()();
TextColumn get customerName => text()(); TextColumn get customerName => text()();
DateTimeColumn get closedAt => dateTime()(); DateTimeColumn get closedAt => dateTime()();
@override @override
@@ -83,10 +80,15 @@ class ClosedTabs extends Table {
@DataClassName('ClosedTabItemRow') @DataClassName('ClosedTabItemRow')
class ClosedTabItems extends Table { class ClosedTabItems extends Table {
TextColumn get id => text()(); TextColumn get id => text()();
TextColumn get closedTabId => text()(); TextColumn get closedTabId => text()();
TextColumn get productId => text()(); TextColumn get productId => text()();
TextColumn get productName => text()(); TextColumn get productName => text()();
IntColumn get quantity => integer()(); IntColumn get quantity => integer()();
IntColumn get unitPriceInCents => integer()(); IntColumn get unitPriceInCents => integer()();
@override @override
@@ -99,7 +101,9 @@ class AppSettingsTable extends Table {
String get tableName => 'app_settings'; String get tableName => 'app_settings';
TextColumn get id => text()(); TextColumn get id => text()();
BoolColumn get pinRequired => boolean().withDefault(const Constant(false))(); BoolColumn get pinRequired => boolean().withDefault(const Constant(false))();
TextColumn get themeMode => text().withDefault(const Constant('system'))(); TextColumn get themeMode => text().withDefault(const Constant('system'))();
@override @override
@@ -115,7 +119,7 @@ class AppSettingsTable extends Table {
ClosedTabs, ClosedTabs,
ClosedTabItems, ClosedTabItems,
AppSettingsTable AppSettingsTable,
], ],
) )
class AppDatabase extends _$AppDatabase { class AppDatabase extends _$AppDatabase {
@@ -140,12 +144,12 @@ class AppDatabase extends _$AppDatabase {
await migrator.addColumn(products, products.imagePath); await migrator.addColumn(products, products.imagePath);
} }
if (from < 4){ if (from < 4) {
await migrator.createTable(closedTabs); await migrator.createTable(closedTabs);
await migrator.createTable(closedTabItems); await migrator.createTable(closedTabItems);
} }
if (from < 5){ if (from < 5) {
await migrator.createTable(appSettingsTable); await migrator.createTable(appSettingsTable);
} }
}, },
+7 -8
View File
@@ -55,9 +55,9 @@ Future<void> main() async {
DriftSettingsService(database: context.read<AppDatabase>()), DriftSettingsService(database: context.read<AppDatabase>()),
), ),
ChangeNotifierProvider<InventoryViewModel>( ChangeNotifierProvider<InventoryViewModel>(
create: (context) => InventoryViewModel( create: (context) =>
productService: context.read<ProductService>(), InventoryViewModel(productService: context.read<ProductService>())
)..load(), ..load(),
), ),
ChangeNotifierProvider<ProductListViewModel>( ChangeNotifierProvider<ProductListViewModel>(
create: (context) => ProductListViewModel( create: (context) => ProductListViewModel(
@@ -66,11 +66,10 @@ Future<void> main() async {
), ),
), ),
ChangeNotifierProvider<BarScreenViewModel>( ChangeNotifierProvider<BarScreenViewModel>(
create: (context) => create: (context) => BarScreenViewModel(
BarScreenViewModel( barTabService: context.read<BarTabService>(),
barTabService: context.read<BarTabService>(), inventory: context.read<InventoryViewModel>(),
inventory: context.read<InventoryViewModel>(), ),
),
), ),
ChangeNotifierProvider<HistoryViewModel>( ChangeNotifierProvider<HistoryViewModel>(
create: (context) => create: (context) =>
+2 -8
View File
@@ -18,17 +18,11 @@ class BarTab {
}); });
int get totalInCents { int get totalInCents {
return items.fold<int>( return items.fold<int>(0, (total, item) => total + item.lineTotalInCents);
0,
(total, item) => total + item.lineTotalInCents,
);
} }
int get itemCount { int get itemCount {
return items.fold<int>( return items.fold<int>(0, (total, item) => total + item.quantity);
0,
(total, item) => total + item.quantity,
);
} }
String get formattedTotal { String get formattedTotal {
+2 -8
View File
@@ -4,15 +4,9 @@ class AppSettings {
final bool pinRequired; final bool pinRequired;
final AppThemeMode themeMode; final AppThemeMode themeMode;
const AppSettings({ const AppSettings({required this.pinRequired, required this.themeMode});
required this.pinRequired,
required this.themeMode,
});
AppSettings copyWith({ AppSettings copyWith({bool? pinRequired, AppThemeMode? themeMode}) {
bool? pinRequired,
AppThemeMode? themeMode,
}) {
return AppSettings( return AppSettings(
pinRequired: pinRequired ?? this.pinRequired, pinRequired: pinRequired ?? this.pinRequired,
themeMode: themeMode ?? this.themeMode, themeMode: themeMode ?? this.themeMode,
+65 -77
View File
@@ -13,9 +13,7 @@ abstract class BarTabService {
Future<BarTab?> getTabById(String id); Future<BarTab?> getTabById(String id);
Future<BarTab> createTab({ Future<BarTab> createTab({required String customerName});
required String customerName,
});
Future<void> addProductToTab({ Future<void> addProductToTab({
required String tabId, required String tabId,
@@ -38,9 +36,7 @@ class DriftBarTabService implements BarTabService {
final AppDatabase database; final AppDatabase database;
final _uuid = const Uuid(); final _uuid = const Uuid();
DriftBarTabService({ DriftBarTabService({required this.database});
required this.database,
});
TabItem _mapItemRow(TabItemRow row) { TabItem _mapItemRow(TabItemRow row) {
return TabItem( return TabItem(
@@ -53,10 +49,7 @@ class DriftBarTabService implements BarTabService {
); );
} }
BarTab _mapTabRow( BarTab _mapTabRow(BarTabRow row, List<TabItem> items) {
BarTabRow row,
List<TabItem> items,
) {
return BarTab( return BarTab(
id: row.id, id: row.id,
customerName: row.customerName, customerName: row.customerName,
@@ -81,9 +74,7 @@ class DriftBarTabService implements BarTabService {
Future<List<TabItem>> _getItemsForTab(String tabId) async { Future<List<TabItem>> _getItemsForTab(String tabId) async {
final query = database.select(database.tabItems) final query = database.select(database.tabItems)
..where((item) => item.tabId.equals(tabId)) ..where((item) => item.tabId.equals(tabId))
..orderBy([ ..orderBy([(item) => OrderingTerm.asc(item.createdAt)]);
(item) => OrderingTerm.asc(item.createdAt),
]);
final rows = await query.get(); final rows = await query.get();
@@ -94,9 +85,7 @@ class DriftBarTabService implements BarTabService {
Future<List<BarTab>> getOpenTabs() async { Future<List<BarTab>> getOpenTabs() async {
final query = database.select(database.barTabs) final query = database.select(database.barTabs)
..where((tab) => tab.status.equals('open')) ..where((tab) => tab.status.equals('open'))
..orderBy([ ..orderBy([(tab) => OrderingTerm.desc(tab.openedAt)]);
(tab) => OrderingTerm.desc(tab.openedAt),
]);
final tabRows = await query.get(); final tabRows = await query.get();
@@ -128,19 +117,19 @@ class DriftBarTabService implements BarTabService {
} }
@override @override
Future<BarTab> createTab({ Future<BarTab> createTab({required String customerName}) async {
required String customerName,
}) async {
final id = _uuid.v4(); final id = _uuid.v4();
await database.into(database.barTabs).insert( await database
BarTabsCompanion.insert( .into(database.barTabs)
id: id, .insert(
customerName: customerName, BarTabsCompanion.insert(
status: const Value('open'), id: id,
openedAt: DateTime.now(), customerName: customerName,
), status: const Value('open'),
); openedAt: DateTime.now(),
),
);
final tab = await getTabById(id); final tab = await getTabById(id);
@@ -159,9 +148,8 @@ class DriftBarTabService implements BarTabService {
await database.transaction(() async { await database.transaction(() async {
final existingItemQuery = database.select(database.tabItems) final existingItemQuery = database.select(database.tabItems)
..where( ..where(
(item) => (item) =>
item.tabId.equals(tabId) & item.tabId.equals(tabId) & item.productId.equals(product.id),
item.productId.equals(product.id),
); );
final existingItem = await existingItemQuery.getSingleOrNull(); final existingItem = await existingItemQuery.getSingleOrNull();
@@ -171,25 +159,25 @@ class DriftBarTabService implements BarTabService {
..where((item) => item.id.equals(existingItem.id)); ..where((item) => item.id.equals(existingItem.id));
await updateQuery.write( await updateQuery.write(
TabItemsCompanion( TabItemsCompanion(quantity: Value(existingItem.quantity + 1)),
quantity: Value(existingItem.quantity + 1),
),
); );
return; return;
} }
await database.into(database.tabItems).insert( await database
TabItemsCompanion.insert( .into(database.tabItems)
id: _uuid.v4(), .insert(
tabId: tabId, TabItemsCompanion.insert(
productId: product.id, id: _uuid.v4(),
productName: product.name, tabId: tabId,
quantity: 1, productId: product.id,
unitPriceInCents: product.priceInCents, productName: product.name,
createdAt: DateTime.now(), quantity: 1,
), unitPriceInCents: product.priceInCents,
); createdAt: DateTime.now(),
),
);
}); });
} }
@@ -209,11 +197,7 @@ class DriftBarTabService implements BarTabService {
final updateQuery = database.update(database.tabItems) final updateQuery = database.update(database.tabItems)
..where((item) => item.id.equals(tabItemId)); ..where((item) => item.id.equals(tabItemId));
await updateQuery.write( await updateQuery.write(TabItemsCompanion(quantity: Value(quantity)));
TabItemsCompanion(
quantity: Value(quantity),
),
);
} }
@override @override
@@ -236,26 +220,30 @@ class DriftBarTabService implements BarTabService {
final closedTabId = _uuid.v4(); final closedTabId = _uuid.v4();
await database.into(database.closedTabs).insert( await database
ClosedTabsCompanion.insert( .into(database.closedTabs)
id: closedTabId, .insert(
originalTabId: tabId, ClosedTabsCompanion.insert(
customerName: tabRow.customerName, id: closedTabId,
closedAt: DateTime.now(), originalTabId: tabId,
), customerName: tabRow.customerName,
); closedAt: DateTime.now(),
),
);
for (final item in items) { for (final item in items) {
await database.into(database.closedTabItems).insert( await database
ClosedTabItemsCompanion.insert( .into(database.closedTabItems)
id: _uuid.v4(), .insert(
closedTabId: closedTabId, ClosedTabItemsCompanion.insert(
productId: item.productId, id: _uuid.v4(),
productName: item.productName, closedTabId: closedTabId,
quantity: item.quantity, productId: item.productId,
unitPriceInCents: item.unitPriceInCents, productName: item.productName,
), quantity: item.quantity,
); unitPriceInCents: item.unitPriceInCents,
),
);
} }
final deleteQuery = database.delete(database.tabItems) final deleteQuery = database.delete(database.tabItems)
@@ -268,9 +256,7 @@ class DriftBarTabService implements BarTabService {
@override @override
Future<List<ClosedTab>> getClosedTabs() async { Future<List<ClosedTab>> getClosedTabs() async {
final query = database.select(database.closedTabs) final query = database.select(database.closedTabs)
..orderBy([ ..orderBy([(tab) => OrderingTerm.desc(tab.closedAt)]);
(tab) => OrderingTerm.desc(tab.closedAt),
]);
final closedTabRows = await query.get(); final closedTabRows = await query.get();
@@ -282,13 +268,15 @@ class DriftBarTabService implements BarTabService {
final itemRows = await itemsQuery.get(); final itemRows = await itemsQuery.get();
closedTabs.add(ClosedTab( closedTabs.add(
id: row.id, ClosedTab(
originalTabId: row.originalTabId, id: row.id,
customerName: row.customerName, originalTabId: row.originalTabId,
closedAt: row.closedAt, customerName: row.customerName,
items: itemRows.map<ClosedTabItem>(_mapClosedItemRow).toList(), closedAt: row.closedAt,
)); items: itemRows.map<ClosedTabItem>(_mapClosedItemRow).toList(),
),
);
} }
return closedTabs; return closedTabs;
+18 -24
View File
@@ -23,6 +23,7 @@ abstract class ProductService {
Future<void> deleteProduct(String id); Future<void> deleteProduct(String id);
Future<void> decreaseStock(String productId, int amount); Future<void> decreaseStock(String productId, int amount);
Future<void> increaseStock(String productId, int amount); Future<void> increaseStock(String productId, int amount);
} }
@@ -30,9 +31,7 @@ class DriftProductService implements ProductService {
final AppDatabase database; final AppDatabase database;
final _uuid = const Uuid(); final _uuid = const Uuid();
DriftProductService({ DriftProductService({required this.database});
required this.database,
});
Product _mapRowToProduct(ProductRow row) { Product _mapRowToProduct(ProductRow row) {
return Product( return Product(
@@ -51,9 +50,7 @@ class DriftProductService implements ProductService {
Future<List<Product>> getProducts() async { Future<List<Product>> getProducts() async {
final query = database.select(database.products) final query = database.select(database.products)
..where((product) => product.active.equals(true)) ..where((product) => product.active.equals(true))
..orderBy([ ..orderBy([(product) => OrderingTerm.asc(product.name)]);
(product) => OrderingTerm.asc(product.name),
]);
final rows = await query.get(); final rows = await query.get();
@@ -83,17 +80,19 @@ class DriftProductService implements ProductService {
required int priceInCents, required int priceInCents,
required String? imagePath, required String? imagePath,
}) async { }) async {
await database.into(database.products).insert( await database
ProductsCompanion.insert( .into(database.products)
id: _uuid.v4(), .insert(
name: name, ProductsCompanion.insert(
category: category, id: _uuid.v4(),
stockQuantity: stockQuantity, name: name,
lowStockThreshold: lowStockThreshold, category: category,
priceInCents: priceInCents, stockQuantity: stockQuantity,
imagePath: Value(imagePath), lowStockThreshold: lowStockThreshold,
), priceInCents: priceInCents,
); imagePath: Value(imagePath),
),
);
} }
@override @override
@@ -135,9 +134,7 @@ class DriftProductService implements ProductService {
} }
await updateProduct( await updateProduct(
product.copyWith( product.copyWith(stockQuantity: product.stockQuantity - amount),
stockQuantity: product.stockQuantity - amount,
),
); );
} }
@@ -150,10 +147,7 @@ class DriftProductService implements ProductService {
} }
await updateProduct( await updateProduct(
product.copyWith( product.copyWith(stockQuantity: product.stockQuantity + amount),
stockQuantity: product.stockQuantity + amount,
),
); );
} }
} }
+10 -8
View File
@@ -19,7 +19,7 @@ class DriftSettingsService implements SettingsService {
return AppSettings( return AppSettings(
pinRequired: row.pinRequired, pinRequired: row.pinRequired,
themeMode: AppThemeMode.values.firstWhere( themeMode: AppThemeMode.values.firstWhere(
(mode) => mode.name == row.themeMode, (mode) => mode.name == row.themeMode,
orElse: () => AppThemeMode.system, orElse: () => AppThemeMode.system,
), ),
); );
@@ -39,12 +39,14 @@ class DriftSettingsService implements SettingsService {
@override @override
Future<void> saveSettings(AppSettings settings) async { Future<void> saveSettings(AppSettings settings) async {
await database.into(database.appSettingsTable).insertOnConflictUpdate( await database
AppSettingsTableCompanion.insert( .into(database.appSettingsTable)
id: _settingsId, .insertOnConflictUpdate(
pinRequired: Value(settings.pinRequired), AppSettingsTableCompanion.insert(
themeMode: Value(settings.themeMode.name), id: _settingsId,
), pinRequired: Value(settings.pinRequired),
); themeMode: Value(settings.themeMode.name),
),
);
} }
} }
+33 -15
View File
@@ -253,10 +253,7 @@ final ThemeData darkTheme = ThemeData(
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
letterSpacing: -0.2, letterSpacing: -0.2,
), ),
titleMedium: TextStyle( titleMedium: TextStyle(color: Colors.white, fontWeight: FontWeight.w700),
color: Colors.white,
fontWeight: FontWeight.w700,
),
bodyLarge: TextStyle(color: Colors.white70, fontSize: 16, height: 1.5), bodyLarge: TextStyle(color: Colors.white70, fontSize: 16, height: 1.5),
bodyMedium: TextStyle(color: Colors.white60, height: 1.4), bodyMedium: TextStyle(color: Colors.white60, height: 1.4),
bodySmall: TextStyle(color: Colors.white38, height: 1.3), bodySmall: TextStyle(color: Colors.white38, height: 1.3),
@@ -311,9 +308,17 @@ final ThemeData neoBrutalDarkTheme = ThemeData(
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
color: Colors.white, color: Colors.white,
), ),
bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
bodyMedium: TextStyle(fontSize: 14, color: Colors.white70), bodyMedium: TextStyle(fontSize: 14, color: Colors.white70),
bodySmall: TextStyle(fontSize: 12, color: Colors.white54, fontWeight: FontWeight.w600), bodySmall: TextStyle(
fontSize: 12,
color: Colors.white54,
fontWeight: FontWeight.w600,
),
), ),
appBarTheme: const AppBarTheme( appBarTheme: const AppBarTheme(
@@ -331,7 +336,10 @@ final ThemeData neoBrutalDarkTheme = ThemeData(
cardTheme: CardThemeData( cardTheme: CardThemeData(
color: _nbSurface, color: _nbSurface,
elevation: 0, elevation: 0,
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm), margin: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.white, width: 3), side: const BorderSide(color: Colors.white, width: 3),
@@ -380,7 +388,10 @@ final ThemeData neoBrutalDarkTheme = ThemeData(
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
filled: true, filled: true,
fillColor: _nbSurface, fillColor: _nbSurface,
hintStyle: const TextStyle(color: Colors.white38, fontWeight: FontWeight.w600), hintStyle: const TextStyle(
color: Colors.white38,
fontWeight: FontWeight.w600,
),
contentPadding: const EdgeInsets.all(18), contentPadding: const EdgeInsets.all(18),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@@ -408,7 +419,10 @@ final ThemeData neoBrutalDarkTheme = ThemeData(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
), ),
contentTextStyle: const TextStyle(color: Colors.white70, fontWeight: FontWeight.w600), contentTextStyle: const TextStyle(
color: Colors.white70,
fontWeight: FontWeight.w600,
),
), ),
floatingActionButtonTheme: const FloatingActionButtonThemeData( floatingActionButtonTheme: const FloatingActionButtonThemeData(
@@ -428,7 +442,10 @@ final ThemeData neoBrutalDarkTheme = ThemeData(
side: const BorderSide(color: Colors.white, width: 3), side: const BorderSide(color: Colors.white, width: 3),
), ),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
contentTextStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600), contentTextStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
), ),
dividerTheme: const DividerThemeData(color: Colors.white, thickness: 3), dividerTheme: const DividerThemeData(color: Colors.white, thickness: 3),
@@ -645,7 +662,11 @@ final ThemeData lightTheme = ThemeData(
contentTextStyle: const TextStyle(color: Colors.white), contentTextStyle: const TextStyle(color: Colors.white),
), ),
dividerTheme: const DividerThemeData(color: _outlineL, thickness: 1, space: 1), dividerTheme: const DividerThemeData(
color: _outlineL,
thickness: 1,
space: 1,
),
chipTheme: ChipThemeData( chipTheme: ChipThemeData(
backgroundColor: _surfaceRaisedL, backgroundColor: _surfaceRaisedL,
@@ -673,10 +694,7 @@ final ThemeData lightTheme = ThemeData(
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
letterSpacing: -0.2, letterSpacing: -0.2,
), ),
titleMedium: TextStyle( titleMedium: TextStyle(color: Colors.black87, fontWeight: FontWeight.w700),
color: Colors.black87,
fontWeight: FontWeight.w700,
),
bodyLarge: TextStyle(color: Colors.black54, fontSize: 16, height: 1.5), bodyLarge: TextStyle(color: Colors.black54, fontSize: 16, height: 1.5),
bodyMedium: TextStyle(color: Colors.black45, height: 1.4), bodyMedium: TextStyle(color: Colors.black45, height: 1.4),
bodySmall: TextStyle(color: Colors.black38, height: 1.3), bodySmall: TextStyle(color: Colors.black38, height: 1.3),
+28 -57
View File
@@ -13,9 +13,7 @@ class UpdateChecker {
_hasChecked = true; _hasChecked = true;
final updater = AppUpdateUtil( final updater = AppUpdateUtil(serverUrl: "https://updater.brammie15.dev");
serverUrl: "https://updater.brammie15.dev",
);
try { try {
final update = await updater.checkForUpdate(); final update = await updater.checkForUpdate();
@@ -35,10 +33,7 @@ class UpdateChecker {
} }
} }
static void _showUpdateDialog( static void _showUpdateDialog(BuildContext context, UpdateInfo update) {
BuildContext context,
UpdateInfo update,
) {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: !update.mandatory, barrierDismissible: !update.mandatory,
@@ -80,64 +75,40 @@ Future<void> _downloadAndInstall(UpdateInfo update) async {
: "https://updater.brammie15.dev${update.download}"; : "https://updater.brammie15.dev${update.download}";
OtaUpdate() OtaUpdate()
.execute( .execute(
url, url,
destinationFilename: "update.apk", destinationFilename: "update.apk",
sha256checksum: update.sha256, sha256checksum: update.sha256,
) )
.listen( .listen((OtaEvent event) {
(OtaEvent event) { debugPrint("OTA status: ${event.status}");
debugPrint( debugPrint("OTA value: ${event.value}");
"OTA status: ${event.status}",
);
debugPrint( switch (event.status) {
"OTA value: ${event.value}", case OtaStatus.DOWNLOADING:
); final progress = double.tryParse(event.value ?? "0") ?? 0;
switch(event.status) { debugPrint("Downloading ${progress.toStringAsFixed(0)}%");
case OtaStatus.DOWNLOADING: break;
final progress =
double.tryParse(event.value ?? "0") ?? 0;
debugPrint( case OtaStatus.INSTALLING:
"Downloading ${progress.toStringAsFixed(0)}%", debugPrint("Installing update");
); break;
break; case OtaStatus.INSTALLATION_ERROR:
debugPrint("Installation error: ${event.value}");
break;
case OtaStatus.DOWNLOAD_ERROR:
debugPrint("Download error: ${event.value}");
break;
case OtaStatus.INSTALLING: default:
debugPrint( break;
"Installing update", }
); });
break;
case OtaStatus.INSTALLATION_ERROR:
debugPrint(
"Installation error: ${event.value}",
);
break;
case OtaStatus.DOWNLOAD_ERROR:
debugPrint(
"Download error: ${event.value}",
);
break;
default:
break;
}
},
);
} catch (e) { } catch (e) {
debugPrint( debugPrint("OTA update failed: $e");
"OTA update failed: $e",
);
} }
} }
+2 -8
View File
@@ -10,10 +10,7 @@ class BarScreenViewModel extends ChangeNotifier {
final BarTabService barTabService; final BarTabService barTabService;
final InventoryViewModel inventory; final InventoryViewModel inventory;
BarScreenViewModel({ BarScreenViewModel({required this.barTabService, required this.inventory});
required this.barTabService,
required this.inventory,
});
List<BarTab> _tabs = []; List<BarTab> _tabs = [];
String? _selectedTabId; String? _selectedTabId;
@@ -91,10 +88,7 @@ class BarScreenViewModel extends ChangeNotifier {
throw Exception('Product is out of stock.'); throw Exception('Product is out of stock.');
} }
await barTabService.addProductToTab( await barTabService.addProductToTab(tabId: tab.id, product: product);
tabId: tab.id,
product: product,
);
await inventory.decreaseStock(product.id, 1); await inventory.decreaseStock(product.id, 1);
+6 -5
View File
@@ -6,9 +6,7 @@ import '../services/bar_tab_service.dart';
class HistoryViewModel extends ChangeNotifier { class HistoryViewModel extends ChangeNotifier {
final BarTabService barTabService; final BarTabService barTabService;
HistoryViewModel({ HistoryViewModel({required this.barTabService});
required this.barTabService,
});
List<ClosedTab> _closedTabs = []; List<ClosedTab> _closedTabs = [];
bool _isLoading = false; bool _isLoading = false;
@@ -20,8 +18,11 @@ class HistoryViewModel extends ChangeNotifier {
if (_searchQuery.isEmpty) return _closedTabs; if (_searchQuery.isEmpty) return _closedTabs;
return _closedTabs return _closedTabs
.where((tab) => .where(
tab.customerName.toLowerCase().contains(_searchQuery.toLowerCase())) (tab) => tab.customerName.toLowerCase().contains(
_searchQuery.toLowerCase(),
),
)
.toList(); .toList();
} }
+7 -21
View File
@@ -6,9 +6,7 @@ import '../services/product_service.dart';
class InventoryViewModel extends ChangeNotifier { class InventoryViewModel extends ChangeNotifier {
final ProductService productService; final ProductService productService;
InventoryViewModel({ InventoryViewModel({required this.productService});
required this.productService,
});
List<Product> _products = []; List<Product> _products = [];
@@ -19,40 +17,28 @@ class InventoryViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> decreaseStock( Future<void> decreaseStock(String productId, int amount) async {
String productId,
int amount,
) async {
await productService.decreaseStock(productId, amount); await productService.decreaseStock(productId, amount);
final index = _products.indexWhere( final index = _products.indexWhere((product) => product.id == productId);
(product) => product.id == productId,
);
if (index != -1) { if (index != -1) {
_products[index] = _products[index].copyWith( _products[index] = _products[index].copyWith(
stockQuantity: stockQuantity: _products[index].stockQuantity - amount,
_products[index].stockQuantity - amount,
); );
} }
notifyListeners(); notifyListeners();
} }
Future<void> increaseStock( Future<void> increaseStock(String productId, int amount) async {
String productId,
int amount,
) async {
await productService.increaseStock(productId, amount); await productService.increaseStock(productId, amount);
final index = _products.indexWhere( final index = _products.indexWhere((product) => product.id == productId);
(product) => product.id == productId,
);
if (index != -1) { if (index != -1) {
_products[index] = _products[index].copyWith( _products[index] = _products[index].copyWith(
stockQuantity: stockQuantity: _products[index].stockQuantity + amount,
_products[index].stockQuantity + amount,
); );
} }
+1 -4
View File
@@ -8,10 +8,7 @@ class ProductListViewModel extends ChangeNotifier {
final ProductService productService; final ProductService productService;
final InventoryViewModel inventory; final InventoryViewModel inventory;
ProductListViewModel({ ProductListViewModel({required this.productService, required this.inventory});
required this.productService,
required this.inventory,
});
static const List<String> _defaultCategories = [ static const List<String> _defaultCategories = [
'Bier', 'Bier',
+10 -16
View File
@@ -24,8 +24,11 @@ class SettingsViewModel extends ChangeNotifier {
bool get checkingForUpdates => _checkingForUpdates; bool get checkingForUpdates => _checkingForUpdates;
AppSettings get settings => _settings; AppSettings get settings => _settings;
bool get isLoading => _isLoading; bool get isLoading => _isLoading;
bool get hasLoaded => _hasLoaded; bool get hasLoaded => _hasLoaded;
String? get errorMessage => _errorMessage; String? get errorMessage => _errorMessage;
Future<void> ensureLoaded() async { Future<void> ensureLoaded() async {
@@ -66,10 +69,9 @@ class SettingsViewModel extends ChangeNotifier {
} }
Future<void> installUpdate( Future<void> installUpdate(
UpdateInfo update, { UpdateInfo update, {
void Function(double progress)? onProgress, void Function(double progress)? onProgress,
}) async { }) async {
final url = update.download.startsWith("http") final url = update.download.startsWith("http")
? update.download ? update.download
: "https://updater.brammie15.dev${update.download}"; : "https://updater.brammie15.dev${update.download}";
@@ -83,28 +85,20 @@ class SettingsViewModel extends ChangeNotifier {
); );
await for (final event in stream) { await for (final event in stream) {
debugPrint("OTA: ${event.status} ${event.value}");
debugPrint(
"OTA: ${event.status} ${event.value}",
);
if (event.status == OtaStatus.DOWNLOADING) { if (event.status == OtaStatus.DOWNLOADING) {
final progress = final progress = double.tryParse(event.value ?? "0") ?? 0;
double.tryParse(event.value ?? "0") ?? 0;
onProgress?.call(progress / 100); onProgress?.call(progress / 100);
} }
if (event.status == OtaStatus.DOWNLOAD_ERROR) { if (event.status == OtaStatus.DOWNLOAD_ERROR) {
throw Exception( throw Exception("Download failed: ${event.value}");
"Download failed: ${event.value}",
);
} }
if (event.status == OtaStatus.INSTALLATION_ERROR) { if (event.status == OtaStatus.INSTALLATION_ERROR) {
throw Exception( throw Exception("Installation failed: ${event.value}");
"Installation failed: ${event.value}",
);
} }
} }
} }
-1
View File
@@ -22,7 +22,6 @@ class BarScreenView extends StatefulWidget {
} }
class _BarScreenViewState extends State<BarScreenView> { class _BarScreenViewState extends State<BarScreenView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
+1 -4
View File
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:kooltab2/viewmodels/bar_screen_view_model.dart'; import 'package:kooltab2/viewmodels/bar_screen_view_model.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
Future<void> showNewTabDialog(BuildContext context) async { Future<void> showNewTabDialog(BuildContext context) async {
final controller = TextEditingController(); final controller = TextEditingController();
@@ -14,9 +13,7 @@ Future<void> showNewTabDialog(BuildContext context) async {
content: TextField( content: TextField(
controller: controller, controller: controller,
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: const InputDecoration(labelText: 'Customer / group name'),
labelText: 'Customer / group name',
),
onSubmitted: (value) { onSubmitted: (value) {
Navigator.of(dialogContext).pop(value); Navigator.of(dialogContext).pop(value);
}, },
+14 -11
View File
@@ -52,9 +52,12 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
appBar: AppBar( appBar: AppBar(
title: Row( title: Row(
children: [ children: [
IconButton(onPressed: (){ IconButton(
context.go('/bar'); onPressed: () {
}, icon: const Icon(Icons.arrow_back)), context.go('/bar');
},
icon: const Icon(Icons.arrow_back),
),
const SizedBox(width: 5), const SizedBox(width: 5),
const Text('Tab History'), const Text('Tab History'),
], ],
@@ -118,15 +121,15 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
child: viewModel.closedTabs.isEmpty child: viewModel.closedTabs.isEmpty
? _EmptyState() ? _EmptyState()
: ListView.separated( : ListView.separated(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20), padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
itemCount: viewModel.closedTabs.length, itemCount: viewModel.closedTabs.length,
separatorBuilder: (_, _) => const SizedBox(height: 10), separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final closedTab = viewModel.closedTabs[index]; final closedTab = viewModel.closedTabs[index];
return ClosedTabCard(closedTab: closedTab); return ClosedTabCard(closedTab: closedTab);
}, },
), ),
), ),
], ],
); );
+16 -18
View File
@@ -14,11 +14,7 @@ class PinEntryView extends StatefulWidget {
final PinEntryMode mode; final PinEntryMode mode;
final VoidCallback? onSuccess; final VoidCallback? onSuccess;
const PinEntryView({ const PinEntryView({super.key, required this.mode, this.onSuccess});
super.key,
required this.mode,
this.onSuccess,
});
@override @override
State<PinEntryView> createState() => _PinEntryViewState(); State<PinEntryView> createState() => _PinEntryViewState();
@@ -167,9 +163,9 @@ class _PinEntryViewState extends State<PinEntryView> {
height: 20, height: 20,
child: _localError != null child: _localError != null
? Text( ? Text(
_localError!, _localError!,
style: TextStyle(color: scheme.error, fontSize: 13), style: TextStyle(color: scheme.error, fontSize: 13),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
const Spacer(flex: 2), const Spacer(flex: 2),
@@ -210,7 +206,9 @@ class _PinDots extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
color: isFilled ? scheme.primary : Colors.transparent, color: isFilled ? scheme.primary : Colors.transparent,
border: Border.all( border: Border.all(
color: isFilled ? scheme.primary : scheme.onSurface.withValues(alpha: 0.3), color: isFilled
? scheme.primary
: scheme.onSurface.withValues(alpha: 0.3),
width: 1.4, width: 1.4,
), ),
), ),
@@ -314,15 +312,15 @@ class _KeypadButton extends StatelessWidget {
child: icon != null child: icon != null
? Icon(icon, color: scheme.onSurface.withValues(alpha: 0.8)) ? Icon(icon, color: scheme.onSurface.withValues(alpha: 0.8))
: Text( : Text(
label!, label!,
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: onTap == null color: onTap == null
? scheme.onSurface.withValues(alpha: 0.3) ? scheme.onSurface.withValues(alpha: 0.3)
: scheme.onSurface, : scheme.onSurface,
), ),
), ),
), ),
), ),
), ),
+167 -182
View File
@@ -13,10 +13,7 @@ import '../viewmodels/product_list_view_model.dart';
class ProductFormView extends StatefulWidget { class ProductFormView extends StatefulWidget {
final String? productId; final String? productId;
const ProductFormView({ const ProductFormView({super.key, this.productId});
super.key,
this.productId,
});
bool get isEditing => productId != null; bool get isEditing => productId != null;
@@ -60,11 +57,9 @@ class _ProductFormViewState extends State<ProductFormView> {
if (!mounted) return; if (!mounted) return;
if (product == null) { if (product == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
const SnackBar( context,
content: Text('Product not found.'), ).showSnackBar(const SnackBar(content: Text('Product not found.')));
),
);
context.go('/products'); context.go('/products');
return; return;
@@ -102,7 +97,8 @@ class _ProductFormViewState extends State<ProductFormView> {
} }
final extension = path.extension(pickedFile.path); final extension = path.extension(pickedFile.path);
final fileName = 'product_${DateTime.now().millisecondsSinceEpoch}$extension'; final fileName =
'product_${DateTime.now().millisecondsSinceEpoch}$extension';
final newPath = path.join(imagesDirectory.path, fileName); final newPath = path.join(imagesDirectory.path, fileName);
final copiedFile = await File(pickedFile.path).copy(newPath); final copiedFile = await File(pickedFile.path).copy(newPath);
@@ -139,19 +135,15 @@ class _ProductFormViewState extends State<ProductFormView> {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
if (_imagePath == null || _imagePath!.isEmpty) { if (_imagePath == null || _imagePath!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
const SnackBar( context,
content: Text('Choose a product image.'), ).showSnackBar(const SnackBar(content: Text('Choose a product image.')));
),
);
return; return;
} }
if (_selectedCategory == null) { if (_selectedCategory == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text('Please select a category.')),
content: Text('Please select a category.'),
),
); );
return; return;
} }
@@ -239,43 +231,38 @@ class _ProductFormViewState extends State<ProductFormView> {
height: 220, height: 220,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(color: Theme.of(context).colorScheme.outline),
color: Theme.of(context).colorScheme.outline,
),
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: hasImage child: hasImage
? Stack( ? Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
Image.file( Image.file(File(_imagePath!), fit: BoxFit.fitHeight),
File(_imagePath!), Positioned(
fit: BoxFit.fitHeight, right: 12,
), bottom: 12,
Positioned( child: FilledButton.icon(
right: 12, onPressed: _pickImage,
bottom: 12, icon: const Icon(Icons.image),
child: FilledButton.icon( label: const Text('Change image'),
onPressed: _pickImage, ),
icon: const Icon(Icons.image), ),
label: const Text('Change image'), ],
), )
),
],
)
: Center( : Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.add_photo_alternate_outlined, size: 48), const Icon(Icons.add_photo_alternate_outlined, size: 48),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'Choose product image', 'Choose product image',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
),
],
),
), ),
],
),
),
), ),
); );
} }
@@ -301,151 +288,149 @@ class _ProductFormViewState extends State<ProductFormView> {
), ),
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
body: _isLoading body: _isLoading
? const Center( ? const Center(child: CircularProgressIndicator())
child: CircularProgressIndicator(),
)
: Center( : Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
child: Form( child: Form(
key: _formKey, key: _formKey,
child: ListView( child: ListView(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
children: [ children: [
_buildImagePicker(context), _buildImagePicker(context),
const SizedBox(height: 24), const SizedBox(height: 24),
TextFormField( TextFormField(
controller: _nameController, controller: _nameController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Product name', labelText: 'Product name',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
validator: (value) { validator: (value) {
if (value == null || value.trim().isEmpty) { if (value == null || value.trim().isEmpty) {
return 'Enter a product name.'; return 'Enter a product name.';
} }
return null; return null;
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Consumer<ProductListViewModel>( Consumer<ProductListViewModel>(
builder: (context, viewModel, child) { builder: (context, viewModel, child) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return DropdownMenu<String>( return DropdownMenu<String>(
width: constraints.maxWidth, width: constraints.maxWidth,
initialSelection: _selectedCategory, initialSelection: _selectedCategory,
enableFilter: true, enableFilter: true,
enableSearch: true, enableSearch: true,
controller: _categoryController, controller: _categoryController,
requestFocusOnTap: true, requestFocusOnTap: true,
label: const Text('Category'), label: const Text('Category'),
hintText: 'Select a category', hintText: 'Select a category',
dropdownMenuEntries: viewModel.categories dropdownMenuEntries: viewModel.categories
.map( .map(
(category) => DropdownMenuEntry<String>( (category) => DropdownMenuEntry<String>(
value: category, value: category,
label: category, label: category,
), ),
) )
.toList(), .toList(),
onSelected: (value) { onSelected: (value) {
setState(() { setState(() {
_selectedCategory = value; _selectedCategory = value;
}); });
}, },
); );
}, },
); );
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _priceController, controller: _priceController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Price', labelText: 'Price',
prefixText: '', prefixText: '',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
keyboardType: const TextInputType.numberWithOptions( keyboardType: const TextInputType.numberWithOptions(
decimal: true, decimal: true,
), ),
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
validator: (value) { validator: (value) {
if (value == null || value.trim().isEmpty) { if (value == null || value.trim().isEmpty) {
return 'Enter a price.'; return 'Enter a price.';
} }
final normalized = value.replaceAll(',', '.'); final normalized = value.replaceAll(',', '.');
final price = double.tryParse(normalized); final price = double.tryParse(normalized);
if (price == null || price < 0) { if (price == null || price < 0) {
return 'Enter a valid price.'; return 'Enter a valid price.';
} }
return null; return null;
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _stockController, controller: _stockController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Current stock', labelText: 'Current stock',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
validator: (value) { validator: (value) {
final number = int.tryParse(value ?? ''); final number = int.tryParse(value ?? '');
if (number == null || number < 0) { if (number == null || number < 0) {
return 'Enter a valid stock amount.'; return 'Enter a valid stock amount.';
} }
return null; return null;
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _lowStockController, controller: _lowStockController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Low stock warning threshold', labelText: 'Low stock warning threshold',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
validator: (value) { validator: (value) {
final number = int.tryParse(value ?? ''); final number = int.tryParse(value ?? '');
if (number == null || number < 0) { if (number == null || number < 0) {
return 'Enter a valid threshold.'; return 'Enter a valid threshold.';
} }
return null; return null;
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
FilledButton.icon( FilledButton.icon(
onPressed: _isSaving ? null : _save, onPressed: _isSaving ? null : _save,
icon: _isSaving icon: _isSaving
? const SizedBox( ? const SizedBox(
width: 18, width: 18,
height: 18, height: 18,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
), ),
) )
: const Icon(Icons.save), : const Icon(Icons.save),
label: Text( label: Text(
widget.isEditing ? 'Save changes' : 'Add product', widget.isEditing ? 'Save changes' : 'Add product',
),
),
],
), ),
), ),
], ),
), ),
),
),
),
); );
} }
} }
+40 -24
View File
@@ -46,7 +46,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
} }
Future<void> _loadVersion() async { Future<void> _loadVersion() async {
final version = await currentVersion(); final version = await currentVersion();
if (!mounted) return; if (!mounted) return;
@@ -87,7 +87,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
void _showError(String message) { void _showError(String message) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
} }
Future<void> _enablePin() async { Future<void> _enablePin() async {
@@ -168,8 +170,10 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
), ),
body: Builder( body: Builder(
builder: (context) { builder: (context) {
final isLoading = settingsViewModel.isLoading || pinLockViewModel.isLoading; final isLoading =
final hasLoaded = settingsViewModel.hasLoaded && pinLockViewModel.hasLoaded; settingsViewModel.isLoading || pinLockViewModel.isLoading;
final hasLoaded =
settingsViewModel.hasLoaded && pinLockViewModel.hasLoaded;
if (isLoading && !hasLoaded) { if (isLoading && !hasLoaded) {
return const Center( return const Center(
@@ -231,7 +235,8 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
AppThemeMode.light => 'Light', AppThemeMode.light => 'Light',
AppThemeMode.dark => 'Dark', AppThemeMode.dark => 'Dark',
}), }),
onChanged: (value) => Navigator.pop(context, value), onChanged: (value) =>
Navigator.pop(context, value),
); );
}).toList(), }).toList(),
), ),
@@ -265,14 +270,10 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
child: Center( child: Center(
child: Text( child: Text(
'Version $_appVersion', 'Version $_appVersion',
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall?.copyWith(
.textTheme color: Theme.of(
.bodySmall context,
?.copyWith( ).colorScheme.onSurface.withValues(alpha: 0.5),
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.5),
), ),
), ),
), ),
@@ -306,9 +307,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text('Update check failed: $e')), context,
); ).showSnackBar(SnackBar(content: Text('Update check failed: $e')));
} }
} }
@@ -349,9 +350,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
SnackBar(content: Text("Update failed: $e")), context,
); ).showSnackBar(SnackBar(content: Text("Update failed: $e")));
} }
}, },
child: const Text("Update"), child: const Text("Update"),
@@ -391,7 +392,9 @@ class _SettingsSection extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: scheme.onSurface.withValues(alpha: 0.04), color: scheme.onSurface.withValues(alpha: 0.04),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: scheme.onSurface.withValues(alpha: 0.06)), border: Border.all(
color: scheme.onSurface.withValues(alpha: 0.06),
),
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column(children: children), child: Column(children: children),
@@ -427,12 +430,19 @@ class _SettingsTile extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row( child: Row(
children: [ children: [
Icon(icon, size: 22, color: scheme.onSurface.withValues(alpha: 0.7)), Icon(
icon,
size: 22,
color: scheme.onSurface.withValues(alpha: 0.7),
),
const SizedBox(width: 14), const SizedBox(width: 14),
Expanded( Expanded(
child: Text( child: Text(
title, title,
style: TextStyle(fontWeight: FontWeight.w600, color: scheme.onSurface), style: TextStyle(
fontWeight: FontWeight.w600,
color: scheme.onSurface,
),
), ),
), ),
if (subtitle != null) ...[ if (subtitle != null) ...[
@@ -440,7 +450,10 @@ class _SettingsTile extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
], ],
if (onTap != null) if (onTap != null)
Icon(Icons.chevron_right_rounded, color: scheme.onSurface.withValues(alpha: 0.3)), Icon(
Icons.chevron_right_rounded,
color: scheme.onSurface.withValues(alpha: 0.3),
),
], ],
), ),
), ),
@@ -475,7 +488,10 @@ class _SettingsSwitchTile extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
title, title,
style: TextStyle(fontWeight: FontWeight.w600, color: scheme.onSurface), style: TextStyle(
fontWeight: FontWeight.w600,
color: scheme.onSurface,
),
), ),
), ),
Switch(value: value, onChanged: onChanged), Switch(value: value, onChanged: onChanged),
+15 -7
View File
@@ -98,7 +98,7 @@ class _ClosedTabCardState extends State<ClosedTabCard> {
const Divider(height: 1), const Divider(height: 1),
const SizedBox(height: 8), const SizedBox(height: 8),
...closedTab.items.map( ...closedTab.items.map(
(item) => _ClosedTabItemRow(item: item), (item) => _ClosedTabItemRow(item: item),
), ),
], ],
), ),
@@ -120,10 +120,12 @@ class _ClosedTabItemRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final unitPrice = final unitPrice = NumberFormat.simpleCurrency().format(
NumberFormat.simpleCurrency().format(item.unitPriceInCents / 100); item.unitPriceInCents / 100,
final lineTotal = );
NumberFormat.simpleCurrency().format(item.lineTotalInCents / 100); final lineTotal = NumberFormat.simpleCurrency().format(
item.lineTotalInCents / 100,
);
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Padding( return Padding(
@@ -135,7 +137,10 @@ class _ClosedTabItemRow extends StatelessWidget {
item.productName, item.productName,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontWeight: FontWeight.w600, color: scheme.onSurface), style: TextStyle(
fontWeight: FontWeight.w600,
color: scheme.onSurface,
),
), ),
), ),
Text( Text(
@@ -148,7 +153,10 @@ class _ClosedTabItemRow extends StatelessWidget {
child: Text( child: Text(
lineTotal, lineTotal,
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: TextStyle(fontWeight: FontWeight.w700, color: scheme.onSurface), style: TextStyle(
fontWeight: FontWeight.w700,
color: scheme.onSurface,
),
), ),
), ),
], ],
+13 -21
View File
@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kooltab2/models/product.dart'; import 'package:kooltab2/models/product.dart';
class ProductTile extends StatelessWidget { class ProductTile extends StatelessWidget {
final Product product; final Product product;
final bool enabled; final bool enabled;
@@ -33,7 +32,7 @@ class ProductTile extends StatelessWidget {
final outOfStock = product.stockQuantity <= 0; final outOfStock = product.stockQuantity <= 0;
final lowStock = final lowStock =
product.stockQuantity > 0 && product.stockQuantity > 0 &&
product.stockQuantity <= product.lowStockThreshold; // adjust name product.stockQuantity <= product.lowStockThreshold; // adjust name
final borderColor = outOfStock final borderColor = outOfStock
? scheme.error.withValues(alpha: 0.7) ? scheme.error.withValues(alpha: 0.7)
@@ -62,16 +61,16 @@ class ProductTile extends StatelessWidget {
opacity: outOfStock ? 0.35 : (enabled ? 1 : 0.4), opacity: outOfStock ? 0.35 : (enabled ? 1 : 0.4),
child: _hasImage child: _hasImage
? Image.file( ? Image.file(
File(product.imagePath!), File(product.imagePath!),
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
) )
: Center( : Center(
child: Icon( child: Icon(
Icons.image_not_supported_outlined, Icons.image_not_supported_outlined,
size: 34, size: 34,
color: scheme.onSurface.withValues(alpha: 0.3), color: scheme.onSurface.withValues(alpha: 0.3),
), ),
), ),
), ),
if (_hasImage) if (_hasImage)
@@ -95,9 +94,7 @@ class ProductTile extends StatelessWidget {
Positioned( Positioned(
top: 10, top: 10,
right: 10, right: 10,
child: _StockBadge( child: _StockBadge(product: product),
product: product,
),
), ),
// Out of stock overlay // Out of stock overlay
@@ -134,9 +131,7 @@ class ProductTile extends StatelessWidget {
class _StockBadge extends StatelessWidget { class _StockBadge extends StatelessWidget {
final Product product; final Product product;
const _StockBadge({ const _StockBadge({required this.product});
required this.product,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -150,10 +145,7 @@ class _StockBadge extends StatelessWidget {
product.stockQuantity <= product.lowStockThreshold; // adjust name product.stockQuantity <= product.lowStockThreshold; // adjust name
return Container( return Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: low color: low
? Colors.amber.withValues(alpha: 0.9) ? Colors.amber.withValues(alpha: 0.9)
-1
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SlideConfirm extends StatefulWidget { class SlideConfirm extends StatefulWidget {
final VoidCallback onConfirmed; final VoidCallback onConfirmed;