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

Improve game architecture/conception

parent 934c31d2
No related branches found
No related tags found
1 merge request!75Resolve "Improve game architecture"
Pipeline #5479 passed
This commit is part of merge request !75. Comments created here will be created in the context of that merge request.
Showing
with 472 additions and 84 deletions
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
app.versionName=0.1.38
app.versionCode=62
app.versionName=0.1.39
app.versionCode=63
File added
File added
File added
File added
{
"app_name": "Word guess",
"bottom_nav_home": "Game",
"bottom_nav_settings": "Settings",
"bottom_nav_about": "About",
"settings_title": "Settings",
"settings_label_theme": "Theme mode",
"about_title": "About",
"about_content": "Word guess game.",
"about_version": "Version: {version}"
}
{
"app_name": "Trouve le mot",
"bottom_nav_home": "Jeu",
"bottom_nav_settings": "Réglages",
"bottom_nav_about": "Infos",
"settings_title": "Réglages",
"settings_label_theme": "Thème de couleurs",
"about_title": "Informations",
"about_content": "Trouve le mot.",
"about_version": "Version : {version}"
}
Improve game conception/architecture.
Amélioration de la conception/architecture du jeu.
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:unicons/unicons.dart';
import 'package:wordguessing/ui/screens/about_page.dart';
import 'package:wordguessing/ui/screens/game_page.dart';
import 'package:wordguessing/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 List<MenuItem> items = [
const MenuItem(
code: 'bottom_nav_home',
icon: Icon(UniconsLine.home),
page: GamePage(),
),
const MenuItem(
code: 'bottom_nav_settings',
icon: Icon(UniconsLine.setting),
page: SettingsPage(),
),
const MenuItem(
code: 'bottom_nav_about',
icon: Icon(UniconsLine.info_circle),
page: AboutPage(),
),
];
static Widget getPageWidget(int pageIndex) {
return Menu.items.elementAt(pageIndex).page;
}
static List<BottomNavigationBarItem> getMenuItems() {
return Menu.items
.map((MenuItem item) => BottomNavigationBarItem(
icon: item.icon,
label: tr(item.code),
))
.toList();
}
static int itemsCount = Menu.items.length;
}
import 'package:flutter/material.dart';
/// Colors from Tailwind CSS (v3.0) - June 2022
///
/// https://tailwindcss.com/docs/customizing-colors
const int _primaryColor = 0xFF6366F1;
const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{
50: Color(0xFFEEF2FF), // indigo-50
100: Color(0xFFE0E7FF), // indigo-100
200: Color(0xFFC7D2FE), // indigo-200
300: Color(0xFFA5B4FC), // indigo-300
400: Color(0xFF818CF8), // indigo-400
500: Color(_primaryColor), // indigo-500
600: Color(0xFF4F46E5), // indigo-600
700: Color(0xFF4338CA), // indigo-700
800: Color(0xFF3730A3), // indigo-800
900: Color(0xFF312E81), // indigo-900
});
const int _textColor = 0xFF64748B;
const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{
50: Color(0xFFF8FAFC), // slate-50
100: Color(0xFFF1F5F9), // slate-100
200: Color(0xFFE2E8F0), // slate-200
300: Color(0xFFCBD5E1), // slate-300
400: Color(0xFF94A3B8), // slate-400
500: Color(_textColor), // slate-500
600: Color(0xFF475569), // slate-600
700: Color(0xFF334155), // slate-700
800: Color(0xFF1E293B), // slate-800
900: Color(0xFF0F172A), // slate-900
});
const Color errorColor = Color(0xFFDC2626); // red-600
final ColorScheme lightColorScheme = ColorScheme.light(
primary: primarySwatch.shade500,
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
background: textSwatch.shade200,
onBackground: textSwatch.shade500,
onSurface: textSwatch.shade500,
surface: textSwatch.shade50,
surfaceVariant: Colors.white,
shadow: textSwatch.shade900.withOpacity(.1),
);
final ColorScheme darkColorScheme = ColorScheme.dark(
primary: primarySwatch.shade500,
secondary: primarySwatch.shade500,
onSecondary: Colors.white,
error: errorColor,
background: const Color(0xFF171724),
onBackground: textSwatch.shade400,
onSurface: textSwatch.shade300,
surface: const Color(0xFF262630),
surfaceVariant: const Color(0xFF282832),
shadow: textSwatch.shade900.withOpacity(.2),
);
final ThemeData lightTheme = ThemeData(
colorScheme: lightColorScheme,
fontFamily: 'Nunito',
textTheme: TextTheme(
displayLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
displayMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
displaySmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
headlineLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
headlineMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
headlineSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
titleLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
titleMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
titleSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
bodyLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
bodyMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
bodySmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
labelLarge: TextStyle(
color: textSwatch.shade700,
fontFamily: 'Nunito',
),
labelMedium: TextStyle(
color: textSwatch.shade600,
fontFamily: 'Nunito',
),
labelSmall: TextStyle(
color: textSwatch.shade500,
fontFamily: 'Nunito',
),
),
);
final ThemeData darkTheme = lightTheme.copyWith(
colorScheme: darkColorScheme,
textTheme: TextTheme(
displayLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
displayMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
displaySmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
headlineLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
headlineMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
headlineSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
titleLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
titleMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
titleSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
bodyLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
bodyMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
bodySmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
labelLarge: TextStyle(
color: textSwatch.shade200,
fontFamily: 'Nunito',
),
labelMedium: TextStyle(
color: textSwatch.shade300,
fontFamily: 'Nunito',
),
labelSmall: TextStyle(
color: textSwatch.shade400,
fontFamily: 'Nunito',
),
),
);
final ThemeData appTheme = darkTheme;
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:wordguessing/config/menu.dart';
class BottomNavCubit extends HydratedCubit<int> {
BottomNavCubit() : super(0);
void updateIndex(int index) {
if (isIndexAllowed(index)) {
emit(index);
} else {
goToHomePage();
}
}
bool isIndexAllowed(int index) {
return (index >= 0) && (index < Menu.itemsCount);
}
void goToHomePage() => emit(0);
@override
int fromJson(Map<String, dynamic> json) {
return 0;
}
@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,
];
}
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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:provider/provider.dart';
import 'package:wordguessing/config/theme.dart';
import 'package:wordguessing/cubit/bottom_nav_cubit.dart';
import 'package:wordguessing/cubit/theme_cubit.dart';
import 'package:wordguessing/provider/data.dart';
import 'package:wordguessing/screens/game_pick_image.dart';
import 'package:wordguessing/screens/game_pick_word.dart';
import 'package:wordguessing/screens/home.dart';
import 'package:wordguessing/utils/tools.dart';
import 'package:wordguessing/ui/skeleton.dart';
void main() {
void main() async {
/// Initialize packages
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(const MyApp()));
await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory();
Hive.init(tmpDir.toString());
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: tmpDir,
);
runApp(
EasyLocalization(
path: 'assets/translations',
supportedLocales: const <Locale>[
Locale('en'),
Locale('fr'),
],
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
......@@ -19,31 +43,34 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()),
BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
],
child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) {
return ChangeNotifierProvider(
create: (BuildContext context) => Data(),
child: Consumer<Data>(builder: (context, data, child) {
child: Consumer<Data>(
builder: (context, data, child) {
return MaterialApp(
title: 'Jeux de mots et lettres',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Home(),
onGenerateRoute: (settings) {
switch (settings.name) {
case '/game-pick-word':
return MaterialPageRoute(builder: (context) => const GamePickWordPage());
case '/game-pick-image':
return MaterialPageRoute(
builder: (context) => const GamePickImagePage(),
);
home: const SkeletonScreen(),
default:
printlog("Unknown menu entry");
}
// Theme stuff
theme: lightTheme,
darkTheme: darkTheme,
themeMode: state.themeMode,
return null;
// Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
);
},
),
);
}),
);
......
......@@ -2,7 +2,11 @@ import 'package:flutter/foundation.dart';
import 'package:wordguessing/models/word.dart';
enum GameType { pickWord, pickImage }
class Data extends ChangeNotifier {
GameType? _gameType;
// Language
String _lang = '';
......@@ -18,6 +22,18 @@ class Data extends ChangeNotifier {
int _goodAnswers = 0;
int _wrongAnswers = 0;
GameType? get gameType => _gameType;
void updateGameType(GameType gameType) {
_gameType = gameType;
notifyListeners();
}
void resetGameType() {
_gameType = null;
notifyListeners();
}
String get lang => _lang;
void updateLang(String value) {
......@@ -55,6 +71,7 @@ class Data extends ChangeNotifier {
}
void resetGame() {
resetGameType();
updateLang('');
updateQuestionsCount(0);
updateGoodAnswers(0);
......
......@@ -3,13 +3,12 @@ import 'package:provider/provider.dart';
import 'package:wordguessing/provider/data.dart';
class Game extends StatelessWidget {
const Game({super.key});
class GameAbstract extends StatelessWidget {
const GameAbstract({super.key});
final int countWords = 4;
Future<void> startGame(Data myProvider, String lang) async {
myProvider.resetGame();
myProvider.updateLang(lang);
await nextWord(myProvider);
}
......@@ -137,27 +136,6 @@ class Game extends StatelessWidget {
Widget buildPage(BuildContext context) {
final Data myProvider = Provider.of<Data>(context);
final Scaffold pageContent = Scaffold(
appBar: AppBar(
elevation: 0,
actions: <Widget>[
IconButton(
icon: const Icon(Icons.loop),
onPressed: () => myProvider.resetGame(),
),
],
),
backgroundColor: Colors.blue,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: buildPageContent(myProvider),
),
),
);
return SizedBox.expand(
child: FittedBox(
fit: BoxFit.contain,
......@@ -165,7 +143,14 @@ class Game extends StatelessWidget {
child: SizedBox(
height: (MediaQuery.of(context).size.height),
width: (MediaQuery.of(context).size.width),
child: pageContent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: buildPageContent(myProvider),
),
),
),
),
);
......
......@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:wordguessing/models/word.dart';
import 'package:wordguessing/provider/data.dart';
import 'package:wordguessing/screens/game.dart';
import 'package:wordguessing/ui/games/abstract_game.dart';
import 'package:wordguessing/utils/random_pick_words.dart';
class GamePickImagePage extends Game {
class GamePickImagePage extends GameAbstract {
const GamePickImagePage({super.key});
@override
......
......@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:wordguessing/models/word.dart';
import 'package:wordguessing/provider/data.dart';
import 'package:wordguessing/screens/game.dart';
import 'package:wordguessing/ui/games/abstract_game.dart';
import 'package:wordguessing/utils/random_pick_words.dart';
class GamePickWordPage extends Game {
class GamePickWordPage extends GameAbstract {
const GamePickWordPage({super.key});
@override
......
......@@ -3,16 +3,14 @@ import 'package:provider/provider.dart';
import 'package:wordguessing/provider/data.dart';
class Home extends StatelessWidget {
const Home({super.key});
static const String id = 'home';
class GameSelector extends StatelessWidget {
const GameSelector({super.key});
@override
Widget build(BuildContext context) {
final Data myProvider = Provider.of<Data>(context);
Container buildMenuItemContainer(String code, Color color) {
Widget buildMenuItemContainer(String code, Color color) {
const double imageSize = 150;
final String imageAsset = 'assets/menu/$code.png';
......@@ -35,18 +33,21 @@ class Home extends StatelessWidget {
),
onTap: () {
myProvider.resetGame();
Navigator.pushNamed(
context,
'/$code',
);
switch (code) {
case 'game-pick-word':
myProvider.updateGameType(GameType.pickWord);
break;
case 'game-pick-image':
myProvider.updateGameType(GameType.pickImage);
break;
default:
}
},
),
);
}
return Scaffold(
backgroundColor: Colors.blue,
body: Center(
Widget content = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
......@@ -56,7 +57,8 @@ class Home extends StatelessWidget {
buildMenuItemContainer('game-pick-image', Colors.yellow),
],
),
),
);
return content;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment