diff --git a/lib/app/router.dart b/lib/app/router.dart index 74146f2..9fc199e 100644 --- a/lib/app/router.dart +++ b/lib/app/router.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:kooltab2/views/settings_view.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../viewmodels/pin_lock_view_model.dart'; import '../views/bar_screen_view.dart'; @@ -16,7 +17,7 @@ final RouteObserver routeObserver = RouteObserver(); GoRouter createAppRouter(PinLockViewModel pinLockViewModel) { return GoRouter( initialLocation: '/bar', - observers: [routeObserver], + observers: [routeObserver, SentryNavigatorObserver()], refreshListenable: pinLockViewModel, redirect: (context, state) { if (!pinLockViewModel.hasLoaded) return null; diff --git a/lib/main.dart b/lib/main.dart index e5c766b..b07ea92 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/date_symbol_data_local.dart'; @@ -10,6 +11,7 @@ import 'package:kooltab2/viewmodels/inventory_view_model.dart'; import 'package:kooltab2/viewmodels/pin_lock_view_model.dart'; import 'package:kooltab2/viewmodels/settings_view_model.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'app/app.dart'; import 'app/app_bootstrap.dart'; @@ -41,60 +43,77 @@ Future main() async { final appRouter = createAppRouter(pinLockViewModel); - runApp( - MultiProvider( - providers: [ - Provider.value(value: database), - Provider( - create: (context) => - DriftProductService(database: context.read()), - ), - Provider( - create: (context) => - DriftBarTabService(database: context.read()), - ), - Provider( - create: (context) => - DriftSettingsService(database: context.read()), - ), - ChangeNotifierProvider( - create: (context) => - InventoryViewModel(productService: context.read()) - ..load(), - ), - ChangeNotifierProvider( - create: (context) => ProductListViewModel( - productService: context.read(), - inventory: context.read(), + await SentryFlutter.init( + (options) { + options.dsn = + 'https://2c31c1baf3ee495288ad08a56d7baf0e@glitchtip.brammie15.dev/1'; + options.tracesSampleRate = kReleaseMode ? 0.1 : 1.0; + options.enableAutoSessionTracking = false; + options.attachScreenshot = true; + options.attachViewHierarchy = true; + options.enableLogs = true; + options.attachThreads = true; + + options.replay.onErrorSampleRate = 1.0; + options.replay.sessionSampleRate = 0.05; + }, + appRunner: () => runApp( + MultiProvider( + providers: [ + Provider.value(value: database), + Provider( + create: (context) => + DriftProductService(database: context.read()), ), - ), - ChangeNotifierProvider( - create: (context) => BarScreenViewModel( - barTabService: context.read(), - inventory: context.read(), + Provider( + create: (context) => + DriftBarTabService(database: context.read()), ), - ), - ChangeNotifierProvider( - create: (context) => - HistoryViewModel(barTabService: context.read()), - ), - ChangeNotifierProvider.value(value: pinLockViewModel), - ChangeNotifierProvider( - create: (context) => SettingsViewModel( - settingsService: context.read(), + Provider( + create: (context) => + DriftSettingsService(database: context.read()), ), - ), - ChangeNotifierProvider( - create: (context) => DevMenuViewModel( - database: context.read(), - productService: context.read(), - barTabService: context.read(), - settingsService: context.read(), - pinLockService: PinLockService(), + ChangeNotifierProvider( + create: (context) => InventoryViewModel( + productService: context.read(), + )..load(), ), - ), - ], - child: AppBootstrap(child: KoolTabApp(router: appRouter)), + ChangeNotifierProvider( + create: (context) => ProductListViewModel( + productService: context.read(), + inventory: context.read(), + ), + ), + ChangeNotifierProvider( + create: (context) => BarScreenViewModel( + barTabService: context.read(), + inventory: context.read(), + ), + ), + ChangeNotifierProvider( + create: (context) => + HistoryViewModel(barTabService: context.read()), + ), + ChangeNotifierProvider.value( + value: pinLockViewModel, + ), + ChangeNotifierProvider( + create: (context) => SettingsViewModel( + settingsService: context.read(), + ), + ), + ChangeNotifierProvider( + create: (context) => DevMenuViewModel( + database: context.read(), + productService: context.read(), + barTabService: context.read(), + settingsService: context.read(), + pinLockService: PinLockService(), + ), + ), + ], + child: AppBootstrap(child: KoolTabApp(router: appRouter)), + ), ), ); } \ No newline at end of file diff --git a/lib/utils/app_updater.dart b/lib/utils/app_updater.dart index d4c197a..e2132ed 100644 --- a/lib/utils/app_updater.dart +++ b/lib/utils/app_updater.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:kooltab2/utils/app_update_util.dart'; import 'package:ota_update/ota_update.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'app_config.dart'; @@ -86,34 +87,31 @@ Future _downloadAndInstall(UpdateInfo update) async { ) .listen((OtaEvent event) { debugPrint("OTA status: ${event.status}"); - debugPrint("OTA value: ${event.value}"); switch (event.status) { case OtaStatus.DOWNLOADING: final progress = double.tryParse(event.value ?? "0") ?? 0; - debugPrint("Downloading ${progress.toStringAsFixed(0)}%"); - break; - case OtaStatus.INSTALLING: debugPrint("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; } + }, onError: (e, stack) { + debugPrint("OTA stream error: $e"); + Sentry.captureException(e, stackTrace: stack); }); - } catch (e) { + } catch (e, stack) { debugPrint("OTA update failed: $e"); + Sentry.captureException(e, stackTrace: stack); } } diff --git a/lib/viewmodels/bar_screen_view_model.dart b/lib/viewmodels/bar_screen_view_model.dart index 7260685..b248ee2 100644 --- a/lib/viewmodels/bar_screen_view_model.dart +++ b/lib/viewmodels/bar_screen_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../models/payment_method.dart'; import '../models/bar_tab.dart'; @@ -76,10 +77,16 @@ class BarScreenViewModel extends ChangeNotifier { } Future createTab(String customerName) async { - final tab = await barTabService.createTab(customerName: customerName); - - _selectedTabId = tab.id; - await _reloadTabs(); + try { + final tab = await barTabService.createTab(customerName: customerName); + _selectedTabId = tab.id; + await _reloadTabs(); + } catch (e, stack) { + debugPrint('BarScreenViewModel: createTab error: $e'); + _errorMessage = 'Could not create tab.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + } } Future addProductToSelectedTab(Product product) async { @@ -88,34 +95,52 @@ class BarScreenViewModel extends ChangeNotifier { if (tab == null) return; if (product.stockQuantity <= 0) { - throw Exception('Product is out of stock.'); + _errorMessage = 'Product is out of stock.'; + notifyListeners(); + return; } - await barTabService.addProductToTab( - tabId: tab.id, - product: product, - stockAdjustment: () => inventory.decreaseStock(product.id, 1), - ); + try { + await barTabService.addProductToTab( + tabId: tab.id, + product: product, + stockAdjustment: () => inventory.decreaseStock(product.id, 1), + ); - await _reloadTabs(); + await _reloadTabs(); + } catch (e, stack) { + debugPrint('BarScreenViewModel: addProductToSelectedTab error: $e'); + _errorMessage = 'Could not add product to tab.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future changeItemQuantity(TabItem item, int quantity) async { final difference = quantity - item.quantity; - await barTabService.updateTabItemQuantity( - tabItemId: item.id, - quantity: quantity, - stockAdjustment: () async { - if (difference > 0) { - await inventory.decreaseStock(item.productId, difference); - } else if (difference < 0) { - await inventory.increaseStock(item.productId, -difference); - } - }, - ); + try { + await barTabService.updateTabItemQuantity( + tabItemId: item.id, + quantity: quantity, + stockAdjustment: () async { + if (difference > 0) { + await inventory.decreaseStock(item.productId, difference); + } else if (difference < 0) { + await inventory.increaseStock(item.productId, -difference); + } + }, + ); - await _reloadTabs(); + await _reloadTabs(); + } catch (e, stack) { + debugPrint('BarScreenViewModel: changeItemQuantity error: $e'); + _errorMessage = 'Could not update item quantity.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future closeSelectedTab({PaymentMethod paymentMethod = PaymentMethod.cash}) async { @@ -123,25 +148,46 @@ class BarScreenViewModel extends ChangeNotifier { if (tab == null) return; - await barTabService.closeTab(tab.id, paymentMethod: paymentMethod); - - await _reloadTabs(); + try { + await barTabService.closeTab(tab.id, paymentMethod: paymentMethod); + await _reloadTabs(); + } catch (e, stack) { + debugPrint('BarScreenViewModel: closeSelectedTab error: $e'); + _errorMessage = 'Could not close tab.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future closeTab(String tabId, {PaymentMethod paymentMethod = PaymentMethod.cash}) async { - await barTabService.closeTab(tabId, paymentMethod: paymentMethod); - - await _reloadTabs(); + try { + await barTabService.closeTab(tabId, paymentMethod: paymentMethod); + await _reloadTabs(); + } catch (e, stack) { + debugPrint('BarScreenViewModel: closeTab error: $e'); + _errorMessage = 'Could not close tab.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future _reloadTabs() async { - _tabs = await barTabService.getOpenTabs(); + try { + _tabs = await barTabService.getOpenTabs(); - if (_selectedTabId == null || - !_tabs.any((tab) => tab.id == _selectedTabId)) { - _selectedTabId = _tabs.isEmpty ? null : _tabs.first.id; + if (_selectedTabId == null || + !_tabs.any((tab) => tab.id == _selectedTabId)) { + _selectedTabId = _tabs.isEmpty ? null : _tabs.first.id; + } + + notifyListeners(); + } catch (e, stack) { + debugPrint('BarScreenViewModel: reloadTabs error: $e'); + _errorMessage = 'Could not reload tabs.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); } - - notifyListeners(); } } diff --git a/lib/viewmodels/dev_menu_view_model.dart b/lib/viewmodels/dev_menu_view_model.dart index ac1f981..734bc5a 100644 --- a/lib/viewmodels/dev_menu_view_model.dart +++ b/lib/viewmodels/dev_menu_view_model.dart @@ -6,6 +6,7 @@ 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'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:uuid/uuid.dart'; class DevMenuViewModel extends ChangeNotifier { @@ -255,6 +256,12 @@ class DevMenuViewModel extends ChangeNotifier { _isLoading = false; notifyListeners(); } + + try { + throw Exception('Test GlitchTip error!'); + } catch (exception, stackTrace) { + Sentry.captureException(exception, stackTrace: stackTrace); + } } Future generateMockOrders({int count = 100}) async { diff --git a/lib/viewmodels/inventory_view_model.dart b/lib/viewmodels/inventory_view_model.dart index 803a180..0857d26 100644 --- a/lib/viewmodels/inventory_view_model.dart +++ b/lib/viewmodels/inventory_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../models/product.dart'; import '../services/product_service.dart'; @@ -13,35 +14,53 @@ class InventoryViewModel extends ChangeNotifier { List get products => _products; Future load() async { - _products = await productService.getProducts(); - notifyListeners(); + try { + _products = await productService.getProducts(); + notifyListeners(); + } catch (e, stack) { + debugPrint('InventoryViewModel: load error: $e'); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future decreaseStock(String productId, int amount) async { - await productService.decreaseStock(productId, amount); + try { + await productService.decreaseStock(productId, amount); - final index = _products.indexWhere((product) => product.id == productId); + final index = _products.indexWhere((product) => product.id == productId); - if (index != -1) { - _products[index] = _products[index].copyWith( - stockQuantity: _products[index].stockQuantity - amount, - ); + if (index != -1) { + _products[index] = _products[index].copyWith( + stockQuantity: _products[index].stockQuantity - amount, + ); + } + + notifyListeners(); + } catch (e, stack) { + debugPrint('InventoryViewModel: decreaseStock error: $e'); + Sentry.captureException(e, stackTrace: stack); + rethrow; } - - notifyListeners(); } Future increaseStock(String productId, int amount) async { - await productService.increaseStock(productId, amount); + try { + await productService.increaseStock(productId, amount); - final index = _products.indexWhere((product) => product.id == productId); + final index = _products.indexWhere((product) => product.id == productId); - if (index != -1) { - _products[index] = _products[index].copyWith( - stockQuantity: _products[index].stockQuantity + amount, - ); + if (index != -1) { + _products[index] = _products[index].copyWith( + stockQuantity: _products[index].stockQuantity + amount, + ); + } + + notifyListeners(); + } catch (e, stack) { + debugPrint('InventoryViewModel: increaseStock error: $e'); + Sentry.captureException(e, stackTrace: stack); + rethrow; } - - notifyListeners(); } } diff --git a/lib/viewmodels/product_list_view_model.dart b/lib/viewmodels/product_list_view_model.dart index ab73b51..4a3de57 100644 --- a/lib/viewmodels/product_list_view_model.dart +++ b/lib/viewmodels/product_list_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../models/product.dart'; import '../services/product_service.dart'; @@ -66,26 +67,50 @@ class ProductListViewModel extends ChangeNotifier { required int priceInCents, required String? imagePath, }) async { - await productService.createProduct( - name: name, - category: category, - stockQuantity: stockQuantity, - lowStockThreshold: lowStockThreshold, - priceInCents: priceInCents, - imagePath: imagePath, - ); + try { + await productService.createProduct( + name: name, + category: category, + stockQuantity: stockQuantity, + lowStockThreshold: lowStockThreshold, + priceInCents: priceInCents, + imagePath: imagePath, + ); - await loadProducts(); + await loadProducts(); + } catch (e, stack) { + debugPrint('ProductListViewModel: addProduct error: $e'); + _errorMessage = 'Could not add product.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future updateProduct(Product product) async { - await productService.updateProduct(product); - await loadProducts(); + try { + await productService.updateProduct(product); + await loadProducts(); + } catch (e, stack) { + debugPrint('ProductListViewModel: updateProduct error: $e'); + _errorMessage = 'Could not update product.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future deleteProduct(String id) async { - await productService.deleteProduct(id); - await loadProducts(); + try { + await productService.deleteProduct(id); + await loadProducts(); + } catch (e, stack) { + debugPrint('ProductListViewModel: deleteProduct error: $e'); + _errorMessage = 'Could not delete product.'; + notifyListeners(); + Sentry.captureException(e, stackTrace: stack); + rethrow; + } } Future getProductById(String id) { diff --git a/lib/views/bar_screen_view.dart b/lib/views/bar_screen_view.dart index 79ac7fc..db0d76f 100644 --- a/lib/views/bar_screen_view.dart +++ b/lib/views/bar_screen_view.dart @@ -6,6 +6,7 @@ import 'package:kooltab2/views/dialogs/close_tab_dialog.dart'; import 'package:kooltab2/views/dialogs/new_tab_dialog.dart'; import 'package:kooltab2/views/widgets/product_tile.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../models/bar_tab.dart'; @@ -53,7 +54,7 @@ class _BarScreenViewState extends State { const SizedBox(width: 6), IconButton( tooltip: 'Refresh', - onPressed: viewModel.load, + onPressed: () => viewModel.load(), icon: const Icon(Icons.refresh_rounded), ), const SizedBox(width: 6), @@ -127,7 +128,8 @@ class _BarScreenViewState extends State { try { await viewModel.addProductToSelectedTab(product); - } catch (e) { + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('$e')), @@ -304,7 +306,7 @@ class _TabPanel extends StatefulWidget { final ValueChanged onTabSelected; final Future Function(TabItem item, int quantity) onItemQuantityChanged; final VoidCallback onCloseTabPressed; - final ValueChanged onTabClosed; + final Future Function(String) onTabClosed; const _TabPanel({ required this.tabs, @@ -460,7 +462,7 @@ class _OpenTabsList extends StatelessWidget { final List tabs; final String? selectedTabId; final ValueChanged onTabSelected; - final ValueChanged onTabClosed; + final Future Function(String) onTabClosed; const _OpenTabsList({ required this.tabs, @@ -498,17 +500,25 @@ class _OpenTabsList extends StatelessWidget { extentRatio: 0.6, children: [ SlidableAction( - onPressed: (_) { - onTabSelected(tab.id); - // TODO: rename/edit tab - }, + onPressed: (_) => onTabSelected(tab.id), icon: Icons.edit_outlined, label: 'Edit', backgroundColor: Theme.of(context).colorScheme.secondary, foregroundColor: Theme.of(context).colorScheme.onSecondary, ), SlidableAction( - onPressed: (_) => onTabClosed(tab.id), + onPressed: (_) async { + try { + await onTabClosed(tab.id); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not close tab: $e')), + ); + } + } + }, icon: Icons.close_rounded, label: 'Close', backgroundColor: Theme.of(context).colorScheme.error, @@ -723,7 +733,20 @@ class _TabItemRow extends StatelessWidget { children: [ IconButton( visualDensity: VisualDensity.compact, - onPressed: () => onQuantityChanged(item, item.quantity - 1), + onPressed: () async { + try { + await onQuantityChanged(item, item.quantity - 1); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Could not update quantity: $e'), + ), + ); + } + } + }, icon: const Icon(Icons.remove_rounded, size: 18), ), SizedBox( @@ -736,7 +759,20 @@ class _TabItemRow extends StatelessWidget { ), IconButton( visualDensity: VisualDensity.compact, - onPressed: () => onQuantityChanged(item, item.quantity + 1), + onPressed: () async { + try { + await onQuantityChanged(item, item.quantity + 1); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Could not update quantity: $e'), + ), + ); + } + } + }, icon: const Icon(Icons.add_rounded, size: 18), ), ], diff --git a/lib/views/dialogs/close_tab_dialog.dart b/lib/views/dialogs/close_tab_dialog.dart index 0bebbe3..e761f89 100644 --- a/lib/views/dialogs/close_tab_dialog.dart +++ b/lib/views/dialogs/close_tab_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../../models/payment_method.dart'; import '../../viewmodels/bar_screen_view_model.dart'; @@ -63,7 +64,15 @@ Future confirmCloseTab(BuildContext context) async { if (confirmed != true) return; - await viewModel.closeSelectedTab(paymentMethod: paymentMethod); + try { + await viewModel.closeSelectedTab(paymentMethod: paymentMethod); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not close tab: $e')), + ); + } } class _PaymentPicker extends StatelessWidget { diff --git a/lib/views/dialogs/new_tab_dialog.dart b/lib/views/dialogs/new_tab_dialog.dart index 20371ce..27c2720 100644 --- a/lib/views/dialogs/new_tab_dialog.dart +++ b/lib/views/dialogs/new_tab_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:kooltab2/viewmodels/bar_screen_view_model.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; Future showNewTabDialog(BuildContext context) async { final controller = TextEditingController(); @@ -39,5 +40,13 @@ Future showNewTabDialog(BuildContext context) async { if (!context.mounted) return; - await context.read().createTab(name.trim()); + try { + await context.read().createTab(name.trim()); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not create tab: $e')), + ); + } } diff --git a/lib/views/product_form_view.dart b/lib/views/product_form_view.dart index 14198bd..29fe887 100644 --- a/lib/views/product_form_view.dart +++ b/lib/views/product_form_view.dart @@ -6,6 +6,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../models/product.dart'; import '../viewmodels/product_list_view_model.dart'; @@ -52,30 +53,39 @@ class _ProductFormViewState extends State { return; } - final viewModel = context.read(); - final product = await viewModel.getProductById(widget.productId!); + try { + final viewModel = context.read(); + final product = await viewModel.getProductById(widget.productId!); - if (!mounted) return; + if (!mounted) return; - if (product == null) { + if (product == null) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Product not found.'))); + + context.go('/products'); + return; + } + + _existingProduct = product; + _nameController.text = product.name; + _selectedCategory = product.category; + _priceController.text = (product.priceInCents / 100).toStringAsFixed(2); + _stockController.text = product.stockQuantity.toString(); + _lowStockController.text = product.lowStockThreshold.toString(); + _imagePath = product.imagePath; + _hasImage = product.imagePath != null && product.imagePath!.isNotEmpty; + + setState(() => _isLoading = false); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (!mounted) return; ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Product not found.'))); - + ).showSnackBar(SnackBar(content: Text('Could not load product: $e'))); context.go('/products'); - return; } - - _existingProduct = product; - _nameController.text = product.name; - _selectedCategory = product.category; - _priceController.text = (product.priceInCents / 100).toStringAsFixed(2); - _stockController.text = product.stockQuantity.toString(); - _lowStockController.text = product.lowStockThreshold.toString(); - _imagePath = product.imagePath; - _hasImage = product.imagePath != null && product.imagePath!.isNotEmpty; - - setState(() => _isLoading = false); } @override @@ -159,29 +169,39 @@ class _ProductFormViewState extends State { final name = _nameController.text.trim(); final category = _selectedCategory!; final priceInCents = _parsePriceToCents(_priceController.text); - final stockQuantity = int.parse(_stockController.text); - final lowStockThreshold = int.parse(_lowStockController.text); + final stockQuantity = int.tryParse(_stockController.text) ?? 0; + final lowStockThreshold = int.tryParse(_lowStockController.text) ?? 0; - if (widget.isEditing) { - final updatedProduct = _existingProduct!.copyWith( - name: name, - category: category, - priceInCents: priceInCents, - stockQuantity: stockQuantity, - lowStockThreshold: lowStockThreshold, - imagePath: _imagePath, - ); + try { + if (widget.isEditing) { + final updatedProduct = _existingProduct!.copyWith( + name: name, + category: category, + priceInCents: priceInCents, + stockQuantity: stockQuantity, + lowStockThreshold: lowStockThreshold, + imagePath: _imagePath, + ); - await viewModel.updateProduct(updatedProduct); - } else { - await viewModel.addProduct( - name: name, - category: category, - priceInCents: priceInCents, - stockQuantity: stockQuantity, - lowStockThreshold: lowStockThreshold, - imagePath: _imagePath, + await viewModel.updateProduct(updatedProduct); + } else { + await viewModel.addProduct( + name: name, + category: category, + priceInCents: priceInCents, + stockQuantity: stockQuantity, + lowStockThreshold: lowStockThreshold, + imagePath: _imagePath, + ); + } + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (!mounted) return; + setState(() => _isSaving = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not save product: $e')), ); + return; } if (!mounted) return; @@ -218,7 +238,17 @@ class _ProductFormViewState extends State { if (confirmed != true) return; final viewModel = context.read(); - await viewModel.deleteProduct(widget.productId!); + + try { + await viewModel.deleteProduct(widget.productId!); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not delete product: $e')), + ); + return; + } if (!mounted) return; diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 59d27bd..f202bac 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../models/settings.dart'; import '../utils/app_update_util.dart'; @@ -117,7 +118,13 @@ class _SettingsScreenViewState extends State { } pinLockViewModel.setPinRequired(true); - await context.read().updatePinRequired(true); + + try { + await context.read().updatePinRequired(true); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + _showError('Could not save PIN setting.'); + } } Future _changePin() async { @@ -156,7 +163,13 @@ class _SettingsScreenViewState extends State { } pinLockViewModel.setPinRequired(false); - await context.read().updatePinRequired(false); + + try { + await context.read().updatePinRequired(false); + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); + _showError('Could not save PIN setting.'); + } } @override @@ -317,7 +330,8 @@ class _SettingsScreenViewState extends State { } _showUpdateDialog(update); - } catch (e) { + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); if (!mounted) return; ScaffoldMessenger.of( @@ -360,7 +374,8 @@ class _SettingsScreenViewState extends State { ); }, ); - } catch (e) { + } catch (e, stack) { + Sentry.captureException(e, stackTrace: stack); if (!mounted) return; ScaffoldMessenger.of( diff --git a/logo.psd b/logo.psd new file mode 100644 index 0000000..8f72761 Binary files /dev/null and b/logo.psd differ diff --git a/pubspec.lock b/pubspec.lock index c02a9a4..809aa25 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -516,18 +516,10 @@ packages: dependency: transitive description: name: jni - sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 url: "https://pub.dev" source: hosted - version: "1.0.0" - jni_flutter: - dependency: transitive - description: - name: jni_flutter - sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" - url: "https://pub.dev" - source: hosted - version: "1.0.1" + version: "0.14.2" json_annotation: dependency: transitive description: @@ -708,10 +700,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.2.23" path_provider_foundation: dependency: transitive description: @@ -816,6 +808,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: a84bf3a83b3ce1c89fce28b9f97659e3826df14a73a256952465aac2c5efdf5a + url: "https://pub.dev" + source: hosted + version: "9.25.0" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: c85575266d91f57364e9b4cb522835156ec3c5581c78339dc7fae491cd0c6f76 + url: "https://pub.dev" + source: hosted + version: "9.25.0" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b45fa14..d597f83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: open_filex: ^4.7.0 ota_update: ^7.1.0 flutter_svg: ^2.3.0 + sentry_flutter: ^9.25.0 # permission_handler: ^12.0.3