diff --git a/coffee_maker/lib/main.dart b/coffee_maker/lib/main.dart index b9c30cf3..b94552b4 100644 --- a/coffee_maker/lib/main.dart +++ b/coffee_maker/lib/main.dart @@ -30,12 +30,7 @@ class _DemoAppState extends State { @override Widget build(BuildContext context) { - return CupertinoApp( - // This is needed to make the app working with CupertinoApp. - // For more details on CupertinoApp support, see the "CupertinoApp Support" section in the README file. - localizationsDelegates: const >[ - DefaultMaterialLocalizations.delegate, - ], + return MaterialApp( scrollBehavior: const CustomScrollBehavior(), debugShowCheckedModeBanner: false, home: Theme( diff --git a/coffee_maker_navigator_2/lib/app/app.dart b/coffee_maker_navigator_2/lib/app/app.dart index 9166f4d9..33953937 100644 --- a/coffee_maker_navigator_2/lib/app/app.dart +++ b/coffee_maker_navigator_2/lib/app/app.dart @@ -19,6 +19,8 @@ class CoffeeMakerApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: AppThemeData.themeData(context), routerDelegate: appLevelDependencyContainer.appRouterDelegate, + routeInformationParser: + appLevelDependencyContainer.appRouteInformationParser, backButtonDispatcher: appLevelDependencyContainer.backButtonDispatcher, ), diff --git a/coffee_maker_navigator_2/lib/app/di/coffee_maker_app_level_dependency_container.dart b/coffee_maker_navigator_2/lib/app/di/coffee_maker_app_level_dependency_container.dart index fc81be10..512a5cff 100644 --- a/coffee_maker_navigator_2/lib/app/di/coffee_maker_app_level_dependency_container.dart +++ b/coffee_maker_navigator_2/lib/app/di/coffee_maker_app_level_dependency_container.dart @@ -2,6 +2,7 @@ import 'package:coffee_maker_navigator_2/app/app_lifecycle/domain/app_lifecyle_s import 'package:coffee_maker_navigator_2/app/auth/data/local/auth_local_data_source.dart'; import 'package:coffee_maker_navigator_2/app/auth/data/repository/auth_repository.dart'; import 'package:coffee_maker_navigator_2/app/auth/domain/auth_service.dart'; +import 'package:coffee_maker_navigator_2/app/router/view/app_route_information_parser.dart'; import 'package:coffee_maker_navigator_2/features/onboarding/data/local/onboarding_local_data_source.dart'; import 'package:coffee_maker_navigator_2/features/onboarding/data/repository/onboarding_repository.dart'; import 'package:coffee_maker_navigator_2/features/onboarding/domain/onboarding_service.dart'; @@ -28,10 +29,13 @@ class CoffeeMakerAppLevelDependencyContainer late final _appLifeCycleService = _createAppLifeCycleService(); late final _appRouterDelegate = _createAppRouterDelegate(); + late final _appRouteInformationParser = _createAppRouteInformationParser(); late final _backButtonDispatcher = _createRootBackButtonDispatcher(); late final _routerViewModel = _createRouterViewModel(); AppRouterDelegate get appRouterDelegate => _appRouterDelegate; + AppRouteInformationParser get appRouteInformationParser => + _appRouteInformationParser; BackButtonDispatcher get backButtonDispatcher => _backButtonDispatcher; RouterViewModel get routerViewModel => _routerViewModel; AuthService get authService => _authService; @@ -69,7 +73,11 @@ class CoffeeMakerAppLevelDependencyContainer } AppRouterDelegate _createAppRouterDelegate() { - return AppRouterDelegate(); + return AppRouterDelegate(routerViewModel); + } + + AppRouteInformationParser _createAppRouteInformationParser() { + return const AppRouteInformationParser(); } RootBackButtonDispatcher _createRootBackButtonDispatcher() { diff --git a/coffee_maker_navigator_2/lib/app/router/entities/app_navigation_stack.dart b/coffee_maker_navigator_2/lib/app/router/entities/app_navigation_stack.dart new file mode 100644 index 00000000..8aa04e18 --- /dev/null +++ b/coffee_maker_navigator_2/lib/app/router/entities/app_navigation_stack.dart @@ -0,0 +1,59 @@ +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_page.dart'; +import 'package:coffee_maker_navigator_2/features/orders/domain/entities/coffee_maker_step.dart'; +import 'package:equatable/equatable.dart'; + +class AppNavigationStack extends Equatable { + final List pages; + + const AppNavigationStack({required this.pages}); + + @override + List get props => [pages]; + + AppRoutePage get lastPage => pages.last; + + factory AppNavigationStack.bootstrapStack() { + return const AppNavigationStack( + pages: [BootstrapRoutePage()], + ); + } + + factory AppNavigationStack.ordersStack({ + CoffeeMakerStep? step, + String? coffeeOrderId, + bool shouldShowOnboardingModal = false, + }) { + return AppNavigationStack( + pages: [ + const OrdersRoutePage(), + if (shouldShowOnboardingModal) const OnboardingModalRoutePage(), + if (step == CoffeeMakerStep.grind && coffeeOrderId != null) + GrindCoffeeModalRoutePage( + coffeeOrderId: coffeeOrderId, + ), + if (step == CoffeeMakerStep.addWater && coffeeOrderId != null) + AddWaterRoutePage(coffeeOrderId), + if (step == CoffeeMakerStep.ready && coffeeOrderId != null) + ReadyCoffeeModalRoutePage( + coffeeOrderId: coffeeOrderId, + ), + ], + ); + } + + factory AppNavigationStack.loginStack() { + return const AppNavigationStack( + pages: [LoginRoutePage()], + ); + } + + factory AppNavigationStack.tutorialsStack([CoffeeMakerStep? step]) { + return AppNavigationStack( + pages: [ + const OrdersRoutePage(), + const TutorialsRoutePage(), + if (step != null) SingleTutorialRoutePage(step), + ], + ); + } +} diff --git a/coffee_maker_navigator_2/lib/app/router/entities/app_route_configuration.dart b/coffee_maker_navigator_2/lib/app/router/entities/app_route_configuration.dart new file mode 100644 index 00000000..40ff705f --- /dev/null +++ b/coffee_maker_navigator_2/lib/app/router/entities/app_route_configuration.dart @@ -0,0 +1,15 @@ +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_path.dart'; + +class AppRouteConfiguration { + final AppRoutePath appRoutePath; + final Map queryParams; + + static const queryParamId = 'id'; + + const AppRouteConfiguration({ + required this.appRoutePath, + this.queryParams = const {}, + }); + + Uri toUri() => Uri(path: appRoutePath.path, queryParameters: queryParams); +} diff --git a/coffee_maker_navigator_2/lib/app/router/entities/app_route_page.dart b/coffee_maker_navigator_2/lib/app/router/entities/app_route_page.dart index 98993a36..9d49c2db 100644 --- a/coffee_maker_navigator_2/lib/app/router/entities/app_route_page.dart +++ b/coffee_maker_navigator_2/lib/app/router/entities/app_route_page.dart @@ -1,10 +1,13 @@ -import 'package:coffee_maker_navigator_2/app/router/entities/app_route_settings_name.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_configuration.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_path.dart'; import 'package:coffee_maker_navigator_2/features/add_water/ui/view/add_water_screen.dart'; import 'package:coffee_maker_navigator_2/features/login/ui/view/login_screen.dart'; import 'package:coffee_maker_navigator_2/features/onboarding/ui/view/onboarding_modal_sheet_page.dart'; +import 'package:coffee_maker_navigator_2/features/orders/di/orders_dependency_container.dart'; import 'package:coffee_maker_navigator_2/features/orders/domain/entities/coffee_maker_step.dart'; import 'package:coffee_maker_navigator_2/features/orders/ui/view/modal_pages/grind/grind_or_reject_modal_page.dart'; import 'package:coffee_maker_navigator_2/features/orders/ui/view/modal_pages/grind/reject_order_modal_page.dart'; +import 'package:coffee_maker_navigator_2/features/orders/ui/view/modal_pages/not_found/order_not_found_modal.dart'; import 'package:coffee_maker_navigator_2/features/orders/ui/view/modal_pages/ready/offer_recommendation_modal_page.dart'; import 'package:coffee_maker_navigator_2/features/orders/ui/view/modal_pages/ready/serve_or_offer_modal_page.dart'; import 'package:coffee_maker_navigator_2/features/orders/ui/view/orders_screen.dart'; @@ -12,17 +15,27 @@ import 'package:coffee_maker_navigator_2/features/tutorial/view/single_tutorial_ import 'package:coffee_maker_navigator_2/features/tutorial/view/tutorials_screen.dart'; import 'package:coffee_maker_navigator_2/utils/extensions/context_extensions.dart'; import 'package:flutter/material.dart'; +import 'package:wolt_di/wolt_di.dart'; import 'package:wolt_modal_sheet/wolt_modal_sheet.dart'; sealed class AppRoutePage extends Page { - const AppRoutePage({LocalKey? key}) : super(key: key); + const AppRoutePage({LocalKey? key, this.configuration}) : super(key: key); + + final AppRouteConfiguration? configuration; } class BootstrapRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.bootstrap.routeName; + String get name => routeName; + + static const String routeName = 'bootstrap'; - const BootstrapRoutePage() : super(key: const ValueKey('BootstrapRoutePage')); + const BootstrapRoutePage() + : super( + key: const ValueKey('BootstrapRoutePage'), + configuration: + const AppRouteConfiguration(appRoutePath: AppRoutePath.bootstrap), + ); @override Route createRoute(BuildContext context) { @@ -37,11 +50,18 @@ class BootstrapRoutePage extends AppRoutePage { } } -class AuthRoutePage extends AppRoutePage { +class LoginRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.auth.routeName; + String get name => routeName; + + static const String routeName = 'login'; - const AuthRoutePage() : super(key: const ValueKey('AuthRoutePage')); + const LoginRoutePage() + : super( + key: const ValueKey('LoginRoutePage'), + configuration: + const AppRouteConfiguration(appRoutePath: AppRoutePath.login), + ); @override Route createRoute(BuildContext context) { @@ -54,10 +74,16 @@ class AuthRoutePage extends AppRoutePage { class OrdersRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.orders.routeName; + String get name => routeName; + + static const String routeName = 'orders'; const OrdersRoutePage([this.initialBottomNavBarTab]) - : super(key: const ValueKey('OrdersRoutePage')); + : super( + key: const ValueKey('OrdersRoutePage'), + configuration: + const AppRouteConfiguration(appRoutePath: AppRoutePath.orders), + ); final CoffeeMakerStep? initialBottomNavBarTab; @@ -74,9 +100,18 @@ class OrdersRoutePage extends AppRoutePage { class AddWaterRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.addWater.routeName; + String get name => routeName; + + static const String routeName = 'addWater'; - AddWaterRoutePage(this.coffeeOrderId) : super(key: ValueKey(coffeeOrderId)); + AddWaterRoutePage(this.coffeeOrderId) + : super( + key: ValueKey(coffeeOrderId), + configuration: AppRouteConfiguration( + appRoutePath: AppRoutePath.addWater, + queryParams: {AppRoutePath.queryParamId: coffeeOrderId}, + ), + ); final String coffeeOrderId; @@ -84,7 +119,9 @@ class AddWaterRoutePage extends AppRoutePage { Route createRoute(BuildContext context) { return MaterialPageRoute( builder: (context) { - return AddWaterScreen(coffeeOrderId: coffeeOrderId); + return AddWaterScreen( + coffeeOrderId: coffeeOrderId, + ); }, settings: this, ); @@ -93,10 +130,17 @@ class AddWaterRoutePage extends AppRoutePage { class SingleTutorialRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.singleTutorial.routeName; + String get name => routeName; + + static const String routeName = 'singleTutorial'; SingleTutorialRoutePage(this.coffeeMakerStep) - : super(key: ValueKey(coffeeMakerStep)); + : super( + key: ValueKey(coffeeMakerStep), + configuration: AppRouteConfiguration( + appRoutePath: AppRoutePath.fromCoffeeMakerStep(coffeeMakerStep), + ), + ); final CoffeeMakerStep coffeeMakerStep; @@ -112,9 +156,16 @@ class SingleTutorialRoutePage extends AppRoutePage { class TutorialsRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.tutorials.routeName; + String get name => routeName; + + static const String routeName = 'tutorials'; - const TutorialsRoutePage() : super(key: const ValueKey('TutorialsRoutePage')); + const TutorialsRoutePage() + : super( + key: const ValueKey('TutorialsRoutePage'), + configuration: + const AppRouteConfiguration(appRoutePath: AppRoutePath.tutorials), + ); @override Route createRoute(BuildContext context) { @@ -127,10 +178,16 @@ class TutorialsRoutePage extends AppRoutePage { class OnboardingModalRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.onboarding.routeName; + String get name => routeName; + + static const String routeName = 'onboarding'; const OnboardingModalRoutePage() - : super(key: const ValueKey('OnboardingRoutePage')); + : super( + key: const ValueKey('OnboardingRoutePage'), + configuration: const AppRouteConfiguration( + appRoutePath: AppRoutePath.onboarding), + ); @override Route createRoute(BuildContext context) { @@ -149,33 +206,54 @@ class OnboardingModalRoutePage extends AppRoutePage { class GrindCoffeeModalRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.onboarding.routeName; + String get name => routeName; + + static const String routeName = 'grindCoffee'; final String coffeeOrderId; - final VoidCallback onCoffeeOrderGrindCompleted; - final VoidCallback onCoffeeOrderRejected; - GrindCoffeeModalRoutePage({ - required this.coffeeOrderId, - required this.onCoffeeOrderGrindCompleted, - required this.onCoffeeOrderRejected, - }) : super(key: ValueKey('GrindCoffeeModalRoutePage-$coffeeOrderId')); + GrindCoffeeModalRoutePage({required this.coffeeOrderId}) + : super( + key: ValueKey('GrindCoffeeModalRoutePage-$coffeeOrderId'), + configuration: AppRouteConfiguration( + appRoutePath: AppRoutePath.grindCoffeeModal, + queryParams: {AppRoutePath.queryParamId: coffeeOrderId}, + ), + ); @override Route createRoute(BuildContext context) { + final viewModel = + DependencyInjector.container(context) + .createViewModel(); return WoltModalSheetRoute( settings: this, pageListBuilderNotifier: ValueNotifier( - (context) => [ - GrindOrRejectModalPage( - coffeeOrderId: coffeeOrderId, - onCoffeeOrderGrindCompleted: onCoffeeOrderGrindCompleted, - ), - RejectOrderModalPage( - coffeeOrderId: coffeeOrderId, - onCoffeeOrderRejected: onCoffeeOrderRejected, - ), - ], + (context) { + final orderExists = viewModel.orderExists( + coffeeOrderId, + CoffeeMakerStep.grind, + ); + return [ + if (orderExists) ...[ + GrindOrRejectModalPage( + coffeeOrderId: coffeeOrderId, + onCoffeeOrderGrindCompleted: () { + viewModel.onOrderStatusChange( + coffeeOrderId, CoffeeMakerStep.addWater); + context.routerViewModel.onGrindCoffeeCompleted(); + }), + RejectOrderModalPage( + coffeeOrderId: coffeeOrderId, + onCoffeeOrderRejected: () { + viewModel.onOrderStatusChange(coffeeOrderId); + context.routerViewModel.onGrindCoffeeCompleted(); + }, + ), + ] else + OrderNotFoundModal(coffeeOrderId, CoffeeMakerStep.grind), + ]; + }, ), ); } @@ -183,32 +261,53 @@ class GrindCoffeeModalRoutePage extends AppRoutePage { class ReadyCoffeeModalRoutePage extends AppRoutePage { @override - String get name => RouteSettingsName.readyCoffeeModal.routeName; + String get name => routeName; + + static const String routeName = 'readyCoffee'; final String coffeeOrderId; - final VoidCallback onCoffeeOrderServed; - ReadyCoffeeModalRoutePage({ - required this.coffeeOrderId, - required this.onCoffeeOrderServed, - }) : super(key: ValueKey('ReadyCoffeeModalRoutePage-$coffeeOrderId')); + ReadyCoffeeModalRoutePage({required this.coffeeOrderId}) + : super( + key: ValueKey('ReadyCoffeeModalRoutePage-$coffeeOrderId'), + configuration: AppRouteConfiguration( + appRoutePath: AppRoutePath.readyCoffeeModal, + queryParams: {AppRoutePath.queryParamId: coffeeOrderId}, + ), + ); @override Route createRoute(BuildContext context) { + final viewModel = + DependencyInjector.container(context) + .createViewModel(); return WoltModalSheetRoute( settings: this, - pageListBuilderNotifier: ValueNotifier( - (context) => [ - ServeOrOfferModalPage( - onCoffeeOrderServed, - coffeeOrderId, - ), - OfferRecommendationModalPage.build( - onCoffeeOrderServed, - coffeeOrderId, - ), - ], - ), + pageListBuilderNotifier: ValueNotifier((context) { + final orderExists = viewModel.orderExists( + coffeeOrderId, + CoffeeMakerStep.ready, + ); + return [ + if (orderExists) ...[ + ServeOrOfferModalPage( + coffeeOrderId: coffeeOrderId, + onCoffeeOrderServed: () { + viewModel.onOrderStatusChange(coffeeOrderId); + context.routerViewModel.onReadyCoffeeStepCompleted(); + }, + ), + OfferRecommendationModalPage.build( + coffeeOrderId: coffeeOrderId, + onCoffeeOrderServed: () { + viewModel.onOrderStatusChange(coffeeOrderId); + context.routerViewModel.onReadyCoffeeStepCompleted(); + }, + ), + ] else + OrderNotFoundModal(coffeeOrderId, CoffeeMakerStep.ready), + ]; + }), ); } } diff --git a/coffee_maker_navigator_2/lib/app/router/entities/app_route_path.dart b/coffee_maker_navigator_2/lib/app/router/entities/app_route_path.dart new file mode 100644 index 00000000..9b71b45f --- /dev/null +++ b/coffee_maker_navigator_2/lib/app/router/entities/app_route_path.dart @@ -0,0 +1,45 @@ +import 'package:coffee_maker_navigator_2/features/orders/domain/entities/coffee_maker_step.dart'; + +enum AppRoutePath { + bootstrap('/'), + login('/login'), + orders('/orders'), + tutorials('/tutorials'), + grindTutorial('/tutorials/grind'), + waterTutorial('/tutorials/water'), + readyTutorial('/tutorials/ready'), + addWater('/orders/addWater', [queryParamId]), + grindCoffeeModal('/orders/grind', [queryParamId, queryParamPageIndex]), + readyCoffeeModal('/orders/ready', [queryParamId, queryParamPageIndex]), + unknown('/unknown'), + onboarding('/welcome'); + + final String path; + final List params; + + static const queryParamId = 'id'; + static const queryParamPageIndex = 'pageIndex'; + + const AppRoutePath(this.path, [this.params = const []]); + + static AppRoutePath findFromPageName(String path, + [Iterable params = const []]) { + return AppRoutePath.values.firstWhere( + (element) => + element.path == path && + params.every((e) => element.params.contains(e)), + orElse: () => AppRoutePath.unknown, + ); + } + + static AppRoutePath fromCoffeeMakerStep(CoffeeMakerStep step) { + switch (step) { + case CoffeeMakerStep.grind: + return grindTutorial; + case CoffeeMakerStep.addWater: + return waterTutorial; + case CoffeeMakerStep.ready: + return readyTutorial; + } + } +} diff --git a/coffee_maker_navigator_2/lib/app/router/entities/app_route_settings_name.dart b/coffee_maker_navigator_2/lib/app/router/entities/app_route_settings_name.dart deleted file mode 100644 index 1622c88a..00000000 --- a/coffee_maker_navigator_2/lib/app/router/entities/app_route_settings_name.dart +++ /dev/null @@ -1,22 +0,0 @@ -enum RouteSettingsName { - bootstrap('bootstrap'), - auth('auth'), - orders('orders'), - tutorials('tutorials'), - singleTutorial('singleTutorial'), - addWater('addWater'), - grindCoffeeModal('grindCoffeeModal'), - readyCoffeeModal('readyCoffeeModal'), - onboarding('welcomeModal'); - - final String routeName; - - const RouteSettingsName(this.routeName); - - static RouteSettingsName findFromPageName(String routeSettingsName) { - return RouteSettingsName.values.firstWhere( - (element) => element.routeName == routeSettingsName, - orElse: () => throw StateError('Invalid page name'), - ); - } -} diff --git a/coffee_maker_navigator_2/lib/app/router/view/app_route_information_parser.dart b/coffee_maker_navigator_2/lib/app/router/view/app_route_information_parser.dart new file mode 100644 index 00000000..43f95c6b --- /dev/null +++ b/coffee_maker_navigator_2/lib/app/router/view/app_route_information_parser.dart @@ -0,0 +1,30 @@ +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_configuration.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_path.dart'; +import 'package:flutter/material.dart'; + +typedef QueryParams = Map; + +class AppRouteInformationParser + extends RouteInformationParser { + const AppRouteInformationParser(); + + @override + Future parseRouteInformation( + RouteInformation routeInformation, + ) async { + final uri = routeInformation.uri; + final queryParams = uri.queryParameters; + final appRoutePath = + AppRoutePath.findFromPageName(uri.path, queryParams.keys); + return AppRouteConfiguration( + appRoutePath: appRoutePath, + queryParams: queryParams, + ); + } + + @override + RouteInformation restoreRouteInformation( + AppRouteConfiguration configuration) { + return RouteInformation(uri: configuration.toUri()); + } +} diff --git a/coffee_maker_navigator_2/lib/app/router/view/app_route_observer.dart b/coffee_maker_navigator_2/lib/app/router/view/app_route_observer.dart new file mode 100644 index 00000000..66329fec --- /dev/null +++ b/coffee_maker_navigator_2/lib/app/router/view/app_route_observer.dart @@ -0,0 +1,40 @@ +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_page.dart'; +import 'package:demo_ui_components/demo_ui_components.dart'; +import 'package:flutter/material.dart'; + +class AppRouteObserver extends RouteObserver> { + final ColorScheme colorScheme; + + AppRouteObserver(this.colorScheme); + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + super.didReplace(newRoute: newRoute, oldRoute: oldRoute); + if (newRoute is ModalRoute) { + _updateSystemUIOverlayStyle(newRoute); + } + } + + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + if (route is ModalRoute) { + _updateSystemUIOverlayStyle(route); + } + } + + @override + void didPop(Route route, Route? previousRoute) { + super.didPop(route, previousRoute); + if (previousRoute != null) { + _updateSystemUIOverlayStyle(previousRoute); + } + } + + void _updateSystemUIOverlayStyle(Route route) { + SystemUIAnnotationWrapper.setSystemUIOverlayStyle( + colorScheme, + hasBottomNavigationBar: route.settings.name == OrdersRoutePage.routeName, + ); + } +} diff --git a/coffee_maker_navigator_2/lib/app/router/view/app_router_delegate.dart b/coffee_maker_navigator_2/lib/app/router/view/app_router_delegate.dart index 6c603e0c..0bec3da5 100644 --- a/coffee_maker_navigator_2/lib/app/router/view/app_router_delegate.dart +++ b/coffee_maker_navigator_2/lib/app/router/view/app_router_delegate.dart @@ -1,30 +1,31 @@ import 'dart:async'; -import 'package:coffee_maker_navigator_2/app/router/entities/app_route_settings_name.dart'; -import 'package:coffee_maker_navigator_2/app/router/entities/app_route_page.dart'; -import 'package:coffee_maker_navigator_2/utils/extensions/context_extensions.dart'; -import 'package:demo_ui_components/demo_ui_components.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_configuration.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_path.dart'; +import 'package:coffee_maker_navigator_2/app/router/view/app_route_observer.dart'; +import 'package:coffee_maker_navigator_2/app/router/view_model/router_view_model.dart'; import 'package:flutter/material.dart'; -class AppRouterDelegate extends RouterDelegate with ChangeNotifier { +class AppRouterDelegate extends RouterDelegate + with ChangeNotifier { GlobalKey navigatorKey = GlobalKey(); + final RouterViewModel routerViewModel; + + AppRouterDelegate(this.routerViewModel) { + routerViewModel.navigationStack.addListener(notifyListeners); + } + @override Widget build(BuildContext context) { - return ValueListenableBuilder>( - valueListenable: context.routerViewModel.pages, - builder: (context, pages, __) { - return Navigator( - key: navigatorKey, - pages: pages, - onPopPage: (route, result) { - navigatorKey.currentContext?.routerViewModel - .onPagePoppedImperatively(); - return route.didPop(result); - }, - observers: [_AppRouteObserver(Theme.of(context).colorScheme)], - ); + return Navigator( + key: navigatorKey, + pages: routerViewModel.navigationStack.value.pages, + onPopPage: (route, result) { + routerViewModel.onPagePoppedImperatively(); + return route.didPop(result); }, + observers: [AppRouteObserver(Theme.of(context).colorScheme)], ); } @@ -34,54 +35,22 @@ class AppRouterDelegate extends RouterDelegate with ChangeNotifier { Future popRoute() { final currentContext = navigatorKey.currentContext; if (currentContext != null) { - return currentContext.routerViewModel - .onPagePoppedWithOperatingSystemIntent(); + return routerViewModel.onPagePoppedWithOperatingSystemIntent(); } return Future.value(false); } @override - // ignore: no-empty-block, nothing to do - Future setNewRoutePath(void configuration) async { - /* Do Nothing */ - } -} - -class _AppRouteObserver extends RouteObserver> { - final ColorScheme colorScheme; - - _AppRouteObserver(this.colorScheme); - - @override - void didReplace({Route? newRoute, Route? oldRoute}) { - super.didReplace(newRoute: newRoute, oldRoute: oldRoute); - if (newRoute is ModalRoute) { - _updateSystemUIOverlayStyle(newRoute); - } - } - - @override - void didPush(Route route, Route? previousRoute) { - super.didPush(route, previousRoute); - if (route is ModalRoute) { - _updateSystemUIOverlayStyle(route); - } + AppRouteConfiguration get currentConfiguration { + final lastPage = routerViewModel.navigationStack.value.lastPage; + return lastPage.configuration ?? + const AppRouteConfiguration(appRoutePath: AppRoutePath.bootstrap); } @override - void didPop(Route route, Route? previousRoute) { - super.didPop(route, previousRoute); - if (previousRoute != null) { - _updateSystemUIOverlayStyle(previousRoute); - } - } - - void _updateSystemUIOverlayStyle(Route route) { - SystemUIAnnotationWrapper.setSystemUIOverlayStyle( - colorScheme, - hasBottomNavigationBar: - route.settings.name == RouteSettingsName.orders.routeName, - ); + // ignore: no-empty-block, nothing to do + Future setNewRoutePath(AppRouteConfiguration configuration) async { + routerViewModel.onNewRoutePathSet(configuration); } } diff --git a/coffee_maker_navigator_2/lib/app/router/view_model/router_view_model.dart b/coffee_maker_navigator_2/lib/app/router/view_model/router_view_model.dart index 75f11ca9..b12f42ca 100644 --- a/coffee_maker_navigator_2/lib/app/router/view_model/router_view_model.dart +++ b/coffee_maker_navigator_2/lib/app/router/view_model/router_view_model.dart @@ -1,7 +1,10 @@ import 'dart:async'; import 'package:coffee_maker_navigator_2/app/auth/domain/auth_service.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_navigation_stack.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_configuration.dart'; import 'package:coffee_maker_navigator_2/app/router/entities/app_route_page.dart'; +import 'package:coffee_maker_navigator_2/app/router/entities/app_route_path.dart'; import 'package:coffee_maker_navigator_2/app/ui/widgets/app_navigation_drawer.dart'; import 'package:coffee_maker_navigator_2/features/onboarding/domain/onboarding_service.dart'; import 'package:coffee_maker_navigator_2/features/orders/domain/entities/coffee_maker_step.dart'; @@ -10,10 +13,12 @@ import 'package:flutter/foundation.dart'; class RouterViewModel { final AuthService authService; final OnboardingService onboardingService; - late final ValueNotifier> _pages = - ValueNotifier([const BootstrapRoutePage()]); + late final ValueNotifier _navigationStack = + ValueNotifier(const AppNavigationStack( + pages: [BootstrapRoutePage()], + )); - ValueListenable> get pages => _pages; + ValueListenable get navigationStack => _navigationStack; RouterViewModel({ required this.authService, @@ -25,11 +30,7 @@ class RouterViewModel { void dispose() { authService.authStateListenable .removeListener(_authStateChangeSubscription); - _pages.dispose(); - } - - void onPagePoppedImperatively() { - _popPage(); + _navigationStack.dispose(); } void onDrawerDestinationSelected( @@ -37,13 +38,10 @@ class RouterViewModel { ) { switch (destination) { case AppNavigationDrawerDestination.ordersScreen: - _pages.value = [const OrdersRoutePage()]; + _navigationStack.value = AppNavigationStack.ordersStack(); break; case AppNavigationDrawerDestination.tutorialsScreen: - _pages.value = [ - const OrdersRoutePage(), - const TutorialsRoutePage(), - ]; + _navigationStack.value = AppNavigationStack.tutorialsStack(); break; case AppNavigationDrawerDestination.logOut: authService.logOut(); @@ -51,13 +49,17 @@ class RouterViewModel { } } + void onPagePoppedImperatively() { + _popPage(); + } + /// The pops caused by Android swipe gesture and hardware button /// is handled in here instead of Navigator's onPopPage callback. /// Returning false will cause the entire app to be popped. Future onPagePoppedWithOperatingSystemIntent() { - switch (_pages.value.last) { + switch (_navigationStack.value.lastPage) { case BootstrapRoutePage(): - case AuthRoutePage(): + case LoginRoutePage(): case OrdersRoutePage(): // false means the entire app will be popped. return Future.value(false); @@ -82,69 +84,118 @@ class RouterViewModel { _pushPage(const TutorialsRoutePage()); } - void onAddWaterStepCompleted() { - _popPage(); - } - void onTutorialDetailSelected(CoffeeMakerStep coffeeMakerStep) { _pushPage(SingleTutorialRoutePage(coffeeMakerStep)); } - void onGrindCoffeeStepSelected({ - required String id, - required VoidCallback onCoffeeGrindCompleted, - required VoidCallback onCoffeeGrindRejected, - }) { - _pushPage(GrindCoffeeModalRoutePage( - coffeeOrderId: id, - onCoffeeOrderGrindCompleted: () { - onCoffeeGrindCompleted(); - _popPage(); - }, - onCoffeeOrderRejected: () { - onCoffeeGrindRejected(); - _popPage(); - }, - )); + void onGrindCoffeeStepSelected(String id) { + _pushPage(GrindCoffeeModalRoutePage(coffeeOrderId: id)); + } + + void onGrindCoffeeCompleted() { + _popPage(); } void onAddWaterCoffeeStepSelected(String coffeeOrderId) { _pushPage(AddWaterRoutePage(coffeeOrderId)); } - void onReadyCoffeeStepSelected({ - required String id, - required VoidCallback onCoffeeServed, - }) { - _pushPage(ReadyCoffeeModalRoutePage( - coffeeOrderId: id, - onCoffeeOrderServed: () { - onCoffeeServed(); - _popPage(); - }, - )); + void onAddWaterStepCompleted() { + _popPage(); + } + + void onReadyCoffeeStepSelected(String id) { + _pushPage(ReadyCoffeeModalRoutePage(coffeeOrderId: id)); + } + + void onReadyCoffeeStepCompleted() { + _popPage(); } void _authStateChangeSubscription() { final isLoggedIn = authService.authStateListenable.value ?? false; if (isLoggedIn) { - final shouldShowTutorial = !onboardingService.isTutorialShown(); - _pages.value = [ - const OrdersRoutePage(CoffeeMakerStep.grind), - if (shouldShowTutorial) const OnboardingModalRoutePage(), - ]; + final shouldShowOnboardingModal = !onboardingService.isTutorialShown(); + _navigationStack.value = AppNavigationStack.ordersStack( + shouldShowOnboardingModal: shouldShowOnboardingModal, + ); } else { - _pages.value = [const AuthRoutePage()]; + _navigationStack.value = AppNavigationStack.loginStack(); } } void _pushPage(AppRoutePage page) { - _pages.value = List.of(pages.value)..add(page); + final currentPages = _navigationStack.value.pages; + _navigationStack.value = AppNavigationStack( + pages: List.of(currentPages)..add(page), + ); } void _popPage() { - if (_pages.value.length > 1) { - _pages.value = _pages.value.sublist(0, _pages.value.length - 1); + final pageCount = _navigationStack.value.pages.length; + if (pageCount > 1) { + final poppedList = _navigationStack.value.pages.sublist(0, pageCount - 1); + _navigationStack.value = AppNavigationStack(pages: poppedList); + } + } + + void onNewRoutePathSet(AppRouteConfiguration configuration) { + final coffeeOrderId = configuration.queryParams[AppRoutePath.queryParamId]; + late AppNavigationStack updatedStack; + + switch (configuration.appRoutePath) { + case AppRoutePath.bootstrap: + updatedStack = AppNavigationStack.bootstrapStack(); + break; + case AppRoutePath.login: + updatedStack = AppNavigationStack.loginStack(); + break; + case AppRoutePath.orders: + updatedStack = AppNavigationStack.ordersStack(); + break; + case AppRoutePath.tutorials: + updatedStack = AppNavigationStack.tutorialsStack(); + break; + case AppRoutePath.grindTutorial: + updatedStack = AppNavigationStack.tutorialsStack(CoffeeMakerStep.grind); + break; + case AppRoutePath.waterTutorial: + updatedStack = + AppNavigationStack.tutorialsStack(CoffeeMakerStep.addWater); + break; + case AppRoutePath.readyTutorial: + updatedStack = AppNavigationStack.tutorialsStack(CoffeeMakerStep.ready); + break; + case AppRoutePath.grindCoffeeModal: + updatedStack = AppNavigationStack.ordersStack( + coffeeOrderId: coffeeOrderId, + step: CoffeeMakerStep.grind, + ); + break; + case AppRoutePath.addWater: + updatedStack = AppNavigationStack.ordersStack( + coffeeOrderId: coffeeOrderId, + step: CoffeeMakerStep.addWater, + ); + break; + case AppRoutePath.readyCoffeeModal: + updatedStack = AppNavigationStack.ordersStack( + coffeeOrderId: coffeeOrderId, + step: CoffeeMakerStep.ready, + ); + break; + case AppRoutePath.unknown: + final isLoggedIn = authService.authStateListenable.value ?? false; + updatedStack = isLoggedIn + ? AppNavigationStack.ordersStack() + : AppNavigationStack.loginStack(); + break; + case AppRoutePath.onboarding: + updatedStack = AppNavigationStack.ordersStack( + shouldShowOnboardingModal: true, + ); + break; } + _navigationStack.value = updatedStack; } } diff --git a/coffee_maker_navigator_2/lib/assets/images/order_not_found.webp b/coffee_maker_navigator_2/lib/assets/images/order_not_found.webp new file mode 100644 index 00000000..773f0cee Binary files /dev/null and b/coffee_maker_navigator_2/lib/assets/images/order_not_found.webp differ diff --git a/coffee_maker_navigator_2/lib/features/add_water/ui/view/add_water_screen.dart b/coffee_maker_navigator_2/lib/features/add_water/ui/view/add_water_screen.dart index 7305c041..b469622b 100644 --- a/coffee_maker_navigator_2/lib/features/add_water/ui/view/add_water_screen.dart +++ b/coffee_maker_navigator_2/lib/features/add_water/ui/view/add_water_screen.dart @@ -1,7 +1,6 @@ import 'package:coffee_maker_navigator_2/features/add_water/di/add_water_dependency_container.dart'; -import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_screen_back_button.dart'; import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_screen_content.dart'; -import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_screen_footer.dart'; +import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_step_order_not_found.dart'; import 'package:coffee_maker_navigator_2/features/add_water/ui/view_model/add_water_view_model.dart'; import 'package:demo_ui_components/demo_ui_components.dart'; import 'package:flutter/material.dart'; @@ -31,6 +30,14 @@ class _AddWaterScreenState extends State ..onInit(widget.coffeeOrderId); } + @override + void didUpdateWidget(covariant AddWaterScreen oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.coffeeOrderId != widget.coffeeOrderId) { + _viewModel.onInit(widget.coffeeOrderId); + } + } + @override void dispose() { super.dispose(); @@ -44,22 +51,18 @@ class _AddWaterScreenState extends State resizeToAvoidBottomInset: false, body: SafeArea( top: false, - child: Stack( - children: [ - AddWaterScreenContent( - onWaterQuantityUpdated: _viewModel.onWaterQuantityUpdated, - onWaterSourceUpdated: _viewModel.onWaterSourceUpdated, - onWaterTemperatureUpdated: _viewModel.onWaterTemperatureUpdated, - ), - const AddWaterScreenBackButton(), - AddWaterScreenFooter( - _viewModel.isReadyToAddWater, - _viewModel.errorMessage, - _viewModel.onCheckValidityPressed, - _viewModel.onAddWaterPressed, - ), - ], - ), + child: _viewModel.orderExists + ? AddWaterScreenContent( + onWaterQuantityUpdated: _viewModel.onWaterQuantityUpdated, + onWaterTemperatureUpdated: + _viewModel.onWaterTemperatureUpdated, + onWaterSourceUpdated: _viewModel.onWaterSourceUpdated, + isReadyToAddWater: _viewModel.isReadyToAddWater, + errorMessage: _viewModel.errorMessage, + onCheckValidityPressed: _viewModel.onCheckValidityPressed, + onAddWaterPressed: _viewModel.onAddWaterPressed, + ) + : const AddWaterStepOrderNotFound(), ), ), ); diff --git a/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_body.dart b/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_body.dart new file mode 100644 index 00000000..7ce945d6 --- /dev/null +++ b/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_body.dart @@ -0,0 +1,103 @@ +import 'package:coffee_maker_navigator_2/features/add_water/domain/entities/water_source.dart'; +import 'package:demo_ui_components/demo_ui_components.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AddWaterScreenBody extends StatelessWidget { + const AddWaterScreenBody({ + super.key, + required this.onWaterQuantityUpdated, + required this.onWaterTemperatureUpdated, + required this.onWaterSourceUpdated, + }); + + final void Function(String) onWaterQuantityUpdated; + final void Function(String) onWaterTemperatureUpdated; + final void Function(WaterSource) onWaterSourceUpdated; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + const Image( + image: AssetImage('lib/assets/images/add_water_description.png'), + fit: BoxFit.cover, + height: 200, + width: double.infinity, + ), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: Padding( + padding: const EdgeInsets.all(16.0) + + const EdgeInsets.only(bottom: 200), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Add water to the coffee', + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Text( + 'Enter the details to see the quality of the water you are adding to the coffee maker.', + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 24), + AppTextFormField( + controller: TextEditingController(), + textInputType: + const TextInputType.numberWithOptions(decimal: true), + onChanged: onWaterQuantityUpdated, + onSubmitted: onWaterQuantityUpdated, + inputFormatters: [ + /* Don't allow minus or space */ + FilteringTextInputFormatter.deny(RegExp(r'(\s|-+)')), + ], + labelText: 'Water quantity (ml)', + ), + const SizedBox(height: 16), + AppTextFormField( + controller: TextEditingController(), + textInputType: + const TextInputType.numberWithOptions(decimal: true), + onChanged: onWaterTemperatureUpdated, + onSubmitted: onWaterTemperatureUpdated, + inputFormatters: [ + /* Don't allow minus or space */ + FilteringTextInputFormatter.deny(RegExp(r'(\s|-+)')), + ], + labelText: 'Water temperature (°C)', + ), + const SizedBox(height: 24), + Text( + 'Select the water source:', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + WoltSelectionList.singleSelect( + tilePadding: + const EdgeInsetsDirectional.symmetric(vertical: 8), + itemTileDataGroup: WoltSelectionListItemDataGroup( + group: WaterSource.values + .map( + (e) => WoltSelectionListItemData( + title: e.label, value: e, isSelected: false), + ) + .toList(), + ), + onSelectionUpdateInSingleSelectionList: (item) { + onWaterSourceUpdated(item.value); + }, + ) + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_content.dart b/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_content.dart index f758943d..738f9b8c 100644 --- a/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_content.dart +++ b/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_screen_content.dart @@ -1,5 +1,9 @@ import 'package:coffee_maker_navigator_2/features/add_water/domain/entities/water_source.dart'; +import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_screen_back_button.dart'; +import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_screen_body.dart'; +import 'package:coffee_maker_navigator_2/features/add_water/ui/view/widgets/add_water_screen_footer.dart'; import 'package:demo_ui_components/demo_ui_components.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -9,95 +13,37 @@ class AddWaterScreenContent extends StatelessWidget { required this.onWaterQuantityUpdated, required this.onWaterTemperatureUpdated, required this.onWaterSourceUpdated, + required this.onCheckValidityPressed, + required this.onAddWaterPressed, + required this.isReadyToAddWater, + required this.errorMessage, }); final void Function(String) onWaterQuantityUpdated; final void Function(String) onWaterTemperatureUpdated; final void Function(WaterSource) onWaterSourceUpdated; + final ValueListenable isReadyToAddWater; + final ValueListenable errorMessage; + final VoidCallback onCheckValidityPressed; + final VoidCallback onAddWaterPressed; @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - children: [ - const Image( - image: AssetImage('lib/assets/images/add_water_description.png'), - fit: BoxFit.cover, - height: 200, - width: double.infinity, - ), - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600), - child: Padding( - padding: const EdgeInsets.all(16.0) + - const EdgeInsets.only(bottom: 200), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Add water to the coffee', - style: Theme.of(context).textTheme.headlineSmall!.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Text( - 'Enter the details to see the quality of the water you are adding to the coffee maker.', - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(height: 24), - AppTextFormField( - controller: TextEditingController(), - textInputType: - const TextInputType.numberWithOptions(decimal: true), - onChanged: onWaterQuantityUpdated, - onSubmitted: onWaterQuantityUpdated, - inputFormatters: [ - /* Don't allow minus or space */ - FilteringTextInputFormatter.deny(RegExp(r'(\s|-+)')), - ], - labelText: 'Water quantity (ml)', - ), - const SizedBox(height: 16), - AppTextFormField( - controller: TextEditingController(), - textInputType: - const TextInputType.numberWithOptions(decimal: true), - onChanged: onWaterTemperatureUpdated, - onSubmitted: onWaterTemperatureUpdated, - inputFormatters: [ - /* Don't allow minus or space */ - FilteringTextInputFormatter.deny(RegExp(r'(\s|-+)')), - ], - labelText: 'Water temperature (°C)', - ), - const SizedBox(height: 24), - Text( - 'Select the water source:', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - WoltSelectionList.singleSelect( - tilePadding: - const EdgeInsetsDirectional.symmetric(vertical: 8), - itemTileDataGroup: WoltSelectionListItemDataGroup( - group: WaterSource.values - .map( - (e) => WoltSelectionListItemData( - title: e.label, value: e, isSelected: false), - ) - .toList(), - ), - onSelectionUpdateInSingleSelectionList: (item) { - onWaterSourceUpdated(item.value); - }, - ) - ], - ), - ), - ), - ], - ), + return Stack( + children: [ + AddWaterScreenBody( + onWaterQuantityUpdated: onWaterQuantityUpdated, + onWaterSourceUpdated: onWaterSourceUpdated, + onWaterTemperatureUpdated: onWaterTemperatureUpdated, + ), + const AddWaterScreenBackButton(), + AddWaterScreenFooter( + isReadyToAddWater, + errorMessage, + onCheckValidityPressed, + onAddWaterPressed, + ), + ], ); } } diff --git a/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_step_order_not_found.dart b/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_step_order_not_found.dart new file mode 100644 index 00000000..87072b93 --- /dev/null +++ b/coffee_maker_navigator_2/lib/features/add_water/ui/view/widgets/add_water_step_order_not_found.dart @@ -0,0 +1,42 @@ +import 'package:coffee_maker_navigator_2/utils/extensions/context_extensions.dart'; +import 'package:demo_ui_components/demo_ui_components.dart'; +import 'package:flutter/material.dart'; + +class AddWaterStepOrderNotFound extends StatelessWidget { + const AddWaterStepOrderNotFound({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Image( + image: AssetImage('lib/assets/images/order_not_found.webp'), + fit: BoxFit.cover, + height: 200, + width: double.infinity, + ), + const Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: Text( + 'Order not found. Please check the order id and make sure that the order is in the correct state.', + ), + ), + ), + WoltElevatedButton( + onPressed: context.routerViewModel.onAddWaterStepCompleted, + child: const Text('Go to orders'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/coffee_maker_navigator_2/lib/features/add_water/ui/view_model/add_water_view_model.dart b/coffee_maker_navigator_2/lib/features/add_water/ui/view_model/add_water_view_model.dart index dff4da0d..2f86b7a2 100644 --- a/coffee_maker_navigator_2/lib/features/add_water/ui/view_model/add_water_view_model.dart +++ b/coffee_maker_navigator_2/lib/features/add_water/ui/view_model/add_water_view_model.dart @@ -14,11 +14,21 @@ class AddWaterViewModel { late String _orderId; final ValueNotifier _isReadyToAddWater = ValueNotifier(false); + ValueListenable get isReadyToAddWater => _isReadyToAddWater; final ValueNotifier _errorMessage = ValueNotifier(null); + ValueListenable get errorMessage => _errorMessage; + bool get orderExists { + return _ordersService.orders.value.any( + (order) => + order.id == _orderId && + order.coffeeMakerStep == CoffeeMakerStep.addWater, + ); + } + AddWaterViewModel({ required AddWaterService addWaterService, required OrdersService ordersService, diff --git a/coffee_maker_navigator_2/lib/features/orders/data/remote/orders_remote_data_source.dart b/coffee_maker_navigator_2/lib/features/orders/data/remote/orders_remote_data_source.dart index fe323162..58f2930b 100644 --- a/coffee_maker_navigator_2/lib/features/orders/data/remote/orders_remote_data_source.dart +++ b/coffee_maker_navigator_2/lib/features/orders/data/remote/orders_remote_data_source.dart @@ -57,46 +57,46 @@ const List _mockCoffeeOrders = [ CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.grind, orderName: 'Yuho W.', - id: '#001', + id: '001', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.grind, orderName: 'John Doe', - id: '#002', + id: '002', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.grind, orderName: 'Jane Smith', - id: '#003', + id: '003', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.grind, orderName: 'Michael Johnson', - id: '#004', + id: '004', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.grind, orderName: 'Sarah Davis', - id: '#005', + id: '005', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.grind, orderName: 'David Wilson', - id: '#006', + id: '006', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.addWater, orderName: 'Emily Brown', - id: '#007', + id: '007', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.addWater, orderName: 'Robert Jones', - id: '#008', + id: '008', ), CoffeeOrder( coffeeMakerStep: CoffeeMakerStep.ready, orderName: 'Jennifer Taylor', - id: '#009', + id: '009', ), ]; diff --git a/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/not_found/order_not_found_modal.dart b/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/not_found/order_not_found_modal.dart new file mode 100644 index 00000000..28ad2399 --- /dev/null +++ b/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/not_found/order_not_found_modal.dart @@ -0,0 +1,42 @@ +import 'package:coffee_maker_navigator_2/features/orders/domain/entities/coffee_maker_step.dart'; +import 'package:demo_ui_components/demo_ui_components.dart'; +import 'package:flutter/widgets.dart'; +import 'package:wolt_modal_sheet/wolt_modal_sheet.dart'; + +class OrderNotFoundModal extends WoltModalSheetPage { + OrderNotFoundModal( + String id, + CoffeeMakerStep step, + ) : super( + child: Padding( + padding: const EdgeInsets.only( + bottom: (WoltElevatedButton.defaultHeight + 32)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ModalSheetContentText( + 'The order you are looking for does not exist in ${step.stepName} step, or in a ' + 'different state. Please check the order id and try again. If you need help, ' + 'do not hesitate to contact us.', + ), + ), + ), + heroImage: const Image( + image: AssetImage('lib/assets/images/order_not_found.webp'), + fit: BoxFit.cover, + ), + pageTitle: ModalSheetTitle( + 'Order $id not found', + textAlign: TextAlign.center, + ), + stickyActionBar: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Builder(builder: (context) { + return WoltElevatedButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ); + }), + ), + trailingNavBarWidget: const WoltModalSheetCloseButton(), + ); +} diff --git a/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/offer_recommendation_modal_page.dart b/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/offer_recommendation_modal_page.dart index 5739b188..18f27c52 100644 --- a/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/offer_recommendation_modal_page.dart +++ b/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/offer_recommendation_modal_page.dart @@ -7,10 +7,10 @@ import 'package:wolt_modal_sheet/wolt_modal_sheet.dart'; class OfferRecommendationModalPage { OfferRecommendationModalPage._(); - static SliverWoltModalSheetPage build( - VoidCallback onCoffeeOrderServed, - String coffeeOrderId, - ) { + static SliverWoltModalSheetPage build({ + required VoidCallback onCoffeeOrderServed, + required String coffeeOrderId, + }) { final selectedItemCountListener = ValueNotifier(0); const pageTitle = 'Recommendations'; const allRecommendations = ExtraRecommendation.values; diff --git a/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/serve_or_offer_modal_page.dart b/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/serve_or_offer_modal_page.dart index 72b877c3..d8d026ac 100644 --- a/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/serve_or_offer_modal_page.dart +++ b/coffee_maker_navigator_2/lib/features/orders/ui/view/modal_pages/ready/serve_or_offer_modal_page.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:wolt_modal_sheet/wolt_modal_sheet.dart'; class ServeOrOfferModalPage extends WoltModalSheetPage { - ServeOrOfferModalPage( - VoidCallback onCoffeeOrderServed, - String coffeeOrderId, - ) : super( + ServeOrOfferModalPage({ + required VoidCallback onCoffeeOrderServed, + required String coffeeOrderId, + }) : super( heroImage: const Image( image: AssetImage('lib/assets/images/coffee_is_ready.png'), fit: BoxFit.cover, diff --git a/coffee_maker_navigator_2/lib/features/orders/ui/view/orders_screen.dart b/coffee_maker_navigator_2/lib/features/orders/ui/view/orders_screen.dart index 077f0b0c..3a45195d 100644 --- a/coffee_maker_navigator_2/lib/features/orders/ui/view/orders_screen.dart +++ b/coffee_maker_navigator_2/lib/features/orders/ui/view/orders_screen.dart @@ -40,7 +40,6 @@ class _OrdersScreenState extends State selectedStepListenable: viewModel.selectedBottomNavBarItem, groupedCoffeeOrders: viewModel.groupedCoffeeOrders, onBottomNavBarItemSelected: viewModel.onBottomNavBarItemSelected, - onOrderStatusChange: viewModel.onOrderStatusChange, ); } } diff --git a/coffee_maker_navigator_2/lib/features/orders/ui/view/widgets/order_screen_content.dart b/coffee_maker_navigator_2/lib/features/orders/ui/view/widgets/order_screen_content.dart index b2cd74db..674df4f0 100644 --- a/coffee_maker_navigator_2/lib/features/orders/ui/view/widgets/order_screen_content.dart +++ b/coffee_maker_navigator_2/lib/features/orders/ui/view/widgets/order_screen_content.dart @@ -1,4 +1,3 @@ -import 'package:coffee_maker_navigator_2/app/router/view_model/router_view_model.dart'; import 'package:coffee_maker_navigator_2/app/ui/widgets/app_navigation_drawer.dart'; import 'package:coffee_maker_navigator_2/features/orders/domain/entities/coffee_maker_step.dart'; import 'package:coffee_maker_navigator_2/features/orders/domain/entities/grouped_coffee_orders.dart'; @@ -24,13 +23,11 @@ class OrderScreenContent extends StatelessWidget { required this.selectedStepListenable, required this.groupedCoffeeOrders, required this.onBottomNavBarItemSelected, - required this.onOrderStatusChange, }); final ValueListenable selectedStepListenable; final ValueListenable groupedCoffeeOrders; final OnOrderScreenBottomNavBarItemSelected onBottomNavBarItemSelected; - final OnCoffeeOrderStatusChange onOrderStatusChange; @override Widget build(BuildContext context) { @@ -52,12 +49,12 @@ class OrderScreenContent extends StatelessWidget { return CoffeeOrderListViewForStep( groupedCoffeeOrders: orders, selectedBottomNavBarItem: selectedTab, - onGrindCoffeeStepSelected: (id) => - _onGrindCoffeeStepSelected(routerViewModel, id), + onGrindCoffeeStepSelected: + routerViewModel.onGrindCoffeeStepSelected, onAddWaterCoffeeStepSelected: routerViewModel.onAddWaterCoffeeStepSelected, - onReadyCoffeeStepSelected: (id) => - _onReadyCoffeeStepSelected(routerViewModel, id), + onReadyCoffeeStepSelected: + routerViewModel.onReadyCoffeeStepSelected, ); }, ), @@ -76,18 +73,4 @@ class OrderScreenContent extends StatelessWidget { ), ); } - - void _onGrindCoffeeStepSelected(RouterViewModel routerViewModel, String id) { - routerViewModel.onGrindCoffeeStepSelected( - id: id, - onCoffeeGrindCompleted: () => - onOrderStatusChange(id, CoffeeMakerStep.addWater), - onCoffeeGrindRejected: () => onOrderStatusChange(id), - ); - } - - void _onReadyCoffeeStepSelected(RouterViewModel routerViewModel, String id) { - routerViewModel.onReadyCoffeeStepSelected( - id: id, onCoffeeServed: () => onOrderStatusChange(id)); - } } diff --git a/coffee_maker_navigator_2/lib/features/orders/ui/view_model/orders_screen_view_model.dart b/coffee_maker_navigator_2/lib/features/orders/ui/view_model/orders_screen_view_model.dart index e9a414de..90320065 100644 --- a/coffee_maker_navigator_2/lib/features/orders/ui/view_model/orders_screen_view_model.dart +++ b/coffee_maker_navigator_2/lib/features/orders/ui/view_model/orders_screen_view_model.dart @@ -47,4 +47,9 @@ class OrdersScreenViewModel { final orders = _ordersService.orders.value; _groupedCoffeeOrders.value = GroupedCoffeeOrders.fromCoffeeOrders(orders); } + + bool orderExists(String orderId, CoffeeMakerStep step) { + return _ordersService.orders.value + .any((order) => order.id == orderId && order.coffeeMakerStep == step); + } } diff --git a/demo_ui_components/lib/src/theme_data/app_theme_data.dart b/demo_ui_components/lib/src/theme_data/app_theme_data.dart index c12802c8..4b59c66c 100644 --- a/demo_ui_components/lib/src/theme_data/app_theme_data.dart +++ b/demo_ui_components/lib/src/theme_data/app_theme_data.dart @@ -15,7 +15,7 @@ class AppThemeData { const AppThemeData(); static ThemeData themeData(BuildContext context) { - final textTheme = GoogleFonts.interTextTheme(Theme.of(context).textTheme); + final textTheme = Theme.of(context).textTheme; return ThemeData( brightness: colorScheme.brightness,