From 7a6975e146fb8388b1027099feef037c484d258a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=BCllner?= Date: Mon, 23 Feb 2026 08:45:27 +0100 Subject: [PATCH] fix: extract collection dialogs into own StatefulWidgets Same _dependents.isEmpty crash as the password dialog the Add/Already Exists dialogs were sharing the parent context across async DB calls. Now each dialog is an independent widget with its own lifecycle. --- lib/main.dart | 146 +++++++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 61 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 72f6e90..7008cb5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -380,15 +380,12 @@ class HomeScreen extends StatelessWidget { /// Opens the scanner, gets the hw_id, then queries the DB. Future _openScanner(BuildContext context) async { - // 1. Navigate to the scanner screen and wait for the result. final hwId = await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const ScannerScreen()), ); - // User cancelled / went back without selecting an ID. if (hwId == null || !context.mounted) return; - // 2. Query the database. try { final data = await supabase .from('hotwheels') @@ -399,11 +396,15 @@ class HomeScreen extends StatelessWidget { if (!context.mounted) return; if (data != null) { - // ── Car already exists ── - _showAlreadyExistsDialog(context, hwId); + showDialog( + context: context, + builder: (_) => _AlreadyExistsDialog(hwId: hwId), + ); } else { - // ── Car is new — offer to add it ── - _showAddDialog(context, hwId); + showDialog( + context: context, + builder: (_) => _AddCarDialog(hwId: hwId), + ); } } catch (e) { if (!context.mounted) return; @@ -412,62 +413,85 @@ class HomeScreen extends StatelessWidget { ); } } +} - /// Shows an alert: this car is already in the collection. - void _showAlreadyExistsDialog(BuildContext context, String hwId) { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - icon: const Icon(Icons.check_circle, color: Colors.green, size: 48), - title: const Text('Already in Collection!'), - content: Text('$hwId is already in your shared garage.'), - actions: [ - ElevatedButton( - onPressed: () => Navigator.of(ctx).pop(), - child: const Text('Got it'), - ), - ], - ), - ); - } +// ── "Already in Collection" Dialog ──────────────────────────────────── +class _AlreadyExistsDialog extends StatelessWidget { + final String hwId; + const _AlreadyExistsDialog({required this.hwId}); - /// Shows a confirmation dialog to add a new car. - void _showAddDialog(BuildContext context, String hwId) { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - icon: const Icon(Icons.add_circle_outline, color: Colors.deepPurple, size: 48), - title: const Text('New Car Found!'), - content: Text('$hwId is not in your collection yet.\nAdd it now?'), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(), - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () async { - try { - await supabase.from('hotwheels').insert({ - 'hw_id': hwId, - 'user_id': supabase.auth.currentUser!.id, - }); - if (!ctx.mounted) return; - Navigator.of(ctx).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('$hwId added to your collection! 🎉')), - ); - } catch (e) { - if (!ctx.mounted) return; - Navigator.of(ctx).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to add: $e'), backgroundColor: Colors.red), - ); - } - }, - child: const Text('Add to Collection'), - ), - ], - ), + @override + Widget build(BuildContext context) { + return AlertDialog( + icon: const Icon(Icons.check_circle, color: Colors.green, size: 48), + title: const Text('Already in Collection!'), + content: Text('$hwId is already in your shared garage.'), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Got it'), + ), + ], + ); + } +} + +// ── "Add to Collection" Dialog ──────────────────────────────────────── +class _AddCarDialog extends StatefulWidget { + final String hwId; + const _AddCarDialog({required this.hwId}); + + @override + State<_AddCarDialog> createState() => _AddCarDialogState(); +} + +class _AddCarDialogState extends State<_AddCarDialog> { + bool _isAdding = false; + + Future _addCar() async { + setState(() => _isAdding = true); + + try { + await supabase.from('hotwheels').insert({ + 'hw_id': widget.hwId, + 'user_id': supabase.auth.currentUser!.id, + }); + if (!mounted) return; + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${widget.hwId} added to your collection! 🎉')), + ); + } catch (e) { + if (!mounted) return; + setState(() => _isAdding = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to add: $e'), backgroundColor: Colors.red), + ); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + icon: const Icon(Icons.add_circle_outline, color: Colors.deepPurple, size: 48), + title: const Text('New Car Found!'), + content: Text('${widget.hwId} is not in your collection yet.\nAdd it now?'), + actions: [ + TextButton( + onPressed: _isAdding ? null : () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: _isAdding ? null : _addCar, + child: _isAdding + ? const SizedBox( + height: 18, + width: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Add to Collection'), + ), + ], ); } }