diff --git a/android/gradle.properties b/android/gradle.properties index 7cc965a085f8a41bab04af9a3eb0a67ded5e75d9..1764beb0b9cf3403ae552dc187f308529a8d428e 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=1.0.50 -app.versionCode=51 +app.versionName=1.0.51 +app.versionCode=52 diff --git a/assets/translations/en.json b/assets/translations/en.json index 7280a8a3e1e7d0316800f5a1005c0893419ca4cd..4f597623aec94ec04f12bc1620c6d051dd96a9c1 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -12,6 +12,7 @@ "api_page_title": "API", "settings_title": "Settings", + "settings_label_theme": "Theme mode", "settings_label_api_url": "API URL:", "settings_label_security_token": "Security token:", "settings_label_interface_type": "Interface type:", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 5ef35e50be00a17d1c45e5deca1517d327ef6341..aa559e2d4280fe5b2fb4591bef9bac2a5db4843b 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -12,6 +12,7 @@ "api_page_title": "API", "settings_title": "Paramètres", + "settings_label_theme": "Thème de couleurs", "settings_label_api_url": "URL de l'API :", "settings_label_security_token": "Jeton de sécurité :", "settings_label_interface_type": "Type d'interface :", diff --git a/lib/config/menu.dart b/lib/config/menu.dart index b06e9eb05c661cd00f4df9d6fd3675ba799f50ad..99f8668f54cc540b33e76d58e7b2d78f276ccdc2 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -62,7 +62,10 @@ class Menu { ]; static Widget getPageWidget(int pageIndex) { - return Menu.items.elementAt(pageIndex).page; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Menu.items.elementAt(pageIndex).page, + ); } static List<BottomNavigationBarItem> getMenuItems() { diff --git a/lib/cubit/theme_cubit.dart b/lib/cubit/theme_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..b793e895dbb0c672d451cd403e0036c3d9ac9b42 --- /dev/null +++ b/lib/cubit/theme_cubit.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +part 'theme_state.dart'; + +class ThemeCubit extends HydratedCubit<ThemeModeState> { + ThemeCubit() : super(const ThemeModeState()); + + void getTheme(ThemeModeState state) { + emit(state); + } + + @override + ThemeModeState? fromJson(Map<String, dynamic> json) { + switch (json['themeMode']) { + case 'ThemeMode.dark': + return const ThemeModeState(themeMode: ThemeMode.dark); + case 'ThemeMode.light': + return const ThemeModeState(themeMode: ThemeMode.light); + case 'ThemeMode.system': + default: + return const ThemeModeState(themeMode: ThemeMode.system); + } + } + + @override + Map<String, String>? toJson(ThemeModeState state) { + return <String, String>{'themeMode': state.themeMode.toString()}; + } +} diff --git a/lib/cubit/theme_state.dart b/lib/cubit/theme_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..e479a50f12fe72a35a1fd1722ff72afbb692a136 --- /dev/null +++ b/lib/cubit/theme_state.dart @@ -0,0 +1,15 @@ +part of 'theme_cubit.dart'; + +@immutable +class ThemeModeState extends Equatable { + const ThemeModeState({ + this.themeMode, + }); + + final ThemeMode? themeMode; + + @override + List<Object?> get props => <Object?>[ + themeMode, + ]; +} diff --git a/lib/main.dart b/lib/main.dart index ca105a76396f0950f7ed21c7aa7d29091f1bf3f6..b4a2c31cd3ff8f43041e32e164f696c875460e15 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,11 +8,12 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:path_provider/path_provider.dart'; import 'package:random/config/theme.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/cubit/api_cubit.dart'; +import 'package:random/cubit/theme_cubit.dart'; import 'package:random/repository/api.dart'; import 'package:random/network/api.dart'; import 'package:random/ui/skeleton.dart'; @@ -48,10 +49,6 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), - BlocProvider<DataCubit>(create: (context) => DataCubit()), - BlocProvider<GameCubit>(create: (context) => GameCubit()), BlocProvider<ApiDataCubit>( create: (context) => ApiDataCubit( apiRepository: ApiRepository( @@ -59,16 +56,26 @@ 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<ThemeCubit>(create: (context) => ThemeCubit()), ], - child: MaterialApp( - title: 'Random application', - theme: appTheme, - home: const SkeletonScreen(), - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ), + child: BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState 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, + ); + }), ); } } diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart index 1f845c855f69c09a9f7b4b5cc76bb824118be192..a0db19d0e8a1d181337c06523614a8f3d91cad00 100644 --- a/lib/ui/screens/game_page.dart +++ b/lib/ui/screens/game_page.dart @@ -25,7 +25,7 @@ class _GamePageState extends State<GamePage> { gameCubit.updateGameState(Game.createNew()); }, icon: const Icon(UniconsSolid.star), - color: Colors.white, + color: Theme.of(context).colorScheme.primary, ) ]; @@ -39,7 +39,7 @@ class _GamePageState extends State<GamePage> { setState(() {}); }, icon: const Icon(UniconsLine.exit), - color: Colors.white, + color: Theme.of(context).colorScheme.primary, )); } diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart index 5cd634890c1575097a977b15eb289560523745e8..facc79d4ad75a3d4e3840340a9be0f0fecd6df31 100644 --- a/lib/ui/widgets/settings_form.dart +++ b/lib/ui/widgets/settings_form.dart @@ -6,6 +6,7 @@ import 'package:unicons/unicons.dart'; import 'package:random/cubit/settings_cubit.dart'; import 'package:random/config/theme.dart'; import 'package:random/models/interface_type.dart'; +import 'package:random/ui/widgets/theme_card.dart'; class SettingsForm extends StatefulWidget { const SettingsForm({super.key}); @@ -54,6 +55,35 @@ class _SettingsFormState extends State<SettingsForm> { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: <Widget>[ + // Light/dark theme + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const Text('settings_label_theme').tr(), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ThemeCard( + mode: ThemeMode.system, + icon: UniconsLine.cog, + ), + ThemeCard( + mode: ThemeMode.light, + icon: UniconsLine.sun, + ), + ThemeCard( + mode: ThemeMode.dark, + icon: UniconsLine.moon, + ) + ], + ), + ], + ), + + const SizedBox(height: 16), + const Text('settings_label_api_url').tr(), TextFormField( controller: apiUrlController, @@ -61,7 +91,9 @@ class _SettingsFormState extends State<SettingsForm> { border: UnderlineInputBorder(), ), ), + const SizedBox(height: 16), + const Text('settings_label_security_token').tr(), TextFormField( controller: securityTokenController, @@ -69,7 +101,9 @@ class _SettingsFormState extends State<SettingsForm> { border: UnderlineInputBorder(), ), ), + const SizedBox(height: 16), + const Text('settings_label_interface_type').tr(), ToggleButtons( direction: Axis.horizontal, diff --git a/lib/ui/widgets/theme_card.dart b/lib/ui/widgets/theme_card.dart new file mode 100644 index 0000000000000000000000000000000000000000..e68441ca95c75152711027b36d7ed68bd71b5c40 --- /dev/null +++ b/lib/ui/widgets/theme_card.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:random/cubit/theme_cubit.dart'; + +class ThemeCard extends StatelessWidget { + const ThemeCard({ + super.key, + required this.mode, + required this.icon, + }); + + final IconData icon; + final ThemeMode mode; + + @override + Widget build(BuildContext context) { + return BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return Card( + elevation: 2, + shadowColor: Theme.of(context).colorScheme.shadow, + color: state.themeMode == mode + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.surface, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + margin: const EdgeInsets.all(5), + child: InkWell( + onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme( + ThemeModeState(themeMode: mode), + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Icon( + icon, + size: 32, + color: + state.themeMode != mode ? Theme.of(context).colorScheme.primary : Colors.white, + ), + ), + ); + }); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3de6bedec6d37ef273a3ca11a440ecc86dc249e0..38056a213177e3cb75e7f77fcd2c00ddb8160cc1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A random application, for testing purpose only. publish_to: 'none' -version: 1.0.50+51 +version: 1.0.51+52 environment: sdk: '^3.0.0'