feat: enhance error handling; implement copyable SnackBar for user messages across various screens
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import 'api_exception.dart';
|
import 'api_exception.dart';
|
||||||
@@ -21,3 +22,18 @@ String mapErrorToUserMessage(Object error, BuildContext context) {
|
|||||||
}
|
}
|
||||||
return l10n.unexpectedError;
|
return l10n.unexpectedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SnackBar buildCopyableErrorSnackBar(BuildContext context, String message) {
|
||||||
|
return SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: context.l10n.errorDialogCopy,
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: message));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(context.l10n.errorDialogCopied)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSaving = false);
|
if (mounted) setState(() => _isSaving = false);
|
||||||
@@ -133,7 +133,7 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,3 +259,4 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class _AdminDatabasePanelState extends ConsumerState<AdminDatabasePanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isRefreshingCategories = false);
|
if (mounted) setState(() => _isRefreshingCategories = false);
|
||||||
@@ -180,3 +180,4 @@ class _AdminDatabasePanelState extends ConsumerState<AdminDatabasePanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class _AdminPendingProductsPanelState
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _processingId = null);
|
if (mounted) setState(() => _processingId = null);
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isApplying = false);
|
if (mounted) setState(() => _isApplying = false);
|
||||||
@@ -181,7 +181,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isAiRunning = false);
|
if (mounted) setState(() => _isAiRunning = false);
|
||||||
@@ -321,7 +321,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,7 +358,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +380,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isApplying = false);
|
if (mounted) setState(() => _isApplying = false);
|
||||||
@@ -399,7 +399,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,7 +435,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
setState(() => _rowCategorySaving.remove(product.id));
|
setState(() => _rowCategorySaving.remove(product.id));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class _ConsumeInventoryScreenState
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(SnackBar(content: Text(mapErrorToUserMessage(e, context))));
|
.showSnackBar(buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _saving = false);
|
if (mounted) setState(() => _saving = false);
|
||||||
@@ -139,3 +139,4 @@ class _ConsumeInventoryScreenState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class _CreateInventoryScreenState
|
|||||||
if (mounted) setState(() => _loadingProducts = false);
|
if (mounted) setState(() => _loadingProducts = false);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ class _CreateInventoryScreenState
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(SnackBar(content: Text(mapErrorToUserMessage(e, context))));
|
.showSnackBar(buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _saving = false);
|
if (mounted) setState(() => _saving = false);
|
||||||
@@ -326,3 +326,4 @@ class _CreateInventoryScreenState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class _DeleteButton extends ConsumerWidget {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,3 +159,4 @@ class _InfoRow extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(SnackBar(content: Text(mapErrorToUserMessage(e, context))));
|
.showSnackBar(buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _saving = false);
|
if (mounted) setState(() => _saving = false);
|
||||||
@@ -297,3 +297,4 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class _SwipeableInventoryTileState
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _acting = false);
|
if (mounted) setState(() => _acting = false);
|
||||||
@@ -372,7 +372,7 @@ class _DeleteButton extends ConsumerWidget {
|
|||||||
ref.invalidate(inventoryProvider);
|
ref.invalidate(inventoryProvider);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,3 +381,4 @@ class _DeleteButton extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class _MealPlanScreenState extends ConsumerState<MealPlanScreen> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(error, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(error, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
|||||||
_logger.severe('Failed to add item to inventory: $error');
|
_logger.severe('Failed to add item to inventory: $error');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(error, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(error, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
|||||||
_logger.severe('Failed to add pantry item: $error');
|
_logger.severe('Failed to add pantry item: $error');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(error, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(error, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSubmitting = false);
|
if (mounted) setState(() => _isSubmitting = false);
|
||||||
@@ -221,7 +221,7 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
|||||||
_logger.severe('Failed to remove pantry item: $error');
|
_logger.severe('Failed to remove pantry item: $error');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(error, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(error, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,3 +419,4 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSaving = false);
|
if (mounted) setState(() => _isSaving = false);
|
||||||
@@ -288,3 +288,4 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class RecipeDetailScreen extends ConsumerWidget {
|
|||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +281,7 @@ class RecipeDetailScreen extends ConsumerWidget {
|
|||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,7 +353,7 @@ class _DeleteButton extends ConsumerWidget {
|
|||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -697,3 +697,4 @@ class _IngredientPreviewRow extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class RecipeApp extends ConsumerWidget {
|
|||||||
final router = ref.watch(appRouterProvider);
|
final router = ref.watch(appRouterProvider);
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
onGenerateTitle: (context) => context.l10n.appTitle,
|
onGenerateTitle: (context) => context.l10n.appTitle,
|
||||||
|
builder: (context, child) {
|
||||||
|
return SelectionArea(child: child ?? const SizedBox.shrink());
|
||||||
|
},
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user