Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • android/org.benoitharrault.sudoku
1 result
Show changes
Commits on Source (2)
Showing
with 405 additions and 120 deletions
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=0.1.20 app.versionName=0.1.21
app.versionCode=69 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'; ...@@ -6,13 +6,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.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/default_global_settings.dart';
import 'package:sudoku/config/theme.dart'; import 'package:sudoku/config/theme.dart';
import 'package:sudoku/cubit/game_cubit.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_game_cubit.dart';
import 'package:sudoku/cubit/settings_global_cubit.dart'; import 'package:sudoku/cubit/settings_global_cubit.dart';
import 'package:sudoku/cubit/theme_cubit.dart';
import 'package:sudoku/ui/skeleton.dart'; import 'package:sudoku/ui/skeleton.dart';
void main() async { void main() async {
...@@ -51,22 +52,30 @@ class MyApp extends StatelessWidget { ...@@ -51,22 +52,30 @@ class MyApp extends StatelessWidget {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<NavCubit>(create: (context) => NavCubit()),
BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()), BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()), BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
], ],
child: OverlaySupport( child: BlocBuilder<ThemeCubit, ThemeModeState>(
child: MaterialApp( builder: (BuildContext context, ThemeModeState state) {
title: 'Sudoku', return MaterialApp(
theme: appTheme, title: 'Sudoku',
home: const SkeletonScreen(), home: const SkeletonScreen(),
// Localization stuff // Theme stuff
localizationsDelegates: context.localizationDelegates, theme: lightTheme,
supportedLocales: context.supportedLocales, darkTheme: darkTheme,
locale: context.locale, themeMode: state.themeMode,
debugShowCheckedModeBanner: false,
), // Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
);
},
), ),
); );
} }
......
...@@ -15,7 +15,7 @@ class BoardLayout extends StatelessWidget { ...@@ -15,7 +15,7 @@ class BoardLayout extends StatelessWidget {
builder: (BuildContext context, GameState gameState) { builder: (BuildContext context, GameState gameState) {
final Game game = gameState.game; final Game game = gameState.game;
const Color borderColor = Colors.black; final Color borderColor = Theme.of(context).colorScheme.onBackground;
return Container( return Container(
margin: const EdgeInsets.all(2), 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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sudoku/cubit/game_cubit.dart'; import 'package:sudoku/config/menu.dart';
import 'package:sudoku/ui/screens/screen_game.dart'; import 'package:sudoku/cubit/nav_cubit.dart';
import 'package:sudoku/ui/screens/screen_parameters.dart';
import 'package:sudoku/ui/widgets/global_app_bar.dart'; import 'package:sudoku/ui/widgets/global_app_bar.dart';
class SkeletonScreen extends StatelessWidget { class SkeletonScreen extends StatelessWidget {
...@@ -14,22 +13,10 @@ class SkeletonScreen extends StatelessWidget { ...@@ -14,22 +13,10 @@ class SkeletonScreen extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: const GlobalAppBar(), appBar: const GlobalAppBar(),
extendBodyBehindAppBar: false, extendBodyBehindAppBar: false,
body: Material( body: BlocBuilder<NavCubit, int>(
color: Theme.of(context).colorScheme.background, builder: (BuildContext context, int pageIndex) {
child: Container( return Menu.getPageWidget(pageIndex);
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,
);
},
),
),
), ),
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
); );
......
...@@ -115,8 +115,8 @@ class CellWidget extends StatelessWidget { ...@@ -115,8 +115,8 @@ class CellWidget extends StatelessWidget {
// Compute cell borders, from board size and cell state // Compute cell borders, from board size and cell state
Border getCellBorders(Game game) { Border getCellBorders(Game game) {
const Color cellBorderDarkColor = Colors.black; final Color cellBorderDarkColor = Colors.grey.shade800;
const Color cellBorderLightColor = Colors.grey; final Color cellBorderLightColor = Colors.grey.shade600;
const Color cellBorderSelectedColor = Colors.red; const Color cellBorderSelectedColor = Colors.red;
Color cellBorderColor = cellBorderSelectedColor; Color cellBorderColor = cellBorderSelectedColor;
......
...@@ -36,7 +36,7 @@ class CellWidgetUpdate extends StatelessWidget { ...@@ -36,7 +36,7 @@ class CellWidgetUpdate extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: backgroundColor,
border: Border.all( border: Border.all(
color: Colors.black, color: Colors.grey.shade700,
width: 2, width: 2,
), ),
), ),
......
...@@ -7,8 +7,8 @@ import 'package:sudoku/ui/layout/board.dart'; ...@@ -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/message_game_end.dart';
import 'package:sudoku/ui/widgets/bar_select_cell_value.dart'; import 'package:sudoku/ui/widgets/bar_select_cell_value.dart';
class ScreenGame extends StatelessWidget { class GameWidget extends StatelessWidget {
const ScreenGame({super.key}); const GameWidget({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -16,17 +16,20 @@ class ScreenGame extends StatelessWidget { ...@@ -16,17 +16,20 @@ class ScreenGame extends StatelessWidget {
builder: (BuildContext context, GameState gameState) { builder: (BuildContext context, GameState gameState) {
final Game game = gameState.game; final Game game = gameState.game;
return Column( return Container(
mainAxisAlignment: MainAxisAlignment.start, padding: const EdgeInsets.all(4),
crossAxisAlignment: CrossAxisAlignment.center, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.start,
const SizedBox(height: 8), crossAxisAlignment: CrossAxisAlignment.center,
const BoardLayout(), children: [
const SizedBox(height: 8), const SizedBox(height: 8),
game.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(), const BoardLayout(),
const Expanded(child: SizedBox.shrink()), const SizedBox(height: 8),
game.isFinished ? const EndGameMessage() : const SizedBox.shrink(), game.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(),
], const Expanded(child: SizedBox.shrink()),
game.isFinished ? const EndGameMessage() : const SizedBox.shrink(),
],
),
); );
}, },
); );
......
import 'package:badges/badges.dart' as badges; import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/default_global_settings.dart';
import 'package:sudoku/config/menu.dart';
import 'package:sudoku/cubit/game_cubit.dart'; import 'package:sudoku/cubit/game_cubit.dart';
import 'package:sudoku/cubit/nav_cubit.dart';
import 'package:sudoku/models/game.dart'; import 'package:sudoku/models/game.dart';
class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
...@@ -14,95 +15,123 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { ...@@ -14,95 +15,123 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>( return BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) { builder: (BuildContext context, GameState gameState) {
final Game game = gameState.game; return BlocBuilder<NavCubit, int>(
builder: (BuildContext context, int pageIndex) {
final Game game = gameState.game;
final List<Widget> menuActions = []; final List<Widget> menuActions = [];
if (game.isRunning && !game.isFinished) { if (game.isRunning && !game.isFinished) {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
menuActions.add(TextButton( menuActions.add(TextButton(
child: Container( onPressed: null,
decoration: BoxDecoration( onLongPress: () {
borderRadius: BorderRadius.circular(4), gameCubit.quitGame();
border: Border.all( },
color: Colors.white, child: Container(
width: 3, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(4),
), border: Border.all(
child: const Image( color: Colors.white,
image: AssetImage('assets/icons/button_back.png'), width: 3,
fit: BoxFit.fill, ),
), ),
), child: const Image(
onPressed: () => toast('Long press to quit game...'), image: AssetImage('assets/icons/button_back.png'),
onLongPress: () { fit: BoxFit.fill,
gameCubit.quitGame(); ),
},
));
menuActions.add(const Spacer(flex: 6));
menuActions.add(TextButton(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.white,
width: 3,
),
),
child: badges.Badge(
showBadge: game.givenTipsCount == 0 ? false : true,
badgeStyle: badges.BadgeStyle(
badgeColor: game.givenTipsCount < 10
? Colors.green
: game.givenTipsCount < 20
? Colors.orange
: Colors.red,
), ),
badgeContent: Text( ));
game.givenTipsCount == 0 ? '' : game.givenTipsCount.toString(), menuActions.add(const Spacer(flex: 6));
style: const TextStyle(color: Colors.white), menuActions.add(TextButton(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.white,
width: 3,
),
),
child: badges.Badge(
showBadge: game.givenTipsCount == 0 ? false : true,
badgeStyle: badges.BadgeStyle(
badgeColor: game.givenTipsCount < 10
? Colors.green
: game.givenTipsCount < 20
? Colors.orange
: Colors.red,
),
badgeContent: Text(
game.givenTipsCount == 0 ? '' : game.givenTipsCount.toString(),
style: const TextStyle(color: Colors.white),
),
child: Container(
padding: EdgeInsets.all(15 *
game.buttonTipsCountdown /
DefaultGlobalSettings.defaultTipCountDownValueInSeconds),
child: const Image(
image: AssetImage('assets/icons/button_help.png'),
fit: BoxFit.fill,
),
),
),
), ),
onPressed: () {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
game.canGiveTip() ? game.showTip(gameCubit) : null;
},
));
menuActions.add(const Spacer());
menuActions.add(TextButton(
child: Container( child: Container(
padding: EdgeInsets.all(15 * decoration: BoxDecoration(
game.buttonTipsCountdown / borderRadius: BorderRadius.circular(4),
DefaultGlobalSettings.defaultTipCountDownValueInSeconds), border: Border.all(
color: game.showConflicts == true ? Colors.blue : Colors.white,
width: 3,
),
),
child: const Image( child: const Image(
image: AssetImage('assets/icons/button_help.png'), image: AssetImage('assets/icons/button_show_conflicts.png'),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), ),
), onPressed: () {
), gameCubit.toggleShowConflicts();
onPressed: () { },
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); ));
game.canGiveTip() ? game.showTip(gameCubit) : null; } else {
}, if (pageIndex == Menu.indexGame) {
)); // go to Settings page
menuActions.add(const Spacer()); menuActions.add(ElevatedButton(
menuActions.add(TextButton( onPressed: () {
child: Container( context.read<NavCubit>().switchToSettingsPage();
decoration: BoxDecoration( },
borderRadius: BorderRadius.circular(4), style: ElevatedButton.styleFrom(
border: Border.all( shape: const CircleBorder(),
color: game.showConflicts == true ? Colors.blue : Colors.white, ),
width: 3, child: Menu.menuItemSettings.icon,
), ));
), } else {
child: const Image( // back to Home page
image: AssetImage('assets/icons/button_show_conflicts.png'), menuActions.add(ElevatedButton(
fit: BoxFit.fill, onPressed: () {
), context.read<NavCubit>().goToGamePage();
), },
onPressed: () { style: ElevatedButton.styleFrom(
gameCubit.toggleShowConflicts(); shape: const CircleBorder(),
}, ),
)); child: Menu.menuItemGame.icon,
} ));
}
}
return AppBar( return AppBar(
title: const SizedBox.shrink(), title: const SizedBox.shrink(),
actions: menuActions, actions: menuActions,
);
},
); );
}, },
); );
......
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'; ...@@ -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/button_resume_saved_game.dart';
import 'package:sudoku/ui/widgets/parameter_image.dart'; import 'package:sudoku/ui/widgets/parameter_image.dart';
class ScreenParameters extends StatelessWidget { class Parameters extends StatelessWidget {
const ScreenParameters({super.key, required this.canResume}); const Parameters({super.key, required this.canResume});
final bool canResume; final bool canResume;
...@@ -22,6 +22,8 @@ class ScreenParameters extends StatelessWidget { ...@@ -22,6 +22,8 @@ class ScreenParameters extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> lines = []; final List<Widget> lines = [];
lines.add(SizedBox(height: separatorHeight));
// Game settings // Game settings
for (String code in DefaultGameSettings.availableParameters) { for (String code in DefaultGameSettings.availableParameters) {
lines.add(Row( lines.add(Row(
......