Skip to content
Snippets Groups Projects
Commit d4f28eae authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Add light/dark theme selector

parent b6ef4ccb
No related branches found
No related tags found
1 merge request!62Resolve "Add light/dark theme selector"
Pipeline #5463 passed
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=1.0.50 app.versionName=1.0.51
app.versionCode=51 app.versionCode=52
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"api_page_title": "API", "api_page_title": "API",
"settings_title": "Settings", "settings_title": "Settings",
"settings_label_theme": "Theme mode",
"settings_label_api_url": "API URL:", "settings_label_api_url": "API URL:",
"settings_label_security_token": "Security token:", "settings_label_security_token": "Security token:",
"settings_label_interface_type": "Interface type:", "settings_label_interface_type": "Interface type:",
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"api_page_title": "API", "api_page_title": "API",
"settings_title": "Paramètres", "settings_title": "Paramètres",
"settings_label_theme": "Thème de couleurs",
"settings_label_api_url": "URL de l'API :", "settings_label_api_url": "URL de l'API :",
"settings_label_security_token": "Jeton de sécurité :", "settings_label_security_token": "Jeton de sécurité :",
"settings_label_interface_type": "Type d'interface :", "settings_label_interface_type": "Type d'interface :",
......
...@@ -62,7 +62,10 @@ class Menu { ...@@ -62,7 +62,10 @@ class Menu {
]; ];
static Widget getPageWidget(int pageIndex) { 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() { static List<BottomNavigationBarItem> getMenuItems() {
......
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()};
}
}
part of 'theme_cubit.dart';
@immutable
class ThemeModeState extends Equatable {
const ThemeModeState({
this.themeMode,
});
final ThemeMode? themeMode;
@override
List<Object?> get props => <Object?>[
themeMode,
];
}
...@@ -8,11 +8,12 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; ...@@ -8,11 +8,12 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:random/config/theme.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/bottom_nav_cubit.dart';
import 'package:random/cubit/data_cubit.dart'; import 'package:random/cubit/data_cubit.dart';
import 'package:random/cubit/game_cubit.dart'; import 'package:random/cubit/game_cubit.dart';
import 'package:random/cubit/settings_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/repository/api.dart';
import 'package:random/network/api.dart'; import 'package:random/network/api.dart';
import 'package:random/ui/skeleton.dart'; import 'package:random/ui/skeleton.dart';
...@@ -48,10 +49,6 @@ class MyApp extends StatelessWidget { ...@@ -48,10 +49,6 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()),
BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()),
BlocProvider<DataCubit>(create: (context) => DataCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<ApiDataCubit>( BlocProvider<ApiDataCubit>(
create: (context) => ApiDataCubit( create: (context) => ApiDataCubit(
apiRepository: ApiRepository( apiRepository: ApiRepository(
...@@ -59,16 +56,26 @@ class MyApp extends StatelessWidget { ...@@ -59,16 +56,26 @@ class MyApp extends StatelessWidget {
), ),
)..fetchApiData(), )..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( child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) {
return MaterialApp(
title: 'Random application', title: 'Random application',
theme: appTheme, theme: lightTheme,
darkTheme: darkTheme,
themeMode: state.themeMode,
home: const SkeletonScreen(), home: const SkeletonScreen(),
localizationsDelegates: context.localizationDelegates, localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
locale: context.locale, locale: context.locale,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
), );
}),
); );
} }
} }
...@@ -25,7 +25,7 @@ class _GamePageState extends State<GamePage> { ...@@ -25,7 +25,7 @@ class _GamePageState extends State<GamePage> {
gameCubit.updateGameState(Game.createNew()); gameCubit.updateGameState(Game.createNew());
}, },
icon: const Icon(UniconsSolid.star), icon: const Icon(UniconsSolid.star),
color: Colors.white, color: Theme.of(context).colorScheme.primary,
) )
]; ];
...@@ -39,7 +39,7 @@ class _GamePageState extends State<GamePage> { ...@@ -39,7 +39,7 @@ class _GamePageState extends State<GamePage> {
setState(() {}); setState(() {});
}, },
icon: const Icon(UniconsLine.exit), icon: const Icon(UniconsLine.exit),
color: Colors.white, color: Theme.of(context).colorScheme.primary,
)); ));
} }
......
...@@ -6,6 +6,7 @@ import 'package:unicons/unicons.dart'; ...@@ -6,6 +6,7 @@ import 'package:unicons/unicons.dart';
import 'package:random/cubit/settings_cubit.dart'; import 'package:random/cubit/settings_cubit.dart';
import 'package:random/config/theme.dart'; import 'package:random/config/theme.dart';
import 'package:random/models/interface_type.dart'; import 'package:random/models/interface_type.dart';
import 'package:random/ui/widgets/theme_card.dart';
class SettingsForm extends StatefulWidget { class SettingsForm extends StatefulWidget {
const SettingsForm({super.key}); const SettingsForm({super.key});
...@@ -54,6 +55,35 @@ class _SettingsFormState extends State<SettingsForm> { ...@@ -54,6 +55,35 @@ class _SettingsFormState extends State<SettingsForm> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ 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(), const Text('settings_label_api_url').tr(),
TextFormField( TextFormField(
controller: apiUrlController, controller: apiUrlController,
...@@ -61,7 +91,9 @@ class _SettingsFormState extends State<SettingsForm> { ...@@ -61,7 +91,9 @@ class _SettingsFormState extends State<SettingsForm> {
border: UnderlineInputBorder(), border: UnderlineInputBorder(),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('settings_label_security_token').tr(), const Text('settings_label_security_token').tr(),
TextFormField( TextFormField(
controller: securityTokenController, controller: securityTokenController,
...@@ -69,7 +101,9 @@ class _SettingsFormState extends State<SettingsForm> { ...@@ -69,7 +101,9 @@ class _SettingsFormState extends State<SettingsForm> {
border: UnderlineInputBorder(), border: UnderlineInputBorder(),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('settings_label_interface_type').tr(), const Text('settings_label_interface_type').tr(),
ToggleButtons( ToggleButtons(
direction: Axis.horizontal, direction: Axis.horizontal,
......
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,
),
),
);
});
}
}
...@@ -3,7 +3,7 @@ description: A random application, for testing purpose only. ...@@ -3,7 +3,7 @@ description: A random application, for testing purpose only.
publish_to: 'none' publish_to: 'none'
version: 1.0.50+51 version: 1.0.51+52
environment: environment:
sdk: '^3.0.0' sdk: '^3.0.0'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment