fix: alot

This commit is contained in:
2026-10-19 21:41:34 +02:00
parent 30dd74af90
commit b0074aeb3f
40 changed files with 1979 additions and 405 deletions
+3
View File
@@ -8,6 +8,7 @@ import '../database/app_database.dart';
import '../services/bar_tab_service.dart';
import '../services/default_product_seeder.dart';
import '../services/export_service.dart';
import '../services/remote_sync_service.dart';
import '../viewmodels/bar_screen_view_model.dart';
import '../viewmodels/product_list_view_model.dart';
@@ -38,9 +39,11 @@ class _AppBootstrapState extends State<AppBootstrap> {
context.read<ProductListViewModel>().ensureLoaded();
context.read<BarScreenViewModel>().ensureLoaded();
final syncService = context.read<RemoteSyncService>();
unawaited(
DefaultExportService(
barTabService: context.read<BarTabService>(),
authToken: () => syncService.authToken,
).uploadToServer(),
);
+18 -2
View File
@@ -1,28 +1,44 @@
import 'package:flutter/material.dart';
/// Locks once the app returns after being in the background for at least
/// [gracePeriod], so a quick trip to the image picker or installer doesn't.
class AppLifecycleLockObserver extends WidgetsBindingObserver {
final VoidCallback onLock;
final VoidCallback? onBackground;
final VoidCallback? onForeground;
final bool Function()? shouldLock;
final Duration gracePeriod;
final DateTime Function() _clock;
DateTime? _backgroundedAt;
AppLifecycleLockObserver({
required this.onLock,
this.onBackground,
this.onForeground,
this.shouldLock,
});
this.gracePeriod = const Duration(seconds: 30),
DateTime Function()? clock,
}) : _clock = clock ?? DateTime.now;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.inactive:
break;
case AppLifecycleState.hidden:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
if (shouldLock?.call() ?? true) onLock();
if (_backgroundedAt != null) return;
_backgroundedAt = _clock();
onBackground?.call();
case AppLifecycleState.resumed:
final backgroundedAt = _backgroundedAt;
if (backgroundedAt == null) return;
_backgroundedAt = null;
final away = _clock().difference(backgroundedAt);
if (away >= gracePeriod && (shouldLock?.call() ?? true)) onLock();
onForeground?.call();
}
}
+32 -24
View File
@@ -12,7 +12,6 @@ import '../views/bar/bar_screen_view.dart';
import '../views/dev_menu_view.dart';
import '../views/error_screen_view.dart';
import '../views/history_screen_view.dart';
import '../views/import_names_view.dart';
import '../views/pin_lock_view.dart';
import '../views/product_form_view.dart';
import '../views/product_list_view.dart';
@@ -35,24 +34,18 @@ String? _redirectApp({
}
final location = state.uri.path;
final isConfigured = remoteSyncViewModel.isConfigured;
final returnTo = _returnLocation(state);
if (!isConfigured && location != '/register') {
return '/register';
if (remoteSyncViewModel.isConfigured && location == '/register') {
return '/admin';
}
if (isConfigured && location == '/register') {
return '/bar';
}
if (!isConfigured) return null;
final goingToLock = location == '/lock';
final goingToAdminLock = location == '/admin/lock';
final mustBeOnLock = _mustBeOnLock(pinLockViewModel);
if (mustBeOnLock && !goingToLock) return '/lock';
if (!mustBeOnLock && goingToLock) return '/bar';
if (mustBeOnLock && !goingToLock) return _lockLocation('/lock', state);
if (!mustBeOnLock && goingToLock) return returnTo ?? '/bar';
final isAdminArea = _isAdminArea(location);
final isDebugShortcutArea = _isDebugShortcutArea(location);
@@ -61,14 +54,34 @@ String? _redirectApp({
!isDebugShortcutArea &&
!goingToAdminLock &&
!adminPinViewModel.isUnlocked) {
return '/admin/lock';
return _lockLocation('/admin/lock', state);
}
if (goingToAdminLock && adminPinViewModel.isUnlocked) return '/admin';
if (goingToAdminLock && adminPinViewModel.isUnlocked) {
return returnTo ?? '/admin';
}
return null;
}
/// Sends the user back where they were headed once the PIN is entered.
String _lockLocation(String lockPath, GoRouterState state) {
return Uri(
path: lockPath,
queryParameters: {'from': state.uri.toString()},
).toString();
}
String? _returnLocation(GoRouterState state) {
final from = state.uri.queryParameters['from'];
if (from == null || !from.startsWith('/')) return null;
final path = Uri.parse(from).path;
if (path == '/lock' || path == '/admin/lock') return null;
return from;
}
bool _mustBeOnLock(PinLockViewModel pinLockViewModel) {
final needsPinSetup =
!pinLockViewModel.isPinSet && pinLockViewModel.pinRequired;
@@ -83,6 +96,9 @@ bool _mustBeOnLock(PinLockViewModel pinLockViewModel) {
bool _isAdminArea(String location) {
return location == '/admin' ||
location.startsWith('/admin/') ||
location == '/settings' ||
location == '/dev' ||
location == '/register' ||
location == '/products' ||
location.startsWith('/products/') ||
location == '/history' ||
@@ -135,9 +151,7 @@ GoRouter createAppRouter({
mode: pinLockViewModel.isPinSet
? PinEntryMode.unlock
: PinEntryMode.create,
onSuccess: () {
context.go('/bar');
},
onSuccess: () => context.go(_returnLocation(state) ?? '/bar'),
),
),
@@ -153,19 +167,13 @@ GoRouter createAppRouter({
onVerify: adminPinViewModel.verify,
onSet: adminPinViewModel.setPin,
error: () => adminPinViewModel.error,
onSuccess: () => context.go('/admin'),
onSuccess: () => context.go(_returnLocation(state) ?? '/admin'),
),
),
GoRoute(
path: '/admin',
builder: (context, state) => const AdminScreenView(),
routes: [
GoRoute(
path: 'import',
builder: (context, state) => const ImportNamesView(),
),
],
),
GoRoute(