From 01a604bcd0af615bc34832db8ed3065979a9271c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Wed, 23 Oct 2024 14:57:08 +0200 Subject: [PATCH] Normalize Activity application architecture --- assets/translations/en.json | 16 +- assets/translations/fr.json | 16 +- assets/ui/button_back.png | Bin 0 -> 2422 bytes assets/ui/button_delete_saved_game.png | Bin 0 -> 7834 bytes assets/ui/button_resume_game.png | Bin 0 -> 3434 bytes assets/ui/button_start.png | Bin 0 -> 3058 bytes lib/common/config/activity_page.dart | 86 ++++++++ lib/common/config/screen.dart | 55 +++++ lib/common/cubit/nav/nav_cubit_pages.dart | 33 +++ lib/common/cubit/nav/nav_cubit_screens.dart | 37 ++++ lib/common/ui/nav/bottom_nav_bar.dart | 46 ++++ lib/common/ui/nav/global_app_bar.dart | 86 ++++++++ .../ui/pages/api.dart} | 10 +- .../ui/pages/camera.dart} | 6 +- lib/common/ui/pages/demo.dart | 195 +++++++++++++++++ .../ui/pages/game.dart} | 37 ++-- .../ui/pages/graph.dart} | 8 +- lib/common/ui/pages/parameters.dart | 148 +++++++++++++ .../ui/parameters/parameter_painter.dart | 196 +++++++++++++++++ .../ui/parameters/parameter_widget.dart | 162 ++++++++++++++ lib/common/ui/screens/about.dart | 38 ++++ lib/common/ui/screens/activity.dart | 18 ++ lib/common/ui/screens/settings.dart | 26 +++ .../ui/settings}/settings_form.dart | 9 +- lib/config/application_config.dart | 3 + lib/config/default_activity_settings.dart | 52 +++++ lib/config/default_global_settings.dart | 28 +++ lib/config/menu.dart | 80 ------- lib/cubit/activity/activity_cubit.dart | 82 ++++++++ lib/cubit/activity/activity_state.dart | 15 ++ lib/cubit/{ => activity}/api_cubit.dart | 0 lib/cubit/{ => activity}/api_state.dart | 0 lib/cubit/{ => activity}/data_cubit.dart | 0 lib/cubit/{ => activity}/data_state.dart | 0 lib/cubit/bottom_nav_cubit.dart | 31 --- lib/cubit/game_cubit.dart | 34 --- lib/cubit/game_state.dart | 15 -- .../settings/settings_activity_cubit.dart | 72 +++++++ .../settings/settings_activity_state.dart | 15 ++ lib/cubit/{ => settings}/settings_cubit.dart | 0 lib/cubit/settings/settings_global_cubit.dart | 59 ++++++ lib/cubit/settings/settings_global_state.dart | 15 ++ lib/cubit/{ => settings}/settings_state.dart | 0 lib/main.dart | 110 ++++++---- .../game.dart => activity/activity.dart} | 52 +++-- lib/models/{game => activity}/game_board.dart | 12 +- lib/models/{game => activity}/game_cell.dart | 0 lib/models/game/game_settings.dart | 46 ---- lib/models/settings/settings_activity.dart | 59 ++++++ lib/models/settings/settings_global.dart | 42 ++++ lib/ui/screens/about_page.dart | 37 ---- lib/ui/screens/demo_page.dart | 198 ------------------ lib/ui/screens/settings_page.dart | 23 -- lib/ui/skeleton.dart | 55 +++-- .../actions/button_delete_saved_game.dart | 22 ++ lib/ui/widgets/actions/button_game_quit.dart | 25 +++ .../actions/button_game_start_new.dart | 38 ++++ .../actions/button_resume_saved_game.dart | 25 +++ lib/ui/widgets/app_bar.dart | 20 -- .../{header_app.dart => custom_title.dart} | 10 +- lib/ui/widgets/game/game_board.dart | 48 ++--- lib/ui/widgets/game/game_score.dart | 18 +- lib/ui/widgets/game/game_settings.dart | 12 -- lib/ui/widgets/theme_card.dart | 43 ---- pubspec.lock | 16 +- pubspec.yaml | 4 +- resources/build_resources.sh | 1 + resources/ui/build_ui_resources.sh | 144 +++++++++++++ resources/ui/images/button_back.svg | 48 +---- .../ui/images/button_delete_saved_game.svg | 40 +--- resources/ui/images/button_resume_game.svg | 54 +---- resources/ui/images/button_start.svg | 48 +---- 72 files changed, 2063 insertions(+), 916 deletions(-) create mode 100644 assets/ui/button_back.png create mode 100644 assets/ui/button_delete_saved_game.png create mode 100644 assets/ui/button_resume_game.png create mode 100644 assets/ui/button_start.png create mode 100644 lib/common/config/activity_page.dart create mode 100644 lib/common/config/screen.dart create mode 100644 lib/common/cubit/nav/nav_cubit_pages.dart create mode 100644 lib/common/cubit/nav/nav_cubit_screens.dart create mode 100644 lib/common/ui/nav/bottom_nav_bar.dart create mode 100644 lib/common/ui/nav/global_app_bar.dart rename lib/{ui/screens/api_page.dart => common/ui/pages/api.dart} (82%) rename lib/{ui/screens/camera_page.dart => common/ui/pages/camera.dart} (82%) create mode 100644 lib/common/ui/pages/demo.dart rename lib/{ui/screens/game_page.dart => common/ui/pages/game.dart} (57%) rename lib/{ui/screens/graph_page.dart => common/ui/pages/graph.dart} (92%) create mode 100644 lib/common/ui/pages/parameters.dart create mode 100644 lib/common/ui/parameters/parameter_painter.dart create mode 100644 lib/common/ui/parameters/parameter_widget.dart create mode 100644 lib/common/ui/screens/about.dart create mode 100644 lib/common/ui/screens/activity.dart create mode 100644 lib/common/ui/screens/settings.dart rename lib/{ui/widgets => common/ui/settings}/settings_form.dart (95%) create mode 100644 lib/config/application_config.dart create mode 100644 lib/config/default_activity_settings.dart create mode 100644 lib/config/default_global_settings.dart delete mode 100644 lib/config/menu.dart create mode 100644 lib/cubit/activity/activity_cubit.dart create mode 100644 lib/cubit/activity/activity_state.dart rename lib/cubit/{ => activity}/api_cubit.dart (100%) rename lib/cubit/{ => activity}/api_state.dart (100%) rename lib/cubit/{ => activity}/data_cubit.dart (100%) rename lib/cubit/{ => activity}/data_state.dart (100%) delete mode 100644 lib/cubit/bottom_nav_cubit.dart delete mode 100644 lib/cubit/game_cubit.dart delete mode 100644 lib/cubit/game_state.dart create mode 100644 lib/cubit/settings/settings_activity_cubit.dart create mode 100644 lib/cubit/settings/settings_activity_state.dart rename lib/cubit/{ => settings}/settings_cubit.dart (100%) create mode 100644 lib/cubit/settings/settings_global_cubit.dart create mode 100644 lib/cubit/settings/settings_global_state.dart rename lib/cubit/{ => settings}/settings_state.dart (100%) rename lib/models/{game/game.dart => activity/activity.dart} (50%) rename lib/models/{game => activity}/game_board.dart (76%) rename lib/models/{game => activity}/game_cell.dart (100%) delete mode 100644 lib/models/game/game_settings.dart create mode 100644 lib/models/settings/settings_activity.dart create mode 100644 lib/models/settings/settings_global.dart delete mode 100644 lib/ui/screens/about_page.dart delete mode 100644 lib/ui/screens/demo_page.dart delete mode 100644 lib/ui/screens/settings_page.dart create mode 100644 lib/ui/widgets/actions/button_delete_saved_game.dart create mode 100644 lib/ui/widgets/actions/button_game_quit.dart create mode 100644 lib/ui/widgets/actions/button_game_start_new.dart create mode 100644 lib/ui/widgets/actions/button_resume_saved_game.dart delete mode 100644 lib/ui/widgets/app_bar.dart rename lib/ui/widgets/{header_app.dart => custom_title.dart} (85%) delete mode 100644 lib/ui/widgets/game/game_settings.dart delete mode 100644 lib/ui/widgets/theme_card.dart create mode 100755 resources/ui/build_ui_resources.sh diff --git a/assets/translations/en.json b/assets/translations/en.json index 4f59762..0b5c2c0 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,13 +1,15 @@ { "app_name": "Sandbox App", - "bottom_nav_sample": "Sample", - "bottom_nav_camera": "Camera", - "bottom_nav_api": "API", - "bottom_nav_chart": "Graph", - "bottom_nav_game": "Game", - "bottom_nav_settings": "Settings", - "bottom_nav_about": "About", + "page_home": "Home", + "page_demo": "Demo", + "page_camera": "Camera", + "page_api": "API", + "page_graph": "Graph", + "page_game": "Game", + + "screen_settings": "Settings", + "screen_about": "About", "api_page_title": "API", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index aa559e2..81fdba3 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,13 +1,15 @@ { "app_name": "App de test", - "bottom_nav_sample": "Démo", - "bottom_nav_camera": "Caméra", - "bottom_nav_api": "API", - "bottom_nav_chart": "Graph", - "bottom_nav_game": "Jeu", - "bottom_nav_settings": "Paramètres", - "bottom_nav_about": "À propos", + "page_home": "Accueil", + "page_demo": "Démo", + "page_camera": "Caméra", + "page_api": "API", + "page_graph": "Graph", + "page_game": "Jeu", + + "screen_settings": "Paramètres", + "screen_about": "À propos", "api_page_title": "API", diff --git a/assets/ui/button_back.png b/assets/ui/button_back.png new file mode 100644 index 0000000000000000000000000000000000000000..51d7a01d171f7d7f047ecf9dee2d7ceee23b310d GIT binary patch literal 2422 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 BcJ%-N literal 0 HcmV?d00001 diff --git a/assets/ui/button_delete_saved_game.png b/assets/ui/button_delete_saved_game.png new file mode 100644 index 0000000000000000000000000000000000000000..4ca5b749c208c4b7eac2a4b141a1bd918d7cb98f GIT binary patch literal 7834 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 HN@xNA9?owD literal 0 HcmV?d00001 diff --git a/assets/ui/button_resume_game.png b/assets/ui/button_resume_game.png new file mode 100644 index 0000000000000000000000000000000000000000..2fe433b7d18a39880a14e3f0af18cb75c4ccbaed GIT binary patch literal 3434 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 J);T3K0RaB++}HpB literal 0 HcmV?d00001 diff --git a/assets/ui/button_start.png b/assets/ui/button_start.png new file mode 100644 index 0000000000000000000000000000000000000000..23c7a4f670de19ffac455d6c510c3c53653a048b GIT binary patch literal 3058 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 literal 0 HcmV?d00001 diff --git a/lib/common/config/activity_page.dart b/lib/common/config/activity_page.dart new file mode 100644 index 0000000..847186e --- /dev/null +++ b/lib/common/config/activity_page.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; +import 'package:random/common/ui/pages/api.dart'; +import 'package:random/common/ui/pages/camera.dart'; +import 'package:random/common/ui/pages/demo.dart'; +import 'package:random/common/ui/pages/graph.dart'; + +import 'package:random/common/ui/pages/game.dart'; +import 'package:random/common/ui/pages/parameters.dart'; + +class ActivityPageItem { + final String code; + final Icon icon; + final Widget page; + + const ActivityPageItem({ + required this.code, + required this.icon, + required this.page, + }); +} + +class ActivityPage { + static const bool displayBottomNavBar = true; + + static const indexHome = 0; + static const pageHome = ActivityPageItem( + code: 'page_home', + icon: Icon(UniconsLine.home), + page: PageParameters(), + ); + + static const indexDemo = 1; + static const pageDemo = ActivityPageItem( + code: 'page_demo', + icon: Icon(UniconsLine.eye), + page: PageDemo(), + ); + + static const indexGame = 2; + static const pageGame = ActivityPageItem( + code: 'page_game', + icon: Icon(UniconsLine.star), + page: PageGame(), + ); + + static const indexCamera = 3; + static const pageCamera = ActivityPageItem( + code: 'page_camera', + icon: Icon(UniconsLine.camera), + page: PageCamera(), + ); + + static const indexGraph = 4; + static const pageGraph = ActivityPageItem( + code: 'page_graph', + icon: Icon(UniconsLine.pen), + page: PageGraph(), + ); + + static const indexApi = 5; + static const pageApi = ActivityPageItem( + code: 'page_api', + icon: Icon(UniconsLine.link), + page: PageApi(), + ); + + static const Map<int, ActivityPageItem> items = { + indexHome: pageHome, + indexDemo: pageDemo, + indexGame: pageGame, + indexCamera: pageCamera, + indexGraph: pageGraph, + indexApi: pageApi, + }; + + static int defaultPageIndex = indexDemo; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); + } + + static Widget getWidget(int pageIndex) { + return items[pageIndex]?.page ?? pageHome.page; + } +} diff --git a/lib/common/config/screen.dart b/lib/common/config/screen.dart new file mode 100644 index 0000000..2a828ee --- /dev/null +++ b/lib/common/config/screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/ui/screens/about.dart'; +import 'package:random/common/ui/screens/activity.dart'; +import 'package:random/common/ui/screens/settings.dart'; + +class ScreenItem { + final String code; + final Icon icon; + final Widget screen; + + const ScreenItem({ + required this.code, + required this.icon, + required this.screen, + }); +} + +class Screen { + static const indexActivity = 0; + static const screenActivity = ScreenItem( + code: 'screen_activity', + icon: Icon(UniconsLine.home), + screen: ScreenActivity(), + ); + + static const indexSettings = 1; + static const screenSettings = ScreenItem( + code: 'screen_settings', + icon: Icon(UniconsLine.setting), + screen: ScreenSettings(), + ); + + static const indexAbout = 2; + static const screenAbout = ScreenItem( + code: 'screen_about', + icon: Icon(UniconsLine.info_circle), + screen: ScreenAbout(), + ); + + static Map<int, ScreenItem> items = { + indexActivity: screenActivity, + indexSettings: screenSettings, + indexAbout: screenAbout, + }; + + static bool isIndexAllowed(int screenIndex) { + return items.keys.contains(screenIndex); + } + + static Widget getWidget(int screenIndex) { + return items[screenIndex]?.screen ?? screenActivity.screen; + } +} diff --git a/lib/common/cubit/nav/nav_cubit_pages.dart b/lib/common/cubit/nav/nav_cubit_pages.dart new file mode 100644 index 0000000..b2dc71b --- /dev/null +++ b/lib/common/cubit/nav/nav_cubit_pages.dart @@ -0,0 +1,33 @@ +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/config/activity_page.dart'; + +class NavCubitPage extends HydratedCubit<int> { + NavCubitPage() : super(0); + + void updateIndex(int index) { + if (ActivityPage.isIndexAllowed(index)) { + emit(index); + } else { + emit(ActivityPage.indexHome); + } + } + + void goToPageHome() { + updateIndex(ActivityPage.indexHome); + } + + void goToPageGame() { + updateIndex(ActivityPage.indexGame); + } + + @override + int fromJson(Map<String, dynamic> json) { + return ActivityPage.indexHome; + } + + @override + Map<String, dynamic>? toJson(int state) { + return <String, int>{'index': state}; + } +} diff --git a/lib/common/cubit/nav/nav_cubit_screens.dart b/lib/common/cubit/nav/nav_cubit_screens.dart new file mode 100644 index 0000000..e0ccaa6 --- /dev/null +++ b/lib/common/cubit/nav/nav_cubit_screens.dart @@ -0,0 +1,37 @@ +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/config/screen.dart'; + +class NavCubitScreen extends HydratedCubit<int> { + NavCubitScreen() : super(0); + + void updateIndex(int index) { + if (Screen.isIndexAllowed(index)) { + emit(index); + } else { + goToScreenActivity(); + } + } + + void goToScreenActivity() { + emit(Screen.indexActivity); + } + + void goToScreenSettings() { + emit(Screen.indexSettings); + } + + void goToScreenAbout() { + emit(Screen.indexAbout); + } + + @override + int fromJson(Map<String, dynamic> json) { + return Screen.indexActivity; + } + + @override + Map<String, dynamic>? toJson(int state) { + return <String, int>{'index': state}; + } +} diff --git a/lib/common/ui/nav/bottom_nav_bar.dart b/lib/common/ui/nav/bottom_nav_bar.dart new file mode 100644 index 0000000..613cb7f --- /dev/null +++ b/lib/common/ui/nav/bottom_nav_bar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/config/activity_page.dart'; +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; + +class BottomNavBar extends StatelessWidget { + const BottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(top: 1, right: 4, left: 4), + elevation: 4, + shadowColor: Theme.of(context).colorScheme.shadow, + color: Theme.of(context).colorScheme.surfaceContainerHighest, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: BlocBuilder<NavCubitPage, int>(builder: (BuildContext context, int state) { + final List<BottomNavigationBarItem> items = []; + + ActivityPage.items.forEach((int pageIndex, ActivityPageItem item) { + items.add(BottomNavigationBarItem( + icon: item.icon, + label: tr(item.code), + )); + }); + + return BottomNavigationBar( + currentIndex: state, + onTap: (int index) => BlocProvider.of<NavCubitPage>(context).updateIndex(index), + type: BottomNavigationBarType.fixed, + elevation: 0, + backgroundColor: Colors.transparent, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, + items: items, + ); + }), + ); + } +} diff --git a/lib/common/ui/nav/global_app_bar.dart b/lib/common/ui/nav/global_app_bar.dart new file mode 100644 index 0000000..6a08a3a --- /dev/null +++ b/lib/common/ui/nav/global_app_bar.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/config/screen.dart'; +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; +import 'package:random/common/cubit/nav/nav_cubit_screens.dart'; + +import 'package:random/cubit/activity/activity_cubit.dart'; +import 'package:random/models/activity/activity.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + return BlocBuilder<NavCubitScreen, int>( + builder: (BuildContext context, int pageIndex) { + final Activity currentActivity = activityState.currentActivity; + + final List<Widget> menuActions = []; + + if (currentActivity.isRunning && !currentActivity.isFinished) { + menuActions.add(StyledButton( + color: Colors.red, + onPressed: () {}, + onLongPress: () { + BlocProvider.of<ActivityCubit>(context).quitActivity(); + BlocProvider.of<NavCubitPage>(context).goToPageHome(); + }, + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + )); + } else { + if (pageIndex == Screen.indexActivity) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + BlocProvider.of<NavCubitScreen>(context).goToScreenSettings(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Screen.screenSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + BlocProvider.of<NavCubitScreen>(context).goToScreenAbout(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Screen.screenAbout.icon, + )); + } else { + // back to Home page + menuActions.add(ElevatedButton( + onPressed: () { + BlocProvider.of<NavCubitScreen>(context).goToScreenActivity(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Screen.screenActivity.icon, + )); + } + } + + return AppBar( + title: const AppHeader(text: 'app_name'), + actions: menuActions, + ); + }, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/screens/api_page.dart b/lib/common/ui/pages/api.dart similarity index 82% rename from lib/ui/screens/api_page.dart rename to lib/common/ui/pages/api.dart index b9a2b22..4fe13e2 100644 --- a/lib/ui/screens/api_page.dart +++ b/lib/common/ui/pages/api.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/cubit/api_cubit.dart'; +import 'package:random/cubit/activity/api_cubit.dart'; import 'package:random/models/api_status.dart'; +import 'package:random/ui/widgets/custom_title.dart'; import 'package:random/ui/widgets/api_data.dart'; -import 'package:random/ui/widgets/header_app.dart'; -class ApiPage extends StatelessWidget { - const ApiPage({super.key}); +class PageApi extends StatelessWidget { + const PageApi({super.key}); @override Widget build(BuildContext context) { @@ -18,7 +18,7 @@ class ApiPage extends StatelessWidget { physics: const BouncingScrollPhysics(), children: <Widget>[ const SizedBox(height: 8), - const AppHeaderCustom(text: 'api_page_title'), + const CustomTitle(text: 'api_page_title'), const SizedBox(height: 20), BlocBuilder<ApiDataCubit, ApiDataState>( builder: (BuildContext context, ApiDataState apiDataState) { diff --git a/lib/ui/screens/camera_page.dart b/lib/common/ui/pages/camera.dart similarity index 82% rename from lib/ui/screens/camera_page.dart rename to lib/common/ui/pages/camera.dart index ca4df4c..71ba41e 100644 --- a/lib/ui/screens/camera_page.dart +++ b/lib/common/ui/pages/camera.dart @@ -3,11 +3,11 @@ import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:random/ui/widgets/take_picture_widget.dart'; -class CameraPage extends StatelessWidget { - const CameraPage({super.key}); +class PageCamera extends StatelessWidget { + const PageCamera({super.key}); static Icon navBarIcon = const Icon(UniconsLine.camera); - static String navBarText = 'bottom_nav_camera'; + static String navBarText = 'page_camera'; @override Widget build(BuildContext context) { diff --git a/lib/common/ui/pages/demo.dart b/lib/common/ui/pages/demo.dart new file mode 100644 index 0000000..1988e22 --- /dev/null +++ b/lib/common/ui/pages/demo.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/cubit/activity/data_cubit.dart'; +import 'package:random/cubit/settings/settings_cubit.dart'; +import 'package:random/ui/widgets/custom_title.dart'; + +class PageDemo extends StatelessWidget { + const PageDemo({super.key}); + + @override + Widget build(BuildContext context) { + return ListView( + padding: const EdgeInsets.symmetric(horizontal: 4), + physics: const BouncingScrollPhysics(), + children: <Widget>[ + const SizedBox(height: 8), + const CustomTitle(text: 'TOP'), + const SizedBox(height: 20), + StyledContainer( + child: persistedCounterBlock(BlocProvider.of<DataCubit>(context)), + ), + const SizedBox(height: 8), + StyledContainer( + borderRadius: 0, + borderWidth: 12, + // depth: 8, + lowerColor: Colors.red, + upperColor: Colors.yellow, + child: testBlocConsumer(), + ), + const SizedBox(height: 8), + StyledContainer( + borderRadius: 10, + borderWidth: 30, + depth: 20, + lowerColor: Colors.blueGrey, + upperColor: Colors.blue, + child: testBlocBuilder(), + ), + const SizedBox(height: 8), + fakeApiCall(), + const SizedBox(height: 8), + const CustomTitle(text: 'BOTTOM'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + StyledButton.text( + caption: 'ABC', + color: Colors.yellow, + onPressed: () { + printlog('A'); + }, + ), + StyledButton.text( + caption: '❤️🔥', + color: Colors.red, + onPressed: () { + printlog('fire!'); + }, + ), + StyledButton.text( + caption: '⭐', + color: Colors.green, + onPressed: () { + printlog('star!'); + }, + ), + StyledButton.text( + caption: '🧁', + color: Colors.blue, + onPressed: () { + printlog('Cupcake'); + }, + ), + StyledButton.icon( + icon: Icon(UniconsLine.setting), + color: Colors.purple, + iconSize: 20, + onPressed: () { + printlog('icon'); + }, + ), + ], + ), + const SizedBox(height: 8), + StyledButton.text( + caption: 'BUTTON - LARGE', + color: Colors.orange, + onPressed: () { + printlog('large button'); + }, + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TextButton( + child: Text('ABC'), + onPressed: () { + printlog('TextButton'); + }, + ), + OutlinedButton( + child: Text('❤️🔥'), + onPressed: () { + printlog('OutlinedButton'); + }, + ), + FilledButton( + child: Text('⭐'), + onPressed: () { + printlog('FilledButton'); + }, + ), + ElevatedButton( + child: Text('🧁'), + onPressed: () { + printlog('ElevatedButton'); + }, + ), + ], + ), + ], + ); + } + + Widget persistedCounterBlock(DataCubit dataCubit) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(UniconsSolid.arrow_circle_down), + color: appTheme.primaryColor, + onPressed: () => dataCubit.updateCounter(-1), + ), + testBlocConsumer(), + IconButton( + icon: const Icon(UniconsSolid.arrow_circle_up), + color: appTheme.primaryColor, + onPressed: () => dataCubit.updateCounter(1), + ), + ], + ); + } + + Widget fakeApiCall() { + return BlocBuilder<SettingsCubit, SettingsState>( + builder: (context, settingsSate) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('apiUrl: ${settingsSate.apiUrl}'), + Text('securityToken: ${settingsSate.securityToken}'), + Text('interfaceType: ${settingsSate.interfaceType}'), + ], + ); + }, + ); + } + + Widget testBlocConsumer() { + return BlocConsumer<DataCubit, DataState>( + listener: (context, dataState) { + // do stuff here based on state + }, + builder: (context, dataState) { + // return widget here based on state + return Text('BlocConsumer / $dataState'); + }, + ); + } + + Widget testBlocListener() { + return BlocListener<DataCubit, DataState>( + listener: (context, dataState) { + // do stuff here based on state + }, + ); + } + + Widget testBlocBuilder() { + return BlocBuilder<DataCubit, DataState>( + builder: (context, dataState) { + // return widget here based on state + return Text('BlocBuilder / $dataState'); + }, + ); + } +} diff --git a/lib/ui/screens/game_page.dart b/lib/common/ui/pages/game.dart similarity index 57% rename from lib/ui/screens/game_page.dart rename to lib/common/ui/pages/game.dart index a8ee1a9..928305b 100644 --- a/lib/ui/screens/game_page.dart +++ b/lib/common/ui/pages/game.dart @@ -1,40 +1,39 @@ import 'package:flutter/material.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/cubit/game_cubit.dart'; -import 'package:random/models/game/game.dart'; -import 'package:random/ui/widgets/game/game_settings.dart'; +import 'package:random/cubit/activity/activity_cubit.dart'; +import 'package:random/models/activity/activity.dart'; import 'package:random/ui/widgets/game/game_board.dart'; -class GamePage extends StatefulWidget { - const GamePage({super.key}); +class PageGame extends StatefulWidget { + const PageGame({super.key}); @override - State<GamePage> createState() => _GamePageState(); + State<PageGame> createState() => _PageGameState(); } -class _GamePageState extends State<GamePage> { +class _PageGameState extends State<PageGame> { Widget buildGameActionsBloc(BuildContext context) { - return BlocBuilder<GameCubit, GameState>(builder: (context, gameState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + return BlocBuilder<ActivityCubit, ActivityState>(builder: (context, activityState) { + final ActivityCubit activityCubit = BlocProvider.of<ActivityCubit>(context); final List<Widget> buttons = [ IconButton( onPressed: () { - gameCubit.updateGameState(Game.createNew()); + activityCubit.updateState(Activity.createNew()); }, icon: const Icon(UniconsSolid.star), color: Theme.of(context).colorScheme.primary, ) ]; - if (gameState.game?.isRunning == true) { + if (activityState.currentActivity.isRunning == true) { buttons.add(IconButton( onPressed: () { - final Game currentGame = gameCubit.state.game!; - currentGame.stop(); + final Activity currentActivity = activityCubit.state.currentActivity; + currentActivity.stop(); - gameCubit.updateGameState(currentGame); + activityCubit.updateState(currentActivity); setState(() {}); }, icon: const Icon(UniconsLine.exit), @@ -55,18 +54,18 @@ class _GamePageState extends State<GamePage> { const double boardWidgetWidth = 300; const double boardWidgetHeight = 300; - return BlocBuilder<GameCubit, GameState>( - builder: (context, gameState) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (context, activityState) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - gameState.game?.isRunning == true + activityState.currentActivity.isRunning == true ? GameBoardWidget( - game: gameState.game!, + activity: activityState.currentActivity, widgetSize: const Size(boardWidgetWidth, boardWidgetHeight), ) - : const GameSettingsWidget(), + : SizedBox.shrink(), buildGameActionsBloc(context), ], ); diff --git a/lib/ui/screens/graph_page.dart b/lib/common/ui/pages/graph.dart similarity index 92% rename from lib/ui/screens/graph_page.dart rename to lib/common/ui/pages/graph.dart index 3b6d770..2222146 100644 --- a/lib/ui/screens/graph_page.dart +++ b/lib/common/ui/pages/graph.dart @@ -3,14 +3,14 @@ import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:random/ui/painters/graph_painter.dart'; -class GraphPage extends StatefulWidget { - const GraphPage({super.key}); +class PageGraph extends StatefulWidget { + const PageGraph({super.key}); @override - State<GraphPage> createState() => _GraphPageState(); + State<PageGraph> createState() => _PageGraphState(); } -class _GraphPageState extends State<GraphPage> { +class _PageGraphState extends State<PageGraph> { double _currentSliderValue = 20; @override diff --git a/lib/common/ui/pages/parameters.dart b/lib/common/ui/pages/parameters.dart new file mode 100644 index 0000000..e37dfda --- /dev/null +++ b/lib/common/ui/pages/parameters.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/ui/parameters/parameter_widget.dart'; + +import 'package:random/config/default_activity_settings.dart'; +import 'package:random/config/default_global_settings.dart'; +import 'package:random/cubit/activity/activity_cubit.dart'; +import 'package:random/cubit/settings/settings_activity_cubit.dart'; +import 'package:random/cubit/settings/settings_global_cubit.dart'; +import 'package:random/models/activity/activity.dart'; +import 'package:random/ui/widgets/actions/button_delete_saved_game.dart'; +import 'package:random/ui/widgets/actions/button_game_start_new.dart'; +import 'package:random/ui/widgets/actions/button_resume_saved_game.dart'; + +class PageParameters extends StatelessWidget { + const PageParameters({super.key}); + + final double separatorHeight = 8.0; + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivityCubit, ActivityState>( + builder: (BuildContext context, ActivityState activityState) { + final Activity currentActivity = activityState.currentActivity; + + final List<Widget> lines = []; + + // Game settings + for (String code in DefaultActivitySettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: false, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + lines.add(Expanded( + child: SizedBox(height: separatorHeight), + )); + + if (currentActivity.canBeResumed == false) { + // Start new game + lines.add( + const AspectRatio( + aspectRatio: 3, + child: StartNewGameButton(), + ), + ); + } else { + // Resume game + lines.add(const AspectRatio( + aspectRatio: 3, + child: ResumeSavedGameButton(), + )); + // Delete saved game + lines.add(SizedBox.square( + dimension: MediaQuery.of(context).size.width / 5, + child: const DeleteSavedGameButton(), + )); + } + + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + return Column( + children: lines, + ); + }, + ); + } + + List<Widget> buildParametersLine({ + required String code, + required bool isGlobal, + }) { + final List<Widget> parameterButtons = []; + + final List<String> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultActivitySettings.getAvailableValues(code); + + if (availableValues.length <= 1) { + return []; + } + + for (String value in availableValues) { + final Widget parameterButton = BlocBuilder<ActivitySettingsCubit, ActivitySettingsState>( + builder: (BuildContext context, ActivitySettingsState activitySettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final ActivitySettingsCubit activitySettingsCubit = + BlocProvider.of<ActivitySettingsCubit>(context); + final GlobalSettingsCubit globalSettingsCubit = + BlocProvider.of<GlobalSettingsCubit>(context); + + final String currentValue = isGlobal + ? globalSettingsCubit.getParameterValue(code) + : activitySettingsCubit.getParameterValue(code); + + final bool isSelected = (value == currentValue); + + final double displayWidth = MediaQuery.of(context).size.width; + final double itemWidth = displayWidth / availableValues.length - 4; + + return SizedBox.square( + dimension: itemWidth, + child: ParameterWidget( + code: code, + value: value, + isSelected: isSelected, + size: itemWidth, + activitySettings: activitySettingsState.settings, + globalSettings: globalSettingsState.settings, + onPressed: () { + isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : activitySettingsCubit.setParameterValue(code, value); + }, + ), + ); + }, + ); + }, + ); + + parameterButtons.add(parameterButton); + } + + return parameterButtons; + } +} diff --git a/lib/common/ui/parameters/parameter_painter.dart b/lib/common/ui/parameters/parameter_painter.dart new file mode 100644 index 0000000..92a6c6a --- /dev/null +++ b/lib/common/ui/parameters/parameter_painter.dart @@ -0,0 +1,196 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/config/default_activity_settings.dart'; +import 'package:random/models/settings/settings_activity.dart'; +import 'package:random/models/settings/settings_global.dart'; + +class ParameterPainter extends CustomPainter { + const ParameterPainter({ + required this.code, + required this.value, + required this.activitySettings, + required this.globalSettings, + }); + + final String code; + final String value; + final ActivitySettings activitySettings; + final GlobalSettings globalSettings; + + @override + void paint(Canvas canvas, Size size) { + // force square + final double canvasSize = min(size.width, size.height); + + // content + switch (code) { + case DefaultActivitySettings.parameterCodeBoardSize: + paintBoardSizeParameterItem(canvas, canvasSize); + break; + case DefaultActivitySettings.parameterCodeColorsCount: + paintColorsCountParameterItem(canvas, canvasSize); + break; + default: + printlog('$ParameterPainter -> unknown parameter: $code/$value'); + paintUnknownParameterItem(canvas, canvasSize); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } + + // "unknown" parameter -> simple block with text + void paintUnknownParameterItem( + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + final textSpan = TextSpan( + text: '$code\n$value', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintBoardSizeParameterItem( + final Canvas canvas, + final double size, + ) { + int gridWidth = 1; + + switch (value) { + case DefaultActivitySettings.boardSizeValueSmall: + gridWidth = 2; + break; + case DefaultActivitySettings.boardSizeValueMedium: + gridWidth = 3; + break; + case DefaultActivitySettings.boardSizeValueLarge: + gridWidth = 4; + break; + case DefaultActivitySettings.boardSizeValueExtraLarge: + gridWidth = 5; + break; + default: + printlog('Wrong value for boardSize parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Mini grid + final squareBackgroundColor = Colors.grey.shade200; + final squareBorderColor = Colors.grey.shade800; + + final double cellSize = size / 7; + final double origin = (size - gridWidth * cellSize) / 2; + + for (int row = 0; row < gridWidth; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(origin + col * cellSize, origin + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + paint.color = squareBackgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + paint.color = squareBorderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } + + void paintColorsCountParameterItem( + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + // Colors preview + const List<Offset> positions = [ + Offset(0, 0), + Offset(1, 0), + Offset(2, 0), + Offset(2, 1), + Offset(2, 2), + Offset(1, 2), + Offset(0, 2), + Offset(0, 1), + ]; + + final double padding = 4 / 100 * size; + final double margin = 3 / 100 * size; + final double width = ((size - 2 * padding) / 3) - 2 * margin; + + final int maxValue = int.parse(value); + for (int colorIndex = 0; colorIndex < maxValue; colorIndex++) { + final Offset position = positions[colorIndex]; + + final Offset topLeft = Offset(padding + margin + position.dx * (width + 2 * margin), + padding + margin + position.dy * (width + 2 * margin)); + + final Offset bottomRight = topLeft + Offset(width, width); + + final squareColor = Colors.pink; + paint.color = squareColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + final borderColor = squareColor.darken(20); + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + + // centered text value + final textSpan = TextSpan( + text: value.toString(), + style: TextStyle( + color: Colors.black, + fontSize: size / 4, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } +} diff --git a/lib/common/ui/parameters/parameter_widget.dart b/lib/common/ui/parameters/parameter_widget.dart new file mode 100644 index 0000000..cec3d7b --- /dev/null +++ b/lib/common/ui/parameters/parameter_widget.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/ui/parameters/parameter_painter.dart'; + +import 'package:random/config/default_activity_settings.dart'; +import 'package:random/config/default_global_settings.dart'; +import 'package:random/models/settings/settings_activity.dart'; +import 'package:random/models/settings/settings_global.dart'; + +class ParameterWidget extends StatelessWidget { + const ParameterWidget({ + super.key, + required this.code, + required this.value, + required this.isSelected, + required this.size, + required this.activitySettings, + required this.globalSettings, + required this.onPressed, + }); + + final String code; + final String value; + final bool isSelected; + final double size; + final ActivitySettings activitySettings; + final GlobalSettings globalSettings; + final VoidCallback onPressed; + + static const Color buttonColorActive = Colors.blue; + static const Color buttonColorInactive = Colors.white; + static const double buttonBorderWidth = 4.0; + static const double buttonBorderRadius = 12.0; + + @override + Widget build(BuildContext context) { + Widget content = const SizedBox.shrink(); + + switch (code) { + case DefaultActivitySettings.parameterCodeBoardSize: + content = getBoardSizeParameterItem(); + break; + case DefaultActivitySettings.parameterCodeColorsCount: + content = getColorsCountParameterItem(); + break; + case DefaultGlobalSettings.parameterCodeSkin: + content = getSkinParameterItem(); + break; + default: + printlog('$ParameterWidget -> unknown parameter: $code/$value'); + content = getUnknownParameterItem(); + } + + final Color buttonColor = isSelected ? buttonColorActive : buttonColorInactive; + + return Container( + decoration: BoxDecoration( + color: buttonColor, + borderRadius: BorderRadius.circular(buttonBorderRadius), + border: Border.all( + color: buttonColor, + width: buttonBorderWidth, + ), + ), + child: content, + ); + } + + // "unknown" parameter -> simple block with text + Widget getUnknownParameterItem() { + return StyledButton.text( + caption: '$code / $value', + color: Colors.grey, + onPressed: null, + ); + } + + Widget getBoardSizeParameterItem() { + Color backgroundColor = Colors.grey; + + switch (value) { + case DefaultActivitySettings.boardSizeValueSmall: + backgroundColor = Colors.green; + break; + case DefaultActivitySettings.boardSizeValueMedium: + backgroundColor = Colors.orange; + break; + case DefaultActivitySettings.boardSizeValueLarge: + backgroundColor = Colors.red; + break; + case DefaultActivitySettings.boardSizeValueExtraLarge: + backgroundColor = Colors.purple; + break; + default: + printlog('Wrong value for size parameter value: $value'); + } + + return StyledButton( + color: backgroundColor, + onPressed: onPressed, + child: CustomPaint( + size: Size(size, size), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + activitySettings: activitySettings, + globalSettings: globalSettings, + ), + isComplex: true, + ), + ); + } + + Widget getColorsCountParameterItem() { + Color backgroundColor = Colors.grey; + + switch (value) { + case DefaultActivitySettings.colorsCountVeryLow: + backgroundColor = Colors.green; + break; + case DefaultActivitySettings.colorsCountLow: + backgroundColor = Colors.orange; + break; + case DefaultActivitySettings.colorsCountMedium: + backgroundColor = Colors.red; + break; + case DefaultActivitySettings.colorsCountHigh: + backgroundColor = Colors.purple; + break; + default: + printlog('Wrong value for level parameter value: $value'); + } + + return StyledButton( + color: backgroundColor, + onPressed: onPressed, + child: CustomPaint( + size: Size(size, size), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + activitySettings: activitySettings, + globalSettings: globalSettings, + ), + isComplex: true, + ), + ); + } + + Widget getSkinParameterItem() { + Color backgroundColor = Colors.grey; + + return StyledButton( + color: backgroundColor, + onPressed: onPressed, + child: Text('skin: $value'), + ); + } +} diff --git a/lib/common/ui/screens/about.dart b/lib/common/ui/screens/about.dart new file mode 100644 index 0000000..f7a14a9 --- /dev/null +++ b/lib/common/ui/screens/about.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +class ScreenAbout extends StatelessWidget { + const ScreenAbout({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + const SizedBox(height: 8), + const AppTitle(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/common/ui/screens/activity.dart b/lib/common/ui/screens/activity.dart new file mode 100644 index 0000000..239e1ee --- /dev/null +++ b/lib/common/ui/screens/activity.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/config/activity_page.dart'; +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; + +class ScreenActivity extends StatelessWidget { + const ScreenActivity({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<NavCubitPage, int>( + builder: (BuildContext context, int pageIndex) { + return ActivityPage.getWidget(pageIndex); + }, + ); + } +} diff --git a/lib/common/ui/screens/settings.dart b/lib/common/ui/screens/settings.dart new file mode 100644 index 0000000..8dc117a --- /dev/null +++ b/lib/common/ui/screens/settings.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/ui/settings/settings_form.dart'; + +class ScreenSettings extends StatelessWidget { + const ScreenSettings({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + SizedBox(height: 8), + AppTitle(text: 'settings_title'), + SizedBox(height: 8), + SettingsForm(), + ], + ), + ); + } +} diff --git a/lib/ui/widgets/settings_form.dart b/lib/common/ui/settings/settings_form.dart similarity index 95% rename from lib/ui/widgets/settings_form.dart rename to lib/common/ui/settings/settings_form.dart index 8273e66..7fda5e8 100644 --- a/lib/ui/widgets/settings_form.dart +++ b/lib/common/ui/settings/settings_form.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/cubit/settings_cubit.dart'; +import 'package:random/cubit/settings/settings_cubit.dart'; import 'package:random/models/interface_type.dart'; -import 'package:random/ui/widgets/theme_card.dart'; class SettingsForm extends StatefulWidget { const SettingsForm({super.key}); @@ -62,15 +61,15 @@ class _SettingsFormState extends State<SettingsForm> { mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ - ThemeCard( + ApplicationSettingsThemeModeCard( mode: ThemeMode.system, icon: UniconsLine.cog, ), - ThemeCard( + ApplicationSettingsThemeModeCard( mode: ThemeMode.light, icon: UniconsLine.sun, ), - ThemeCard( + ApplicationSettingsThemeModeCard( mode: ThemeMode.dark, icon: UniconsLine.moon, ) diff --git a/lib/config/application_config.dart b/lib/config/application_config.dart new file mode 100644 index 0000000..8d0a234 --- /dev/null +++ b/lib/config/application_config.dart @@ -0,0 +1,3 @@ +class ApplicationConfig { + static const String appTitle = 'Random application'; +} diff --git a/lib/config/default_activity_settings.dart b/lib/config/default_activity_settings.dart new file mode 100644 index 0000000..5f34d9d --- /dev/null +++ b/lib/config/default_activity_settings.dart @@ -0,0 +1,52 @@ +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +class DefaultActivitySettings { + // available game parameters codes + static const String parameterCodeBoardSize = 'boardSize'; + static const String parameterCodeColorsCount = 'colorsCount'; + static const List<String> availableParameters = [ + parameterCodeBoardSize, + parameterCodeColorsCount, + ]; + + // board size: available values + static const String boardSizeValueSmall = '6'; + static const String boardSizeValueMedium = '10'; + static const String boardSizeValueLarge = '16'; + static const String boardSizeValueExtraLarge = '24'; + static const List<String> allowedBoardSizeValues = [ + boardSizeValueSmall, + boardSizeValueMedium, + boardSizeValueLarge, + boardSizeValueExtraLarge, + ]; + // board size: default value + static const String defaultBoardSizeValue = boardSizeValueLarge; + + // colors count: available values + static const String colorsCountVeryLow = '4'; + static const String colorsCountLow = '5'; + static const String colorsCountMedium = '6'; + static const String colorsCountHigh = '7'; + static const List<String> allowedColorsCountValues = [ + colorsCountVeryLow, + colorsCountLow, + colorsCountMedium, + colorsCountHigh, + ]; + // colors count: default value + static const String defaultColorsCountValue = colorsCountMedium; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeBoardSize: + return DefaultActivitySettings.allowedBoardSizeValues; + case parameterCodeColorsCount: + return DefaultActivitySettings.allowedColorsCountValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000..d92229c --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,28 @@ +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +class DefaultGlobalSettings { + // available global parameters codes + static const String parameterCodeSkin = 'skin'; + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + // skin: available values + static const String skinValueDefault = 'default'; + static const List<String> allowedSkinValues = [ + skinValueDefault, + ]; + // skin: default value + static const String defaultSkinValue = skinValueDefault; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart deleted file mode 100644 index 69a2ac1..0000000 --- a/lib/config/menu.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; - -import 'package:random/ui/screens/about_page.dart'; -import 'package:random/ui/screens/api_page.dart'; -import 'package:random/ui/screens/camera_page.dart'; -import 'package:random/ui/screens/demo_page.dart'; -import 'package:random/ui/screens/game_page.dart'; -import 'package:random/ui/screens/graph_page.dart'; -import 'package:random/ui/screens/settings_page.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 List<MenuItem> items = [ - const MenuItem( - code: 'bottom_nav_sample', - icon: Icon(UniconsLine.image), - page: DemoPage(), - ), - const MenuItem( - code: 'bottom_nav_api', - icon: Icon(UniconsLine.globe), - page: ApiPage(), - ), - const MenuItem( - code: 'bottom_nav_camera', - icon: Icon(UniconsLine.camera), - page: CameraPage(), - ), - const MenuItem( - code: 'bottom_nav_chart', - icon: Icon(UniconsLine.pen), - page: GraphPage(), - ), - const MenuItem( - code: 'bottom_nav_game', - icon: Icon(UniconsLine.star), - page: GamePage(), - ), - const MenuItem( - code: 'bottom_nav_settings', - icon: Icon(UniconsLine.setting), - page: SettingsPage(), - ), - const MenuItem( - code: 'bottom_nav_about', - icon: Icon(UniconsLine.info_circle), - page: AboutPage(), - ), - ]; - - static Widget getPageWidget(int pageIndex) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Menu.items.elementAt(pageIndex).page, - ); - } - - static List<BottomNavigationBarItem> getMenuItems() { - return Menu.items - .map((MenuItem item) => BottomNavigationBarItem( - icon: item.icon, - label: tr(item.code), - )) - .toList(); - } - - static int itemsCount = Menu.items.length; -} diff --git a/lib/cubit/activity/activity_cubit.dart b/lib/cubit/activity/activity_cubit.dart new file mode 100644 index 0000000..5fc69c6 --- /dev/null +++ b/lib/cubit/activity/activity_cubit.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/models/activity/activity.dart'; +import 'package:random/models/settings/settings_activity.dart'; +import 'package:random/models/settings/settings_global.dart'; + +part 'activity_state.dart'; + +class ActivityCubit extends HydratedCubit<ActivityState> { + ActivityCubit() + : super(ActivityState( + currentActivity: Activity.createNull(), + )); + + void updateState(Activity activity) { + emit(ActivityState( + currentActivity: activity, + )); + } + + void refresh() { + final Activity activity = Activity( + activitySettings: state.currentActivity.activitySettings, + globalSettings: state.currentActivity.globalSettings, + isRunning: state.currentActivity.isRunning, + board: state.currentActivity.board, + ); + // game.dump(); + + updateState(activity); + } + + void startNewActivity({ + required ActivitySettings activitySettings, + required GlobalSettings globalSettings, + }) { + final Activity newActivity = Activity.createNew( + // Settings + activitySettings: activitySettings, + globalSettings: globalSettings, + ); + + newActivity.dump(); + + updateState(newActivity); + refresh(); + } + + void quitActivity() { + state.currentActivity.isRunning = false; + refresh(); + } + + void resumeSavedActivity() { + state.currentActivity.isRunning = true; + refresh(); + } + + void deleteSavedActivity() { + state.currentActivity.isRunning = false; + state.currentActivity.isFinished = true; + updateState(Activity.createNull()); + refresh(); + } + + @override + ActivityState? fromJson(Map<String, dynamic> json) { + Activity activity = json['currentActivity'] as Activity; + + return ActivityState( + currentActivity: activity, + ); + } + + @override + Map<String, dynamic>? toJson(ActivityState state) { + return <String, dynamic>{ + 'currentActivity': state.currentActivity.toJson(), + }; + } +} diff --git a/lib/cubit/activity/activity_state.dart b/lib/cubit/activity/activity_state.dart new file mode 100644 index 0000000..887b45e --- /dev/null +++ b/lib/cubit/activity/activity_state.dart @@ -0,0 +1,15 @@ +part of 'activity_cubit.dart'; + +@immutable +class ActivityState extends Equatable { + const ActivityState({ + required this.currentActivity, + }); + + final Activity currentActivity; + + @override + List<dynamic> get props => <dynamic>[ + currentActivity, + ]; +} diff --git a/lib/cubit/api_cubit.dart b/lib/cubit/activity/api_cubit.dart similarity index 100% rename from lib/cubit/api_cubit.dart rename to lib/cubit/activity/api_cubit.dart diff --git a/lib/cubit/api_state.dart b/lib/cubit/activity/api_state.dart similarity index 100% rename from lib/cubit/api_state.dart rename to lib/cubit/activity/api_state.dart diff --git a/lib/cubit/data_cubit.dart b/lib/cubit/activity/data_cubit.dart similarity index 100% rename from lib/cubit/data_cubit.dart rename to lib/cubit/activity/data_cubit.dart diff --git a/lib/cubit/data_state.dart b/lib/cubit/activity/data_state.dart similarity index 100% rename from lib/cubit/data_state.dart rename to lib/cubit/activity/data_state.dart diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart deleted file mode 100644 index f27411d..0000000 --- a/lib/cubit/bottom_nav_cubit.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; - -import 'package:random/config/menu.dart'; - -class BottomNavCubit extends HydratedCubit<int> { - BottomNavCubit() : super(0); - - void updateIndex(int index) { - if (isIndexAllowed(index)) { - emit(index); - } else { - goToHomePage(); - } - } - - bool isIndexAllowed(int index) { - return (index >= 0) && (index < Menu.itemsCount); - } - - void goToHomePage() => emit(0); - - @override - int fromJson(Map<String, dynamic> json) { - return 0; - } - - @override - Map<String, dynamic>? toJson(int state) { - return <String, int>{'pageIndex': state}; - } -} diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart deleted file mode 100644 index 1f39160..0000000 --- a/lib/cubit/game_cubit.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:flutter/material.dart'; - -import 'package:random/models/game/game.dart'; - -part 'game_state.dart'; - -class GameCubit extends HydratedCubit<GameState> { - GameCubit() : super(const GameState()); - - void getData(GameState gameState) { - emit(gameState); - } - - void updateGameState(Game gameData) { - emit(GameState(game: gameData)); - } - - @override - GameState? fromJson(Map<String, dynamic> json) { - Game game = json['game'] as Game; - - return GameState( - game: game, - ); - } - - @override - Map<String, dynamic>? toJson(GameState state) { - return <String, dynamic>{ - 'game': state.game?.toJson(), - }; - } -} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart deleted file mode 100644 index 3e4d0d0..0000000 --- a/lib/cubit/game_state.dart +++ /dev/null @@ -1,15 +0,0 @@ -part of 'game_cubit.dart'; - -@immutable -class GameState extends Equatable { - const GameState({ - this.game, - }); - - final Game? game; - - @override - List<Object?> get props => <Object?>[ - game, - ]; -} diff --git a/lib/cubit/settings/settings_activity_cubit.dart b/lib/cubit/settings/settings_activity_cubit.dart new file mode 100644 index 0000000..d404017 --- /dev/null +++ b/lib/cubit/settings/settings_activity_cubit.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/config/default_activity_settings.dart'; +import 'package:random/models/settings/settings_activity.dart'; + +part 'settings_activity_state.dart'; + +class ActivitySettingsCubit extends HydratedCubit<ActivitySettingsState> { + ActivitySettingsCubit() + : super(ActivitySettingsState(settings: ActivitySettings.createDefault())); + + void setValues({ + String? itemsCount, + String? timerValue, + }) { + emit( + ActivitySettingsState( + settings: ActivitySettings( + boardSize: itemsCount ?? state.settings.boardSize, + colorsCount: timerValue ?? state.settings.colorsCount, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultActivitySettings.parameterCodeBoardSize: + return ActivitySettings.getItemsCountValueFromUnsafe(state.settings.boardSize); + case DefaultActivitySettings.parameterCodeColorsCount: + return ActivitySettings.getTimerValueFromUnsafe(state.settings.colorsCount); + } + + return ''; + } + + void setParameterValue(String code, String value) { + final String itemsCount = code == DefaultActivitySettings.parameterCodeBoardSize + ? value + : getParameterValue(DefaultActivitySettings.parameterCodeBoardSize); + final String timerValue = code == DefaultActivitySettings.parameterCodeColorsCount + ? value + : getParameterValue(DefaultActivitySettings.parameterCodeColorsCount); + + setValues( + itemsCount: itemsCount, + timerValue: timerValue, + ); + } + + @override + ActivitySettingsState? fromJson(Map<String, dynamic> json) { + final String itemsCount = json[DefaultActivitySettings.parameterCodeBoardSize] as String; + final String timerValue = json[DefaultActivitySettings.parameterCodeColorsCount] as String; + + return ActivitySettingsState( + settings: ActivitySettings( + boardSize: itemsCount, + colorsCount: timerValue, + ), + ); + } + + @override + Map<String, dynamic>? toJson(ActivitySettingsState state) { + return <String, dynamic>{ + DefaultActivitySettings.parameterCodeBoardSize: state.settings.boardSize, + DefaultActivitySettings.parameterCodeColorsCount: state.settings.colorsCount, + }; + } +} diff --git a/lib/cubit/settings/settings_activity_state.dart b/lib/cubit/settings/settings_activity_state.dart new file mode 100644 index 0000000..2b2de42 --- /dev/null +++ b/lib/cubit/settings/settings_activity_state.dart @@ -0,0 +1,15 @@ +part of 'settings_activity_cubit.dart'; + +@immutable +class ActivitySettingsState extends Equatable { + const ActivitySettingsState({ + required this.settings, + }); + + final ActivitySettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; +} diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings/settings_cubit.dart similarity index 100% rename from lib/cubit/settings_cubit.dart rename to lib/cubit/settings/settings_cubit.dart diff --git a/lib/cubit/settings/settings_global_cubit.dart b/lib/cubit/settings/settings_global_cubit.dart new file mode 100644 index 0000000..0353c6b --- /dev/null +++ b/lib/cubit/settings/settings_global_cubit.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/config/default_global_settings.dart'; +import 'package:random/models/settings/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings/settings_global_state.dart b/lib/cubit/settings/settings_global_state.dart new file mode 100644 index 0000000..ebcddd7 --- /dev/null +++ b/lib/cubit/settings/settings_global_state.dart @@ -0,0 +1,15 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings/settings_state.dart similarity index 100% rename from lib/cubit/settings_state.dart rename to lib/cubit/settings/settings_state.dart diff --git a/lib/main.dart b/lib/main.dart index 1437a56..a40d00d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,19 +1,25 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/cubit/api_cubit.dart'; -import 'package:random/cubit/bottom_nav_cubit.dart'; -import 'package:random/cubit/data_cubit.dart'; -import 'package:random/cubit/game_cubit.dart'; -import 'package:random/cubit/settings_cubit.dart'; -import 'package:random/repository/api.dart'; +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; +import 'package:random/common/cubit/nav/nav_cubit_screens.dart'; + +import 'package:random/config/application_config.dart'; +import 'package:random/cubit/activity/activity_cubit.dart'; +import 'package:random/cubit/activity/api_cubit.dart'; +import 'package:random/cubit/activity/data_cubit.dart'; +import 'package:random/cubit/settings/settings_activity_cubit.dart'; +import 'package:random/cubit/settings/settings_cubit.dart'; +import 'package:random/cubit/settings/settings_global_cubit.dart'; import 'package:random/network/api.dart'; +import 'package:random/repository/api.dart'; import 'package:random/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -22,18 +28,17 @@ void main() async { storageDirectory: tmpDir, ); - runApp( - EasyLocalization( - path: 'assets/translations', - supportedLocales: const <Locale>[ - Locale('en'), - Locale('fr'), - ], - fallbackLocale: const Locale('en'), - useFallbackTranslations: true, - child: const MyApp(), - ), - ); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) + .then((value) => runApp(EasyLocalization( + path: 'assets/translations', + supportedLocales: const <Locale>[ + Locale('en'), + Locale('fr'), + ], + fallbackLocale: const Locale('en'), + useFallbackTranslations: true, + child: const MyApp(), + ))); } class MyApp extends StatelessWidget { @@ -43,6 +48,27 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + // default providers + BlocProvider<NavCubitPage>( + create: (context) => NavCubitPage(), + ), + BlocProvider<NavCubitScreen>( + create: (context) => NavCubitScreen(), + ), + BlocProvider<ApplicationThemeModeCubit>( + create: (context) => ApplicationThemeModeCubit(), + ), + BlocProvider<ActivityCubit>( + create: (context) => ActivityCubit(), + ), + BlocProvider<GlobalSettingsCubit>( + create: (context) => GlobalSettingsCubit(), + ), + BlocProvider<ActivitySettingsCubit>( + create: (context) => ActivitySettingsCubit(), + ), + + // custom providers BlocProvider<ApiDataCubit>( create: (context) => ApiDataCubit( apiRepository: ApiRepository( @@ -50,27 +76,35 @@ class MyApp extends StatelessWidget { ), )..fetchApiData(), ), - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), - BlocProvider<DataCubit>(create: (context) => DataCubit()), - BlocProvider<GameCubit>(create: (context) => GameCubit()), - BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), - BlocProvider<ApplicationThemeModeCubit>( - create: (context) => ApplicationThemeModeCubit()), + BlocProvider<DataCubit>( + create: (context) => DataCubit(), + ), + BlocProvider<ActivityCubit>( + create: (context) => ActivityCubit(), + ), + BlocProvider<SettingsCubit>( + create: (context) => SettingsCubit(), + ), ], child: BlocBuilder<ApplicationThemeModeCubit, ApplicationThemeModeState>( - builder: (BuildContext context, ApplicationThemeModeState state) { - return MaterialApp( - title: 'Random application', - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, - home: const SkeletonScreen(), - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ); - }), + builder: (BuildContext context, ApplicationThemeModeState state) { + return MaterialApp( + title: ApplicationConfig.appTitle, + home: const SkeletonScreen(), + + // 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/models/game/game.dart b/lib/models/activity/activity.dart similarity index 50% rename from lib/models/game/game.dart rename to lib/models/activity/activity.dart index 966b251..c3d4875 100644 --- a/lib/models/game/game.dart +++ b/lib/models/activity/activity.dart @@ -2,38 +2,48 @@ import 'dart:math'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/models/game/game_board.dart'; -import 'package:random/models/game/game_cell.dart'; -import 'package:random/models/game/game_settings.dart'; +import 'package:random/models/activity/game_board.dart'; +import 'package:random/models/activity/game_cell.dart'; +import 'package:random/models/settings/settings_activity.dart'; +import 'package:random/models/settings/settings_global.dart'; -class Game { +class Activity { GameBoard board; - GameSettings settings; + ActivitySettings activitySettings; + GlobalSettings globalSettings; bool isRunning = false; bool isFinished = false; int availableBlocksCount = 0; int movesCount = 0; int score = 0; - Game({ + Activity({ required this.board, - required this.settings, + required this.activitySettings, + required this.globalSettings, this.isRunning = false, }); - factory Game.createNull() { - return Game( + factory Activity.createNull() { + return Activity( board: GameBoard.createNull(), - settings: GameSettings.createDefault(), + activitySettings: ActivitySettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), ); } - factory Game.createNew({GameSettings? gameSettings}) { - GameSettings settings = gameSettings ?? GameSettings.createDefault(); - - return Game( - board: GameBoard.createRandom(settings), - settings: settings, + factory Activity.createNew({ + ActivitySettings? activitySettings, + GlobalSettings? globalSettings, + }) { + ActivitySettings newActivitySettings = + activitySettings ?? ActivitySettings.createDefault(); + GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + return Activity( + board: GameBoard.createRandom(newActivitySettings), + activitySettings: newActivitySettings, + globalSettings: newGlobalSettings, isRunning: true, ); } @@ -43,6 +53,8 @@ class Game { isFinished = true; } + bool get canBeResumed => !isFinished && isRunning; + GameCell getCell(int x, int y) { return board.cells[y][x]; } @@ -55,8 +67,8 @@ class Game { board.cells[y][x].value = value; } - void setRandomCellValue(int x, int y, GameSettings settings) { - final int maxValue = settings.colorsCount; + void setRandomCellValue(int x, int y, ActivitySettings settings) { + final int maxValue = settings.colorsCountValue; final rand = Random(); int value = 1 + rand.nextInt(maxValue); @@ -79,7 +91,7 @@ class Game { Map<String, dynamic>? toJson() { return <String, dynamic>{ 'board': board.toJson(), - 'settings': settings.toJson(), + 'settings': activitySettings.toJson(), 'isRunning': isRunning, 'isFinished': isFinished, 'availableBlocksCount': availableBlocksCount, @@ -90,7 +102,7 @@ class Game { void dump() { GameBoard.printGrid(board.cells); - printlog(settings.toString()); + printlog(activitySettings.toString()); printlog(toString()); } } diff --git a/lib/models/game/game_board.dart b/lib/models/activity/game_board.dart similarity index 76% rename from lib/models/game/game_board.dart rename to lib/models/activity/game_board.dart index 8f0135e..d5854f8 100644 --- a/lib/models/game/game_board.dart +++ b/lib/models/activity/game_board.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/models/game/game_cell.dart'; -import 'package:random/models/game/game_settings.dart'; +import 'package:random/models/activity/game_cell.dart'; +import 'package:random/models/settings/settings_activity.dart'; class GameBoard { final List<List<GameCell>> cells; @@ -16,10 +16,10 @@ class GameBoard { return GameBoard(cells: []); } - factory GameBoard.createRandom(GameSettings gameSettings) { - final int boardSizeHorizontal = gameSettings.boardSize; - final int boardSizeVertical = gameSettings.boardSize; - final int maxValue = gameSettings.colorsCount; + factory GameBoard.createRandom(ActivitySettings activitySettings) { + final int boardSizeHorizontal = activitySettings.boardSizeValue; + final int boardSizeVertical = activitySettings.boardSizeValue; + final int maxValue = activitySettings.colorsCountValue; final rand = Random(); diff --git a/lib/models/game/game_cell.dart b/lib/models/activity/game_cell.dart similarity index 100% rename from lib/models/game/game_cell.dart rename to lib/models/activity/game_cell.dart diff --git a/lib/models/game/game_settings.dart b/lib/models/game/game_settings.dart deleted file mode 100644 index 2b108da..0000000 --- a/lib/models/game/game_settings.dart +++ /dev/null @@ -1,46 +0,0 @@ -class DefaultGameSettings { - static const int defaultBoardSizeValue = 6; - static const List<int> allowedBoardSizeValues = [ - 5, - 6, - 10, - 15, - ]; - - static const int defaultColorsCountValue = 7; - static const List<int> allowedColorsCountValues = [ - 4, - 5, - 6, - 7, - ]; -} - -class GameSettings { - final int boardSize; - final int colorsCount; - - GameSettings({ - required this.boardSize, - required this.colorsCount, - }); - - factory GameSettings.createDefault() { - return GameSettings( - boardSize: DefaultGameSettings.defaultBoardSizeValue, - colorsCount: DefaultGameSettings.defaultColorsCountValue, - ); - } - - @override - String toString() { - return 'GameSettings(${toJson()})'; - } - - Map<String, dynamic>? toJson() { - return <String, dynamic>{ - 'boardSize': boardSize, - 'colorsCount': colorsCount, - }; - } -} diff --git a/lib/models/settings/settings_activity.dart b/lib/models/settings/settings_activity.dart new file mode 100644 index 0000000..4346eba --- /dev/null +++ b/lib/models/settings/settings_activity.dart @@ -0,0 +1,59 @@ +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/config/default_activity_settings.dart'; + +class ActivitySettings { + final String boardSize; + final String colorsCount; + + ActivitySettings({ + required this.boardSize, + required this.colorsCount, + }); + + // Getters to convert String to int + int get boardSizeValue => int.parse(boardSize); + int get colorsCountValue => int.parse(colorsCount); + + static String getItemsCountValueFromUnsafe(String itemsCount) { + if (DefaultActivitySettings.allowedBoardSizeValues.contains(itemsCount)) { + return itemsCount; + } + + return DefaultActivitySettings.defaultBoardSizeValue; + } + + static String getTimerValueFromUnsafe(String timerValue) { + if (DefaultActivitySettings.allowedColorsCountValues.contains(timerValue)) { + return timerValue; + } + + return DefaultActivitySettings.defaultColorsCountValue; + } + + factory ActivitySettings.createDefault() { + return ActivitySettings( + boardSize: DefaultActivitySettings.defaultBoardSizeValue, + colorsCount: DefaultActivitySettings.defaultColorsCountValue, + ); + } + + void dump() { + printlog('$ActivitySettings:'); + printlog(' ${DefaultActivitySettings.parameterCodeBoardSize}: $boardSize'); + printlog(' ${DefaultActivitySettings.parameterCodeColorsCount}: $colorsCount'); + printlog(''); + } + + @override + String toString() { + return '$ActivitySettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultActivitySettings.parameterCodeBoardSize: boardSize, + DefaultActivitySettings.parameterCodeColorsCount: colorsCount, + }; + } +} diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart new file mode 100644 index 0000000..c8b1fd6 --- /dev/null +++ b/lib/models/settings/settings_global.dart @@ -0,0 +1,42 @@ +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/config/default_global_settings.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings:'); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/about_page.dart deleted file mode 100644 index 7903553..0000000 --- a/lib/ui/screens/about_page.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:flutter/material.dart'; - -import 'package:random/ui/widgets/header_app.dart'; - -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - const SizedBox(height: 8), - const AppHeaderCustom(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/demo_page.dart b/lib/ui/screens/demo_page.dart deleted file mode 100644 index a7b4e83..0000000 --- a/lib/ui/screens/demo_page.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; - -import 'package:random/cubit/data_cubit.dart'; -import 'package:random/cubit/settings_cubit.dart'; -import 'package:random/ui/widgets/header_app.dart'; - -class DemoPage extends StatelessWidget { - const DemoPage({super.key}); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.surface, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 4), - physics: const BouncingScrollPhysics(), - children: <Widget>[ - const SizedBox(height: 8), - const AppHeaderCustom(text: 'TOP'), - const SizedBox(height: 20), - StyledContainer( - child: persistedCounterBlock(BlocProvider.of<DataCubit>(context)), - ), - const SizedBox(height: 8), - StyledContainer( - borderRadius: 0, - borderWidth: 12, - // depth: 8, - lowerColor: Colors.red, - upperColor: Colors.yellow, - child: testBlocConsumer(), - ), - const SizedBox(height: 8), - StyledContainer( - borderRadius: 10, - borderWidth: 30, - depth: 20, - lowerColor: Colors.blueGrey, - upperColor: Colors.blue, - child: testBlocBuilder(), - ), - const SizedBox(height: 8), - fakeApiCall(), - const SizedBox(height: 8), - const AppHeaderCustom(text: 'BOTTOM'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - StyledButton.text( - caption: 'ABC', - color: Colors.yellow, - onPressed: () { - printlog('A'); - }, - ), - StyledButton.text( - caption: '❤️🔥', - color: Colors.red, - onPressed: () { - printlog('fire!'); - }, - ), - StyledButton.text( - caption: '⭐', - color: Colors.green, - onPressed: () { - printlog('star!'); - }, - ), - StyledButton.text( - caption: '🧁', - color: Colors.blue, - onPressed: () { - printlog('Cupcake'); - }, - ), - StyledButton.icon( - icon: Icon(UniconsLine.setting), - color: Colors.purple, - iconSize: 20, - onPressed: () { - printlog('icon'); - }, - ), - ], - ), - const SizedBox(height: 8), - StyledButton.text( - caption: 'BUTTON - LARGE', - color: Colors.orange, - onPressed: () { - printlog('large button'); - }, - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - TextButton( - child: Text('ABC'), - onPressed: () { - printlog('TextButton'); - }, - ), - OutlinedButton( - child: Text('❤️🔥'), - onPressed: () { - printlog('OutlinedButton'); - }, - ), - FilledButton( - child: Text('⭐'), - onPressed: () { - printlog('FilledButton'); - }, - ), - ElevatedButton( - child: Text('🧁'), - onPressed: () { - printlog('ElevatedButton'); - }, - ), - ], - ), - ], - ), - ); - } - - Widget persistedCounterBlock(DataCubit dataCubit) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconButton( - icon: const Icon(UniconsSolid.arrow_circle_down), - color: appTheme.primaryColor, - onPressed: () => dataCubit.updateCounter(-1), - ), - testBlocConsumer(), - IconButton( - icon: const Icon(UniconsSolid.arrow_circle_up), - color: appTheme.primaryColor, - onPressed: () => dataCubit.updateCounter(1), - ), - ], - ); - } - - Widget fakeApiCall() { - return BlocBuilder<SettingsCubit, SettingsState>( - builder: (context, settingsSate) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('apiUrl: ${settingsSate.apiUrl}'), - Text('securityToken: ${settingsSate.securityToken}'), - Text('interfaceType: ${settingsSate.interfaceType}'), - ], - ); - }, - ); - } - - Widget testBlocConsumer() { - return BlocConsumer<DataCubit, DataState>( - listener: (context, dataState) { - // do stuff here based on state - }, - builder: (context, dataState) { - // return widget here based on state - return Text('BlocConsumer / $dataState'); - }, - ); - } - - Widget testBlocListener() { - return BlocListener<DataCubit, DataState>( - listener: (context, dataState) { - // do stuff here based on state - }, - ); - } - - Widget testBlocBuilder() { - return BlocBuilder<DataCubit, DataState>( - builder: (context, dataState) { - // return widget here based on state - return Text('BlocBuilder / $dataState'); - }, - ); - } -} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/settings_page.dart deleted file mode 100644 index 6995b42..0000000 --- a/lib/ui/screens/settings_page.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:random/ui/widgets/header_app.dart'; -import 'package:random/ui/widgets/settings_form.dart'; - -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); - - @override - Widget build(BuildContext context) { - return const Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - SizedBox(height: 8), - AppHeaderCustom(text: 'settings_title'), - SizedBox(height: 8), - SettingsForm(), - ], - ); - } -} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index 3d71d26..7649abe 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,40 +1,37 @@ -import 'package:curved_navigation_bar/curved_navigation_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/config/menu.dart'; -import 'package:random/cubit/bottom_nav_cubit.dart'; -import 'package:random/ui/widgets/app_bar.dart'; +import 'package:random/common/config/activity_page.dart'; +import 'package:random/common/config/screen.dart'; +import 'package:random/common/cubit/nav/nav_cubit_screens.dart'; +import 'package:random/common/ui/nav/global_app_bar.dart'; +import 'package:random/common/ui/nav/bottom_nav_bar.dart'; -class SkeletonScreen extends StatefulWidget { +class SkeletonScreen extends StatelessWidget { const SkeletonScreen({super.key}); - @override - State<SkeletonScreen> createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: false, - appBar: const StandardAppBar(), - body: BlocBuilder<BottomNavCubit, int>( - builder: (context, pageIndex) { - return Menu.getPageWidget(pageIndex); - }, - ), - backgroundColor: Theme.of(context).colorScheme.surface, - bottomNavigationBar: CurvedNavigationBar( - color: Theme.of(context).colorScheme.onSurface, - backgroundColor: Theme.of(context).colorScheme.surface, - animationDuration: const Duration(milliseconds: 200), - height: 50, - items: Menu.items.map((MenuItem item) => item.icon).toList(), - onTap: (newPageIndex) { - BlocProvider.of<BottomNavCubit>(context).updateIndex(newPageIndex); - }, - ), + return BlocBuilder<NavCubitScreen, int>( + builder: (BuildContext context, int screenIndex) { + return Scaffold( + appBar: const GlobalAppBar(), + extendBodyBehindAppBar: false, + body: Material( + color: Theme.of(context).colorScheme.surface, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Screen.getWidget(screenIndex), + ), + ), + backgroundColor: Theme.of(context).colorScheme.surface, + bottomNavigationBar: ActivityPage.displayBottomNavBar ? const BottomNavBar() : null, + ); + }, ); } } diff --git a/lib/ui/widgets/actions/button_delete_saved_game.dart b/lib/ui/widgets/actions/button_delete_saved_game.dart new file mode 100644 index 0000000..e6fb9fa --- /dev/null +++ b/lib/ui/widgets/actions/button_delete_saved_game.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/cubit/activity/activity_cubit.dart'; + +class DeleteSavedGameButton extends StatelessWidget { + const DeleteSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return StyledButton( + color: Colors.grey, + onPressed: () { + BlocProvider.of<ActivityCubit>(context).deleteSavedActivity(); + }, + child: const Image( + image: AssetImage('assets/ui/button_delete_saved_game.png'), + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_quit.dart b/lib/ui/widgets/actions/button_game_quit.dart new file mode 100644 index 0000000..14ca1c9 --- /dev/null +++ b/lib/ui/widgets/actions/button_game_quit.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; + +import 'package:random/cubit/activity/activity_cubit.dart'; + +class QuitGameButton extends StatelessWidget { + const QuitGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return StyledButton( + color: Colors.red, + onPressed: () { + BlocProvider.of<ActivityCubit>(context).quitActivity(); + BlocProvider.of<NavCubitPage>(context).goToPageHome(); + }, + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_start_new.dart b/lib/ui/widgets/actions/button_game_start_new.dart new file mode 100644 index 0000000..bb079be --- /dev/null +++ b/lib/ui/widgets/actions/button_game_start_new.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; + +import 'package:random/cubit/activity/activity_cubit.dart'; +import 'package:random/cubit/settings/settings_activity_cubit.dart'; +import 'package:random/cubit/settings/settings_global_cubit.dart'; + +class StartNewGameButton extends StatelessWidget { + const StartNewGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<ActivitySettingsCubit, ActivitySettingsState>( + builder: (BuildContext context, ActivitySettingsState activitySettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + return StyledButton( + color: Colors.blue, + onPressed: () { + BlocProvider.of<ActivityCubit>(context).startNewActivity( + activitySettings: activitySettingsState.settings, + globalSettings: globalSettingsState.settings, + ); + BlocProvider.of<NavCubitPage>(context).goToPageGame(); + }, + child: const Image( + image: AssetImage('assets/ui/button_start.png'), + fit: BoxFit.fill, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_resume_saved_game.dart b/lib/ui/widgets/actions/button_resume_saved_game.dart new file mode 100644 index 0000000..41ec5d0 --- /dev/null +++ b/lib/ui/widgets/actions/button_resume_saved_game.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; + +import 'package:random/common/cubit/nav/nav_cubit_pages.dart'; + +import 'package:random/cubit/activity/activity_cubit.dart'; + +class ResumeSavedGameButton extends StatelessWidget { + const ResumeSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return StyledButton( + color: Colors.blue, + onPressed: () { + BlocProvider.of<ActivityCubit>(context).resumeSavedActivity(); + BlocProvider.of<NavCubitPage>(context).goToPageGame(); + }, + child: const Image( + image: AssetImage('assets/ui/button_resume_game.png'), + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index 7343f6f..0000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:random/ui/widgets/header_app.dart'; - -class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { - const StandardAppBar({super.key}); - - @override - Widget build(BuildContext context) { - return AppBar( - title: const AppHeaderCustom(text: 'app_name'), - actions: const [ - // - ], - ); - } - - @override - Size get preferredSize => const Size.fromHeight(50); -} diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/custom_title.dart similarity index 85% rename from lib/ui/widgets/header_app.dart rename to lib/ui/widgets/custom_title.dart index 8670546..99bf427 100644 --- a/lib/ui/widgets/header_app.dart +++ b/lib/ui/widgets/custom_title.dart @@ -1,13 +1,13 @@ import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:flutter/material.dart'; -import 'package:random/cubit/data_cubit.dart'; -import 'package:random/cubit/api_cubit.dart'; -import 'package:random/cubit/settings_cubit.dart'; +import 'package:random/cubit/activity/data_cubit.dart'; +import 'package:random/cubit/activity/api_cubit.dart'; +import 'package:random/cubit/settings/settings_cubit.dart'; import 'package:random/models/interface_type.dart'; -class AppHeaderCustom extends StatelessWidget { - const AppHeaderCustom({super.key, required this.text}); +class CustomTitle extends StatelessWidget { + const CustomTitle({super.key, required this.text}); final String text; diff --git a/lib/ui/widgets/game/game_board.dart b/lib/ui/widgets/game/game_board.dart index 21bd65e..ad9d873 100644 --- a/lib/ui/widgets/game/game_board.dart +++ b/lib/ui/widgets/game/game_board.dart @@ -1,20 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; -import 'package:random/cubit/game_cubit.dart'; -import 'package:random/models/game/game.dart'; -import 'package:random/models/game/game_settings.dart'; +import 'package:random/cubit/activity/activity_cubit.dart'; +import 'package:random/models/activity/activity.dart'; +import 'package:random/models/settings/settings_activity.dart'; import 'package:random/ui/painters/cell_painter.dart'; import 'package:random/ui/widgets/game/game_score.dart'; class GameBoardWidget extends StatefulWidget { const GameBoardWidget({ super.key, - required this.game, + required this.activity, required this.widgetSize, }); - final Game game; + final Activity activity; final Size widgetSize; @override @@ -24,8 +24,8 @@ class GameBoardWidget extends StatefulWidget { class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMixin { List<List<Animation<double>?>> animations = []; - void resetAnimations(GameSettings gameSettings) { - final boardSize = gameSettings.boardSize; + void resetAnimations(ActivitySettings activitySettings) { + final int boardSize = activitySettings.boardSizeValue; animations = List.generate( boardSize, @@ -37,8 +37,8 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi } void removeCell(BuildContext context, int x, int y) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - final Game updatedGame = gameCubit.state.game ?? Game.createNew(); + final ActivityCubit activityCubit = BlocProvider.of<ActivityCubit>(context); + final Activity updatedGame = activityCubit.state.currentActivity; // "remove" cell, update counters updatedGame.increaseScore(updatedGame.getCellValue(x, y)); @@ -73,9 +73,9 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi for (var i = 0; i < y; i++) { updatedGame.updateCellValue(x, (y - i), updatedGame.getCellValue(x, (y - i) - 1)); } - updatedGame.setRandomCellValue(x, 0, updatedGame.settings); + updatedGame.setRandomCellValue(x, 0, updatedGame.activitySettings); - resetAnimations(updatedGame.settings); + resetAnimations(updatedGame.activitySettings); setState(() {}); controller.dispose(); @@ -90,24 +90,24 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi } Widget buildBoard() { - final widgetWidth = widget.widgetSize.width; - final widgetHeight = widget.widgetSize.height; + final double widgetWidth = widget.widgetSize.width; + final double widgetHeight = widget.widgetSize.height; - final rowsCount = widget.game.settings.boardSize; - final columnsCount = widget.game.settings.boardSize; + final int rowsCount = widget.activity.activitySettings.boardSizeValue; + final int columnsCount = widget.activity.activitySettings.boardSizeValue; - final cellWidth = widgetWidth / columnsCount; - final cellHeight = widgetHeight / rowsCount; + final double cellWidth = widgetWidth / columnsCount; + final double cellHeight = widgetHeight / rowsCount; if (animations.isEmpty) { - resetAnimations(widget.game.settings); + resetAnimations(widget.activity.activitySettings); } final List<Widget> cells = []; for (var y = 0; y < rowsCount; y++) { for (var x = 0; x < columnsCount; x++) { - final int? value = widget.game.getCellValue(x, y); + final int? value = widget.activity.getCellValue(x, y); if (value != null) { final Animation<double>? translation = animations[y][x]; @@ -144,11 +144,11 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi } Widget interactiveBoard(BuildContext context) { - final widgetWidth = widget.widgetSize.width; - final widgetHeight = widget.widgetSize.height; + final double widgetWidth = widget.widgetSize.width; + final double widgetHeight = widget.widgetSize.height; - final rowsCount = widget.game.settings.boardSize; - final columnsCount = widget.game.settings.boardSize; + final int rowsCount = widget.activity.activitySettings.boardSizeValue; + final int columnsCount = widget.activity.activitySettings.boardSizeValue; return GestureDetector( child: buildBoard(), @@ -183,7 +183,7 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi return Column( children: [ interactiveBoard(context), - GameScoreWidget(game: widget.game), + GameScoreWidget(activity: widget.activity), ], ); } diff --git a/lib/ui/widgets/game/game_score.dart b/lib/ui/widgets/game/game_score.dart index cea8f9a..13598e0 100644 --- a/lib/ui/widgets/game/game_score.dart +++ b/lib/ui/widgets/game/game_score.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:random/models/game/game.dart'; +import 'package:random/models/activity/activity.dart'; class GameScoreWidget extends StatelessWidget { const GameScoreWidget({ super.key, - required this.game, + required this.activity, }); - final Game game; + final Activity activity; @override Widget build(BuildContext context) { @@ -20,13 +20,13 @@ class GameScoreWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Settings:'), - Text(' board size: ${game.settings.boardSize}'), - Text(' colors count: ${game.settings.colorsCount}'), + Text(' board size: ${activity.activitySettings.boardSize}'), + Text(' colors count: ${activity.activitySettings.colorsCount}'), const Text('Game:'), - Text(' isRunning: ${game.isRunning}'), - Text(' isFinished: ${game.isFinished}'), - Text(' movesCount: ${game.movesCount}'), - Text(' score: ${game.score}'), + Text(' isRunning: ${activity.isRunning}'), + Text(' isFinished: ${activity.isFinished}'), + Text(' movesCount: ${activity.movesCount}'), + Text(' score: ${activity.score}'), ], ), ); diff --git a/lib/ui/widgets/game/game_settings.dart b/lib/ui/widgets/game/game_settings.dart deleted file mode 100644 index 783e770..0000000 --- a/lib/ui/widgets/game/game_settings.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class GameSettingsWidget extends StatelessWidget { - const GameSettingsWidget({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return const Text('(fake settings block)'); - } -} diff --git a/lib/ui/widgets/theme_card.dart b/lib/ui/widgets/theme_card.dart deleted file mode 100644 index 18fffe0..0000000 --- a/lib/ui/widgets/theme_card.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_custom_toolbox/flutter_toolbox.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<ApplicationThemeModeCubit, ApplicationThemeModeState>( - builder: (BuildContext context, ApplicationThemeModeState 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<ApplicationThemeModeCubit>(context).getTheme( - ApplicationThemeModeState(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 f45210b..1c082e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,14 +113,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - curved_navigation_bar: - dependency: "direct main" - description: - name: curved_navigation_bar - sha256: bb4ab128fcb6f4a9f0f1f72d227db531818b20218984789777f049fcbf919279 - url: "https://pub.dev" - source: hosted - version: "1.0.6" dio: dependency: "direct main" description: @@ -325,10 +317,10 @@ packages: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: @@ -530,10 +522,10 @@ packages: dependency: transitive description: name: win32 - sha256: "2735daae5150e8b1dfeb3eb0544b4d3af0061e9e82cef063adcd583bdae4306a" + sha256: "10169d3934549017f0ae278ccb07f828f9d6ea21573bab0fb77b0e1ef0fce454" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.7.2" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e7063fc..6985ebe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A random application, for testing purpose only. publish_to: "none" -version: 1.2.1+70 +version: 1.3.0+71 environment: sdk: "^3.0.0" @@ -20,7 +20,6 @@ dependencies: # specific camera: ^0.11.0+2 - curved_navigation_bar: ^1.0.3 dio: ^5.3.3 path: ^1.9.0 @@ -31,3 +30,4 @@ flutter: uses-material-design: true assets: - assets/translations/ + - assets/ui/ diff --git a/resources/build_resources.sh b/resources/build_resources.sh index 4b76d1c..774953c 100755 --- a/resources/build_resources.sh +++ b/resources/build_resources.sh @@ -3,3 +3,4 @@ CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" ${CURRENT_DIR}/app/build_application_resources.sh +${CURRENT_DIR}/ui/build_ui_resources.sh diff --git a/resources/ui/build_ui_resources.sh b/resources/ui/build_ui_resources.sh new file mode 100755 index 0000000..f6c3f33 --- /dev/null +++ b/resources/ui/build_ui_resources.sh @@ -0,0 +1,144 @@ +#! /bin/bash + +# Check dependencies +command -v inkscape >/dev/null 2>&1 || { + echo >&2 "I require inkscape but it's not installed. Aborting." + exit 1 +} +command -v scour >/dev/null 2>&1 || { + echo >&2 "I require scour but it's not installed. Aborting." + exit 1 +} +command -v optipng >/dev/null 2>&1 || { + echo >&2 "I require optipng but it's not installed. Aborting." + exit 1 +} + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" +ASSETS_DIR="${BASE_DIR}/assets" + +OPTIPNG_OPTIONS="-preserve -quiet -o7" +IMAGE_SIZE=192 + +####################################################### + +# Game images (svg files found in `images` folder) +AVAILABLE_GAME_SVG_IMAGES="" +AVAILABLE_GAME_PNG_IMAGES="" +if [ -d "${CURRENT_DIR}/images" ]; then + AVAILABLE_GAME_SVG_IMAGES="$(find "${CURRENT_DIR}/images" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort)" + AVAILABLE_GAME_PNG_IMAGES="$(find "${CURRENT_DIR}/images" -type f -name "*.png" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort)" +fi + +# Skins (subfolders found in `skins` folder) +AVAILABLE_SKINS="" +if [ -d "${CURRENT_DIR}/skins" ]; then + AVAILABLE_SKINS="$(find "${CURRENT_DIR}/skins" -mindepth 1 -type d | awk -F/ '{print $NF}')" +fi + +# Images per skin (svg files found recursively in `skins` folder and subfolders) +SKIN_SVG_IMAGES="" +SKIN_PNG_IMAGES="" +if [ -d "${CURRENT_DIR}/skins" ]; then + SKIN_SVG_IMAGES="$(find "${CURRENT_DIR}/skins" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort | uniq)" + SKIN_PNG_IMAGES="$(find "${CURRENT_DIR}/skins" -type f -name "*.png" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort | uniq)" +fi + +####################################################### + +# optimize svg +function optimize_svg() { + SOURCE="$1" + + cp ${SOURCE} ${SOURCE}.tmp + scour \ + --remove-descriptive-elements \ + --enable-id-stripping \ + --enable-viewboxing \ + --enable-comment-stripping \ + --nindent=4 \ + --quiet \ + -i ${SOURCE}.tmp \ + -o ${SOURCE} + rm ${SOURCE}.tmp +} + +# build png from svg +function build_svg_image() { + SOURCE="$1" + TARGET="$2" + + echo "Building ${TARGET}" + + if [ ! -f "${SOURCE}" ]; then + echo "Missing file: ${SOURCE}" + exit 1 + fi + + optimize_svg "${SOURCE}" + + mkdir -p "$(dirname "${TARGET}")" + + inkscape \ + --export-width=${IMAGE_SIZE} \ + --export-height=${IMAGE_SIZE} \ + --export-filename=${TARGET} \ + "${SOURCE}" + + optipng ${OPTIPNG_OPTIONS} "${TARGET}" +} + +# build png from png +function build_png_image() { + SOURCE="$1" + TARGET="$2" + + echo "Building ${TARGET}" + + if [ ! -f "${SOURCE}" ]; then + echo "Missing file: ${SOURCE}" + exit 1 + fi + + mkdir -p "$(dirname "${TARGET}")" + + convert -resize ${IMAGE_SIZE}x${IMAGE_SIZE} "${SOURCE}" "${TARGET}" + + optipng ${OPTIPNG_OPTIONS} "${TARGET}" +} + +function build_images_for_skin() { + SKIN_CODE="$1" + + # skin images + for SKIN_SVG_IMAGE in ${SKIN_SVG_IMAGES}; do + build_svg_image ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_SVG_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_SVG_IMAGE}.png + done + for SKIN_PNG_IMAGE in ${SKIN_PNG_IMAGES}; do + build_png_image ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_PNG_IMAGE}.png ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_PNG_IMAGE}.png + done +} + +####################################################### + +# Delete existing generated images +if [ -d "${ASSETS_DIR}/ui" ]; then + find ${ASSETS_DIR}/ui -type f -name "*.png" -delete +fi +if [ -d "${ASSETS_DIR}/skins" ]; then + find ${ASSETS_DIR}/skins -type f -name "*.png" -delete +fi + +# build game images +for GAME_SVG_IMAGE in ${AVAILABLE_GAME_SVG_IMAGES}; do + build_svg_image ${CURRENT_DIR}/images/${GAME_SVG_IMAGE}.svg ${ASSETS_DIR}/ui/${GAME_SVG_IMAGE}.png +done +for GAME_PNG_IMAGE in ${AVAILABLE_GAME_PNG_IMAGES}; do + build_png_image ${CURRENT_DIR}/images/${GAME_PNG_IMAGE}.png ${ASSETS_DIR}/ui/${GAME_PNG_IMAGE}.png +done + +# build skins images +for SKIN in ${AVAILABLE_SKINS}; do + build_images_for_skin "${SKIN}" +done diff --git a/resources/ui/images/button_back.svg b/resources/ui/images/button_back.svg index 646fb03..018d8b7 100644 --- a/resources/ui/images/button_back.svg +++ b/resources/ui/images/button_back.svg @@ -1,46 +1,2 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - enable-background="new 0 0 100 100" - version="1.1" - viewBox="0 0 93.665 93.676" - xml:space="preserve" - id="svg2" - sodipodi:docname="button_back.svg" - inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"><defs - id="defs2" /><sodipodi:namedview - id="namedview2" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="0" - inkscape:deskcolor="#d1d1d1" - inkscape:zoom="2.8284271" - inkscape:cx="13.788582" - inkscape:cy="69.473241" - inkscape:window-width="1480" - inkscape:window-height="987" - inkscape:window-x="3693" - inkscape:window-y="22" - inkscape:window-maximized="0" - inkscape:current-layer="svg2" /><path - sodipodi:type="star" - style="fill:#ffffff;fill-opacity:1;stroke:#950e4f;stroke-width:7.28322966;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" - id="path3" - inkscape:flatsided="true" - sodipodi:sides="3" - sodipodi:cx="-9.412034" - sodipodi:cy="-29.827261" - sodipodi:r1="25.983957" - sodipodi:r2="0.25983959" - sodipodi:arg1="0.62599396" - sodipodi:arg2="1.6731915" - inkscape:rounded="0" - inkscape:randomized="0" - d="m 11.644875,-14.603181 -44.769804,-4.600301 26.3688801,-36.471637 z" - transform="matrix(1.3783311,0.61746806,-0.61746806,1.3783311,45.198281,93.762039)" /></svg> +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path transform="matrix(1.3783 .61747 -.61747 1.3783 45.198 93.762)" d="m11.645-14.603-44.77-4.6003 26.369-36.472z" fill="#fff" stroke="#950e4f" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.2832"/></svg> diff --git a/resources/ui/images/button_delete_saved_game.svg b/resources/ui/images/button_delete_saved_game.svg index 2096130..c3f872e 100644 --- a/resources/ui/images/button_delete_saved_game.svg +++ b/resources/ui/images/button_delete_saved_game.svg @@ -1,38 +1,2 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - enable-background="new 0 0 100 100" - version="1.1" - viewBox="0 0 93.665 93.676" - xml:space="preserve" - id="svg1" - sodipodi:docname="button_delete_saved_game.svg" - inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"><defs - id="defs1" /><sodipodi:namedview - id="namedview1" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="0" - inkscape:deskcolor="#d1d1d1" - inkscape:zoom="8.9457276" - inkscape:cx="46.838001" - inkscape:cy="46.893894" - inkscape:window-width="1480" - inkscape:window-height="987" - inkscape:window-x="3532" - inkscape:window-y="83" - inkscape:window-maximized="0" - inkscape:current-layer="svg1" /><path - d="m76.652 23.303-3.6441 58.302c-0.28153 4.5103-4.0223 8.0241-8.5413 8.0241h-35.27c-4.5189 0-8.2598-3.5138-8.5413-8.0241l-3.6441-58.302h-5.4824c-1.7723 0-3.2093-1.437-3.2093-3.2093 0-1.773 1.437-3.2093 3.2093-3.2093h70.605c1.7723 0 3.2093 1.4363 3.2093 3.2093 0 1.7723-1.437 3.2093-3.2093 3.2093zm-6.8314 0h-45.979l3.0819 55.867c0.12535 2.268 2.0008 4.0433 4.2732 4.0433h31.268c2.2724 0 4.1478-1.7752 4.2732-4.0433zm-22.99 6.4188c1.6541 0 2.9952 1.3411 2.9952 2.9952v41.08c0 1.6541-1.3411 2.9952-2.9952 2.9952-1.6542 0-2.9952-1.3411-2.9952-2.9952v-41.08c0-1.6541 1.3411-2.9952 2.9952-2.9952zm-12.837 0c1.6756 0 3.0553 1.3181 3.1312 2.9921l1.8776 41.3c0.06665 1.4664-1.0681 2.7087-2.5345 2.7762-0.04011 0.0015-0.08024 0.0021-0.12108 0.0021-1.5595 0-2.8476-1.2193-2.9328-2.7774l-2.253-41.3c-0.08524-1.5646 1.114-2.9012 2.6779-2.9864 0.05157-0.0029 0.10317-0.0042 0.15474-0.0042zm25.675 0c1.5667 0 2.8361 1.2694 2.8361 2.8361 0 0.05156-6.87e-4 0.10317-0.0036 0.15474l-2.2416 41.088c-0.09171 1.6778-1.4786 2.991-3.1586 2.991-1.5667 0-2.8361-1.2694-2.8361-2.8361 0-0.05156 7.31e-4 -0.10315 0.0036-0.15474l2.2417-41.088c0.09172-1.6778 1.4786-2.991 3.1586-2.991zm-21.397-25.675h17.117c4.7265 0 8.5578 3.8313 8.5578 8.5578v4.2795h-34.231v-4.2795c0-4.7265 3.8313-8.5578 8.5578-8.5578zm0.42837 6.4188c-1.4184 0-2.5675 1.1491-2.5675 2.5675v3.8512h21.394v-3.8512c0-1.4184-1.1491-2.5675-2.5675-2.5675z" - fill="#fff" - fill-rule="evenodd" - stroke="#bd4812" - stroke-width=".75383" - id="path1" - style="stroke:#050200;stroke-opacity:1;stroke-width:1;stroke-dasharray:none" /></svg> +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m76.652 23.303-3.6441 58.302c-0.28153 4.5103-4.0223 8.0241-8.5413 8.0241h-35.27c-4.5189 0-8.2598-3.5138-8.5413-8.0241l-3.6441-58.302h-5.4824c-1.7723 0-3.2093-1.437-3.2093-3.2093 0-1.773 1.437-3.2093 3.2093-3.2093h70.605c1.7723 0 3.2093 1.4363 3.2093 3.2093 0 1.7723-1.437 3.2093-3.2093 3.2093zm-6.8314 0h-45.979l3.0819 55.867c0.12535 2.268 2.0008 4.0433 4.2732 4.0433h31.268c2.2724 0 4.1478-1.7752 4.2732-4.0433zm-22.99 6.4188c1.6541 0 2.9952 1.3411 2.9952 2.9952v41.08c0 1.6541-1.3411 2.9952-2.9952 2.9952-1.6542 0-2.9952-1.3411-2.9952-2.9952v-41.08c0-1.6541 1.3411-2.9952 2.9952-2.9952zm-12.837 0c1.6756 0 3.0553 1.3181 3.1312 2.9921l1.8776 41.3c0.06665 1.4664-1.0681 2.7087-2.5345 2.7762-0.04011 0.0015-0.08024 0.0021-0.12108 0.0021-1.5595 0-2.8476-1.2193-2.9328-2.7774l-2.253-41.3c-0.08524-1.5646 1.114-2.9012 2.6779-2.9864 0.05157-0.0029 0.10317-0.0042 0.15474-0.0042zm25.675 0c1.5667 0 2.8361 1.2694 2.8361 2.8361 0 0.05156-6.87e-4 0.10317-0.0036 0.15474l-2.2416 41.088c-0.09171 1.6778-1.4786 2.991-3.1586 2.991-1.5667 0-2.8361-1.2694-2.8361-2.8361 0-0.05156 7.31e-4 -0.10315 0.0036-0.15474l2.2417-41.088c0.09172-1.6778 1.4786-2.991 3.1586-2.991zm-21.397-25.675h17.117c4.7265 0 8.5578 3.8313 8.5578 8.5578v4.2795h-34.231v-4.2795c0-4.7265 3.8313-8.5578 8.5578-8.5578zm0.42837 6.4188c-1.4184 0-2.5675 1.1491-2.5675 2.5675v3.8512h21.394v-3.8512c0-1.4184-1.1491-2.5675-2.5675-2.5675z" fill="#fff" fill-rule="evenodd" stroke="#050200"/></svg> diff --git a/resources/ui/images/button_resume_game.svg b/resources/ui/images/button_resume_game.svg index 7b62a38..2bf9732 100644 --- a/resources/ui/images/button_resume_game.svg +++ b/resources/ui/images/button_resume_game.svg @@ -1,52 +1,2 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - enable-background="new 0 0 100 100" - version="1.1" - viewBox="0 0 93.665 93.676" - xml:space="preserve" - id="svg3" - sodipodi:docname="button_resume_game.svg" - inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"><defs - id="defs3" /><sodipodi:namedview - id="namedview3" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="0" - inkscape:deskcolor="#d1d1d1" - inkscape:zoom="6.3255846" - inkscape:cx="42.525713" - inkscape:cy="46.161741" - inkscape:window-width="1920" - inkscape:window-height="1032" - inkscape:window-x="1600" - inkscape:window-y="25" - inkscape:window-maximized="1" - inkscape:current-layer="svg3" /><g - id="g4" - transform="translate(-5.6180493)"><path - sodipodi:type="star" - style="fill:#ffffff;fill-opacity:1;stroke:#105ea2;stroke-width:7.28323;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" - id="path3" - inkscape:flatsided="true" - sodipodi:sides="3" - sodipodi:cx="-9.412034" - sodipodi:cy="-29.827261" - sodipodi:r1="25.983957" - sodipodi:r2="0.25983959" - sodipodi:arg1="0.62599396" - sodipodi:arg2="1.6731915" - inkscape:rounded="0" - inkscape:randomized="0" - d="m 11.644875,-14.603181 -44.769804,-4.600301 26.3688801,-36.471637 z" - transform="matrix(-1.3783311,-0.61746806,0.61746806,-1.3783311,55.567492,-0.08603523)" /><path - id="path4" - style="fill:#ffffff;stroke:#105ea2;stroke-width:11;stroke-linecap:round;stroke-linejoin:round" - d="m 15.534575,12.851645 0.002,67.972712 z" - sodipodi:nodetypes="ccc" /></g></svg> +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-5.618)" fill="#fff" stroke="#105ea2" stroke-linecap="round" stroke-linejoin="round"><path transform="matrix(-1.3783 -.61747 .61747 -1.3783 55.567 -.086035)" d="m11.645-14.603-44.77-4.6003 26.369-36.472z" stroke-width="7.2832"/><path d="m15.535 12.852 2e-3 67.973z" stroke-width="11"/></g></svg> diff --git a/resources/ui/images/button_start.svg b/resources/ui/images/button_start.svg index 014e565..4d7634a 100644 --- a/resources/ui/images/button_start.svg +++ b/resources/ui/images/button_start.svg @@ -1,46 +1,2 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - enable-background="new 0 0 100 100" - version="1.1" - viewBox="0 0 93.665 93.676" - xml:space="preserve" - id="svg3" - sodipodi:docname="button_start.svg" - inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"><defs - id="defs3" /><sodipodi:namedview - id="namedview3" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="0" - inkscape:deskcolor="#d1d1d1" - inkscape:zoom="3.1627923" - inkscape:cx="-9.4852892" - inkscape:cy="1.8970578" - inkscape:window-width="1920" - inkscape:window-height="1032" - inkscape:window-x="1600" - inkscape:window-y="25" - inkscape:window-maximized="1" - inkscape:current-layer="svg3" /><path - sodipodi:type="star" - style="fill:#ffffff;fill-opacity:1;stroke:#105ea2;stroke-width:7.28323;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" - id="path3" - inkscape:flatsided="true" - sodipodi:sides="3" - sodipodi:cx="-9.412034" - sodipodi:cy="-29.827261" - sodipodi:r1="25.983957" - sodipodi:r2="0.25983959" - sodipodi:arg1="0.62599396" - sodipodi:arg2="1.6731915" - inkscape:rounded="0" - inkscape:randomized="0" - d="m 11.644875,-14.603181 -44.769804,-4.600301 26.3688801,-36.471637 z" - transform="matrix(-1.3783311,-0.61746806,0.61746806,-1.3783311,46.954337,-0.08603523)" /></svg> +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path transform="matrix(-1.3783 -.61747 .61747 -1.3783 46.954 -.086035)" d="m11.645-14.603-44.77-4.6003 26.369-36.472z" fill="#fff" stroke="#105ea2" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.2832"/></svg> -- GitLab