diff --git a/lib/models/bar_tab.dart b/lib/models/bar_tab.dart index fa98b6e..4e5344b 100644 --- a/lib/models/bar_tab.dart +++ b/lib/models/bar_tab.dart @@ -1,4 +1,5 @@ import 'tab_item.dart'; +import '../utils/collection_utils.dart'; class BarTab { final String id; @@ -40,7 +41,7 @@ class BarTab { status == other.status && openedAt == other.openedAt && closedAt == other.closedAt && - _listEquals(items, other.items); + listEquals(items, other.items); @override int get hashCode => Object.hash( @@ -53,12 +54,4 @@ class BarTab { ); } -bool _listEquals(List? a, List? b) { - if (identical(a, b)) return true; - if (a == null || b == null) return a == b; - if (a.length != b.length) return false; - for (int i = 0; i < a.length; i++) { - if (a[i] != b[i]) return false; - } - return true; -} +// _listEquals moved to utils/collection_utils.dart diff --git a/lib/models/closed_tab.dart b/lib/models/closed_tab.dart index 9c2d60c..7e7a4fe 100644 --- a/lib/models/closed_tab.dart +++ b/lib/models/closed_tab.dart @@ -1,16 +1,18 @@ import 'package:intl/intl.dart'; import 'closed_tab_item.dart'; +import 'payment_method.dart'; +import '../utils/collection_utils.dart'; class ClosedTab { final String id; final String originalTabId; final String customerName; final DateTime closedAt; - final String paymentMethod; - final List items; +final PaymentMethod paymentMethod; - ClosedTab({ + final List items; + const ClosedTab({ required this.id, required this.originalTabId, required this.customerName, @@ -27,13 +29,7 @@ class ClosedTab { String get formattedTotal => NumberFormat.simpleCurrency().format(totalInCents / 100); - String get formattedPaymentMethod { - return switch (paymentMethod) { - 'cash' => 'Cash', - 'payconiq' => 'Payconiq', - _ => 'Cash', - }; - } + String get formattedPaymentMethod => paymentMethod.label; @override bool operator ==(Object other) => @@ -44,7 +40,7 @@ class ClosedTab { customerName == other.customerName && closedAt == other.closedAt && paymentMethod == other.paymentMethod && - _listEquals(items, other.items); + listEquals(items, other.items); @override int get hashCode => Object.hash( @@ -57,12 +53,4 @@ class ClosedTab { ); } -bool _listEquals(List? a, List? b) { - if (identical(a, b)) return true; - if (a == null || b == null) return a == b; - if (a.length != b.length) return false; - for (int i = 0; i < a.length; i++) { - if (a[i] != b[i]) return false; - } - return true; -} \ No newline at end of file +// _listEquals moved to utils/collection_utils.dart \ No newline at end of file diff --git a/lib/models/payment_method.dart b/lib/models/payment_method.dart new file mode 100644 index 0000000..9015387 --- /dev/null +++ b/lib/models/payment_method.dart @@ -0,0 +1,15 @@ +enum PaymentMethod { + cash('cash', 'Cash', 'Cash payment'), + payconiq('payconiq', 'Payconiq', 'Payconiq QR payment'); + + final String value; + final String label; + final String description; + + const PaymentMethod(this.value, this.label, this.description); + + static PaymentMethod fromValue(String value) => PaymentMethod.values.firstWhere( + (e) => e.value == value, + orElse: () => PaymentMethod.cash, + ); +} \ No newline at end of file diff --git a/lib/services/bar_tab_service.dart b/lib/services/bar_tab_service.dart index 63e8f0f..1121454 100644 --- a/lib/services/bar_tab_service.dart +++ b/lib/services/bar_tab_service.dart @@ -5,6 +5,7 @@ import '../database/app_database.dart'; import '../models/bar_tab.dart'; import '../models/closed_tab.dart'; import '../models/closed_tab_item.dart'; +import '../models/payment_method.dart'; import '../models/product.dart'; import '../models/tab_item.dart'; @@ -29,7 +30,7 @@ abstract class BarTabService { /// Archives the tab's current items into history and clears them. /// The tab itself stays open under the same customer name. - Future closeTab(String tabId, {String paymentMethod = 'cash'}); + Future closeTab(String tabId, {PaymentMethod paymentMethod = PaymentMethod.cash}); Future> getClosedTabs(); @@ -234,7 +235,7 @@ class DriftBarTabService implements BarTabService { } @override - Future closeTab(String tabId, {String paymentMethod = 'cash'}) async { + Future closeTab(String tabId, {PaymentMethod paymentMethod = PaymentMethod.cash}) async { await database.transaction(() async { final tabQuery = database.select(database.barTabs) ..where((tab) => tab.id.equals(tabId)); @@ -261,7 +262,7 @@ class DriftBarTabService implements BarTabService { originalTabId: tabId, customerName: tabRow.customerName, closedAt: DateTime.now(), - paymentMethod: Value(paymentMethod), + paymentMethod: Value(paymentMethod.value), ), ); @@ -326,7 +327,7 @@ class DriftBarTabService implements BarTabService { originalTabId: row.originalTabId, customerName: row.customerName, closedAt: row.closedAt, - paymentMethod: row.paymentMethod, + paymentMethod: PaymentMethod.fromValue(row.paymentMethod), items: itemRows.map(_mapClosedItemRow).toList(), ), ); diff --git a/lib/utils/app_update_util.dart b/lib/utils/app_update_util.dart index 9af9913..3e6b0f6 100644 --- a/lib/utils/app_update_util.dart +++ b/lib/utils/app_update_util.dart @@ -83,101 +83,4 @@ class AppUpdateUtil { return UpdateInfo.fromJson(data); } - - // /// Downloads the APK - // Future downloadApk( - // UpdateInfo update, { - // - // Function(double progress)? onProgress, - // }) async { - // final url = update.download.startsWith("http") - // ? update.download - // : "$serverUrl${update.download}"; - // - // final request = http.Request("GET", Uri.parse(url)); - // - // final response = await request.send(); - // - // if (response.statusCode != 200) { - // throw Exception("APK download failed"); - // } - // - // final total = response.contentLength ?? 0; - // - // int received = 0; - // - // final directory = await getTemporaryDirectory(); - // - // final file = File("${directory.path}/update.apk"); - // - // final sink = file.openWrite(); - // - // await for (final chunk in response.stream) { - // sink.add(chunk); - // - // received += chunk.length; - // - // if (total > 0 && onProgress != null) { - // onProgress(received / total); - // } - // } - // - // await sink.close(); - // - // return file; - // } - // - // /// Verifies APK checksum - // Future verifySha256(File file, String expectedHash) async { - // final bytes = await file.readAsBytes(); - // - // final digest = sha256.convert(bytes); - // - // return digest.toString().toLowerCase() == expectedHash.toLowerCase(); - // } - // - // /// Opens Android APK installer - // Future installApk(File file) async { - // final result = - // await OpenFilex.open( - // file.path, - // ); - // - // if(result.type == ResultType.done) { - // debugPrint("APK installer opened successfully"); - // } - // - // - // if (result.type != ResultType.done) { - // throw Exception( - // "Could not open APK installer", - // ); - // } - // } - // - // /// Full update flow - // /// - // /// Returns: - // /// - null if no update exists - // /// - UpdateInfo if update is available - // /// - // Future update({Function(double progress)? onProgress}) async { - // final info = await checkForUpdate(); - // - // if (info == null) { - // return null; - // } - // - // final apk = await downloadApk(info, onProgress: onProgress); - // - // final valid = await verifySha256(apk, info.sha256); - // - // if (!valid) { - // throw Exception("APK checksum mismatch"); - // } - // - // await installApk(apk); - // - // return info; - // } } diff --git a/lib/utils/collection_utils.dart b/lib/utils/collection_utils.dart new file mode 100644 index 0000000..7181695 --- /dev/null +++ b/lib/utils/collection_utils.dart @@ -0,0 +1,9 @@ +bool listEquals(List? a, List? b) { + if (identical(a, b)) return true; + if (a == null || b == null) return a == b; + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + return true; +} \ No newline at end of file diff --git a/lib/viewmodels/bar_screen_view_model.dart b/lib/viewmodels/bar_screen_view_model.dart index 2cfb85a..7260685 100644 --- a/lib/viewmodels/bar_screen_view_model.dart +++ b/lib/viewmodels/bar_screen_view_model.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; +import '../models/payment_method.dart'; import '../models/bar_tab.dart'; import '../models/product.dart'; import '../models/tab_item.dart'; @@ -33,7 +34,8 @@ class BarScreenViewModel extends ChangeNotifier { try { return _tabs.firstWhere((tab) => tab.id == _selectedTabId); - } catch (_) { + } catch (e) { + debugPrint('BarScreenViewModel: error finding selected tab: $e'); return null; } } @@ -58,7 +60,8 @@ class BarScreenViewModel extends ChangeNotifier { !_tabs.any((tab) => tab.id == _selectedTabId)) { _selectedTabId = _tabs.isEmpty ? null : _tabs.first.id; } - } catch (_) { + } catch (e) { + debugPrint('BarScreenViewModel: load error: $e'); _errorMessage = 'Could not load bar screen.'; } finally { _hasLoaded = true; @@ -115,7 +118,7 @@ class BarScreenViewModel extends ChangeNotifier { await _reloadTabs(); } - Future closeSelectedTab({String paymentMethod = 'cash'}) async { + Future closeSelectedTab({PaymentMethod paymentMethod = PaymentMethod.cash}) async { final tab = selectedTab; if (tab == null) return; @@ -125,7 +128,7 @@ class BarScreenViewModel extends ChangeNotifier { await _reloadTabs(); } - Future closeTab(String tabId, {String paymentMethod = 'cash'}) async { + Future closeTab(String tabId, {PaymentMethod paymentMethod = PaymentMethod.cash}) async { await barTabService.closeTab(tabId, paymentMethod: paymentMethod); await _reloadTabs(); diff --git a/lib/viewmodels/dev_menu_view_model.dart b/lib/viewmodels/dev_menu_view_model.dart index 1df5e17..ac1f981 100644 --- a/lib/viewmodels/dev_menu_view_model.dart +++ b/lib/viewmodels/dev_menu_view_model.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:drift/drift.dart'; import 'package:kooltab2/database/app_database.dart'; import 'package:kooltab2/services/bar_tab_service.dart'; +import 'package:kooltab2/models/payment_method.dart'; import 'package:kooltab2/services/pin_lock_service.dart'; import 'package:kooltab2/services/product_service.dart'; import 'package:kooltab2/services/settings_service.dart'; @@ -275,7 +276,7 @@ class DevMenuViewModel extends ChangeNotifier { 'Sam', 'Tina', 'Umar', 'Vera', 'Wes', 'Xena', 'Yves', 'Zara', ]; - const paymentMethods = ['cash', 'payconiq']; + const paymentMethods = [PaymentMethod.cash, PaymentMethod.payconiq]; await database.transaction(() async { for (var i = 0; i < count; i++) { @@ -297,7 +298,7 @@ class DevMenuViewModel extends ChangeNotifier { originalTabId: uuid.v4(), customerName: customer, closedAt: closedAt, - paymentMethod: Value(paymentMethods[(i * 3) % paymentMethods.length]), + paymentMethod: Value(paymentMethods[(i * 3) % paymentMethods.length].value), ), ); diff --git a/lib/viewmodels/history_view_model.dart b/lib/viewmodels/history_view_model.dart index 9945525..1db9596 100644 --- a/lib/viewmodels/history_view_model.dart +++ b/lib/viewmodels/history_view_model.dart @@ -73,7 +73,8 @@ class HistoryViewModel extends ChangeNotifier { _totalCount = results[1] as int; _customerNames = results[2] as List; _offset = _closedTabs.length; - } catch (_) { + } catch (e) { + debugPrint('HistoryViewModel: load error: $e'); _errorMessage = 'Could not load tab history.'; } finally { _hasLoaded = true; @@ -92,7 +93,8 @@ class HistoryViewModel extends ChangeNotifier { final more = await _fetchPage(offset: _offset); _closedTabs = [..._closedTabs, ...more]; _offset = _closedTabs.length; - } catch (_) { + } catch (e) { + debugPrint('HistoryViewModel: loadMore error: $e'); _errorMessage = 'Could not load more tabs.'; } finally { _isLoadingMore = false; diff --git a/lib/viewmodels/pin_lock_view_model.dart b/lib/viewmodels/pin_lock_view_model.dart index 992f9d2..a23d8f7 100644 --- a/lib/viewmodels/pin_lock_view_model.dart +++ b/lib/viewmodels/pin_lock_view_model.dart @@ -56,7 +56,8 @@ class PinLockViewModel extends ChangeNotifier { final settings = await svc.getSettings(); _pinRequired = settings.pinRequired; } - } catch (_) { + } catch (e) { + debugPrint('PinLockViewModel: load error: $e'); _errorMessage = 'Could not check PIN status.'; } finally { _hasLoaded = true; @@ -85,7 +86,8 @@ class PinLockViewModel extends ChangeNotifier { notifyListeners(); return true; - } catch (_) { + } catch (e) { + debugPrint('PinLockViewModel: setPin error: $e'); _errorMessage = 'Could not save PIN.'; notifyListeners(); return false; @@ -108,7 +110,8 @@ class PinLockViewModel extends ChangeNotifier { _errorMessage = 'Incorrect PIN.'; notifyListeners(); return false; - } catch (_) { + } catch (e) { + debugPrint('PinLockViewModel: verify error: $e'); _errorMessage = 'Could not verify PIN.'; notifyListeners(); return false; diff --git a/lib/viewmodels/product_list_view_model.dart b/lib/viewmodels/product_list_view_model.dart index febecfb..ab73b51 100644 --- a/lib/viewmodels/product_list_view_model.dart +++ b/lib/viewmodels/product_list_view_model.dart @@ -48,27 +48,14 @@ class ProductListViewModel extends ChangeNotifier { try { await inventory.load(); - } catch (_) { + } catch (e) { + debugPrint('ProductListViewModel: load error: $e'); _errorMessage = 'Could not load products.'; } finally { _hasLoaded = true; _isLoading = false; notifyListeners(); } - - // try { - // _products = await productService.getProducts(); - // - // debugPrint( - // _products.map((p) => '${p.name}: ${p.stockQuantity}').join('\n'), - // ); - // } catch (_) { - // _errorMessage = 'Could not load products.'; - // } finally { - // _hasLoaded = true; - // _isLoading = false; - // notifyListeners(); - // } } Future addProduct({ diff --git a/lib/viewmodels/settings_view_model.dart b/lib/viewmodels/settings_view_model.dart index e3a7c3c..9d3a115 100644 --- a/lib/viewmodels/settings_view_model.dart +++ b/lib/viewmodels/settings_view_model.dart @@ -44,7 +44,8 @@ class SettingsViewModel extends ChangeNotifier { try { _settings = await settingsService.getSettings(); - } catch (_) { + } catch (e) { + debugPrint('SettingsViewModel: load error: $e'); _errorMessage = 'Could not load settings.'; } finally { _hasLoaded = true; @@ -118,7 +119,8 @@ class SettingsViewModel extends ChangeNotifier { try { await settingsService.saveSettings(updated); - } catch (_) { + } catch (e) { + debugPrint('SettingsViewModel: save error: $e'); _settings = previous; _errorMessage = 'Could not save settings.'; notifyListeners(); diff --git a/lib/views/bar_screen_view.dart b/lib/views/bar_screen_view.dart index aa248ff..79ac7fc 100644 --- a/lib/views/bar_screen_view.dart +++ b/lib/views/bar_screen_view.dart @@ -125,7 +125,14 @@ class _BarScreenViewState extends State { return; } - await viewModel.addProductToSelectedTab(product); + try { + await viewModel.addProductToSelectedTab(product); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$e')), + ); + } }, ), ), diff --git a/lib/views/dialogs/close_tab_dialog.dart b/lib/views/dialogs/close_tab_dialog.dart index a22c2d4..0bebbe3 100644 --- a/lib/views/dialogs/close_tab_dialog.dart +++ b/lib/views/dialogs/close_tab_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; +import '../../models/payment_method.dart'; import '../../viewmodels/bar_screen_view_model.dart'; import '../widgets/slide_confirm.dart'; @@ -11,7 +12,7 @@ Future confirmCloseTab(BuildContext context) async { if (tab == null) return; - String paymentMethod = 'cash'; + PaymentMethod paymentMethod = PaymentMethod.cash; final confirmed = await showDialog( context: context, @@ -66,8 +67,8 @@ Future confirmCloseTab(BuildContext context) async { } class _PaymentPicker extends StatelessWidget { - final String selected; - final ValueChanged onChanged; + final PaymentMethod selected; + final ValueChanged onChanged; const _PaymentPicker({ required this.selected, @@ -75,8 +76,8 @@ class _PaymentPicker extends StatelessWidget { }); static const _options = [ - ('cash', 'Cash'), - ('payconiq', 'Payconiq'), + (PaymentMethod.cash, 'Cash'), + (PaymentMethod.payconiq, 'Payconiq'), ]; @override @@ -96,7 +97,7 @@ class _PaymentPicker extends StatelessWidget { label: Row( mainAxisSize: MainAxisSize.min, children: [ - if (value == 'cash') + if (value == PaymentMethod.cash) const Icon( Icons.attach_money, size: 16, diff --git a/lib/views/widgets/closed_tab_card.dart b/lib/views/widgets/closed_tab_card.dart index 4d4f583..961c3ae 100644 --- a/lib/views/widgets/closed_tab_card.dart +++ b/lib/views/widgets/closed_tab_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:kooltab2/models/closed_tab.dart'; import 'package:kooltab2/models/closed_tab_item.dart'; +import 'package:kooltab2/models/payment_method.dart'; class ClosedTabCard extends StatefulWidget { final ClosedTab closedTab; @@ -15,10 +16,10 @@ class ClosedTabCard extends StatefulWidget { class _ClosedTabCardState extends State { bool _expanded = false; - IconData _paymentIcon(String method) { + IconData _paymentIcon(PaymentMethod method) { return switch (method) { - 'payconiq' => Icons.qr_code_rounded, - _ => Icons.money_rounded, + PaymentMethod.payconiq => Icons.qr_code_rounded, + PaymentMethod.cash => Icons.money_rounded, }; }