fix: formatting
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -33,4 +30,4 @@ class _AppBootstrapState extends State<AppBootstrap> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return widget.child;
|
return widget.child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -160,4 +164,4 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-8
@@ -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) =>
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -36,4 +30,4 @@ class BarTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get isOpen => status == 'open';
|
bool get isOpen => status == 'open';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ class ClosedTab {
|
|||||||
|
|
||||||
String get formattedTotal =>
|
String get formattedTotal =>
|
||||||
NumberFormat.simpleCurrency().format(totalInCents / 100);
|
NumberFormat.simpleCurrency().format(totalInCents / 100);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ class ClosedTabItem {
|
|||||||
});
|
});
|
||||||
|
|
||||||
int get lineTotalInCents => quantity * unitPriceInCents;
|
int get lineTotalInCents => quantity * unitPriceInCents;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ class Product {
|
|||||||
active: active ?? this.active,
|
active: active ?? this.active,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -23,4 +17,4 @@ class AppSettings {
|
|||||||
pinRequired: false,
|
pinRequired: false,
|
||||||
themeMode: AppThemeMode.system,
|
themeMode: AppThemeMode.system,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ class TabItem {
|
|||||||
String get formattedUnitPrice {
|
String get formattedUnitPrice {
|
||||||
return '€${(unitPriceInCents / 100).toStringAsFixed(2)}';
|
return '€${(unitPriceInCents / 100).toStringAsFixed(2)}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,15 +268,17 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-16
@@ -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,13 +694,10 @@ 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),
|
||||||
labelLarge: TextStyle(color: Colors.black87, fontWeight: FontWeight.w700),
|
labelLarge: TextStyle(color: Colors.black87, fontWeight: FontWeight.w700),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+29
-58
@@ -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",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,4 +60,4 @@ class HistoryViewModel extends ChangeNotifier {
|
|||||||
_searchQuery = query;
|
_searchQuery = query;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,43 +17,31 @@ 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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,4 +131,4 @@ class PinLockViewModel extends ChangeNotifier {
|
|||||||
_isUnlocked = false;
|
_isUnlocked = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -120,4 +117,4 @@ class ProductListViewModel extends ChangeNotifier {
|
|||||||
|
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,4 +125,4 @@ class SettingsViewModel extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ Future<void> confirmCloseTab(BuildContext context) async {
|
|||||||
if (confirmed != true) return;
|
if (confirmed != true) return;
|
||||||
|
|
||||||
await viewModel.closeSelectedTab();
|
await viewModel.closeSelectedTab();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
@@ -43,4 +40,4 @@ Future<void> showNewTabDialog(BuildContext context) async {
|
|||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
await context.read<BarScreenViewModel>().createTab(name.trim());
|
await context.read<BarScreenViewModel>().createTab(name.trim());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,18 +312,18 @@ 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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+168
-183
@@ -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',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
@@ -483,4 +499,4 @@ class _SettingsSwitchTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,11 +153,14 @@ 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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -170,4 +162,4 @@ class _StockBadge extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -95,4 +94,4 @@ class _SlideConfirmState extends State<SlideConfirm> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user