From 2a608403f1182ec7b2ecb726b7ab7ef79f745d87 Mon Sep 17 00:00:00 2001 From: nero Date: Thu, 22 Aug 2024 17:31:56 +0900 Subject: [PATCH] Add GetNavigatorObserverUsecase. --- .gitignore | 1 + lib/app/app.dart | 7 +- .../widgets/game_exit_confirm_dialog.dart | 6 +- lib/app/pages/home/home_page_model.dart | 6 +- lib/app/routes/routes.dart | 192 -------------- lib/app/routes/routes_helper.dart | 23 +- lib/app/routes/routes_setting.dart | 238 ++++++++++++++++++ .../sources/firebase_analytics_source.dart | 8 + .../data/repository/analytics_repository.dart | 11 - .../repository/analytics_repository_impl.dart | 10 - .../event_analytics_repository.dart | 8 + ...nt_analytics_repository_with_firebase.dart | 4 + .../route_analytics_repository.dart | 12 + ...e_analytics_repository_with_firebase.dart} | 40 ++- .../firebase_analytics_event_source.dart | 32 +++ .../source/firebase_analytics_source.dart | 26 -- .../domain/service/analytics_service.dart | 16 +- .../get_navigator_observer_usecase.dart | 28 +++ 18 files changed, 386 insertions(+), 282 deletions(-) create mode 100644 lib/app/routes/routes_setting.dart create mode 100644 lib/core/data/sources/firebase_analytics_source.dart delete mode 100644 lib/features/analytics/data/repository/analytics_repository.dart delete mode 100644 lib/features/analytics/data/repository/analytics_repository_impl.dart create mode 100644 lib/features/analytics/data/repository/event_analytics_repository.dart create mode 100644 lib/features/analytics/data/repository/event_analytics_repository_with_firebase.dart create mode 100644 lib/features/analytics/data/repository/route_analytics_repository.dart rename lib/{app/routes/routes_observer.dart => features/analytics/data/repository/route_analytics_repository_with_firebase.dart} (58%) create mode 100644 lib/features/analytics/data/source/firebase_analytics_event_source.dart delete mode 100644 lib/features/analytics/data/source/firebase_analytics_source.dart create mode 100644 lib/features/analytics/domain/usecase/get_navigator_observer_usecase.dart diff --git a/.gitignore b/.gitignore index c0bb1be..984f0de 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ migrate_working_dir/ # VS Code .vscode/* !launch.json +!snippets.code-snippets # Flutter/Dart/Pub related **/doc/api/ diff --git a/lib/app/app.dart b/lib/app/app.dart index a77772d..fb999b3 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:x_pr/app/routes/routes.dart'; +import 'package:x_pr/app/routes/routes_setting.dart'; import 'package:x_pr/core/localization/generated/l10n.dart'; import 'package:x_pr/core/theme/components/toast/toast.dart'; import 'package:x_pr/core/theme/foundations/app_theme.dart'; @@ -50,8 +50,9 @@ class _AppState extends ConsumerState { } final config = ref.watch(ConfigService.$); + final routesSetting = ref.read(RoutesSetting.$); return InheritedAppTheme( - navigatorKey: Routes.navigatorKey, + navigatorKey: routesSetting.navigatorKey, isLightTheme: config.isLightTheme, builder: (context) => MaterialApp.router( localizationsDelegates: const [ @@ -64,7 +65,7 @@ class _AppState extends ConsumerState { debugShowCheckedModeBanner: false, locale: config.language.locale, theme: context.themeData, - routerConfig: Routes.config, + routerConfig: routesSetting.router, builder: (context, child) => Stack( fit: StackFit.expand, children: [ diff --git a/lib/app/pages/game/widgets/game_exit_confirm_dialog.dart b/lib/app/pages/game/widgets/game_exit_confirm_dialog.dart index 4465e4f..53b5dd0 100644 --- a/lib/app/pages/game/widgets/game_exit_confirm_dialog.dart +++ b/lib/app/pages/game/widgets/game_exit_confirm_dialog.dart @@ -9,7 +9,7 @@ class GameExitConfirmDialog extends StatelessWidget { const GameExitConfirmDialog({super.key}); @override - Widget build(BuildContext dialogContext) { + Widget build(BuildContext context) { return ConfirmDialog( title: S.current.gamePagePopTitle, content: TextBalancer( @@ -18,8 +18,8 @@ class GameExitConfirmDialog extends StatelessWidget { ), confirmText: S.current.leave, onConfirm: () { - if (dialogContext.mounted) dialogContext.pop(); - Routes.context.pop(); + if (context.mounted) context.pop(); + context.popUntil(Routes.homePage); }, ); } diff --git a/lib/app/pages/home/home_page_model.dart b/lib/app/pages/home/home_page_model.dart index bbe5ec3..795ad6b 100644 --- a/lib/app/pages/home/home_page_model.dart +++ b/lib/app/pages/home/home_page_model.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:x_pr/app/pages/home/home_page_state.dart'; import 'package:x_pr/app/routes/routes.dart'; +import 'package:x_pr/app/routes/routes_setting.dart'; import 'package:x_pr/core/utils/log/logger.dart'; import 'package:x_pr/core/utils/time/network_time_ext.dart'; import 'package:x_pr/core/view/base_view_model.dart'; @@ -20,6 +21,7 @@ abstract class HomePageModel extends BaseViewModel { HomePageModel(super.buildState); Config get config => ref.read(ConfigService.$); + BuildContext get globalContext => ref.read(RoutesSetting.$).context; ConfigService get configService => ref.read(ConfigService.$.notifier); AuthServiceState get authServiceState => ref.read(AuthService.$); late GameService gameService = ref.read(GameService.$.notifier); @@ -47,8 +49,8 @@ abstract class HomePageModel extends BaseViewModel { Logger.d('🔗 AppLink : $uri'); final roomId = uri.queryParameters["room"]; if (roomId != null && roomId.isNotEmpty) { - if (await enter(roomId) && Routes.context.mounted) { - Routes.context.pushNamed(Routes.gamePage.name); + if (await enter(roomId) && globalContext.mounted) { + globalContext.pushNamed(Routes.gamePage.name); } } }); diff --git a/lib/app/routes/routes.dart b/lib/app/routes/routes.dart index 0da9eb5..a649af9 100644 --- a/lib/app/routes/routes.dart +++ b/lib/app/routes/routes.dart @@ -1,33 +1,5 @@ -// ignore_for_file: library_prefixes import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:x_pr/app/pages/dev/component/component_page.dart'; -import 'package:x_pr/app/pages/dev/dev_page.dart'; -import 'package:x_pr/app/pages/dev/local_data/local_data_page.dart'; -import 'package:x_pr/app/pages/dev/log/log_page.dart'; -import 'package:x_pr/app/pages/game/game_page.dart'; -import 'package:x_pr/app/pages/game/widgets/game_exit_confirm_dialog.dart'; -import 'package:x_pr/app/pages/home/dialogs/notice_dialog.dart'; -import 'package:x_pr/app/pages/home/home_page.dart'; -import 'package:x_pr/app/pages/join/join_page.dart'; -import 'package:x_pr/app/pages/login/bottom_sheets/login_bottom_sheet.dart'; -import 'package:x_pr/app/pages/login/dialogs/logout_dialog.dart'; -import 'package:x_pr/app/pages/nickname/nickname_page.dart'; -import 'package:x_pr/app/pages/setting/app_license/app_license_page.dart'; -import 'package:x_pr/app/pages/setting/edit_nickname/edit_nickname_page.dart'; -import 'package:x_pr/app/pages/setting/language/language_bottom_sheet.dart'; -import 'package:x_pr/app/pages/setting/setting_page.dart'; -import 'package:x_pr/app/pages/splash/dialogs/maintenance_dialog.dart'; -import 'package:x_pr/app/pages/splash/dialogs/update_dialog.dart'; -import 'package:x_pr/app/pages/splash/splash_page.dart'; -import 'package:x_pr/app/routes/routes_observer.dart'; -import 'package:x_pr/core/theme/components/pages/bottom_sheet_page.dart'; -import 'package:x_pr/core/theme/components/pages/custom_page_transition.dart'; -import 'package:x_pr/core/theme/components/pages/dialog_page.dart'; -import 'package:x_pr/core/utils/ext/string_ext.dart'; -import 'package:x_pr/features/config/domain/entities/maintenance_dialog_data.dart'; -import 'package:x_pr/features/config/domain/entities/notice_dialog_data.dart'; -import 'package:x_pr/features/config/domain/entities/update_dialog_data.dart'; part 'routes_helper.dart'; @@ -64,168 +36,4 @@ enum Routes { @override String toString() => name; - - static final GlobalKey navigatorKey = - config.routerDelegate.navigatorKey; - - static BuildContext get context => Routes.navigatorKey.currentContext!; - - static final GoRouter config = GoRouter( - observers: [ - RoutesObserver(), - ], - routes: [ - /// Splash - GoRoute( - path: '/', - name: Routes.splashPage.name, - builder: (context, state) => const SplashPage(), - ), - GoRoute( - path: '/update', - name: Routes.updateDialog.name, - pageBuilder: (context, state) => DialogPage( - route: Routes.updateDialog, - barrierDismissible: false, - child: UpdateDialog( - updateDialogData: state.extra as UpdateDialogData, - ), - ), - ), - GoRoute( - path: '/maintenance', - name: Routes.maintenanceDialog.name, - pageBuilder: (context, state) => DialogPage( - route: Routes.maintenanceDialog, - barrierDismissible: false, - child: MaintenanceDialog( - maintenanceDialogData: state.extra as MaintenanceDialogData, - ), - ), - ), - GoRoute( - path: '/nickname', - name: Routes.nicknamePage.name, - builder: (context, state) => const NicknamePage(), - ), - - /// Home - GoRoute( - path: '/home', - name: Routes.homePage.name, - pageBuilder: (context, state) { - return CustomPageTransition.page( - const HomePage(), - name: Routes.homePage.name, - isVertical: true, - ); - }, - ), - GoRoute( - path: '/home/join', - name: Routes.joinPage.name, - pageBuilder: (context, state) { - return CustomPageTransition.page( - const JoinPage(), - name: Routes.joinPage.name, - isBlur: true, - ); - }, - ), - GoRoute( - path: '/home/notice', - name: Routes.noticeDialog.name, - pageBuilder: (context, state) => DialogPage( - route: Routes.noticeDialog, - child: NoticeDialog( - noticeData: state.extra as NoticeDialogData, - ), - ), - ), - - /// Game - GoRoute( - path: '/home/game', - name: Routes.gamePage.name, - builder: (context, state) => const GamePage(), - ), - GoRoute( - path: '/home/game/exit', - name: Routes.gameExitDialog.name, - pageBuilder: (context, state) => const DialogPage( - route: Routes.gameExitDialog, - child: GameExitConfirmDialog(), - ), - ), - - /// Setting - GoRoute( - path: '/home/setting', - name: Routes.settingPage.name, - builder: (context, state) => const SettingPage(), - ), - GoRoute( - path: '/home/setting/editNickname', - name: Routes.editNicknamePage.name, - pageBuilder: (context, state) { - return CustomPageTransition.page( - const EditNicknamePage(), - name: Routes.editNicknamePage.name, - isBlur: true, - ); - }, - ), - GoRoute( - path: '/home/setting/language', - name: Routes.languageBottomSheet.name, - pageBuilder: (context, state) => const BottomSheetPage( - route: Routes.languageBottomSheet, - child: LanguageBottomSheet(), - ), - ), - GoRoute( - path: '/home/setting/license', - name: Routes.licensePage.name, - builder: (context, state) => const AppLicensePage(), - ), - - /// Dev - GoRoute( - path: '/home/setting/dev', - name: Routes.devPage.name, - builder: (context, state) => const DevPage(), - ), - GoRoute( - path: '/home/setting/dev/log', - name: Routes.devLogPage.name, - builder: (context, state) => const LogPage(), - ), - GoRoute( - path: '/home/setting/dev/component', - name: Routes.devComponentPage.name, - builder: (context, state) => const ComponentPage(), - ), - GoRoute( - path: '/home/setting/dev/local_data', - name: Routes.devLocalDataPage.name, - builder: (context, state) => const LocalDataPage(), - ), - GoRoute( - path: '/home/setting/dev/local_data/logout', - name: Routes.devLogoutDialog.name, - pageBuilder: (context, state) => const DialogPage( - route: Routes.devLogoutDialog, - child: LogoutDialog(), - ), - ), - GoRoute( - path: '/home/setting/dev/local_data/login', - // name: Routes.devLoginBottomSheet.name, - pageBuilder: (context, state) => const BottomSheetPage( - route: Routes.devLoginBottomSheet, - child: LoginBottomSheet(), - ), - ), - ], - ); } diff --git a/lib/app/routes/routes_helper.dart b/lib/app/routes/routes_helper.dart index 9eb0428..05eeee7 100644 --- a/lib/app/routes/routes_helper.dart +++ b/lib/app/routes/routes_helper.dart @@ -1,20 +1,13 @@ part of 'routes.dart'; extension RoutesHelper on BuildContext { - Future bottomSheet( - Widget bottomSheet, { - String? name, - bool isScrollControlled = false, - Color? backgroundColor, - }) { - return showModalBottomSheet( - context: this, - builder: (_) => bottomSheet, - routeSettings: RouteSettings( - name: (name ?? "${bottomSheet.runtimeType}").toLowerFirst(), - ), - isScrollControlled: isScrollControlled, - backgroundColor: backgroundColor, - ); + void popUntil(Routes target) { + final delegate = GoRouter.of(this).routerDelegate; + final routes = delegate.currentConfiguration.routes; + for (int i = routes.length - 1; i >= 0; i--) { + final route = routes[i] as GoRoute; + if (route.name == target.name) break; + pop(); + } } } diff --git a/lib/app/routes/routes_setting.dart b/lib/app/routes/routes_setting.dart new file mode 100644 index 0000000..308f22a --- /dev/null +++ b/lib/app/routes/routes_setting.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:x_pr/app/pages/dev/component/component_page.dart'; +import 'package:x_pr/app/pages/dev/dev_page.dart'; +import 'package:x_pr/app/pages/dev/local_data/local_data_page.dart'; +import 'package:x_pr/app/pages/dev/log/log_page.dart'; +import 'package:x_pr/app/pages/game/game_page.dart'; +import 'package:x_pr/app/pages/game/widgets/game_exit_confirm_dialog.dart'; +import 'package:x_pr/app/pages/home/dialogs/notice_dialog.dart'; +import 'package:x_pr/app/pages/home/home_page.dart'; +import 'package:x_pr/app/pages/join/join_page.dart'; +import 'package:x_pr/app/pages/login/bottom_sheets/login_bottom_sheet.dart'; +import 'package:x_pr/app/pages/login/dialogs/logout_dialog.dart'; +import 'package:x_pr/app/pages/nickname/nickname_page.dart'; +import 'package:x_pr/app/pages/setting/app_license/app_license_page.dart'; +import 'package:x_pr/app/pages/setting/edit_nickname/edit_nickname_page.dart'; +import 'package:x_pr/app/pages/setting/language/language_bottom_sheet.dart'; +import 'package:x_pr/app/pages/setting/setting_page.dart'; +import 'package:x_pr/app/pages/splash/dialogs/maintenance_dialog.dart'; +import 'package:x_pr/app/pages/splash/dialogs/update_dialog.dart'; +import 'package:x_pr/app/pages/splash/splash_page.dart'; +import 'package:x_pr/app/routes/routes.dart'; +import 'package:x_pr/core/theme/components/pages/bottom_sheet_page.dart'; +import 'package:x_pr/core/theme/components/pages/custom_page_transition.dart'; +import 'package:x_pr/core/theme/components/pages/dialog_page.dart'; +import 'package:x_pr/features/analytics/domain/service/analytics_service.dart'; +import 'package:x_pr/features/config/domain/entities/maintenance_dialog_data.dart'; +import 'package:x_pr/features/config/domain/entities/notice_dialog_data.dart'; +import 'package:x_pr/features/config/domain/entities/update_dialog_data.dart'; + +class RoutesSetting { + static final $ = Provider((ref) { + return RoutesSetting( + analyticsService: ref.read(AnalyticsService.$), + ); + }); + RoutesSetting({ + required AnalyticsService analyticsService, + }) : _analyticsService = analyticsService; + + final AnalyticsService _analyticsService; + + /// NavigatorKey BuildContext + BuildContext get context => navigatorKey.currentContext!; + + /// NavigatorKey + late final GlobalKey navigatorKey = + router.routerDelegate.navigatorKey; + + /// Router + late final GoRouter router = GoRouter( + observers: [ + _analyticsService.getNavigatorObserver(), + ], + routes: [ + /// SplashPage + GoRoute( + path: '/', + name: Routes.splashPage.name, + builder: (context, state) => const SplashPage(), + ), + + /// SplashPage / UpdateDialog + GoRoute( + path: '/update', + name: Routes.updateDialog.name, + pageBuilder: (context, state) => DialogPage( + route: Routes.updateDialog, + barrierDismissible: false, + child: UpdateDialog( + updateDialogData: state.extra as UpdateDialogData, + ), + ), + ), + + /// SplashPage / MaintenanceDialog + GoRoute( + path: '/maintenance', + name: Routes.maintenanceDialog.name, + pageBuilder: (context, state) => DialogPage( + route: Routes.maintenanceDialog, + barrierDismissible: false, + child: MaintenanceDialog( + maintenanceDialogData: state.extra as MaintenanceDialogData, + ), + ), + ), + + /// SplashPage / NicknamePage + GoRoute( + path: '/nickname', + name: Routes.nicknamePage.name, + builder: (context, state) => const NicknamePage(), + ), + + /// HomePage + GoRoute( + path: '/home', + name: Routes.homePage.name, + pageBuilder: (context, state) { + return CustomPageTransition.page( + const HomePage(), + name: Routes.homePage.name, + isVertical: true, + ); + }, + ), + + /// HomePage / JoinPage + GoRoute( + path: '/home/join', + name: Routes.joinPage.name, + pageBuilder: (context, state) { + return CustomPageTransition.page( + const JoinPage(), + name: Routes.joinPage.name, + isBlur: true, + ); + }, + ), + + /// HomePage / NoticeDialog + GoRoute( + path: '/home/notice', + name: Routes.noticeDialog.name, + pageBuilder: (context, state) => DialogPage( + route: Routes.noticeDialog, + child: NoticeDialog( + noticeData: state.extra as NoticeDialogData, + ), + ), + ), + + /// HomePage / GamePage + GoRoute( + path: '/home/game', + name: Routes.gamePage.name, + builder: (context, state) => const GamePage(), + ), + + /// HomePage / GamePage / GameExitConfirmDialog + GoRoute( + path: '/home/game/exit', + name: Routes.gameExitDialog.name, + pageBuilder: (context, state) => const DialogPage( + route: Routes.gameExitDialog, + child: GameExitConfirmDialog(), + ), + ), + + /// HomePage / SettingPage + GoRoute( + path: '/home/setting', + name: Routes.settingPage.name, + builder: (context, state) => const SettingPage(), + ), + + /// HomePage / SettingPage / EditNicknamePage + GoRoute( + path: '/home/setting/editNickname', + name: Routes.editNicknamePage.name, + pageBuilder: (context, state) { + return CustomPageTransition.page( + const EditNicknamePage(), + name: Routes.editNicknamePage.name, + isBlur: true, + ); + }, + ), + + /// HomePage / SettingPage / LanguageBottomSheet + GoRoute( + path: '/home/setting/language', + name: Routes.languageBottomSheet.name, + pageBuilder: (context, state) => const BottomSheetPage( + route: Routes.languageBottomSheet, + child: LanguageBottomSheet(), + ), + ), + + /// HomePage / SettingPage / AppLicensePage + GoRoute( + path: '/home/setting/license', + name: Routes.licensePage.name, + builder: (context, state) => const AppLicensePage(), + ), + + /// HomePage / SettingPage / DevPage + GoRoute( + path: '/home/setting/dev', + name: Routes.devPage.name, + builder: (context, state) => const DevPage(), + ), + + /// HomePage / SettingPage / DevPage / LogPage + GoRoute( + path: '/home/setting/dev/log', + name: Routes.devLogPage.name, + builder: (context, state) => const LogPage(), + ), + + /// HomePage / SettingPage / DevPage / ComponentPage + GoRoute( + path: '/home/setting/dev/component', + name: Routes.devComponentPage.name, + builder: (context, state) => const ComponentPage(), + ), + + /// HomePage / SettingPage / DevPage / LocalDataPage + GoRoute( + path: '/home/setting/dev/local_data', + name: Routes.devLocalDataPage.name, + builder: (context, state) => const LocalDataPage(), + ), + + /// HomePage / SettingPage / DevPage / LogoutDialog + GoRoute( + path: '/home/setting/dev/local_data/logout', + name: Routes.devLogoutDialog.name, + pageBuilder: (context, state) => const DialogPage( + route: Routes.devLogoutDialog, + child: LogoutDialog(), + ), + ), + + /// HomePage / SettingPage / DevPage / LoginBottomSheet + GoRoute( + path: '/home/setting/dev/local_data/login', + name: Routes.devLoginBottomSheet.name, + pageBuilder: (context, state) => const BottomSheetPage( + route: Routes.devLoginBottomSheet, + child: LoginBottomSheet(), + ), + ), + ], + ); +} diff --git a/lib/core/data/sources/firebase_analytics_source.dart b/lib/core/data/sources/firebase_analytics_source.dart new file mode 100644 index 0000000..c8a242b --- /dev/null +++ b/lib/core/data/sources/firebase_analytics_source.dart @@ -0,0 +1,8 @@ +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class FirebaseAnalyticsSource { + static final $ = AutoDisposeProvider((ref) { + return FirebaseAnalytics.instance; + }); +} diff --git a/lib/features/analytics/data/repository/analytics_repository.dart b/lib/features/analytics/data/repository/analytics_repository.dart deleted file mode 100644 index a0b928b..0000000 --- a/lib/features/analytics/data/repository/analytics_repository.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:x_pr/features/analytics/data/repository/analytics_repository_impl.dart'; -import 'package:x_pr/features/analytics/data/source/firebase_analytics_source.dart'; - -abstract interface class AnalyticsRepository { - static final $ = AutoDisposeProvider((ref) { - return AnalyticsRepositoryImpl( - firebaseAnalyticsSource: ref.read(FirebaseAnalyticsSource.$), - ); - }); -} diff --git a/lib/features/analytics/data/repository/analytics_repository_impl.dart b/lib/features/analytics/data/repository/analytics_repository_impl.dart deleted file mode 100644 index 3bd4f4f..0000000 --- a/lib/features/analytics/data/repository/analytics_repository_impl.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:x_pr/features/analytics/data/repository/analytics_repository.dart'; -import 'package:x_pr/features/analytics/data/source/firebase_analytics_source.dart'; - -class AnalyticsRepositoryImpl implements AnalyticsRepository { - const AnalyticsRepositoryImpl({ - required this.firebaseAnalyticsSource, - }); - - final FirebaseAnalyticsSource firebaseAnalyticsSource; -} diff --git a/lib/features/analytics/data/repository/event_analytics_repository.dart b/lib/features/analytics/data/repository/event_analytics_repository.dart new file mode 100644 index 0000000..33326b3 --- /dev/null +++ b/lib/features/analytics/data/repository/event_analytics_repository.dart @@ -0,0 +1,8 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:x_pr/features/analytics/data/repository/event_analytics_repository_with_firebase.dart'; + +abstract interface class EventAnalyticsRepository { + static final firebase = AutoDisposeProvider((ref) { + return EventAnalyticsRepositoryWithFirebase(); + }); +} diff --git a/lib/features/analytics/data/repository/event_analytics_repository_with_firebase.dart b/lib/features/analytics/data/repository/event_analytics_repository_with_firebase.dart new file mode 100644 index 0000000..7c2410e --- /dev/null +++ b/lib/features/analytics/data/repository/event_analytics_repository_with_firebase.dart @@ -0,0 +1,4 @@ +import 'package:x_pr/features/analytics/data/repository/event_analytics_repository.dart'; + +class EventAnalyticsRepositoryWithFirebase + implements EventAnalyticsRepository {} diff --git a/lib/features/analytics/data/repository/route_analytics_repository.dart b/lib/features/analytics/data/repository/route_analytics_repository.dart new file mode 100644 index 0000000..ab4ffbf --- /dev/null +++ b/lib/features/analytics/data/repository/route_analytics_repository.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:x_pr/features/analytics/data/repository/route_analytics_repository_with_firebase.dart'; +import 'package:x_pr/features/analytics/data/source/firebase_analytics_event_source.dart'; + +abstract class RouteAnalyticsRepository extends NavigatorObserver { + static final $ = AutoDisposeProvider((ref) { + return RouteAnalyticsRepositoryWithFirebase( + analyticsEventSource: ref.read(FirebaseAnalyticsEventSource.$), + ); + }); +} diff --git a/lib/app/routes/routes_observer.dart b/lib/features/analytics/data/repository/route_analytics_repository_with_firebase.dart similarity index 58% rename from lib/app/routes/routes_observer.dart rename to lib/features/analytics/data/repository/route_analytics_repository_with_firebase.dart index 9b37ac8..1ebb8d2 100644 --- a/lib/app/routes/routes_observer.dart +++ b/lib/features/analytics/data/repository/route_analytics_repository_with_firebase.dart @@ -1,29 +1,45 @@ -import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:x_pr/core/utils/log/logger.dart'; +import 'package:x_pr/features/analytics/data/repository/route_analytics_repository.dart'; +import 'package:x_pr/features/analytics/data/source/firebase_analytics_event_source.dart'; -class RoutesObserver extends NavigatorObserver { - final FirebaseAnalytics analytics = FirebaseAnalytics.instance; +class RouteAnalyticsRepositoryWithFirebase extends RouteAnalyticsRepository { + static final $ = Provider((ref) { + return RouteAnalyticsRepositoryWithFirebase( + analyticsEventSource: ref.read(FirebaseAnalyticsEventSource.$), + ); + }); - void _print(String message, [bool isShow = true]) { - if (!isShow) return; - Logger.d("🚪 $message"); - } + RouteAnalyticsRepositoryWithFirebase({ + required FirebaseAnalyticsEventSource analyticsEventSource, + }) : _analyticsEventSource = analyticsEventSource; - bool _logFilter(Route? route) { - return !(route?.settings.name?.startsWith('dev') ?? true); - } + final FirebaseAnalyticsEventSource _analyticsEventSource; - Future _sendScreenViewLog(Route route) async { + Future _sendScreenViewLog( + Route route, [ + bool isShow = false, + ]) async { final String? screenName = route.settings.name; if (screenName == null) return; try { - await analytics.logScreenView(screenName: screenName); + if (isShow) Logger.d("🧐 logScreenView : $screenName"); + await _analyticsEventSource.logScreenView(screenName); } catch (e, s) { Logger.e("Failed to sendLog", e, s); } } + void _print(String message, [bool isShow = false]) { + if (!isShow) return; + Logger.d("🚪 $message"); + } + + bool _logFilter(Route? route) { + return !(route?.settings.name?.startsWith('dev') ?? true); + } + @override void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); diff --git a/lib/features/analytics/data/source/firebase_analytics_event_source.dart b/lib/features/analytics/data/source/firebase_analytics_event_source.dart new file mode 100644 index 0000000..a0e20cc --- /dev/null +++ b/lib/features/analytics/data/source/firebase_analytics_event_source.dart @@ -0,0 +1,32 @@ +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:x_pr/core/data/sources/firebase_analytics_source.dart'; + +class FirebaseAnalyticsEventSource { + static final $ = AutoDisposeProvider((ref) { + return FirebaseAnalyticsEventSource( + analytics: ref.read(FirebaseAnalyticsSource.$), + ); + }); + + FirebaseAnalyticsEventSource({ + required FirebaseAnalytics analytics, + }) : _analytics = analytics; + final FirebaseAnalytics _analytics; + + Future logScreenView(String screenName) { + return _analytics.logScreenView(screenName: screenName); + } + + Future logEvent({ + required String name, + Map? parameters, + AnalyticsCallOptions? callOptions, + }) { + return _analytics.logEvent( + name: name, + parameters: parameters, + callOptions: callOptions, + ); + } +} diff --git a/lib/features/analytics/data/source/firebase_analytics_source.dart b/lib/features/analytics/data/source/firebase_analytics_source.dart deleted file mode 100644 index 2250ef2..0000000 --- a/lib/features/analytics/data/source/firebase_analytics_source.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class FirebaseAnalyticsSource { - static final $ = AutoDisposeProvider((ref) { - return FirebaseAnalyticsSource(); - }); - - final FirebaseAnalytics _analytics = FirebaseAnalytics.instance; - - FirebaseAnalyticsObserver getObserver() { - return FirebaseAnalyticsObserver( - analytics: _analytics, - ); - } - - void screenView() async { - await _analytics.logEvent( - name: 'screen_view', - parameters: { - // 'firebase_screen': screenName, - // 'firebase_screen_class': screenClass, - }, - ); - } -} diff --git a/lib/features/analytics/domain/service/analytics_service.dart b/lib/features/analytics/domain/service/analytics_service.dart index 5f9418b..386a6c1 100644 --- a/lib/features/analytics/domain/service/analytics_service.dart +++ b/lib/features/analytics/domain/service/analytics_service.dart @@ -1,16 +1,16 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:x_pr/features/analytics/data/repository/analytics_repository.dart'; +import 'package:x_pr/features/analytics/domain/usecase/get_navigator_observer_usecase.dart'; class AnalyticsService { static final $ = Provider((ref) { - return AnalyticsService( - analyticsRepository: ref.read(AnalyticsRepository.$), - ); + return AnalyticsService(ref); }); - const AnalyticsService({ - required this.analyticsRepository, - }); + AnalyticsService(this._ref); + final ProviderRef _ref; - final AnalyticsRepository analyticsRepository; + NavigatorObserver getNavigatorObserver() { + return _ref.read(GetNavigatorObserverUsecase.$).call(); + } } diff --git a/lib/features/analytics/domain/usecase/get_navigator_observer_usecase.dart b/lib/features/analytics/domain/usecase/get_navigator_observer_usecase.dart new file mode 100644 index 0000000..c8205ab --- /dev/null +++ b/lib/features/analytics/domain/usecase/get_navigator_observer_usecase.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:x_pr/core/domain/usecases/base_usecase.dart'; +import 'package:x_pr/features/analytics/data/repository/route_analytics_repository.dart'; + +class GetNavigatorObserverParam { + GetNavigatorObserverParam(); +} + +class GetNavigatorObserverUsecase + implements BaseUsecase { + static final $ = AutoDisposeProvider((ref) { + return GetNavigatorObserverUsecase( + routeAnalyticsRepository: ref.read(RouteAnalyticsRepository.$), + ); + }); + + GetNavigatorObserverUsecase({ + required this.routeAnalyticsRepository, + }); + + final RouteAnalyticsRepository routeAnalyticsRepository; + + @override + NavigatorObserver call([GetNavigatorObserverParam? param]) { + return routeAnalyticsRepository; + } +}