From 6c20de9daadd982d1bcdbda6a734f9cf46643b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Sat, 4 May 2024 15:14:44 +0200 Subject: [PATCH] Clean/update some code, add "settings" and "about" pages. --- android/gradle.properties | 4 +- assets/translations/en.json | 13 ++- assets/translations/fr.json | 13 ++- .../metadata/android/en-US/changelogs/26.txt | 1 + .../metadata/android/fr-FR/changelogs/26.txt | 1 + lib/config/default_game_settings.dart | 15 ++-- lib/config/menu.dart | 57 +++++++++++++ lib/cubit/game_cubit.dart | 30 +++---- lib/cubit/nav_cubit.dart | 37 ++++++++ lib/cubit/theme_cubit.dart | 31 +++++++ lib/cubit/theme_state.dart | 15 ++++ lib/main.dart | 30 +++++-- lib/ui/painters/parameter_painter.dart | 2 +- lib/ui/screens/page_about.dart | 37 ++++++++ lib/ui/screens/page_game.dart | 18 ++++ lib/ui/screens/page_settings.dart | 22 +++++ lib/ui/skeleton.dart | 20 +++-- lib/ui/widgets/button_game_start_new.dart | 34 ++++++++ .../{games => game}/buttons_yes_no.dart | 0 .../game/game.dart} | 11 ++- .../{ => game}/game_bottom_buttons.dart | 0 lib/ui/widgets/{ => game}/game_question.dart | 2 +- .../{ => game}/game_top_indicator.dart | 4 +- .../indicator_position.dart | 0 .../{indicators => game}/indicator_score.dart | 0 lib/ui/widgets/global_app_bar.dart | 85 ++++++++++++++----- lib/ui/widgets/helpers/app_header.dart | 24 ++++++ .../{app_titles.dart => app_title.dart} | 0 .../parameters.dart} | 58 ++----------- lib/ui/widgets/settings/settings_form.dart | 63 ++++++++++++++ lib/ui/widgets/settings/theme_card.dart | 47 ++++++++++ pubspec.lock | 72 +++++++++++++++- pubspec.yaml | 10 ++- 33 files changed, 624 insertions(+), 132 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/26.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/26.txt create mode 100644 lib/config/menu.dart create mode 100644 lib/cubit/nav_cubit.dart create mode 100644 lib/cubit/theme_cubit.dart create mode 100644 lib/cubit/theme_state.dart create mode 100644 lib/ui/screens/page_about.dart create mode 100644 lib/ui/screens/page_game.dart create mode 100644 lib/ui/screens/page_settings.dart create mode 100644 lib/ui/widgets/button_game_start_new.dart rename lib/ui/widgets/{games => game}/buttons_yes_no.dart (100%) rename lib/ui/{screens/screen_game.dart => widgets/game/game.dart} (76%) rename lib/ui/widgets/{ => game}/game_bottom_buttons.dart (100%) rename lib/ui/widgets/{ => game}/game_question.dart (95%) rename lib/ui/widgets/{ => game}/game_top_indicator.dart (82%) rename lib/ui/widgets/{indicators => game}/indicator_position.dart (100%) rename lib/ui/widgets/{indicators => game}/indicator_score.dart (100%) create mode 100644 lib/ui/widgets/helpers/app_header.dart rename lib/ui/widgets/helpers/{app_titles.dart => app_title.dart} (100%) rename lib/ui/{screens/screen_parameters.dart => widgets/parameters.dart} (71%) create mode 100644 lib/ui/widgets/settings/settings_form.dart create mode 100644 lib/ui/widgets/settings/theme_card.dart diff --git a/android/gradle.properties b/android/gradle.properties index 357cef3..f0fcf6c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.25 -app.versionCode=25 +app.versionName=0.0.26 +app.versionCode=26 diff --git a/assets/translations/en.json b/assets/translations/en.json index 5f42856..5b07e64 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,3 +1,14 @@ { - "app_name": "SortGame" + "app_name": "SortGame", + + "bottom_nav_home": "Game", + "bottom_nav_settings": "Settings", + "bottom_nav_about": "About", + + "settings_title": "Settings", + "settings_label_theme": "Theme mode", + + "about_title": "About", + "about_content": "Sort game, simple and classic.", + "about_version": "Version: {version}" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 5f42856..0cd4ca7 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,3 +1,14 @@ { - "app_name": "SortGame" + "app_name": "SortGame", + + "bottom_nav_home": "Jeu", + "bottom_nav_settings": "Réglages", + "bottom_nav_about": "Infos", + + "settings_title": "Réglages", + "settings_label_theme": "Thème de couleurs", + + "about_title": "Informations", + "about_content": "Jeu de tri.", + "about_version": "Version : {version}" } diff --git a/fastlane/metadata/android/en-US/changelogs/26.txt b/fastlane/metadata/android/en-US/changelogs/26.txt new file mode 100644 index 0000000..7182a72 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/26.txt @@ -0,0 +1 @@ +Clean/update some code. Add dark/light theme selector page. Add "about" page. diff --git a/fastlane/metadata/android/fr-FR/changelogs/26.txt b/fastlane/metadata/android/fr-FR/changelogs/26.txt new file mode 100644 index 0000000..0a14039 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/26.txt @@ -0,0 +1 @@ +Nettoyage/mise à jour de code. Ajout de page de thème clair/sombre. Ajout de page "informations". diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart index 72aed12..dd16dbf 100644 --- a/lib/config/default_game_settings.dart +++ b/lib/config/default_game_settings.dart @@ -2,23 +2,27 @@ import 'package:sortgame/data/fetch_data_helper.dart'; import 'package:sortgame/utils/tools.dart'; class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeItemsCount = 'itemsCount'; + static const String parameterCodeThemeIndex = 'theme'; static const List<String> availableParameters = [ - 'itemsCount', - 'theme', + parameterCodeItemsCount, + parameterCodeThemeIndex, ]; + // items count: available values static const int itemsCountValueLow = 5; static const int itemsCountValueMedium = 10; static const int itemsCountValueHigh = 15; static const int itemsCountValueVeryHigh = 20; - - static const int defaultItemsCountValue = itemsCountValueMedium; static const List<int> allowedItemsCountValues = [ itemsCountValueLow, itemsCountValueMedium, itemsCountValueHigh, itemsCountValueVeryHigh, ]; + // items count: default value + static const int defaultItemsCountValue = itemsCountValueMedium; static const int defaultThemeValue = 0; @@ -26,9 +30,6 @@ class DefaultGameSettings { switch (parameterCode) { case 'itemsCount': return DefaultGameSettings.allowedItemsCountValues; - } - - switch (parameterCode) { case 'theme': final int count = FetchDataHelper().getThemes().length; return List<int>.generate(count, (i) => i); diff --git a/lib/config/menu.dart b/lib/config/menu.dart new file mode 100644 index 0000000..674e6fe --- /dev/null +++ b/lib/config/menu.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:sortgame/ui/screens/page_about.dart'; +import 'package:sortgame/ui/screens/page_game.dart'; +import 'package:sortgame/ui/screens/page_settings.dart'; + +class MenuItem { + final String code; + final Icon icon; + final Widget page; + + const MenuItem({ + required this.code, + required this.icon, + required this.page, + }); +} + +class Menu { + static const indexGame = 0; + static const menuItemGame = MenuItem( + code: 'bottom_nav_game', + icon: Icon(UniconsLine.home), + page: PageGame(), + ); + + static const indexSettings = 1; + static const menuItemSettings = MenuItem( + code: 'bottom_nav_settings', + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + code: 'bottom_nav_about', + icon: Icon(UniconsLine.info_circle), + page: PageAbout(), + ); + + static Map<int, MenuItem> items = { + indexGame: menuItemGame, + indexSettings: menuItemSettings, + indexAbout: menuItemAbout, + }; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); + } + + static Widget getPageWidget(int pageIndex) { + return items[pageIndex]?.page ?? menuItemGame.page; + } + + static int itemsCount = Menu.items.length; +} diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart index 3841e16..b21f2af 100644 --- a/lib/cubit/game_cubit.dart +++ b/lib/cubit/game_cubit.dart @@ -34,6 +34,21 @@ class GameCubit extends HydratedCubit<GameState> { updateState(game); } + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + Game newGame = Game.createNew( + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + refresh(); + } + void quitGame() { state.currentGame.updateGameIsRunning(false); refresh(); @@ -53,21 +68,6 @@ class GameCubit extends HydratedCubit<GameState> { refresh(); } - void startNewGame({ - required GameSettings gameSettings, - required GlobalSettings globalSettings, - }) { - Game newGame = Game.createNew( - gameSettings: gameSettings, - globalSettings: globalSettings, - ); - - newGame.dump(); - - updateState(newGame); - refresh(); - } - @override GameState? fromJson(Map<String, dynamic> json) { Game currentGame = json['currentGame'] as Game; diff --git a/lib/cubit/nav_cubit.dart b/lib/cubit/nav_cubit.dart new file mode 100644 index 0000000..fedff47 --- /dev/null +++ b/lib/cubit/nav_cubit.dart @@ -0,0 +1,37 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:sortgame/config/menu.dart'; + +class NavCubit extends HydratedCubit<int> { + NavCubit() : super(0); + + void updateIndex(int index) { + if (Menu.isIndexAllowed(index)) { + emit(index); + } else { + goToGamePage(); + } + } + + void goToGamePage() { + emit(Menu.indexGame); + } + + void goToSettingsPage() { + emit(Menu.indexSettings); + } + + void goToAboutPage() { + emit(Menu.indexAbout); + } + + @override + int fromJson(Map<String, dynamic> json) { + return Menu.indexGame; + } + + @override + Map<String, dynamic>? toJson(int state) { + return <String, int>{'pageIndex': state}; + } +} diff --git a/lib/cubit/theme_cubit.dart b/lib/cubit/theme_cubit.dart new file mode 100644 index 0000000..b793e89 --- /dev/null +++ b/lib/cubit/theme_cubit.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +part 'theme_state.dart'; + +class ThemeCubit extends HydratedCubit<ThemeModeState> { + ThemeCubit() : super(const ThemeModeState()); + + void getTheme(ThemeModeState state) { + emit(state); + } + + @override + ThemeModeState? fromJson(Map<String, dynamic> json) { + switch (json['themeMode']) { + case 'ThemeMode.dark': + return const ThemeModeState(themeMode: ThemeMode.dark); + case 'ThemeMode.light': + return const ThemeModeState(themeMode: ThemeMode.light); + case 'ThemeMode.system': + default: + return const ThemeModeState(themeMode: ThemeMode.system); + } + } + + @override + Map<String, String>? toJson(ThemeModeState state) { + return <String, String>{'themeMode': state.themeMode.toString()}; + } +} diff --git a/lib/cubit/theme_state.dart b/lib/cubit/theme_state.dart new file mode 100644 index 0000000..e479a50 --- /dev/null +++ b/lib/cubit/theme_state.dart @@ -0,0 +1,15 @@ +part of 'theme_cubit.dart'; + +@immutable +class ThemeModeState extends Equatable { + const ThemeModeState({ + this.themeMode, + }); + + final ThemeMode? themeMode; + + @override + List<Object?> get props => <Object?>[ + themeMode, + ]; +} diff --git a/lib/main.dart b/lib/main.dart index 3d37ac6..667de35 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,8 +9,10 @@ import 'package:path_provider/path_provider.dart'; import 'package:sortgame/config/theme.dart'; import 'package:sortgame/cubit/game_cubit.dart'; +import 'package:sortgame/cubit/nav_cubit.dart'; import 'package:sortgame/cubit/settings_game_cubit.dart'; import 'package:sortgame/cubit/settings_global_cubit.dart'; +import 'package:sortgame/cubit/theme_cubit.dart'; import 'package:sortgame/ui/skeleton.dart'; void main() async { @@ -44,20 +46,30 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider<NavCubit>(create: (context) => NavCubit()), + BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), BlocProvider<GameCubit>(create: (context) => GameCubit()), BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()), ], - child: MaterialApp( - title: 'SortGame', - theme: appTheme, - home: const SkeletonScreen(), + child: BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return MaterialApp( + title: 'SortGame', + home: const SkeletonScreen(), - // Localization stuff - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, + + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ); + }, ), ); } diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart index 3444bb3..2f14e73 100644 --- a/lib/ui/painters/parameter_painter.dart +++ b/lib/ui/painters/parameter_painter.dart @@ -60,7 +60,7 @@ class ParameterPainter extends CustomPainter { return false; } - // "unknown" parameter -> simple bock with text + // "unknown" parameter -> simple block with text void paintUnknownParameterItem( final int value, final Canvas canvas, diff --git a/lib/ui/screens/page_about.dart b/lib/ui/screens/page_about.dart new file mode 100644 index 0000000..ff9f710 --- /dev/null +++ b/lib/ui/screens/page_about.dart @@ -0,0 +1,37 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:sortgame/ui/widgets/helpers/app_header.dart'; + +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + const AppHeader(text: 'about_title'), + const Text('about_content').tr(), + FutureBuilder<PackageInfo>( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return const Text('about_version').tr( + namedArgs: { + 'version': snapshot.data!.version, + }, + ); + default: + return const SizedBox(); + } + }, + ), + ], + ); + } +} diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000..2f459d3 --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sortgame/cubit/game_cubit.dart'; +import 'package:sortgame/ui/widgets/game/game.dart'; +import 'package:sortgame/ui/widgets/parameters.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); + }); + } +} diff --git a/lib/ui/screens/page_settings.dart b/lib/ui/screens/page_settings.dart new file mode 100644 index 0000000..14fbe4d --- /dev/null +++ b/lib/ui/screens/page_settings.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import 'package:sortgame/ui/widgets/helpers/app_header.dart'; +import 'package:sortgame/ui/widgets/settings/settings_form.dart'; + +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + AppHeader(text: 'settings_title'), + SizedBox(height: 8), + SettingsForm(), + ], + ); + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index ecf652e..285eeae 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sortgame/cubit/game_cubit.dart'; -import 'package:sortgame/ui/screens/screen_game.dart'; -import 'package:sortgame/ui/screens/screen_parameters.dart'; +import 'package:sortgame/config/menu.dart'; +import 'package:sortgame/cubit/nav_cubit.dart'; import 'package:sortgame/ui/widgets/global_app_bar.dart'; class SkeletonScreen extends StatelessWidget { @@ -16,11 +15,16 @@ class SkeletonScreen extends StatelessWidget { extendBodyBehindAppBar: false, body: Material( color: Theme.of(context).colorScheme.background, - child: BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - return gameState.currentGame.isRunning - ? const ScreenGame() - : const ScreenParameters(); + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Menu.getPageWidget(pageIndex), + ); }, ), ), diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart new file mode 100644 index 0000000..e302ac5 --- /dev/null +++ b/lib/ui/widgets/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sortgame/cubit/game_cubit.dart'; +import 'package:sortgame/cubit/settings_game_cubit.dart'; +import 'package:sortgame/cubit/settings_global_cubit.dart'; + +class StartNewGameButton extends StatelessWidget { + const StartNewGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () => gameCubit.startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/games/buttons_yes_no.dart b/lib/ui/widgets/game/buttons_yes_no.dart similarity index 100% rename from lib/ui/widgets/games/buttons_yes_no.dart rename to lib/ui/widgets/game/buttons_yes_no.dart diff --git a/lib/ui/screens/screen_game.dart b/lib/ui/widgets/game/game.dart similarity index 76% rename from lib/ui/screens/screen_game.dart rename to lib/ui/widgets/game/game.dart index f285927..f218354 100644 --- a/lib/ui/screens/screen_game.dart +++ b/lib/ui/widgets/game/game.dart @@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sortgame/cubit/game_cubit.dart'; import 'package:sortgame/models/game.dart'; -import 'package:sortgame/ui/widgets/game_bottom_buttons.dart'; -import 'package:sortgame/ui/widgets/game_question.dart'; -import 'package:sortgame/ui/widgets/game_top_indicator.dart'; +import 'package:sortgame/ui/widgets/game/game_bottom_buttons.dart'; +import 'package:sortgame/ui/widgets/game/game_question.dart'; +import 'package:sortgame/ui/widgets/game/game_top_indicator.dart'; -class ScreenGame extends StatelessWidget { - const ScreenGame({super.key}); +class GameWidget extends StatelessWidget { + const GameWidget({super.key}); @override Widget build(BuildContext context) { @@ -20,7 +20,6 @@ class ScreenGame extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(height: 8), const GameTopIndicatorWidget(), const SizedBox(height: 8), Expanded( diff --git a/lib/ui/widgets/game_bottom_buttons.dart b/lib/ui/widgets/game/game_bottom_buttons.dart similarity index 100% rename from lib/ui/widgets/game_bottom_buttons.dart rename to lib/ui/widgets/game/game_bottom_buttons.dart diff --git a/lib/ui/widgets/game_question.dart b/lib/ui/widgets/game/game_question.dart similarity index 95% rename from lib/ui/widgets/game_question.dart rename to lib/ui/widgets/game/game_question.dart index 1f0c6da..e123272 100644 --- a/lib/ui/widgets/game_question.dart +++ b/lib/ui/widgets/game/game_question.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sortgame/cubit/game_cubit.dart'; import 'package:sortgame/models/data/game_item.dart'; import 'package:sortgame/models/game.dart'; -import 'package:sortgame/ui/widgets/games/buttons_yes_no.dart'; +import 'package:sortgame/ui/widgets/game/buttons_yes_no.dart'; import 'package:sortgame/ui/widgets/helpers/outlined_text_widget.dart'; class GameQuestionWidget extends StatelessWidget { diff --git a/lib/ui/widgets/game_top_indicator.dart b/lib/ui/widgets/game/game_top_indicator.dart similarity index 82% rename from lib/ui/widgets/game_top_indicator.dart rename to lib/ui/widgets/game/game_top_indicator.dart index 6ff8755..b6840f8 100644 --- a/lib/ui/widgets/game_top_indicator.dart +++ b/lib/ui/widgets/game/game_top_indicator.dart @@ -3,8 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sortgame/cubit/game_cubit.dart'; import 'package:sortgame/models/game.dart'; -import 'package:sortgame/ui/widgets/indicators/indicator_position.dart'; -import 'package:sortgame/ui/widgets/indicators/indicator_score.dart'; +import 'package:sortgame/ui/widgets/game/indicator_position.dart'; +import 'package:sortgame/ui/widgets/game/indicator_score.dart'; class GameTopIndicatorWidget extends StatelessWidget { const GameTopIndicatorWidget({super.key}); diff --git a/lib/ui/widgets/indicators/indicator_position.dart b/lib/ui/widgets/game/indicator_position.dart similarity index 100% rename from lib/ui/widgets/indicators/indicator_position.dart rename to lib/ui/widgets/game/indicator_position.dart diff --git a/lib/ui/widgets/indicators/indicator_score.dart b/lib/ui/widgets/game/indicator_score.dart similarity index 100% rename from lib/ui/widgets/indicators/indicator_score.dart rename to lib/ui/widgets/game/indicator_score.dart diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart index 02ff38f..1ec5af7 100644 --- a/lib/ui/widgets/global_app_bar.dart +++ b/lib/ui/widgets/global_app_bar.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:sortgame/config/menu.dart'; import 'package:sortgame/cubit/game_cubit.dart'; +import 'package:sortgame/cubit/nav_cubit.dart'; import 'package:sortgame/models/game.dart'; -import 'package:sortgame/ui/widgets/helpers/app_titles.dart'; +import 'package:sortgame/ui/widgets/helpers/app_title.dart'; class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { const GlobalAppBar({super.key}); @@ -12,27 +14,66 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { - final Game currentGame = gameState.currentGame; - - final List<Widget> menuActions = []; - - if (currentGame.isRunning) { - menuActions.add(TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () {}, - onLongPress: () { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - gameCubit.quitGame(); - }, - )); - } - - return AppBar( - title: const AppTitle(text: 'app_name'), - actions: menuActions, + return BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning) { + menuActions.add(TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () {}, + onLongPress: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + )); + } else { + if (pageIndex == Menu.indexGame) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToSettingsPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToAboutPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemAbout.icon, + )); + } else { + // back to Home page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToGamePage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemGame.icon, + )); + } + } + + return AppBar( + title: const AppTitle(text: 'app_name'), + actions: menuActions, + ); + }, ); }, ); diff --git a/lib/ui/widgets/helpers/app_header.dart b/lib/ui/widgets/helpers/app_header.dart new file mode 100644 index 0000000..b5c5be0 --- /dev/null +++ b/lib/ui/widgets/helpers/app_header.dart @@ -0,0 +1,24 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppHeader extends StatelessWidget { + const AppHeader({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), + ), + const SizedBox(height: 8), + ], + ); + } +} diff --git a/lib/ui/widgets/helpers/app_titles.dart b/lib/ui/widgets/helpers/app_title.dart similarity index 100% rename from lib/ui/widgets/helpers/app_titles.dart rename to lib/ui/widgets/helpers/app_title.dart diff --git a/lib/ui/screens/screen_parameters.dart b/lib/ui/widgets/parameters.dart similarity index 71% rename from lib/ui/screens/screen_parameters.dart rename to lib/ui/widgets/parameters.dart index 3c43c7b..800dce1 100644 --- a/lib/ui/screens/screen_parameters.dart +++ b/lib/ui/widgets/parameters.dart @@ -3,13 +3,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sortgame/config/default_game_settings.dart'; import 'package:sortgame/config/default_global_settings.dart'; -import 'package:sortgame/cubit/game_cubit.dart'; import 'package:sortgame/cubit/settings_game_cubit.dart'; import 'package:sortgame/cubit/settings_global_cubit.dart'; import 'package:sortgame/ui/painters/parameter_painter.dart'; +import 'package:sortgame/ui/widgets/button_game_start_new.dart'; -class ScreenParameters extends StatelessWidget { - const ScreenParameters({super.key}); +class Parameters extends StatelessWidget { + const Parameters({super.key}); final double separatorHeight = 8.0; @@ -31,7 +31,7 @@ class ScreenParameters extends StatelessWidget { } lines.add(SizedBox(height: separatorHeight)); - lines.add(Expanded(child: buildStartNewGameButton())); + lines.add(const Expanded(child: StartNewGameButton())); lines.add(SizedBox(height: separatorHeight)); // Global settings @@ -62,6 +62,10 @@ class ScreenParameters extends StatelessWidget { ? DefaultGlobalSettings.getAvailableValues(code) : DefaultGameSettings.getAvailableValues(code); + if (availableValues.length <= 1) { + return []; + } + for (int value in availableValues) { final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( builder: (BuildContext context, GameSettingsState gameSettingsState) { @@ -79,7 +83,7 @@ class ScreenParameters extends StatelessWidget { final bool isActive = (value == currentValue); final double displayWidth = MediaQuery.of(context).size.width; - final double itemWidth = displayWidth / availableValues.length - 25; + final double itemWidth = displayWidth / availableValues.length - 30; return TextButton( child: Container( @@ -112,48 +116,4 @@ class ScreenParameters extends StatelessWidget { return parameterButtons; } - - Image buildImageWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/$imageAssetCode.png'), - fit: BoxFit.fill, - ); - } - - Container buildImageContainerWidget(String imageAssetCode) { - return Container( - child: buildImageWidget(imageAssetCode), - ); - } - - Column buildDecorationImageWidget() { - return Column( - children: [ - TextButton( - child: buildImageContainerWidget('placeholder'), - onPressed: () {}, - ), - ], - ); - } - - Widget buildStartNewGameButton() { - return BlocBuilder<GameSettingsCubit, GameSettingsState>( - builder: (BuildContext context, GameSettingsState gameSettingsState) { - return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( - builder: (BuildContext context, GlobalSettingsState globalSettingsState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - - return TextButton( - child: buildImageContainerWidget('button_start'), - onPressed: () => gameCubit.startNewGame( - gameSettings: gameSettingsState.settings, - globalSettings: globalSettingsState.settings, - ), - ); - }, - ); - }, - ); - } } diff --git a/lib/ui/widgets/settings/settings_form.dart b/lib/ui/widgets/settings/settings_form.dart new file mode 100644 index 0000000..2e5d8ac --- /dev/null +++ b/lib/ui/widgets/settings/settings_form.dart @@ -0,0 +1,63 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:sortgame/ui/widgets/settings/theme_card.dart'; + +class SettingsForm extends StatefulWidget { + const SettingsForm({super.key}); + + @override + State<SettingsForm> createState() => _SettingsFormState(); +} + +class _SettingsFormState extends State<SettingsForm> { + @override + void dispose() { + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + // Light/dark theme + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const Text('settings_label_theme').tr(), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ThemeCard( + mode: ThemeMode.system, + icon: UniconsLine.cog, + ), + ThemeCard( + mode: ThemeMode.light, + icon: UniconsLine.sun, + ), + ThemeCard( + mode: ThemeMode.dark, + icon: UniconsLine.moon, + ) + ], + ), + ], + ), + + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/ui/widgets/settings/theme_card.dart b/lib/ui/widgets/settings/theme_card.dart new file mode 100644 index 0000000..27cdb0e --- /dev/null +++ b/lib/ui/widgets/settings/theme_card.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sortgame/cubit/theme_cubit.dart'; + +class ThemeCard extends StatelessWidget { + const ThemeCard({ + super.key, + required this.mode, + required this.icon, + }); + + final IconData icon; + final ThemeMode mode; + + @override + Widget build(BuildContext context) { + return BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return Card( + elevation: 2, + shadowColor: Theme.of(context).colorScheme.shadow, + color: state.themeMode == mode + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.surface, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + margin: const EdgeInsets.all(5), + child: InkWell( + onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme( + ThemeModeState(themeMode: mode), + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Icon( + icon, + size: 32, + color: state.themeMode != mode + ? Theme.of(context).colorScheme.primary + : Colors.white, + ), + ), + ); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index e2bc2cc..25b5be0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" bloc: dependency: transitive description: @@ -53,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" easy_logger: dependency: transitive description: @@ -128,6 +136,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.3" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" hydrated_bloc: dependency: "direct main" description: @@ -176,6 +200,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + url: "https://pub.dev" + source: hosted + version: "8.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + url: "https://pub.dev" + source: hosted + version: "3.0.0" path: dependency: transitive description: @@ -317,6 +357,22 @@ packages: description: flutter source: sdk version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" synchronized: dependency: transitive description: @@ -325,6 +381,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" typed_data: dependency: transitive description: @@ -361,10 +425,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.5.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9300eb7..ac69ef5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,21 +1,23 @@ name: sortgame description: A sorting game application. -publish_to: 'none' +publish_to: "none" -version: 0.0.25+25 +version: 0.0.26+26 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: sdk: flutter + easy_localization: ^3.0.1 equatable: ^2.0.5 flutter_bloc: ^8.1.1 hive: ^2.2.3 hydrated_bloc: ^9.0.0 + package_info_plus: ^8.0.0 path_provider: ^2.0.11 unicons: ^2.1.1 @@ -23,7 +25,7 @@ dev_dependencies: flutter_lints: ^3.0.1 flutter: - uses-material-design: false + uses-material-design: true assets: - assets/icons/ - assets/translations/ -- GitLab