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

Add "settings" page with dart/light theme selector

parent 45fe7ba0
No related branches found
No related tags found
1 merge request!74Resolve "Add "settings" page"
Pipeline #5612 passed
This commit is part of merge request !74. Comments created here will be created in the context of that merge request.
Showing
with 405 additions and 120 deletions
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
app.versionName=0.1.20
app.versionCode=69
app.versionName=0.1.21
app.versionCode=70
{
"app_name": "Sudoku"
"app_name": "Sudoku",
"bottom_nav_game": "Game",
"bottom_nav_settings": "Settings",
"settings_title": "Settings",
"settings_label_theme": "Theme mode"
}
{
"app_name": "Sudoku"
"app_name": "Sudoku",
"bottom_nav_game": "Jeu",
"bottom_nav_settings": "Réglages",
"settings_title": "Réglages",
"settings_label_theme": "Thème de couleurs"
}
Add "settings" page with dark/light theme selector.
Ajout d'une page de réglages avec sélection du thème clair ou sombre.
import 'package:flutter/material.dart';
import 'package:unicons/unicons.dart';
import 'package:sudoku/ui/screens/game_page.dart';
import 'package:sudoku/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 const indexGame = 0;
static const menuItemGame = MenuItem(
code: 'bottom_nav_game',
icon: Icon(UniconsLine.home),
page: GamePage(),
);
static const indexSettings = 1;
static const menuItemSettings = MenuItem(
code: 'bottom_nav_settings',
icon: Icon(UniconsLine.setting),
page: SettingsPage(),
);
static Map<int, MenuItem> items = {
indexGame: menuItemGame,
indexSettings: menuItemSettings,
};
static bool isIndexAllowed(int pageIndex) {
return items.keys.contains(pageIndex);
}
static Widget getPageWidget(int pageIndex) {
return items[pageIndex]?.page ?? menuItemGame.page;
}
static int itemsCount = Menu.items.length;
}
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:sudoku/config/menu.dart';
class NavCubit extends HydratedCubit<int> {
NavCubit() : super(0);
void updateIndex(int index) {
if (Menu.isIndexAllowed(index)) {
emit(index);
} else {
goToGamePage();
}
}
void goToGamePage() {
emit(Menu.indexGame);
}
void switchToSettingsPage() {
if (state != Menu.indexSettings) {
emit(Menu.indexSettings);
} else {
goToGamePage();
}
}
@override
int fromJson(Map<String, dynamic> json) {
return Menu.indexGame;
}
@override
Map<String, dynamic>? toJson(int state) {
return <String, int>{'pageIndex': state};
}
}
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,
];
}
......@@ -6,13 +6,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:sudoku/config/default_global_settings.dart';
import 'package:sudoku/config/theme.dart';
import 'package:sudoku/cubit/game_cubit.dart';
import 'package:sudoku/cubit/nav_cubit.dart';
import 'package:sudoku/cubit/settings_game_cubit.dart';
import 'package:sudoku/cubit/settings_global_cubit.dart';
import 'package:sudoku/cubit/theme_cubit.dart';
import 'package:sudoku/ui/skeleton.dart';
void main() async {
......@@ -51,22 +52,30 @@ class MyApp extends StatelessWidget {
return MultiBlocProvider(
providers: [
BlocProvider<NavCubit>(create: (context) => NavCubit()),
BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
],
child: OverlaySupport(
child: MaterialApp(
child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) {
return MaterialApp(
title: 'Sudoku',
theme: appTheme,
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,
),
);
},
),
);
}
......
......@@ -15,7 +15,7 @@ class BoardLayout extends StatelessWidget {
builder: (BuildContext context, GameState gameState) {
final Game game = gameState.game;
const Color borderColor = Colors.black;
final Color borderColor = Theme.of(context).colorScheme.onBackground;
return Container(
margin: const EdgeInsets.all(2),
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sudoku/cubit/game_cubit.dart';
import 'package:sudoku/ui/widgets/game.dart';
import 'package:sudoku/ui/widgets/parameters.dart';
class GamePage extends StatelessWidget {
const GamePage({super.key});
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.background,
child: BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
return gameState.game.isRunning
? const GameWidget()
: Parameters(
canResume: gameState.game.isStarted && !gameState.game.isFinished,
);
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:sudoku/ui/widgets/header_app.dart';
import 'package:sudoku/ui/widgets/settings/settings_form.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({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),
AppHeader(text: 'settings_title'),
SizedBox(height: 8),
SettingsForm(),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sudoku/cubit/game_cubit.dart';
import 'package:sudoku/ui/screens/screen_game.dart';
import 'package:sudoku/ui/screens/screen_parameters.dart';
import 'package:sudoku/config/menu.dart';
import 'package:sudoku/cubit/nav_cubit.dart';
import 'package:sudoku/ui/widgets/global_app_bar.dart';
class SkeletonScreen extends StatelessWidget {
......@@ -14,23 +13,11 @@ class SkeletonScreen extends StatelessWidget {
return Scaffold(
appBar: const GlobalAppBar(),
extendBodyBehindAppBar: false,
body: Material(
color: Theme.of(context).colorScheme.background,
child: Container(
margin: const EdgeInsets.only(
top: 8.0,
),
child: BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
return gameState.game.isRunning
? const ScreenGame()
: ScreenParameters(
canResume: gameState.game.isStarted && !gameState.game.isFinished,
);
body: BlocBuilder<NavCubit, int>(
builder: (BuildContext context, int pageIndex) {
return Menu.getPageWidget(pageIndex);
},
),
),
),
backgroundColor: Theme.of(context).colorScheme.background,
);
}
......
......@@ -115,8 +115,8 @@ class CellWidget extends StatelessWidget {
// Compute cell borders, from board size and cell state
Border getCellBorders(Game game) {
const Color cellBorderDarkColor = Colors.black;
const Color cellBorderLightColor = Colors.grey;
final Color cellBorderDarkColor = Colors.grey.shade800;
final Color cellBorderLightColor = Colors.grey.shade600;
const Color cellBorderSelectedColor = Colors.red;
Color cellBorderColor = cellBorderSelectedColor;
......
......@@ -36,7 +36,7 @@ class CellWidgetUpdate extends StatelessWidget {
decoration: BoxDecoration(
color: backgroundColor,
border: Border.all(
color: Colors.black,
color: Colors.grey.shade700,
width: 2,
),
),
......
......@@ -7,8 +7,8 @@ import 'package:sudoku/ui/layout/board.dart';
import 'package:sudoku/ui/widgets/message_game_end.dart';
import 'package:sudoku/ui/widgets/bar_select_cell_value.dart';
class ScreenGame extends StatelessWidget {
const ScreenGame({super.key});
class GameWidget extends StatelessWidget {
const GameWidget({super.key});
@override
Widget build(BuildContext context) {
......@@ -16,7 +16,9 @@ class ScreenGame extends StatelessWidget {
builder: (BuildContext context, GameState gameState) {
final Game game = gameState.game;
return Column(
return Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
......@@ -27,6 +29,7 @@ class ScreenGame extends StatelessWidget {
const Expanded(child: SizedBox.shrink()),
game.isFinished ? const EndGameMessage() : const SizedBox.shrink(),
],
),
);
},
);
......
import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:sudoku/config/default_global_settings.dart';
import 'package:sudoku/config/menu.dart';
import 'package:sudoku/cubit/game_cubit.dart';
import 'package:sudoku/cubit/nav_cubit.dart';
import 'package:sudoku/models/game.dart';
class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
......@@ -14,6 +15,8 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
return BlocBuilder<NavCubit, int>(
builder: (BuildContext context, int pageIndex) {
final Game game = gameState.game;
final List<Widget> menuActions = [];
......@@ -22,6 +25,10 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
menuActions.add(TextButton(
onPressed: null,
onLongPress: () {
gameCubit.quitGame();
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
......@@ -35,10 +42,6 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
fit: BoxFit.fill,
),
),
onPressed: () => toast('Long press to quit game...'),
onLongPress: () {
gameCubit.quitGame();
},
));
menuActions.add(const Spacer(flex: 6));
menuActions.add(TextButton(
......@@ -98,6 +101,30 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
gameCubit.toggleShowConflicts();
},
));
} else {
if (pageIndex == Menu.indexGame) {
// go to Settings page
menuActions.add(ElevatedButton(
onPressed: () {
context.read<NavCubit>().switchToSettingsPage();
},
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
),
child: Menu.menuItemSettings.icon,
));
} else {
// back to Home page
menuActions.add(ElevatedButton(
onPressed: () {
context.read<NavCubit>().goToGamePage();
},
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
),
child: Menu.menuItemGame.icon,
));
}
}
return AppBar(
......@@ -106,6 +133,8 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
);
},
);
},
);
}
@override
......
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class AppHeader extends StatelessWidget {
const AppHeader({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tr(text),
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2),
),
const SizedBox(height: 8),
],
);
}
}
......@@ -11,8 +11,8 @@ import 'package:sudoku/ui/widgets/button_game_start_new.dart';
import 'package:sudoku/ui/widgets/button_resume_saved_game.dart';
import 'package:sudoku/ui/widgets/parameter_image.dart';
class ScreenParameters extends StatelessWidget {
const ScreenParameters({super.key, required this.canResume});
class Parameters extends StatelessWidget {
const Parameters({super.key, required this.canResume});
final bool canResume;
......@@ -22,6 +22,8 @@ class ScreenParameters extends StatelessWidget {
Widget build(BuildContext context) {
final List<Widget> lines = [];
lines.add(SizedBox(height: separatorHeight));
// Game settings
for (String code in DefaultGameSettings.availableParameters) {
lines.add(Row(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment