diff --git a/assets/translations/en.json b/assets/translations/en.json index 4f597623aec94ec04f12bc1620c6d051dd96a9c1..0b5c2c037846ccf9eb84f06f26aa73934ffffd24 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 aa559e2d4280fe5b2fb4591bef9bac2a5db4843b..81fdba3878e0dcbc1739ffbe5562155a93dbd816 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 Binary files /dev/null and b/assets/ui/button_back.png differ 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 Binary files /dev/null and b/assets/ui/button_delete_saved_game.png differ diff --git a/assets/ui/button_resume_game.png b/assets/ui/button_resume_game.png new file mode 100644 index 0000000000000000000000000000000000000000..2fe433b7d18a39880a14e3f0af18cb75c4ccbaed Binary files /dev/null and b/assets/ui/button_resume_game.png differ diff --git a/assets/ui/button_start.png b/assets/ui/button_start.png new file mode 100644 index 0000000000000000000000000000000000000000..23c7a4f670de19ffac455d6c510c3c53653a048b Binary files /dev/null and b/assets/ui/button_start.png differ diff --git a/lib/common/config/activity_page.dart b/lib/common/config/activity_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..847186e7d22b75896797f61fdb2bbd602373b7d2 --- /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 0000000000000000000000000000000000000000..2a828ee74ac8ba25a30efbd26b0e8a69c2b2cc5d --- /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 0000000000000000000000000000000000000000..b2dc71bc8a7c36a4dafa0f061515ef6f5ea0ad1f --- /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 0000000000000000000000000000000000000000..e0ccaa67f234b76d94fe8ef284b0f7c9f5c7d6d3 --- /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 0000000000000000000000000000000000000000..613cb7f08db49b723900c739fed7f1bc17d23a51 --- /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 0000000000000000000000000000000000000000..6a08a3a80a534b5fa7153d8fef49756e414a66b0 --- /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 b9a2b22fe72d59225817556b48550114293b8a22..4fe13e246cafa292bb1b62ed90032450f195fb59 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 ca4df4c1e3d13f9f9f870a4370f743c453bee36a..71ba41eb79535c475e1fd7165558b4f1648e2ca0 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 0000000000000000000000000000000000000000..1988e22404d91a70a145bd021e05be14168bbf1a --- /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 a8ee1a983fefea36078d8cd1b18fa74666fc6acf..928305bda7b25358c492e4f1bf999350d8f5abf3 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 3b6d7700f06e03edc7bc0bf3a8395d4ed3294096..2222146ed0f8cdd92472d0114def24487169ebec 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 0000000000000000000000000000000000000000..e37dfda33f82503ce3de18c37f1325ea327964f1 --- /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 0000000000000000000000000000000000000000..92a6c6a3ec4a292d098d1badc37f60c58b74cedb --- /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 0000000000000000000000000000000000000000..cec3d7b344638a40d52a6ed9ab85413cb107b0fd --- /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 0000000000000000000000000000000000000000..f7a14a9a7e574a7b6f9ed181f38d08ba8c2285fe --- /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 0000000000000000000000000000000000000000..239e1eea0dd7188dc938113d72c696e02dff525a --- /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 0000000000000000000000000000000000000000..8dc117ae80d0bf33ecb0fd7f6f496a21f5a31988 --- /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 8273e66dcba13e52ac96c34b7873fbaf00d8a28a..7fda5e860b9cd59c2517807273f88cdf35418af0 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 0000000000000000000000000000000000000000..8d0a234cb4a20962d6909203be7c967aa3ff5879 --- /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 0000000000000000000000000000000000000000..5f34d9d18fffef4d3276010aa103e2cf43b13ed7 --- /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 0000000000000000000000000000000000000000..d92229c2e02064684ed4a2caeb272ff63325776a --- /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 69a2ac1e502fe0f902e2d52a481297b1b9f513ea..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..5fc69c6072851df515f02d3a3f007c070772dde3 --- /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 0000000000000000000000000000000000000000..887b45e4255fd7de1cc7744569d82a38a66602f2 --- /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 f27411d271e9af6a19eee72ad62e02eca35c00ca..0000000000000000000000000000000000000000 --- 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 1f39160a8253a7637b9cf1e626e2b91f2a528f8f..0000000000000000000000000000000000000000 --- 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 3e4d0d092cf5da751fd66f72dedea0501878acbd..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..d404017b617c99e095f67a661f1e9163c5c5ae76 --- /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 0000000000000000000000000000000000000000..2b2de42011634e81ae9e6f8bcaa1577f239c778b --- /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 0000000000000000000000000000000000000000..0353c6b695296f3adbfaa131cc5e2f0d91b3bb19 --- /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 0000000000000000000000000000000000000000..ebcddd700f252257223ca8e16c85202b04f3ff24 --- /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 1437a56c2ea93b567899a20e816c2318ed2edfc2..a40d00dcf8c5cd8a7463dbc0a54889699a7d8196 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 966b251b144be0e2ad3a980e120114ca7e37c78d..c3d4875e4e31d707936ca914496b1a99b3ebd62f 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 8f0135e140001d5c6cfe05dd14579e90790bb3ee..d5854f8d75be4687aa5161612e2ae616574c5989 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 2b108da4f3288c8430bb199f511ee8a7125acfef..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..4346ebab42276bffaf8acdfa778986eeebe74f7b --- /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 0000000000000000000000000000000000000000..c8b1fd67149e00a12c2b90ed08d95d2700242004 --- /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 7903553aec3f0915ef0ef23b43bfeddc00a1a0f1..0000000000000000000000000000000000000000 --- 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 a7b4e83e09cef980c9c0898ea11f471c4c6de477..0000000000000000000000000000000000000000 --- 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 6995b4275ca4b7121cc9c228a297f7ec392390ff..0000000000000000000000000000000000000000 --- 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 3d71d2655f0e14530c78158347d4be00ada7b8f9..7649abe947201b14253cfda80a252cf527b0abfa 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 0000000000000000000000000000000000000000..e6fb9fadd6d25fdb183fc2af4aea31549b246de7 --- /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 0000000000000000000000000000000000000000..14ca1c9c49868807a34db438521815850f3f5e3d --- /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 0000000000000000000000000000000000000000..bb079befa77777ef289a653d0a16c9c16d7e3e9f --- /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 0000000000000000000000000000000000000000..41ec5d05d30d0a364a1e91a1bf942cca56693db3 --- /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 7343f6f2e141e5471145ac1a16ecc7804f939d3d..0000000000000000000000000000000000000000 --- 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 8670546e9967b4575264600feb2bd9057021b98e..99bf4274c9f90e54816ab23fb2fae9ffe444b052 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 21bd65e532d0dda0040ed9d9bac7be7c0b0a13db..ad9d873400fa061faa061144b977c4919106e81c 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 cea8f9addc05677985b83856a1f5ae5878d152cf..13598e0bd9f54ad6d09ce3b6168026a28e7af08f 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 783e770c7f1d2607102e03379112349d9881b4a9..0000000000000000000000000000000000000000 --- 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 18fffe0ed4e34cfa7483b618b317ebed67cfc13d..0000000000000000000000000000000000000000 --- 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 f45210bcb4e5951d0ad8a9f45603671510c22f7d..1c082e295afaae367b7bc7e6ce4ebe44c711b515 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 e7063fcd9238540b54b8bea895ab4c0c2aa15fe9..6985ebe71d84ec9f517caf440d7ff87279e08289 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 4b76d1ca261adfe8db14b4e9cc8898dc3f7cda26..774953c5b885aae73f710aaa9d8b55a0d8dcc2c0 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 0000000000000000000000000000000000000000..f6c3f33201ae81b1f43cf677bb76c91e04727f33 --- /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 646fb03e0c0f0986e10206db9d03321f81db07e3..018d8b734d2932028fbfce1643c4e888ff1b45b1 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 20961300a46bfb9a0ac5c665978f7879f4b94159..c3f872e434052a6b4e7036b530ced8e6233508e4 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 7b62a384c8268a0e307da3e3d60428a4132fb572..2bf973276aefa564ecff7d6149899298344819f9 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 014e5653816d62daff482c4e40be79cc1a1b9a65..4d7634a9f3fb559e590ee965e1341ae2634bf80f 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>