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
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(
title: 'Sudoku',
theme: appTheme,
home: const SkeletonScreen(),
child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) {
return MaterialApp(
title: 'Sudoku',
home: const SkeletonScreen(),
// Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
),
// 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,22 +13,10 @@ 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,17 +16,20 @@ class ScreenGame extends StatelessWidget {
builder: (BuildContext context, GameState gameState) {
final Game game = gameState.game;
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 8),
const BoardLayout(),
const SizedBox(height: 8),
game.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(),
const Expanded(child: SizedBox.shrink()),
game.isFinished ? const EndGameMessage() : const SizedBox.shrink(),
],
return Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 8),
const BoardLayout(),
const SizedBox(height: 8),
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: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,95 +15,123 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
Widget build(BuildContext context) {
return BlocBuilder<GameCubit, 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) {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
if (game.isRunning && !game.isFinished) {
final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
menuActions.add(TextButton(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.white,
width: 3,
),
),
child: const Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill,
),
),
onPressed: () => toast('Long press to quit game...'),
onLongPress: () {
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,
menuActions.add(TextButton(
onPressed: null,
onLongPress: () {
gameCubit.quitGame();
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.white,
width: 3,
),
),
child: const Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill,
),
),
badgeContent: Text(
game.givenTipsCount == 0 ? '' : game.givenTipsCount.toString(),
style: const TextStyle(color: Colors.white),
));
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(),
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(
padding: EdgeInsets.all(15 *
game.buttonTipsCountdown /
DefaultGlobalSettings.defaultTipCountDownValueInSeconds),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: game.showConflicts == true ? Colors.blue : Colors.white,
width: 3,
),
),
child: const Image(
image: AssetImage('assets/icons/button_help.png'),
image: AssetImage('assets/icons/button_show_conflicts.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(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: game.showConflicts == true ? Colors.blue : Colors.white,
width: 3,
),
),
child: const Image(
image: AssetImage('assets/icons/button_show_conflicts.png'),
fit: BoxFit.fill,
),
),
onPressed: () {
gameCubit.toggleShowConflicts();
},
));
}
onPressed: () {
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(
title: const SizedBox.shrink(),
actions: menuActions,
return AppBar(
title: const SizedBox.shrink(),
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';
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(
......