From 0d7455c334b14fb3963df0b99219902dd90c74b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Irland?= Date: Sat, 18 Feb 2023 13:17:22 -0300 Subject: [PATCH] feat: Remove declarative routing to support deplinking (#92) --- .../extension/stream_future_extensions.dart | 1 + .../store/secure_storage_cached_source.dart | 2 +- lib/core/di/di_utils_module.dart | 7 +- lib/main.dart | 2 +- lib/ui/about/about_cubit.dart | 10 +- lib/ui/app_router.dart | 48 ++-- lib/ui/app_router.gr.dart | 165 ++++++++----- lib/ui/common/base_bloc_extensions.dart | 12 + lib/ui/common/fab.dart | 1 + lib/ui/common/my_back_button.dart | 2 +- lib/ui/main/main_cubit.dart | 32 --- lib/ui/main/main_cubit.freezed.dart | 223 ------------------ lib/ui/main/main_screen.dart | 79 ++----- lib/ui/main/main_state.dart | 8 - .../privacy_policy/privacy_policy_screen.dart | 8 + .../terms_and_condition_screen.dart | 8 + lib/ui/router/app_status_router_guard.dart | 68 ++++++ lib/ui/section/global_ui/global_ui_cubit.dart | 3 +- lib/ui/section/section_router.dart | 1 + lib/ui/webView/webview_screen.dart | 16 +- 20 files changed, 280 insertions(+), 416 deletions(-) create mode 100644 lib/ui/common/base_bloc_extensions.dart delete mode 100644 lib/ui/main/main_cubit.dart delete mode 100644 lib/ui/main/main_cubit.freezed.dart delete mode 100644 lib/ui/main/main_state.dart create mode 100644 lib/ui/privacy_policy/privacy_policy_screen.dart create mode 100644 lib/ui/privacy_policy/terms_and_condition_screen.dart create mode 100644 lib/ui/router/app_status_router_guard.dart diff --git a/lib/core/common/extension/stream_future_extensions.dart b/lib/core/common/extension/stream_future_extensions.dart index 34bbde8..0cfd47c 100644 --- a/lib/core/common/extension/stream_future_extensions.dart +++ b/lib/core/common/extension/stream_future_extensions.dart @@ -1,3 +1,4 @@ +// ignore_for_file: unused-files import 'package:fluttips/core/common/logger.dart'; import 'package:fluttips/core/common/result.dart'; import 'package:rxdart/rxdart.dart'; diff --git a/lib/core/common/store/secure_storage_cached_source.dart b/lib/core/common/store/secure_storage_cached_source.dart index f421eb4..b62ceed 100644 --- a/lib/core/common/store/secure_storage_cached_source.dart +++ b/lib/core/common/store/secure_storage_cached_source.dart @@ -20,7 +20,7 @@ class SharedPreferencesSourceOfTruth @override @protected Future write(String key, String? value) async { - await super.write(key, value); await _storage.write(key: key, value: value); + await super.write(key, value); } } diff --git a/lib/core/di/di_utils_module.dart b/lib/core/di/di_utils_module.dart index a5a7d91..52de438 100644 --- a/lib/core/di/di_utils_module.dart +++ b/lib/core/di/di_utils_module.dart @@ -2,6 +2,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:fluttips/core/source/database.dart'; import 'package:fluttips/core/source/providers/shared_preferences_provider.dart'; import 'package:fluttips/ui/app_router.dart'; +import 'package:fluttips/ui/router/app_status_router_guard.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:fluttips/core/common/config.dart'; @@ -25,7 +26,11 @@ class UtilsDiModule { extension _GetItUtilsDiModuleExtensions on GetIt { void _setupModule() { - registerSingleton(AppRouter()); + registerSingleton(NotOnboardedGuard(get())); + registerSingleton(OnboardedGuard(get())); + registerSingleton( + AppRouter(notOnboardedGuard: get(), onboardedGuard: get()), + ); } void _setupProviders() { diff --git a/lib/main.dart b/lib/main.dart index 42ed054..48470b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,6 +76,6 @@ class MyApp extends StatelessWidget { designSize: const Size(812, 360), minTextAdapt: false, splitScreenMode: false, - builder: (_, __) => const MainScreen(), + builder: (_, __) => MainScreen(), ); } diff --git a/lib/ui/about/about_cubit.dart b/lib/ui/about/about_cubit.dart index 4cd5758..3a91a27 100644 --- a/lib/ui/about/about_cubit.dart +++ b/lib/ui/about/about_cubit.dart @@ -7,7 +7,6 @@ import 'package:fluttips/core/di/di_provider.dart'; import 'package:fluttips/ui/app_router.dart'; import 'package:fluttips/core/common/config.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:fluttips/gen/assets.gen.dart'; part 'about_cubit.freezed.dart'; @@ -21,13 +20,10 @@ class AboutCubit extends Cubit { AboutCubit(this._errorHandler) : super(const AboutBaseState.state()); - void onTermsAndConditionsButtonPressed() => _router.navigate( - WebViewRoute(path: Assets.policyAndTerms.termsAndConditions), - ); + void onTermsAndConditionsButtonPressed() => + _router.navigate(TermsAndConditionsRoute()); - void onPrivacyPolicyButtonPressed() => _router.navigate( - WebViewRoute(path: Assets.policyAndTerms.privacyPolicy), - ); + void onPrivacyPolicyButtonPressed() => _router.navigate(PrivacyPolicyRoute()); Future sendEmail() async { final PackageInfo packageInfo = await PackageInfo.fromPlatform(); diff --git a/lib/ui/app_router.dart b/lib/ui/app_router.dart index cec5966..d4f943c 100644 --- a/lib/ui/app_router.dart +++ b/lib/ui/app_router.dart @@ -4,9 +4,11 @@ import 'package:fluttips/ui/about/about_screen.dart'; import 'package:fluttips/ui/catalog/catalog_screen.dart'; import 'package:fluttips/ui/home/home_screen.dart'; import 'package:fluttips/ui/onboarding/onboarding_screen.dart'; +import 'package:fluttips/ui/privacy_policy/privacy_policy_screen.dart'; +import 'package:fluttips/ui/privacy_policy/terms_and_condition_screen.dart'; +import 'package:fluttips/ui/router/app_status_router_guard.dart'; import 'package:fluttips/ui/section/section_router.dart'; import 'package:fluttips/ui/videos_details_screen/videos_details_screen.dart'; -import 'package:fluttips/ui/webView/webview_screen.dart'; import 'package:fluttips/ui/favourites_tip_details/favourites_tip_details_screen.dart'; import 'package:fluttips/ui/favourites_tip_simple_list/favourites_tip_simple_list_screen.dart'; import 'package:fluttips/ui/image_tip_details/image_tip_details_screen.dart'; @@ -25,8 +27,12 @@ part 'app_router.gr.dart'; AutoRoute( name: 'UncompletedOnboardingRouter', page: SectionRouter, + initial: true, + path: '/onboarding', + guards: [NotOnboardedGuard], children: [ AutoRoute( + path: '', initial: true, name: 'OnboardingRoute', page: OnboardingScreen, @@ -36,54 +42,58 @@ part 'app_router.gr.dart'; AutoRoute( name: 'UserOnboardedRouter', page: SectionRouter, - path: '', + path: '/', + guards: [OnboardedGuard], children: [ AutoRoute( page: HomeScreen, initial: true, + path: 'home', children: [ AutoRoute( - path: AppRouter.tipsPath, + path: 'images', name: 'HomeTipsScreenRoute', page: ImageTipDetailsScreen, ), AutoRoute( - path: AppRouter.videosPath, + path: 'videos', name: 'HomeVideosScreenRoute', page: VideosDetailsScreen, ), AutoRoute( - path: AppRouter.favouritesTipsPath, + path: 'favourites', name: 'HomeFavouritesTipsScreenRoute', page: FavouritesTipSimpleListScreen, ), AutoRoute( - path: AppRouter.aboutPath, + path: 'about', name: 'HomeAboutScreenRoute', page: AboutScreen, ), ], ), AutoRoute( - path: AppRouter.webViewPath, - name: 'WebViewRoute', - page: WebViewScreen, - ), - AutoRoute( - path: AppRouter.listFavouritesTipsPath, + path: 'favourite_images', name: 'FavoritesTipDetailsScreen', page: FavouritesTipDetailsScreen, ), ], ), + AutoRoute( + path: '/privacy_policy', + name: 'PrivacyPolicyRoute', + page: PrivacyPolicyScreen, + ), + AutoRoute( + path: '/terms_and_conditions', + name: 'TermsAndConditionsRoute', + page: TermsAndConditionsScreen, + ), ], ) class AppRouter extends _$AppRouter { - static const signInPath = 'signin'; - static const tipsPath = 'tips'; - static const videosPath = 'videos'; - static const favouritesTipsPath = 'favourite'; - static const listFavouritesTipsPath = 'list_favourite'; - static const aboutPath = 'about'; - static const webViewPath = 'webView'; + AppRouter({ + required super.notOnboardedGuard, + required super.onboardedGuard, + }); } diff --git a/lib/ui/app_router.gr.dart b/lib/ui/app_router.gr.dart index c78db4d..46dd160 100644 --- a/lib/ui/app_router.gr.dart +++ b/lib/ui/app_router.gr.dart @@ -13,7 +13,15 @@ part of 'app_router.dart'; class _$AppRouter extends RootStackRouter { - _$AppRouter([GlobalKey? navigatorKey]) : super(navigatorKey); + _$AppRouter({ + GlobalKey? navigatorKey, + required this.notOnboardedGuard, + required this.onboardedGuard, + }) : super(navigatorKey); + + final NotOnboardedGuard notOnboardedGuard; + + final OnboardedGuard onboardedGuard; @override final Map pagesMap = { @@ -35,26 +43,32 @@ class _$AppRouter extends RootStackRouter { child: const SectionRouter(), ); }, - OnboardingRoute.name: (routeData) { + PrivacyPolicyRoute.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const PrivacyPolicyRouteArgs()); return MaterialPageX( routeData: routeData, - child: const OnboardingScreen(), + child: PrivacyPolicyScreen(key: args.key), ); }, - HomeScreenRoute.name: (routeData) { + TermsAndConditionsRoute.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const TermsAndConditionsRouteArgs()); return MaterialPageX( routeData: routeData, - child: const HomeScreen(), + child: TermsAndConditionsScreen(key: args.key), ); }, - WebViewRoute.name: (routeData) { - final args = routeData.argsAs(); + OnboardingRoute.name: (routeData) { return MaterialPageX( routeData: routeData, - child: WebViewScreen( - args.path, - key: args.key, - ), + child: const OnboardingScreen(), + ); + }, + HomeScreenRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const HomeScreen(), ); }, FavoritesTipDetailsScreen.name: (routeData) { @@ -107,7 +121,8 @@ class _$AppRouter extends RootStackRouter { ), RouteConfig( UncompletedOnboardingRouter.name, - path: '/section-router', + path: '/onboarding', + guards: [notOnboardedGuard], children: [ RouteConfig( OnboardingRoute.name, @@ -118,16 +133,24 @@ class _$AppRouter extends RootStackRouter { ), RouteConfig( UserOnboardedRouter.name, - path: '', + path: '/', + guards: [onboardedGuard], children: [ RouteConfig( - HomeScreenRoute.name, + '#redirect', path: '', parent: UserOnboardedRouter.name, + redirectTo: 'home', + fullMatch: true, + ), + RouteConfig( + HomeScreenRoute.name, + path: 'home', + parent: UserOnboardedRouter.name, children: [ RouteConfig( HomeTipsScreenRoute.name, - path: 'tips', + path: 'images', parent: HomeScreenRoute.name, ), RouteConfig( @@ -137,7 +160,7 @@ class _$AppRouter extends RootStackRouter { ), RouteConfig( HomeFavouritesTipsScreenRoute.name, - path: 'favourite', + path: 'favourites', parent: HomeScreenRoute.name, ), RouteConfig( @@ -147,18 +170,21 @@ class _$AppRouter extends RootStackRouter { ), ], ), - RouteConfig( - WebViewRoute.name, - path: 'webView', - parent: UserOnboardedRouter.name, - ), RouteConfig( FavoritesTipDetailsScreen.name, - path: 'list_favourite', + path: 'favourite_images', parent: UserOnboardedRouter.name, ), ], ), + RouteConfig( + PrivacyPolicyRoute.name, + path: '/privacy_policy', + ), + RouteConfig( + TermsAndConditionsRoute.name, + path: '/terms_and_conditions', + ), ]; } @@ -180,7 +206,7 @@ class UncompletedOnboardingRouter extends PageRouteInfo { const UncompletedOnboardingRouter({List? children}) : super( UncompletedOnboardingRouter.name, - path: '/section-router', + path: '/onboarding', initialChildren: children, ); @@ -193,13 +219,62 @@ class UserOnboardedRouter extends PageRouteInfo { const UserOnboardedRouter({List? children}) : super( UserOnboardedRouter.name, - path: '', + path: '/', initialChildren: children, ); static const String name = 'UserOnboardedRouter'; } +/// generated route for +/// [PrivacyPolicyScreen] +class PrivacyPolicyRoute extends PageRouteInfo { + PrivacyPolicyRoute({Key? key}) + : super( + PrivacyPolicyRoute.name, + path: '/privacy_policy', + args: PrivacyPolicyRouteArgs(key: key), + ); + + static const String name = 'PrivacyPolicyRoute'; +} + +class PrivacyPolicyRouteArgs { + const PrivacyPolicyRouteArgs({this.key}); + + final Key? key; + + @override + String toString() { + return 'PrivacyPolicyRouteArgs{key: $key}'; + } +} + +/// generated route for +/// [TermsAndConditionsScreen] +class TermsAndConditionsRoute + extends PageRouteInfo { + TermsAndConditionsRoute({Key? key}) + : super( + TermsAndConditionsRoute.name, + path: '/terms_and_conditions', + args: TermsAndConditionsRouteArgs(key: key), + ); + + static const String name = 'TermsAndConditionsRoute'; +} + +class TermsAndConditionsRouteArgs { + const TermsAndConditionsRouteArgs({this.key}); + + final Key? key; + + @override + String toString() { + return 'TermsAndConditionsRouteArgs{key: $key}'; + } +} + /// generated route for /// [OnboardingScreen] class OnboardingRoute extends PageRouteInfo { @@ -218,47 +293,13 @@ class HomeScreenRoute extends PageRouteInfo { const HomeScreenRoute({List? children}) : super( HomeScreenRoute.name, - path: '', + path: 'home', initialChildren: children, ); static const String name = 'HomeScreenRoute'; } -/// generated route for -/// [WebViewScreen] -class WebViewRoute extends PageRouteInfo { - WebViewRoute({ - required String path, - Key? key, - }) : super( - WebViewRoute.name, - path: 'webView', - args: WebViewRouteArgs( - path: path, - key: key, - ), - ); - - static const String name = 'WebViewRoute'; -} - -class WebViewRouteArgs { - const WebViewRouteArgs({ - required this.path, - this.key, - }); - - final String path; - - final Key? key; - - @override - String toString() { - return 'WebViewRouteArgs{path: $path, key: $key}'; - } -} - /// generated route for /// [FavouritesTipDetailsScreen] class FavoritesTipDetailsScreen @@ -269,7 +310,7 @@ class FavoritesTipDetailsScreen Key? key, }) : super( FavoritesTipDetailsScreen.name, - path: 'list_favourite', + path: 'favourite_images', args: FavoritesTipDetailsScreenArgs( showTipType: showTipType, tip: tip, @@ -308,7 +349,7 @@ class HomeTipsScreenRoute extends PageRouteInfo { Key? key, }) : super( HomeTipsScreenRoute.name, - path: 'tips', + path: 'images', args: HomeTipsScreenRouteArgs( showTipType: showTipType, tip: tip, @@ -356,7 +397,7 @@ class HomeFavouritesTipsScreenRoute extends PageRouteInfo { const HomeFavouritesTipsScreenRoute() : super( HomeFavouritesTipsScreenRoute.name, - path: 'favourite', + path: 'favourites', ); static const String name = 'HomeFavouritesTipsScreenRoute'; diff --git a/lib/ui/common/base_bloc_extensions.dart b/lib/ui/common/base_bloc_extensions.dart new file mode 100644 index 0000000..bcbaae1 --- /dev/null +++ b/lib/ui/common/base_bloc_extensions.dart @@ -0,0 +1,12 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +extension CubitExtensions on Cubit { + @protected + void emitSafe(T state) { + if (!isClosed) { + // ignore: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member + emit(state); + } + } +} diff --git a/lib/ui/common/fab.dart b/lib/ui/common/fab.dart index 655e327..5f98246 100644 --- a/lib/ui/common/fab.dart +++ b/lib/ui/common/fab.dart @@ -24,6 +24,7 @@ class Fab extends StatelessWidget { @override Widget build(BuildContext context) => FloatingActionButton( onPressed: action, + heroTag: null, backgroundColor: state.when( notSelected: () => context.theme.colors.primary.shade100, selected: () => context.theme.colors.primary, diff --git a/lib/ui/common/my_back_button.dart b/lib/ui/common/my_back_button.dart index 2ba04ef..1cac9be 100644 --- a/lib/ui/common/my_back_button.dart +++ b/lib/ui/common/my_back_button.dart @@ -14,7 +14,7 @@ class MyBackButton extends StatelessWidget { @override Widget build(BuildContext context) => MaterialButton( - onPressed: () => context.router.navigateBack(), + onPressed: () => context.router.topMostRouter().pop(), color: context.theme.colors.primary, padding: EdgeInsets.all(max(10, kIsWeb ? 12 : 8.w)), shape: const CircleBorder(), diff --git a/lib/ui/main/main_cubit.dart b/lib/ui/main/main_cubit.dart deleted file mode 100644 index 7fdf4e3..0000000 --- a/lib/ui/main/main_cubit.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'dart:async'; - -import 'package:bloc/bloc.dart'; -import 'package:fluttips/core/common/extension/stream_future_extensions.dart'; -import 'package:fluttips/core/di/di_provider.dart'; -import 'package:fluttips/core/repository/session_repository.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -import 'package:fluttips/core/model/onboarding_status.dart'; - -part 'main_cubit.freezed.dart'; - -part 'main_state.dart'; - -class MainCubit extends Cubit { - final SessionRepository _sessionRepository = DiProvider.get(); - - late StreamSubscription _authenticationStatusSubscription; - - MainCubit() : super(const MainBaseState.state()) { - _authenticationStatusSubscription = _sessionRepository - .getSessionStatus() - .filterSuccess() - .listen((status) => emit(state.copyWith(appSessionStatus: status))); - } - - @override - Future close() async { - await _authenticationStatusSubscription.cancel(); - await super.close(); - } -} diff --git a/lib/ui/main/main_cubit.freezed.dart b/lib/ui/main/main_cubit.freezed.dart deleted file mode 100644 index 50f64fd..0000000 --- a/lib/ui/main/main_cubit.freezed.dart +++ /dev/null @@ -1,223 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target - -part of 'main_cubit.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$MainBaseState { - AppSessionStatus? get appSessionStatus => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when({ - required TResult Function(AppSessionStatus? appSessionStatus) state, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(AppSessionStatus? appSessionStatus)? state, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(AppSessionStatus? appSessionStatus)? state, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(_MainState value) state, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_MainState value)? state, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_MainState value)? state, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $MainBaseStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $MainBaseStateCopyWith<$Res> { - factory $MainBaseStateCopyWith( - MainBaseState value, $Res Function(MainBaseState) then) = - _$MainBaseStateCopyWithImpl<$Res, MainBaseState>; - @useResult - $Res call({AppSessionStatus? appSessionStatus}); -} - -/// @nodoc -class _$MainBaseStateCopyWithImpl<$Res, $Val extends MainBaseState> - implements $MainBaseStateCopyWith<$Res> { - _$MainBaseStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? appSessionStatus = freezed, - }) { - return _then(_value.copyWith( - appSessionStatus: freezed == appSessionStatus - ? _value.appSessionStatus - : appSessionStatus // ignore: cast_nullable_to_non_nullable - as AppSessionStatus?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_MainStateCopyWith<$Res> - implements $MainBaseStateCopyWith<$Res> { - factory _$$_MainStateCopyWith( - _$_MainState value, $Res Function(_$_MainState) then) = - __$$_MainStateCopyWithImpl<$Res>; - @override - @useResult - $Res call({AppSessionStatus? appSessionStatus}); -} - -/// @nodoc -class __$$_MainStateCopyWithImpl<$Res> - extends _$MainBaseStateCopyWithImpl<$Res, _$_MainState> - implements _$$_MainStateCopyWith<$Res> { - __$$_MainStateCopyWithImpl( - _$_MainState _value, $Res Function(_$_MainState) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? appSessionStatus = freezed, - }) { - return _then(_$_MainState( - appSessionStatus: freezed == appSessionStatus - ? _value.appSessionStatus - : appSessionStatus // ignore: cast_nullable_to_non_nullable - as AppSessionStatus?, - )); - } -} - -/// @nodoc - -class _$_MainState implements _MainState { - const _$_MainState({this.appSessionStatus = null}); - - @override - @JsonKey() - final AppSessionStatus? appSessionStatus; - - @override - String toString() { - return 'MainBaseState.state(appSessionStatus: $appSessionStatus)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_MainState && - (identical(other.appSessionStatus, appSessionStatus) || - other.appSessionStatus == appSessionStatus)); - } - - @override - int get hashCode => Object.hash(runtimeType, appSessionStatus); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_MainStateCopyWith<_$_MainState> get copyWith => - __$$_MainStateCopyWithImpl<_$_MainState>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(AppSessionStatus? appSessionStatus) state, - }) { - return state(appSessionStatus); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(AppSessionStatus? appSessionStatus)? state, - }) { - return state?.call(appSessionStatus); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(AppSessionStatus? appSessionStatus)? state, - required TResult orElse(), - }) { - if (state != null) { - return state(appSessionStatus); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_MainState value) state, - }) { - return state(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_MainState value)? state, - }) { - return state?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_MainState value)? state, - required TResult orElse(), - }) { - if (state != null) { - return state(this); - } - return orElse(); - } -} - -abstract class _MainState implements MainBaseState { - const factory _MainState({final AppSessionStatus? appSessionStatus}) = - _$_MainState; - - @override - AppSessionStatus? get appSessionStatus; - @override - @JsonKey(ignore: true) - _$$_MainStateCopyWith<_$_MainState> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/ui/main/main_screen.dart b/lib/ui/main/main_screen.dart index 22aa24c..8244d70 100644 --- a/lib/ui/main/main_screen.dart +++ b/lib/ui/main/main_screen.dart @@ -1,72 +1,43 @@ import 'dart:ui'; -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:fluttips/core/di/di_provider.dart'; import 'package:fluttips/ui/app_router.dart'; -import 'package:fluttips/ui/main/main_cubit.dart'; import 'package:fluttips/ui/resources.dart'; import 'package:fluttips/ui/theme/app_theme.dart'; -import 'package:fluttips/core/model/onboarding_status.dart'; - class MainScreen extends StatelessWidget { - const MainScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => BlocProvider( - create: (_) => MainCubit(), - child: _SplashContentScreen(), - ); -} + final _router = DiProvider.get(); -class _SplashContentScreen extends StatelessWidget { - final router = DiProvider.get(); + MainScreen({Key? key}) : super(key: key); @override - Widget build(BuildContext context) => BlocBuilder( - builder: (context, state) => MaterialApp.router( - scrollBehavior: const MaterialScrollBehavior().copyWith( - dragDevices: { - PointerDeviceKind.mouse, - PointerDeviceKind.touch, - PointerDeviceKind.stylus, - PointerDeviceKind.unknown, - }, - ), - debugShowCheckedModeBanner: false, - theme: AppTheme.provideAppTheme(context), - routerDelegate: AutoRouterDelegate.declarative( - router, - routes: (_) => provideRoutes(state), - ), - routeInformationParser: - router.defaultRouteParser(includePrefixMatches: true), - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: AppLocalizations.supportedLocales, - builder: (context, child) { - Resources.setup(context); - return child!; + Widget build(BuildContext context) => MaterialApp.router( + scrollBehavior: const MaterialScrollBehavior().copyWith( + dragDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.unknown, }, ), + debugShowCheckedModeBanner: false, + theme: AppTheme.provideAppTheme(context), + routerDelegate: _router.delegate(), + routeInformationParser: + _router.defaultRouteParser(includePrefixMatches: true), + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + builder: (context, child) { + Resources.setup(context); + return child!; + }, ); - - List> provideRoutes(MainBaseState state) { - switch (state.appSessionStatus) { - case AppSessionStatus.onboarded: - return [const UserOnboardedRouter()]; - case AppSessionStatus.notOnboarded: - return [const UncompletedOnboardingRouter()]; - case null: - return []; - } - } } diff --git a/lib/ui/main/main_state.dart b/lib/ui/main/main_state.dart deleted file mode 100644 index 3149b14..0000000 --- a/lib/ui/main/main_state.dart +++ /dev/null @@ -1,8 +0,0 @@ -part of 'main_cubit.dart'; - -@freezed -class MainBaseState with _$MainBaseState { - const factory MainBaseState.state({ - @Default(null) AppSessionStatus? appSessionStatus, - }) = _MainState; -} diff --git a/lib/ui/privacy_policy/privacy_policy_screen.dart b/lib/ui/privacy_policy/privacy_policy_screen.dart new file mode 100644 index 0000000..35926cc --- /dev/null +++ b/lib/ui/privacy_policy/privacy_policy_screen.dart @@ -0,0 +1,8 @@ +import 'package:flutter/foundation.dart'; +import 'package:fluttips/gen/assets.gen.dart'; +import 'package:fluttips/ui/webView/webview_screen.dart'; + +class PrivacyPolicyScreen extends WebViewScreen { + PrivacyPolicyScreen({Key? key}) + : super(path: Assets.policyAndTerms.privacyPolicy, key: key); +} diff --git a/lib/ui/privacy_policy/terms_and_condition_screen.dart b/lib/ui/privacy_policy/terms_and_condition_screen.dart new file mode 100644 index 0000000..88eb6f0 --- /dev/null +++ b/lib/ui/privacy_policy/terms_and_condition_screen.dart @@ -0,0 +1,8 @@ +import 'package:flutter/foundation.dart'; +import 'package:fluttips/gen/assets.gen.dart'; +import 'package:fluttips/ui/webView/webview_screen.dart'; + +class TermsAndConditionsScreen extends WebViewScreen { + TermsAndConditionsScreen({Key? key}) + : super(path: Assets.policyAndTerms.termsAndConditions, key: key); +} diff --git a/lib/ui/router/app_status_router_guard.dart b/lib/ui/router/app_status_router_guard.dart new file mode 100644 index 0000000..4fb9fda --- /dev/null +++ b/lib/ui/router/app_status_router_guard.dart @@ -0,0 +1,68 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:dartx/dartx.dart'; +import 'package:fluttips/core/model/onboarding_status.dart'; +import 'package:fluttips/core/repository/session_repository.dart'; +import 'package:fluttips/ui/app_router.dart'; + +class _AppSessionStatusGuard extends AutoRedirectGuard { + final SessionRepository _sessionRepository; + final AppSessionStatus _requiredAppStatus; + final PageRouteInfo _redirectPage; + + _AppSessionStatusGuard( + this._requiredAppStatus, + this._redirectPage, + this._sessionRepository, + ) { + _sessionRepository.getSessionStatus().distinct().skip(1).listen( + (event) => reevaluate( + strategy: ReevaluationStrategy.removeAllAndPush(_redirectPage), + ), + ); + } + + @override + Future canNavigate(RouteMatch route) => _sessionRepository + .getSessionStatus() + .first + .then((value) => value == _requiredAppStatus); + + @override + Future onNavigation( + NavigationResolver resolver, + StackRouter router, + ) async { + if (await canNavigate(resolver.route)) { + resolver.next(); + } else { + switch (resolver.pendingRoutes.lastOrNull?.name) { + case PrivacyPolicyRoute.name: + redirect(PrivacyPolicyRoute(), resolver: resolver); + break; + case TermsAndConditionsRoute.name: + redirect(TermsAndConditionsRoute(), resolver: resolver); + break; + default: + redirect(_redirectPage, resolver: resolver); + } + } + } +} + +class NotOnboardedGuard extends _AppSessionStatusGuard { + NotOnboardedGuard(SessionRepository sessionRepository) + : super( + AppSessionStatus.notOnboarded, + const UserOnboardedRouter(), + sessionRepository, + ); +} + +class OnboardedGuard extends _AppSessionStatusGuard { + OnboardedGuard(SessionRepository sessionRepository) + : super( + AppSessionStatus.onboarded, + const UncompletedOnboardingRouter(), + sessionRepository, + ); +} diff --git a/lib/ui/section/global_ui/global_ui_cubit.dart b/lib/ui/section/global_ui/global_ui_cubit.dart index 363f3da..2c4456d 100644 --- a/lib/ui/section/global_ui/global_ui_cubit.dart +++ b/lib/ui/section/global_ui/global_ui_cubit.dart @@ -1,4 +1,5 @@ import 'package:bloc/bloc.dart'; +import 'package:fluttips/ui/common/base_bloc_extensions.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:fluttips/core/di/di_provider.dart'; @@ -13,7 +14,7 @@ class GlobalUICubit extends Cubit { GlobalUICubit() : super(const GlobalUIState.state()) { globalRouter.addListener( - () => emit( + () => emitSafe( state.copyWith( showUIActionComponent: true, ), diff --git a/lib/ui/section/section_router.dart b/lib/ui/section/section_router.dart index 3a789e8..de0bcda 100644 --- a/lib/ui/section/section_router.dart +++ b/lib/ui/section/section_router.dart @@ -13,6 +13,7 @@ class SectionRouter extends StatelessWidget { Widget build(BuildContext context) => MultiBlocProvider( providers: [ BlocProvider(create: (BuildContext context) => ErrorHandlerCubit()), + // TODO: Create a custom section for the home BlocProvider(create: (context) => GlobalUICubit()), ], child: BlocListener( diff --git a/lib/ui/webView/webview_screen.dart b/lib/ui/webView/webview_screen.dart index 5e51047..7a87867 100644 --- a/lib/ui/webView/webview_screen.dart +++ b/lib/ui/webView/webview_screen.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -7,7 +8,7 @@ import 'package:webview_flutter/webview_flutter.dart'; import 'package:fluttips/ui/common/my_back_button.dart'; class WebViewScreen extends StatefulWidget { - const WebViewScreen(this.path, {Key? key}) : super(key: key); + const WebViewScreen({required this.path, Key? key}) : super(key: key); final String path; @@ -33,18 +34,21 @@ class _WebViewScreenState extends State { if (!kIsWeb) { _webViewController.setBackgroundColor(context.theme.colors.background); } - final colors = context.theme.colors; return Scaffold( - backgroundColor: colors.background, + backgroundColor: context.theme.colors.background, body: SafeArea( child: Container( margin: EdgeInsets.only(left: 20.w, top: 10.h, right: 40.w), alignment: Alignment.centerLeft, child: Row( children: [ - Container( - alignment: Alignment.topLeft, - child: const MyBackButton(), + Visibility( + visible: context.router.stack.length > 1, + replacement: SizedBox.square(dimension: 20.w), + child: Container( + alignment: Alignment.topLeft, + child: const MyBackButton(), + ), ), Expanded(child: WebViewWidget(controller: _webViewController)), ],