diff --git a/analysis_options.yaml b/analysis_options.yaml index ea11260e..2787175f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -13,6 +13,7 @@ analyzer: implicit-dynamic: false exclude: - "**/*.g.dart" + - "**/*.gr.dart" - "**/*.freezed.dart" linter: diff --git a/build_runner.sh b/build_runner.sh index 51804a4a..247f3e8d 100755 --- a/build_runner.sh +++ b/build_runner.sh @@ -1,4 +1,5 @@ #!/bin/bash find . -name "*.g.dart" -type f -delete; +find . -name "*.gr.dart" -type f -delete; find . -name "*.freezed.dart" -type f -delete; -flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file +flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/lib/game/game_widget.dart b/lib/game/game_page.dart similarity index 98% rename from lib/game/game_widget.dart rename to lib/game/game_page.dart index 15471788..76579a48 100644 --- a/lib/game/game_widget.dart +++ b/lib/game/game_page.dart @@ -15,16 +15,16 @@ import 'pause_dialog.dart'; import 'tile/tile.dart'; import 'tile/tile_converter.dart'; -class GameWidget extends ConsumerWidget { +class GamePage extends ConsumerWidget { final MyGame _game; + final Map arguments; - GameWidget({Key? key}) + GamePage({Key? key, required this.arguments}) : _game = MyGame(), super(key: key); @override Widget build(BuildContext context, ScopedReader watch) { - final arguments = ModalRoute.of(context)!.settings.arguments as Map; final midiLoaded = watch(midiProvider); if (!midiLoaded) { return const LoadingSoundWidget(); diff --git a/lib/game_config/game_config_page.dart b/lib/game_config/game_config_page.dart index f46f304c..099c2138 100644 --- a/lib/game_config/game_config_page.dart +++ b/lib/game_config/game_config_page.dart @@ -1,16 +1,21 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sprintf/sprintf.dart'; -import '../routes.dart'; +import '../game/colors.dart'; +import '../router/router.dart'; import '../songs/song.dart'; import 'game_config_model.dart'; class GameConfigPage extends ConsumerWidget { + final Song song; + + GameConfigPage({required this.song}); + @override Widget build(BuildContext context, ScopedReader watch) { - final song = ModalRoute.of(context)!.settings.arguments as Song; final gameConfigState = watch(gameConfigStateProvider); return Scaffold( @@ -129,12 +134,22 @@ class GameConfigPage extends ConsumerWidget { Expanded( child: ElevatedButton( onPressed: () { - Navigator.pushNamed(context, Routes.game, - arguments: { - 'song': song, - 'difficulty': gameConfigState.difficulty, - 'speed': gameConfigState.speed - }); + primaryColor = Theme.of(context).colorScheme.primary; + secondaryColor = + Theme.of(context).colorScheme.secondary; + backgroundColor = + Theme.of(context).colorScheme.background; + onBackgroundColor = + Theme.of(context).colorScheme.onBackground; + paint = Paint() + ..colorFilter = + ColorFilter.mode(primaryColor, BlendMode.srcIn); + AutoRouter.of(context) + .push(GameRoute(arguments: { + 'song': song, + 'difficulty': gameConfigState.difficulty, + 'speed': gameConfigState.speed + })); }, child: Text(AppLocalizations.of(context)!.txt_start)), ), diff --git a/lib/home/home_page.dart b/lib/home/home_page.dart index ebd58ccc..af3edf1b 100644 --- a/lib/home/home_page.dart +++ b/lib/home/home_page.dart @@ -1,10 +1,11 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../midi/midi_model.dart'; -import '../routes.dart'; +import '../router/router.dart'; import '../search/search_widget.dart'; import '../songs/songs_model.dart'; import '../songs/songs_widget.dart'; @@ -39,36 +40,35 @@ class HomePage extends HookWidget { image: const AssetImage('assets/images/img_guitar.png'), color: Theme.of(context).appBarTheme.iconTheme!.color, ), - onPressed: () async { - await Navigator.pushNamed(context, Routes.instrument); - }), - IconButton(icon: ClipOval(child: Consumer( - builder: (context, watch, child) { - // FIXME To load midi - watch(midiProvider); - final user = watch(userProvider); - return user.when( - data: (user) => Image.network( - user.photoUrl, - errorBuilder: (context, exception, stackTrace) { - return const Icon( - Icons.account_circle_rounded); - }, - ), - loading: () => - const Icon(Icons.account_circle_rounded), - error: (_, __) => - const Icon(Icons.account_circle_rounded)); - }, - )), onPressed: () async { - await Navigator.pushNamed(context, Routes.account); - }), + onPressed: () => AutoRouter.of(context) + .push(const InstrumentsRoute())), IconButton( - icon: const Icon(Icons.settings), - onPressed: () async { - await Navigator.pushNamed(context, Routes.setting); - }, - ), + icon: ClipOval(child: Consumer( + builder: (context, watch, child) { + // FIXME To load midi + watch(midiProvider); + final user = watch(userProvider); + return user.when( + data: (user) => Image.network( + user.photoUrl, + errorBuilder: + (context, exception, stackTrace) { + return const Icon( + Icons.account_circle_rounded); + }, + ), + loading: () => + const Icon(Icons.account_circle_rounded), + error: (_, __) => + const Icon(Icons.account_circle_rounded)); + }, + )), + onPressed: () => + AutoRouter.of(context).push(const UserRoute())), + IconButton( + icon: const Icon(Icons.settings), + onPressed: () => + AutoRouter.of(context).push(const SettingsRoute())), ], bottom: TabBar( isScrollable: true, diff --git a/lib/main.dart b/lib/main.dart index d67962af..6671f6cf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,22 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'game/colors.dart'; -import 'game/game_widget.dart'; -import 'game_config/game_config_page.dart'; -import 'home/home_page.dart'; -import 'instrument/instruments_page.dart'; import 'locale/locale_model.dart'; -import 'locale/locale_page.dart'; import 'preferences.dart'; -import 'routes.dart'; -import 'setting/settings_page.dart'; -import 'splash_page.dart'; +import 'router/router.dart'; import 'theme/theme_model.dart'; -import 'theme/theme_page.dart'; -import 'user/user_page.dart'; class Logger extends ProviderObserver { @override @@ -44,11 +35,13 @@ Future main() async { } class App extends ConsumerWidget { + final _rootRouter = RootRouter(); + @override Widget build(BuildContext context, ScopedReader watch) { final themeMode = watch(themeModeProvider); final locale = watch(localeProvider); - return MaterialApp( + return MaterialApp.router( title: 'Hit Notes', debugShowCheckedModeBanner: false, locale: locale, @@ -57,41 +50,11 @@ class App extends ConsumerWidget { themeMode: themeMode, theme: buildTheme(), darkTheme: buildTheme(isDark: true), - routes: { - Routes.splash: (context) { - return SplashPage(); - }, - Routes.home: (context) { - return HomePage(); - }, - Routes.gameConfig: (context) { - return GameConfigPage(); - }, - Routes.game: (context) { - primaryColor = Theme.of(context).colorScheme.primary; - secondaryColor = Theme.of(context).colorScheme.secondary; - backgroundColor = Theme.of(context).colorScheme.background; - onBackgroundColor = Theme.of(context).colorScheme.onBackground; - paint = Paint() - ..colorFilter = ColorFilter.mode(primaryColor, BlendMode.srcIn); - return GameWidget(); - }, - Routes.account: (context) { - return UserPage(); - }, - Routes.language: (context) { - return LocalePage(); - }, - Routes.theme: (context) { - return ThemePage(); - }, - Routes.instrument: (context) { - return InstrumentsPage(); - }, - Routes.setting: (context) { - return SettingsPage(); - }, - }, + routerDelegate: AutoRouterDelegate( + _rootRouter, + navigatorObservers: () => [AutoRouteObserver()], + ), + routeInformationParser: _rootRouter.defaultRouteParser(), ); } } diff --git a/lib/router/router.dart b/lib/router/router.dart new file mode 100644 index 00000000..6c367550 --- /dev/null +++ b/lib/router/router.dart @@ -0,0 +1,57 @@ +import 'package:auto_route/auto_route.dart'; + +import '../game/game_page.dart'; +import '../game_config/game_config_page.dart'; +import '../home/home_page.dart'; +import '../instrument/instruments_page.dart'; +import '../locale/locale_page.dart'; +import '../setting/settings_page.dart'; +import '../splash_page.dart'; +import '../theme/theme_page.dart'; +import '../user/user_page.dart'; + +export 'router.gr.dart'; + +@MaterialAutoRouter( + replaceInRouteName: 'Page,Route', + routes: [ + // app stack + AutoRoute( + path: '/', + page: SplashPage, + ), + AutoRoute( + path: '/home', + page: HomePage, + ), + AutoRoute( + path: '/gameConfig', + page: GameConfigPage, + ), + AutoRoute( + path: '/game', + page: GamePage, + ), + AutoRoute( + path: '/user', + page: UserPage, + ), + AutoRoute( + path: '/locale', + page: LocalePage, + ), + AutoRoute( + path: '/theme', + page: ThemePage, + ), + AutoRoute( + path: '/instrument', + page: InstrumentsPage, + ), + AutoRoute( + path: '/setting', + page: SettingsPage, + ), + ], +) +class $RootRouter {} diff --git a/lib/router/router.gr.dart b/lib/router/router.gr.dart new file mode 100644 index 00000000..73b664c6 --- /dev/null +++ b/lib/router/router.gr.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouteGenerator +// ************************************************************************** + +import 'package:auto_route/auto_route.dart' as _i1; +import 'package:flutter/material.dart' as _i2; + +import '../game/game_page.dart' as _i6; +import '../game_config/game_config_page.dart' as _i5; +import '../home/home_page.dart' as _i4; +import '../instrument/instruments_page.dart' as _i10; +import '../locale/locale_page.dart' as _i8; +import '../setting/settings_page.dart' as _i11; +import '../songs/song.dart' as _i12; +import '../splash_page.dart' as _i3; +import '../theme/theme_page.dart' as _i9; +import '../user/user_page.dart' as _i7; + +class RootRouter extends _i1.RootStackRouter { + RootRouter([_i2.GlobalKey<_i2.NavigatorState>? navigatorKey]) + : super(navigatorKey); + + @override + final Map pagesMap = { + SplashRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i3.SplashPage(); + }), + HomeRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i4.HomePage(); + }), + GameConfigRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (data) { + final args = data.argsAs(); + return _i5.GameConfigPage(song: args.song); + }), + GameRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (data) { + final args = data.argsAs(); + return _i6.GamePage(key: args.key, arguments: args.arguments); + }), + UserRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i7.UserPage(); + }), + LocaleRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i8.LocalePage(); + }), + ThemeRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i9.ThemePage(); + }), + InstrumentsRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i10.InstrumentsPage(); + }), + SettingsRoute.name: (routeData) => _i1.MaterialPageX( + routeData: routeData, + builder: (_) { + return _i11.SettingsPage(); + }) + }; + + @override + List<_i1.RouteConfig> get routes => [ + _i1.RouteConfig(SplashRoute.name, path: '/'), + _i1.RouteConfig(HomeRoute.name, path: '/home'), + _i1.RouteConfig(GameConfigRoute.name, path: '/gameConfig'), + _i1.RouteConfig(GameRoute.name, path: '/game'), + _i1.RouteConfig(UserRoute.name, path: '/user'), + _i1.RouteConfig(LocaleRoute.name, path: '/locale'), + _i1.RouteConfig(ThemeRoute.name, path: '/theme'), + _i1.RouteConfig(InstrumentsRoute.name, path: '/instrument'), + _i1.RouteConfig(SettingsRoute.name, path: '/setting') + ]; +} + +class SplashRoute extends _i1.PageRouteInfo { + const SplashRoute() : super(name, path: '/'); + + static const String name = 'SplashRoute'; +} + +class HomeRoute extends _i1.PageRouteInfo { + const HomeRoute() : super(name, path: '/home'); + + static const String name = 'HomeRoute'; +} + +class GameConfigRoute extends _i1.PageRouteInfo { + GameConfigRoute({required _i12.Song song}) + : super(name, path: '/gameConfig', args: GameConfigRouteArgs(song: song)); + + static const String name = 'GameConfigRoute'; +} + +class GameConfigRouteArgs { + const GameConfigRouteArgs({required this.song}); + + final _i12.Song song; +} + +class GameRoute extends _i1.PageRouteInfo { + GameRoute({_i2.Key? key, required Map arguments}) + : super(name, + path: '/game', args: GameRouteArgs(key: key, arguments: arguments)); + + static const String name = 'GameRoute'; +} + +class GameRouteArgs { + const GameRouteArgs({this.key, required this.arguments}); + + final _i2.Key? key; + + final Map arguments; +} + +class UserRoute extends _i1.PageRouteInfo { + const UserRoute() : super(name, path: '/user'); + + static const String name = 'UserRoute'; +} + +class LocaleRoute extends _i1.PageRouteInfo { + const LocaleRoute() : super(name, path: '/locale'); + + static const String name = 'LocaleRoute'; +} + +class ThemeRoute extends _i1.PageRouteInfo { + const ThemeRoute() : super(name, path: '/theme'); + + static const String name = 'ThemeRoute'; +} + +class InstrumentsRoute extends _i1.PageRouteInfo { + const InstrumentsRoute() : super(name, path: '/instrument'); + + static const String name = 'InstrumentsRoute'; +} + +class SettingsRoute extends _i1.PageRouteInfo { + const SettingsRoute() : super(name, path: '/setting'); + + static const String name = 'SettingsRoute'; +} diff --git a/lib/routes.dart b/lib/routes.dart deleted file mode 100644 index 1555b856..00000000 --- a/lib/routes.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Routes { - static final splash = '/'; - static final home = '/home'; - static final gameConfig = '/gameConfig'; - static final game = '/game'; - static final account = '/account'; - static final language = '/language'; - static final theme = '/theme'; - static final instrument = '/instrument'; - static final setting = '/setting'; -} diff --git a/lib/search/search_widget.dart b/lib/search/search_widget.dart index c37e03f1..0d95806d 100644 --- a/lib/search/search_widget.dart +++ b/lib/search/search_widget.dart @@ -1,8 +1,9 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../loading_widget.dart'; -import '../routes.dart'; +import '../router/router.dart'; import '../songs/song.dart'; import '../songs/song_widget.dart'; import '../songs/songs_repository_impl.dart'; @@ -51,12 +52,9 @@ class SearchWidget extends SearchDelegate { itemBuilder: (context, index) { final song = songs[index]; return SongWidget( - song: song, - onTap: () async { - await Navigator.pushNamed(context, Routes.gameConfig, - arguments: song); - }, - ); + song: song, + onTap: () => AutoRouter.of(context) + .push(GameConfigRoute(song: song))); }, ); } else { diff --git a/lib/setting/settings_page.dart b/lib/setting/settings_page.dart index 7c2dcc90..96c34cab 100644 --- a/lib/setting/settings_page.dart +++ b/lib/setting/settings_page.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:auto_route/auto_route.dart'; import 'package:device_info/device_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_email_sender/flutter_email_sender.dart'; @@ -9,7 +10,7 @@ import 'package:in_app_review/in_app_review.dart'; import 'package:package_info/package_info.dart'; import '../locale/locale_page.dart'; -import '../routes.dart'; +import '../router/router.dart'; import '../theme/theme_model.dart'; import '../theme/theme_page.dart'; @@ -23,31 +24,26 @@ class SettingsPage extends StatelessWidget { body: ListView( children: [ ListTile( - title: Text( - AppLocalizations.of(context)!.txt_language, - style: Theme.of(context).textTheme.headline6, - ), - subtitle: Text( - localeStrings[Localizations.localeOf(context).languageCode]!, - style: Theme.of(context).textTheme.subtitle1), - onTap: () async { - await Navigator.pushNamed(context, Routes.language); - }, - ), + title: Text( + AppLocalizations.of(context)!.txt_language, + style: Theme.of(context).textTheme.headline6, + ), + subtitle: Text( + localeStrings[ + Localizations.localeOf(context).languageCode]!, + style: Theme.of(context).textTheme.subtitle1), + onTap: () => AutoRouter.of(context).push(const LocaleRoute())), ListTile( - title: Text( - AppLocalizations.of(context)!.txt_theme, - style: Theme.of(context).textTheme.headline6, - ), - subtitle: Consumer(builder: (context, watch, child) { - final themeMode = watch(themeModeProvider); - return Text(getThemeName(context, themeMode), - style: Theme.of(context).textTheme.subtitle1); - }), - onTap: () async { - await Navigator.pushNamed(context, Routes.theme); - }, - ), + title: Text( + AppLocalizations.of(context)!.txt_theme, + style: Theme.of(context).textTheme.headline6, + ), + subtitle: Consumer(builder: (context, watch, child) { + final themeMode = watch(themeModeProvider); + return Text(getThemeName(context, themeMode), + style: Theme.of(context).textTheme.subtitle1); + }), + onTap: () => AutoRouter.of(context).push(const ThemeRoute())), ListTile( title: Text( AppLocalizations.of(context)!.txt_button_feedback, diff --git a/lib/songs/songs_widget.dart b/lib/songs/songs_widget.dart index 7988ce2f..e52ffacc 100644 --- a/lib/songs/songs_widget.dart +++ b/lib/songs/songs_widget.dart @@ -1,8 +1,9 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../loading_widget.dart'; -import '../routes.dart'; +import '../router/router.dart'; import 'song_widget.dart'; import 'songs_model.dart'; @@ -36,13 +37,9 @@ class SongsWidget extends ConsumerWidget { itemBuilder: (context, index) { final song = songsByTag[index]; return SongWidget( - song: song, - onTap: () async { - await Navigator.pushNamed( - context, Routes.gameConfig, - arguments: song); - }, - ); + song: song, + onTap: () => AutoRouter.of(context) + .push(GameConfigRoute(song: song))); }, separatorBuilder: (context, index) { return const Divider( diff --git a/lib/splash_page.dart b/lib/splash_page.dart index 13bd21bf..e8a6359e 100644 --- a/lib/splash_page.dart +++ b/lib/splash_page.dart @@ -1,9 +1,10 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'authentication/authentication_model.dart'; import 'authentication/authentication_state.dart'; -import 'routes.dart'; +import 'router/router.dart'; class SplashPage extends ConsumerWidget { @override @@ -12,8 +13,7 @@ class SplashPage extends ConsumerWidget { provider: authenticationProvider, onChange: (context, state) { if (state is AuthenticationStateAuthenticated) { - Navigator.pushNamedAndRemoveUntil( - context, Routes.home, (route) => false); + AutoRouter.of(context).replace(const HomeRoute()); } }, child: const Scaffold( diff --git a/pubspec.lock b/pubspec.lock index 26ba678a..74b7732f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -36,6 +36,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.6.1" + auto_route: + dependency: "direct main" + description: + name: auto_route + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" build: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 44274753..279816b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter + auto_route: cloud_firestore: cloud_functions: collection: @@ -32,6 +33,7 @@ dependencies: sprintf: dev_dependencies: + auto_route_generator: build_runner: freezed: json_serializable: