fix: navigation is more consistant

This commit is contained in:
2026-08-05 10:57:53 +02:00
parent 2896074ce4
commit 2c734493cf
10 changed files with 159 additions and 78 deletions
+13
View File
@@ -0,0 +1,13 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
extension AppNavigation on BuildContext {
void popOrGo(String fallbackLocation) {
if (canPop()) {
pop();
return;
}
go(fallbackLocation);
}
}
+5 -5
View File
@@ -53,13 +53,13 @@ class _BarScreenViewState extends State<BarScreenView> {
actions: [ actions: [
IconButton( IconButton(
tooltip: l10n.manageProducts, tooltip: l10n.manageProducts,
onPressed: () => context.go('/products'), onPressed: () => context.push('/products'),
icon: const Icon(Icons.inventory_2_outlined), icon: const Icon(Icons.inventory_2_outlined),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
IconButton( IconButton(
tooltip: l10n.tabHistory, tooltip: l10n.tabHistory,
onPressed: () => context.go('/history'), onPressed: () => context.push('/history'),
icon: const Icon(Icons.history_rounded), icon: const Icon(Icons.history_rounded),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
@@ -71,7 +71,7 @@ class _BarScreenViewState extends State<BarScreenView> {
const SizedBox(width: 6), const SizedBox(width: 6),
IconButton( IconButton(
tooltip: l10n.settings, tooltip: l10n.settings,
onPressed: () => context.go('/settings'), onPressed: () => context.push('/settings'),
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings),
), ),
if (context.watch<PinLockViewModel>().isPinSet) ...[ if (context.watch<PinLockViewModel>().isPinSet) ...[
@@ -230,7 +230,7 @@ class _ProductGrid extends StatelessWidget {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
FilledButton.icon( FilledButton.icon(
onPressed: () => context.go('/products/new'), onPressed: () => context.push('/products/new'),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: Text(l10n.addProduct), label: Text(l10n.addProduct),
), ),
@@ -324,7 +324,7 @@ class _ProductGrid extends StatelessWidget {
enabled: hasSelectedTab, enabled: hasSelectedTab,
onTap: () => onProductTap(product), onTap: () => onProductTap(product),
onLongPress: () => onLongPress: () =>
context.go('/products/${product.id}/edit'), context.push('/products/${product.id}/edit'),
); );
}, },
); );
+2 -1
View File
@@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../utils/app_update_util.dart'; import '../utils/app_update_util.dart';
import '../utils/navigation.dart';
import '../viewmodels/dev_menu_view_model.dart'; import '../viewmodels/dev_menu_view_model.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../l10n/app_localizations_helpers.dart'; import '../l10n/app_localizations_helpers.dart';
@@ -58,7 +59,7 @@ class _DevMenuViewState extends State<DevMenuView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
onPressed: () => context.pop(), onPressed: () => context.popOrGo('/bar'),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
title: Text(l10n.devMenu), title: Text(l10n.devMenu),
+2 -1
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../utils/navigation.dart';
class ErrorScreenView extends StatelessWidget { class ErrorScreenView extends StatelessWidget {
const ErrorScreenView({super.key}); const ErrorScreenView({super.key});
@@ -14,7 +15,7 @@ class ErrorScreenView extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
onPressed: () => context.pop(), onPressed: () => context.popOrGo('/bar'),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
title: Text(l10n.errorScreen), title: Text(l10n.errorScreen),
+4 -9
View File
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:kooltab2/views/widgets/closed_tab_card.dart'; import 'package:kooltab2/views/widgets/closed_tab_card.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../app/router.dart'; import '../app/router.dart';
import '../utils/navigation.dart';
import '../viewmodels/history_view_model.dart'; import '../viewmodels/history_view_model.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../l10n/app_localizations_helpers.dart'; import '../l10n/app_localizations_helpers.dart';
@@ -61,16 +61,11 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Row( leading: IconButton(
children: [ onPressed: () => context.popOrGo('/bar'),
IconButton(
onPressed: () => context.go('/bar'),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
const SizedBox(width: 5), title: Text(l10n.tabHistory),
Text(l10n.tabHistory),
],
),
actionsPadding: const EdgeInsets.symmetric(horizontal: 8), actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
actions: [ actions: [
IconButton( IconButton(
+11 -7
View File
@@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -12,6 +11,7 @@ import '../models/product.dart';
import '../viewmodels/product_list_view_model.dart'; import '../viewmodels/product_list_view_model.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../l10n/app_localizations_helpers.dart'; import '../l10n/app_localizations_helpers.dart';
import '../utils/navigation.dart';
class ProductFormView extends StatefulWidget { class ProductFormView extends StatefulWidget {
final String? productId; final String? productId;
@@ -70,7 +70,7 @@ class _ProductFormViewState extends State<ProductFormView> {
context, context,
).showSnackBar(SnackBar(content: Text(l10n.productNotFound))); ).showSnackBar(SnackBar(content: Text(l10n.productNotFound)));
context.go('/products'); _leaveProductForm();
return; return;
} }
@@ -90,10 +90,14 @@ class _ProductFormViewState extends State<ProductFormView> {
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
).showSnackBar(SnackBar(content: Text(l10n.couldNotLoadProduct))); ).showSnackBar(SnackBar(content: Text(l10n.couldNotLoadProduct)));
context.go('/products'); _leaveProductForm();
} }
} }
void _leaveProductForm() {
context.popOrGo('/products');
}
@override @override
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
@@ -214,7 +218,7 @@ class _ProductFormViewState extends State<ProductFormView> {
if (!mounted) return; if (!mounted) return;
setState(() => _isSaving = false); setState(() => _isSaving = false);
context.go('/products'); _leaveProductForm();
} }
Future<void> _delete() async { Future<void> _delete() async {
@@ -241,7 +245,7 @@ class _ProductFormViewState extends State<ProductFormView> {
}, },
); );
if (confirmed != true) return; if (confirmed != true || !mounted) return;
final viewModel = context.read<ProductListViewModel>(); final viewModel = context.read<ProductListViewModel>();
@@ -258,7 +262,7 @@ class _ProductFormViewState extends State<ProductFormView> {
if (!mounted) return; if (!mounted) return;
context.go('/products'); _leaveProductForm();
} }
Widget _buildImagePicker(BuildContext context) { Widget _buildImagePicker(BuildContext context) {
@@ -328,7 +332,7 @@ class _ProductFormViewState extends State<ProductFormView> {
title: Text(title), title: Text(title),
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () => context.go('/products'), onPressed: _leaveProductForm,
), ),
actions: [ actions: [
if (widget.isEditing) if (widget.isEditing)
+6 -10
View File
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../viewmodels/product_list_view_model.dart'; import '../viewmodels/product_list_view_model.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../l10n/app_localizations_helpers.dart'; import '../l10n/app_localizations_helpers.dart';
import '../utils/navigation.dart';
class ProductListView extends StatefulWidget { class ProductListView extends StatefulWidget {
const ProductListView({super.key}); const ProductListView({super.key});
@@ -32,20 +33,15 @@ class _ProductListViewState extends State<ProductListView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Row( leading: IconButton(
children: [ onPressed: () => context.popOrGo('/bar'),
IconButton(
onPressed: () => context.go('/bar'),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
const SizedBox(width: 8), title: Text(l10n.products),
Text(l10n.products),
],
),
), ),
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () => context.go('/products/new'), onPressed: () => context.push('/products/new'),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: Text(l10n.addProduct), label: Text(l10n.addProduct),
), ),
@@ -102,7 +98,7 @@ class _ProductListViewState extends State<ProductListView> {
), ),
), ),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () => context.go('/products/${product.id}/edit'), onTap: () => context.push('/products/${product.id}/edit'),
), ),
); );
}, },
+4 -8
View File
@@ -9,6 +9,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
import '../models/settings.dart'; import '../models/settings.dart';
import '../utils/app_update_util.dart'; import '../utils/app_update_util.dart';
import '../utils/navigation.dart';
import '../viewmodels/pin_lock_view_model.dart'; import '../viewmodels/pin_lock_view_model.dart';
import '../viewmodels/settings_view_model.dart'; import '../viewmodels/settings_view_model.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
@@ -197,16 +198,11 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Row( leading: IconButton(
children: [ onPressed: () => context.popOrGo('/bar'),
IconButton(
onPressed: () => context.go('/bar'),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
const SizedBox(width: 5), title: Text(l10n.settingsTitle),
Text(l10n.settingsTitle),
],
),
), ),
body: Builder( body: Builder(
builder: (context) { builder: (context) {
+10 -4
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../utils/app_update_util.dart'; import '../utils/app_update_util.dart';
import '../utils/navigation.dart';
import '../viewmodels/update_progress_view_model.dart'; import '../viewmodels/update_progress_view_model.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../l10n/app_localizations_helpers.dart'; import '../l10n/app_localizations_helpers.dart';
@@ -60,7 +60,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
this.context.go('/bar'); this.context.popOrGo('/bar');
}, },
child: Text(l10n.later), child: Text(l10n.later),
), ),
@@ -104,7 +104,12 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context); final l10n = AppLocalizations.of(context);
return Scaffold( return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) _showCancelDialog();
},
child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
@@ -139,6 +144,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
); );
}, },
), ),
),
); );
} }
@@ -298,7 +304,7 @@ class _UpdateProgressViewState extends State<UpdateProgressView> {
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
_vm.cancel(); _vm.cancel();
this.context.go('/bar'); this.context.popOrGo('/bar');
}, },
child: Text(l10n.cancelUpdateAction), child: Text(l10n.cancelUpdateAction),
), ),
+69
View File
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:kooltab2/l10n/app_localizations.dart';
import 'package:kooltab2/models/product.dart';
import 'package:kooltab2/views/widgets/product_tile.dart';
void main() {
testWidgets('long-pressing a product tile returns to the bar after edit', (
tester,
) async {
final product = Product(
id: 'product-1',
name: 'Cola',
category: 'Drinks',
stockQuantity: 5,
lowStockThreshold: 1,
priceInCents: 250,
);
final router = GoRouter(
initialLocation: '/bar',
routes: [
GoRoute(
path: '/bar',
builder: (context, state) => Scaffold(
body: ProductTile(
product: product,
enabled: false,
onTap: () {},
onLongPress: () => context.push('/products/${product.id}/edit'),
),
),
),
GoRoute(
path: '/products',
builder: (context, state) => const Scaffold(body: Text('Products')),
routes: [
GoRoute(
path: ':id/edit',
builder: (context, state) =>
const Scaffold(body: Text('Edit product')),
),
],
),
],
);
await tester.pumpWidget(
MaterialApp.router(
routerConfig: router,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
),
);
await tester.pumpAndSettle();
await tester.longPress(find.byType(ProductTile));
await tester.pumpAndSettle();
expect(find.text('Edit product'), findsOneWidget);
router.pop();
await tester.pumpAndSettle();
expect(find.byType(ProductTile), findsOneWidget);
expect(find.text('Edit product'), findsNothing);
});
}