diff --git a/android/gradle.properties b/android/gradle.properties index 1716fb7f8a2bf4d5084295416dc53574c3ad38db..588e695c30a170652ec0f6e7ae0648d5d3a099ae 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=1.2.28 -app.versionCode=34 +app.versionName=1.2.29 +app.versionCode=35 diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9 Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497 Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1 Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784 Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ diff --git a/assets/translations/en.json b/assets/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..42ca82760c02ee4dfb2a118e5147f15875ad76f7 --- /dev/null +++ b/assets/translations/en.json @@ -0,0 +1,16 @@ +{ + "app_name": "Categories", + + "bottom_nav_home": "Game", + "bottom_nav_settings": "Settings", + "bottom_nav_about": "About", + + "settings_title": "Settings", + "settings_label_timer_value":"Game turn allowed time", + + "about_title": "Informations", + "about_content": "Categories", + "about_version": "Version: {version}", + + "": "" +} diff --git a/assets/translations/fr.json b/assets/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..d953c429bb3b25ba086ce5f855f1931c22320866 --- /dev/null +++ b/assets/translations/fr.json @@ -0,0 +1,16 @@ +{ + "app_name": "Petit Bac", + + "bottom_nav_home": "Jeu", + "bottom_nav_settings": "Réglages", + "bottom_nav_about": "Infos", + + "settings_title": "Réglages", + "settings_label_timer_value":"Durée du tour de jeu", + + "about_title": "Informations", + "about_content": "Petit Bac.", + "about_version": "Version : {version}", + + "": "" +} diff --git a/fastlane/metadata/android/en-US/changelogs/35.txt b/fastlane/metadata/android/en-US/changelogs/35.txt new file mode 100644 index 0000000000000000000000000000000000000000..6e253a4fc4840823e9a2501baad2a1c3d9c59c5e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/35.txt @@ -0,0 +1 @@ +Improve game architecture / conception. diff --git a/fastlane/metadata/android/fr-FR/changelogs/35.txt b/fastlane/metadata/android/fr-FR/changelogs/35.txt new file mode 100644 index 0000000000000000000000000000000000000000..6b644ba4d7245c59acd73fcbeb8fc47e1e843daa --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/35.txt @@ -0,0 +1 @@ +Amélioration de l'architecture / conception du jeu. diff --git a/lib/config/default_settings.dart b/lib/config/default_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..fa6914950abd4edacadc6209c65cc769aceabf97 --- /dev/null +++ b/lib/config/default_settings.dart @@ -0,0 +1,12 @@ +class DefaultSettings { + static const List<int> allowedTimerValues = [ + 5, + 10, + 20, + 30, + 60, + 90, + ]; + + static const int defaultTimerValue = 10; +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart new file mode 100644 index 0000000000000000000000000000000000000000..4ba90b267d401a8c9ddfb8a611e88a7c0662f3c1 --- /dev/null +++ b/lib/config/menu.dart @@ -0,0 +1,54 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:petitbac/ui/screens/about_page.dart'; +import 'package:petitbac/ui/screens/game_page.dart'; +import 'package:petitbac/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; +} diff --git a/lib/config/theme.dart b/lib/config/theme.dart new file mode 100644 index 0000000000000000000000000000000000000000..be390348c7868e7c63387df13e13c46de43f8a23 --- /dev/null +++ b/lib/config/theme.dart @@ -0,0 +1,196 @@ +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; diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..ac26ae9efa1973afc7bbc10f413d59f0fa2155d8 --- /dev/null +++ b/lib/cubit/bottom_nav_cubit.dart @@ -0,0 +1,31 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:petitbac/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}; + } +} diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..bfe94d2013ffa3bbf089f6a836373856c3fadb6c --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:petitbac/models/game/game.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() : super(const GameState()); + + void getData(GameState gameState) { + emit(gameState); + } + + void updateGameState(Game gameData) { + emit(GameState(game: gameData)); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Game game = json['game'] as Game; + + return GameState( + game: game, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'game': state.game?.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..3e4d0d092cf5da751fd66f72dedea0501878acbd --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,15 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + this.game, + }); + + final Game? game; + + @override + List<Object?> get props => <Object?>[ + game, + ]; +} diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..d105a3fd9046cfcd57ad9cd9cda694698cb6f94a --- /dev/null +++ b/lib/cubit/settings_cubit.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:petitbac/config/default_settings.dart'; + +part 'settings_state.dart'; + +class SettingsCubit extends HydratedCubit<SettingsState> { + SettingsCubit() : super(const SettingsState()); + + Object getSetting(String key, [String? defaultValue]) { + if (state.values.keys.contains(key)) { + return state.values[key] ?? defaultValue ?? ''; + } + + return defaultValue ?? ''; + } + + int getTimerValue() { + return state.timerValue ?? DefaultSettings.defaultTimerValue; + } + + void setValues({ + int? timerValue, + }) { + emit(SettingsState( + timerValue: timerValue ?? state.timerValue, + )); + } + + @override + SettingsState? fromJson(Map<String, dynamic> json) { + int timerValue = json['timerValue'] as int; + + return SettingsState( + timerValue: timerValue, + ); + } + + @override + Map<String, dynamic>? toJson(SettingsState state) { + return <String, dynamic>{ + 'timerValue': state.timerValue ?? DefaultSettings.defaultTimerValue, + }; + } +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..6048256f247aeb898ba1b3b10ca2daae3468a7c9 --- /dev/null +++ b/lib/cubit/settings_state.dart @@ -0,0 +1,19 @@ +part of 'settings_cubit.dart'; + +@immutable +class SettingsState extends Equatable { + const SettingsState({ + this.timerValue, + }); + + final int? timerValue; + + @override + List<dynamic> get props => <dynamic>[ + timerValue, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'timerValue': timerValue, + }; +} diff --git a/lib/main.dart b/lib/main.dart index 099ea049c3496af9005fad712fec50da153392d2..b90609c0fa03bf7963ee98aabd965f253995dc05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,42 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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:flutter/services.dart'; +import 'package:petitbac/config/theme.dart'; +import 'package:petitbac/cubit/bottom_nav_cubit.dart'; +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/cubit/settings_cubit.dart'; import 'package:petitbac/provider/data.dart'; -import 'package:petitbac/screens/home.dart'; +import 'package:petitbac/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 { @@ -16,22 +44,27 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>( - builder: (context, data, child) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - primaryColor: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: const Home(), - routes: { - Home.id: (context) => const Home(), - }, - ); - }, + return MultiBlocProvider( + providers: [ + BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<GameCubit>(create: (context) => GameCubit()), + BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), + ], + child: ChangeNotifierProvider( + create: (BuildContext context) => Data(), + child: Consumer<Data>( + builder: (context, data, child) { + return MaterialApp( + title: 'Petit Bac', + theme: appTheme, + home: const SkeletonScreen(), + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ); + }, + ), ), ); } diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..ebe3d57f78a1ea5ed0b817ff40a1e293cde65ee7 --- /dev/null +++ b/lib/models/game/game.dart @@ -0,0 +1,38 @@ +import 'package:petitbac/utils/tools.dart'; + +class Game { + bool isRunning = false; + + Game({ + this.isRunning = false, + }); + + factory Game.createNull() { + return Game(); + } + + factory Game.createNew() { + return Game( + isRunning: true, + ); + } + + void stop() { + isRunning = false; + } + + @override + String toString() { + return 'Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'isRunning': isRunning, + }; + } + + void dump() { + printlog(toString()); + } +} diff --git a/lib/screens/home.dart b/lib/screens/home.dart deleted file mode 100644 index c54e9a612d5727d8b77746b2e7ce0077dfb5b410..0000000000000000000000000000000000000000 --- a/lib/screens/home.dart +++ /dev/null @@ -1,306 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:petitbac/provider/data.dart'; -import 'package:petitbac/utils/random_pick_category.dart'; -import 'package:petitbac/utils/random_pick_letter.dart'; - -class Home extends StatelessWidget { - const Home({super.key}); - - static const String id = 'home'; - - static Timer? _timer; - static int _countdownStart = 10; - - Future<void> startMiniGame(Data myProvider) async { - if (myProvider.countdown <= 0) { - pickCategory(myProvider); - pickLetter(myProvider); - startTimer(myProvider); - } - } - - Future<void> startTimer(Data myProvider) async { - const oneSec = Duration(seconds: 1); - if (_timer != null) { - dispose(); - } - _countdownStart = 10; - myProvider.updateCountdown(_countdownStart); - _timer = Timer.periodic( - oneSec, - (Timer timer) { - if (_countdownStart < 0) { - timer.cancel(); - } else { - _countdownStart--; - myProvider.updateCountdown(_countdownStart); - } - }, - ); - } - - void dispose() { - _timer?.cancel(); - } - - Future<void> pickCategory(Data myProvider) async { - myProvider.setSearchingCategory(true); - RandomPickCategory randomPickCategory; - int attempts = 0; - do { - randomPickCategory = RandomPickCategory(); - await randomPickCategory.init(); - if (!myProvider.isCategoryRecentlyPicked(randomPickCategory.category)) { - myProvider.updateCategory(randomPickCategory.category); - myProvider.setSearchingCategory(false); - break; - } - attempts++; - } while (attempts < 10); - } - - Future<void> pickLetter(Data myProvider) async { - myProvider.setSearchingLetter(true); - RandomPickLetter randomPickLetter; - int attempts = 0; - do { - randomPickLetter = RandomPickLetter(); - await randomPickLetter.init(); - if (!myProvider.isLetterRecentlyPicked(randomPickLetter.letter)) { - myProvider.updateLetter(randomPickLetter.letter); - myProvider.setSearchingLetter(false); - break; - } - attempts++; - } while (attempts < 10); - } - - Color darken(Color baseColor, {double amount = 0.2}) { - final hsl = HSLColor.fromColor(baseColor); - final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); - return hslDark.toColor(); - } - - Container mainLetterButtonContainer( - Data myProvider, - Color backgroundColor, - double borderWidth, - ) { - final Color borderColor = darken(backgroundColor); - - return Container( - margin: EdgeInsets.all(borderWidth), - padding: EdgeInsets.all(borderWidth), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(borderWidth), - border: Border.all( - color: borderColor, - width: borderWidth, - ), - ), - child: TextButton( - onPressed: () => pickLetter(myProvider), - child: Text( - myProvider.letter == '' ? "🔀" : myProvider.letter, - style: const TextStyle( - fontSize: 40, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - ), - ); - } - - Container previousLetterBlockContainer(Data myProvider, int position, bool displayed) { - const double spacingWidth = 2; - const double borderWidth = 3; - Color backgroundColor = Colors.grey; - Color borderColor = darken(backgroundColor); - Color fontColor = Colors.black; - - final String letter = myProvider.recentlyPickedLetter(position); - - if (letter == '' || displayed == false) { - backgroundColor = Colors.white; - borderColor = Colors.white; - fontColor = Colors.white; - } - - return Container( - margin: const EdgeInsets.all(spacingWidth), - padding: const EdgeInsets.all(spacingWidth), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(borderWidth), - border: Border.all( - color: borderColor, - width: borderWidth, - ), - ), - child: Text( - ' $letter ', - style: TextStyle( - fontSize: 35.0 - (7 * position), - fontWeight: FontWeight.w600, - color: fontColor, - ), - ), - ); - } - - Container _buildPickedLetterContainer( - Data myProvider, - Color backgroundColor, - double borderWidth, - ) { - const int previousLettersCountToShow = 3; - - final List<Widget> cells = []; - - // Add previous letters blocks - for (var i = 0; i < previousLettersCountToShow; i++) { - cells.add(TableCell( - verticalAlignment: TableCellVerticalAlignment.middle, - child: previousLetterBlockContainer(myProvider, previousLettersCountToShow - i, true), - )); - } - - // Add current letter block - cells.add(TableCell( - verticalAlignment: TableCellVerticalAlignment.middle, - child: mainLetterButtonContainer(myProvider, backgroundColor, borderWidth), - )); - - // Pad with empty blocks to keep symetrical layout - for (var i = 0; i < previousLettersCountToShow; i++) { - cells.add(TableCell( - verticalAlignment: TableCellVerticalAlignment.middle, - child: previousLetterBlockContainer(myProvider, i + 1, false), - )); - } - - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow(children: cells), - ], - ), - ); - } - - Widget _buildPickedCategoryContainer( - Data myProvider, - Color backgroundColor, - double borderWidth, - ) { - final Color borderColor = darken(backgroundColor); - - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: EdgeInsets.all(borderWidth), - padding: EdgeInsets.all(borderWidth), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(borderWidth), - border: Border.all( - color: borderColor, - width: borderWidth, - ), - ), - child: TextButton( - onPressed: () => pickCategory(myProvider), - child: Text( - myProvider.category == '' ? "🔀" : myProvider.category, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - ), - ), - ], - ); - } - - Widget _buildMiniGameContainer( - Data myProvider, - Color backgroundColor, - double borderWidth, - ) { - final Color borderColor = darken(backgroundColor); - - Color countDownColor = Colors.black; - if (myProvider.countdown == 0) { - countDownColor = Colors.red; - } - - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: EdgeInsets.all(borderWidth), - padding: EdgeInsets.all(borderWidth), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(borderWidth), - border: Border.all( - color: borderColor, - width: borderWidth, - ), - ), - child: TextButton( - onPressed: (myProvider.countdown >= 0) ? null : () => startMiniGame(myProvider), - child: Text( - (myProvider.countdown >= 0) ? myProvider.countdown.toString() : '🎲', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 50, - fontWeight: FontWeight.w600, - color: countDownColor, - ), - ), - ), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - const double borderWidth = 8; - - return Scaffold( - appBar: AppBar( - title: const Text('Petit bac'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - _buildPickedLetterContainer(myProvider, Colors.orange, borderWidth), - const SizedBox(height: 5), - _buildMiniGameContainer(myProvider, Colors.blue, borderWidth), - const SizedBox(height: 5), - _buildPickedCategoryContainer(myProvider, Colors.green, borderWidth), - ], - ), - ), - ); - } -} diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/about_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..dd5c1767d113be9f7f3ef7c9b37c66e77b1650de --- /dev/null +++ b/lib/ui/screens/about_page.dart @@ -0,0 +1,38 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:petitbac/ui/widgets/header_app.dart'; + +class AboutPage extends StatelessWidget { + const AboutPage({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + const SizedBox(height: 8), + const AppHeader(text: 'about_title'), + const Text('about_content').tr(), + FutureBuilder<PackageInfo>( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return const Text('about_version').tr( + namedArgs: { + 'version': snapshot.data!.version, + }, + ); + default: + return const SizedBox(); + } + }, + ), + ], + ); + } +} diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..125ea9568037cb27654abf48c21263f653899b30 --- /dev/null +++ b/lib/ui/screens/game_page.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +import 'package:petitbac/ui/widgets/mini_game.dart'; +import 'package:petitbac/ui/widgets/picked_category.dart'; +import 'package:petitbac/ui/widgets/picked_letter.dart'; + +class GamePage extends StatelessWidget { + const GamePage({super.key}); + + @override + Widget build(BuildContext context) { + const double borderWidth = 8; + + return const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + PickedLetter( + backgroundColor: Colors.orange, + borderWidth: borderWidth, + ), + SizedBox(height: 5), + MiniGame( + backgroundColor: Colors.blue, + borderWidth: borderWidth, + ), + SizedBox(height: 5), + PickedCategory( + backgroundColor: Colors.green, + borderWidth: borderWidth, + ), + ], + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/settings_page.dart new file mode 100644 index 0000000000000000000000000000000000000000..216fad9ec8786385142e21dc3aea08866e6b4d02 --- /dev/null +++ b/lib/ui/screens/settings_page.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'package:petitbac/ui/widgets/header_app.dart'; +import 'package:petitbac/ui/widgets/settings_form.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + SizedBox(height: 8), + AppHeader(text: 'settings_title'), + SizedBox(height: 8), + SettingsForm(), + ], + ); + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart new file mode 100644 index 0000000000000000000000000000000000000000..f5fc21f57e76940a071bdc1ad2abdc4369e2adff --- /dev/null +++ b/lib/ui/skeleton.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_swipe/flutter_swipe.dart'; + +import 'package:petitbac/config/menu.dart'; +import 'package:petitbac/cubit/bottom_nav_cubit.dart'; +import 'package:petitbac/ui/widgets/app_bar.dart'; +import 'package:petitbac/ui/widgets/bottom_nav_bar.dart'; + +class SkeletonScreen extends StatefulWidget { + const SkeletonScreen({super.key}); + + @override + State<SkeletonScreen> createState() => _SkeletonScreenState(); +} + +class _SkeletonScreenState extends State<SkeletonScreen> { + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: false, + appBar: const StandardAppBar(), + body: Swiper( + itemCount: Menu.itemsCount, + itemBuilder: (BuildContext context, int index) { + return Menu.getPageWidget(index); + }, + pagination: SwiperPagination( + margin: const EdgeInsets.all(0), + builder: SwiperCustomPagination( + builder: (BuildContext context, SwiperPluginConfig config) { + return BottomNavBar(swipeController: config.controller); + }, + ), + ), + onIndexChanged: (newPageIndex) { + BlocProvider.of<BottomNavCubit>(context).updateIndex(newPageIndex); + }, + outer: true, + loop: false, + ), + backgroundColor: Theme.of(context).colorScheme.background, + ); + } +} diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..2e76fc6cb263f14b38699cc41299cebc31d50a03 --- /dev/null +++ b/lib/ui/widgets/app_bar.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +import 'package:petitbac/ui/widgets/header_app.dart'; + +class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { + const StandardAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + title: const AppHeader(text: 'app_name'), + actions: const [ + // + ], + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..d9f18eea081cb830b659f6d582be1e6255ef74c2 --- /dev/null +++ b/lib/ui/widgets/bottom_nav_bar.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_swipe/flutter_swipe.dart'; + +import 'package:petitbac/config/menu.dart'; +import 'package:petitbac/cubit/bottom_nav_cubit.dart'; + +class BottomNavBar extends StatelessWidget { + const BottomNavBar({super.key, required this.swipeController}); + + final SwiperController swipeController; + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.all(0), + elevation: 4, + shadowColor: Theme.of(context).colorScheme.shadow, + color: Theme.of(context).colorScheme.surfaceVariant, + shape: const ContinuousRectangleBorder(), + child: BlocBuilder<BottomNavCubit, int>( + builder: (BuildContext context, int state) { + return BottomNavigationBar( + currentIndex: state, + onTap: (int index) { + context.read<BottomNavCubit>().updateIndex(index); + swipeController.move(index); + }, + type: BottomNavigationBarType.fixed, + elevation: 0, + backgroundColor: Colors.transparent, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, + items: Menu.getMenuItems(), + ); + }, + ), + ); + } +} diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/header_app.dart new file mode 100644 index 0000000000000000000000000000000000000000..bf54b77375fbd0260f876f2885d0572b71715383 --- /dev/null +++ b/lib/ui/widgets/header_app.dart @@ -0,0 +1,23 @@ +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 Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/mini_game.dart b/lib/ui/widgets/mini_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..3734c5f92597dc44c6ded3ab416437bf5c915a4c --- /dev/null +++ b/lib/ui/widgets/mini_game.dart @@ -0,0 +1,107 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:petitbac/config/default_settings.dart'; +import 'package:petitbac/cubit/settings_cubit.dart'; +import 'package:provider/provider.dart'; + +import 'package:petitbac/utils/color_extensions.dart'; +import 'package:petitbac/utils/game_utils.dart'; +import 'package:petitbac/provider/data.dart'; + +class MiniGame extends StatelessWidget { + const MiniGame({ + super.key, + required this.backgroundColor, + required this.borderWidth, + }); + + final Color backgroundColor; + final double borderWidth; + + static Timer? _timer; + static int _countdownStart = 0; + + void dispose() { + _timer?.cancel(); + } + + Future<void> startTimer(Data myProvider, int timerDuration) async { + const oneSec = Duration(seconds: 1); + if (_timer != null) { + dispose(); + } + _countdownStart = timerDuration; + myProvider.updateCountdown(_countdownStart); + _timer = Timer.periodic( + oneSec, + (Timer timer) { + if (_countdownStart < 0) { + timer.cancel(); + } else { + _countdownStart--; + myProvider.updateCountdown(_countdownStart); + } + }, + ); + } + + Future<void> startMiniGame(Data myProvider, int timerDuration) async { + if (myProvider.countdown <= 0) { + GameUtils.pickCategory(myProvider); + GameUtils.pickLetter(myProvider); + startTimer(myProvider, timerDuration); + } + } + + @override + Widget build(BuildContext context) { + final Data myProvider = Provider.of<Data>(context); + + final Color borderColor = backgroundColor.darken(); + + Color countDownColor = Colors.black; + if (myProvider.countdown == 0) { + countDownColor = Colors.red; + } + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(borderWidth), + padding: EdgeInsets.all(borderWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: BlocBuilder<SettingsCubit, SettingsState>( + builder: (context, settingsSate) { + return TextButton( + onPressed: (myProvider.countdown >= 0) + ? null + : () => startMiniGame(myProvider, + settingsSate.timerValue ?? DefaultSettings.defaultTimerValue), + child: Text( + (myProvider.countdown >= 0) ? myProvider.countdown.toString() : '🎲', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 50, + fontWeight: FontWeight.w600, + color: countDownColor, + ), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/picked_category.dart b/lib/ui/widgets/picked_category.dart new file mode 100644 index 0000000000000000000000000000000000000000..de5781b4cf3cfa2ded6ad0064c6c35f1eb0ce93b --- /dev/null +++ b/lib/ui/widgets/picked_category.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:petitbac/utils/color_extensions.dart'; +import 'package:petitbac/utils/game_utils.dart'; +import 'package:petitbac/provider/data.dart'; + +class PickedCategory extends StatelessWidget { + const PickedCategory({ + super.key, + required this.backgroundColor, + required this.borderWidth, + }); + + final Color backgroundColor; + final double borderWidth; + + @override + Widget build(BuildContext context) { + final Data myProvider = Provider.of<Data>(context); + + final Color borderColor = backgroundColor.darken(); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(borderWidth), + padding: EdgeInsets.all(borderWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: TextButton( + onPressed: () => GameUtils.pickCategory(myProvider), + child: Text( + myProvider.category == '' ? "🔀" : myProvider.category, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 30, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/ui/widgets/picked_letter.dart b/lib/ui/widgets/picked_letter.dart new file mode 100644 index 0000000000000000000000000000000000000000..0c0e009301cbea545baf3762a1b5813990215c36 --- /dev/null +++ b/lib/ui/widgets/picked_letter.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'package:petitbac/ui/widgets/previous_letter.dart'; +import 'package:petitbac/utils/color_extensions.dart'; +import 'package:petitbac/utils/game_utils.dart'; +import 'package:petitbac/provider/data.dart'; + +class PickedLetter extends StatelessWidget { + const PickedLetter({ + super.key, + required this.backgroundColor, + required this.borderWidth, + }); + + final Color backgroundColor; + final double borderWidth; + + Container mainLetterButtonContainer( + Data myProvider, + Color backgroundColor, + double borderWidth, + ) { + final Color borderColor = backgroundColor.darken(); + + return Container( + margin: EdgeInsets.all(borderWidth), + padding: EdgeInsets.all(borderWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: TextButton( + onPressed: () => GameUtils.pickLetter(myProvider), + child: Text( + myProvider.letter == '' ? "🔀" : myProvider.letter, + style: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final Data myProvider = Provider.of<Data>(context); + + const int previousLettersCountToShow = 3; + + final List<Widget> cells = []; + + // Add previous letters blocks + for (var i = 0; i < previousLettersCountToShow; i++) { + final int position = previousLettersCountToShow - i; + cells.add(TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: PreviousLetter( + letter: myProvider.recentlyPickedLetter(position), + position: position, + displayed: true, + ), + )); + } + + // Add current letter block + cells.add(TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: mainLetterButtonContainer(myProvider, backgroundColor, borderWidth), + )); + + // Pad with empty blocks to keep symetrical layout + for (var i = 0; i < previousLettersCountToShow; i++) { + cells.add(TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: PreviousLetter( + letter: myProvider.recentlyPickedLetter(i + 1), + position: i + 1, + displayed: false, + ), + )); + } + + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + TableRow(children: cells), + ], + ), + ); + } +} diff --git a/lib/ui/widgets/previous_letter.dart b/lib/ui/widgets/previous_letter.dart new file mode 100644 index 0000000000000000000000000000000000000000..558f27ccad159e8636aa5d1db189f65b33a9389f --- /dev/null +++ b/lib/ui/widgets/previous_letter.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import 'package:petitbac/utils/color_extensions.dart'; + +class PreviousLetter extends StatelessWidget { + const PreviousLetter({ + super.key, + required this.letter, + required this.position, + required this.displayed, + }); + + final String letter; + final int position; + final bool displayed; + + @override + Widget build(BuildContext context) { + const double spacingWidth = 2; + const double borderWidth = 3; + Color backgroundColor = Colors.grey; + Color borderColor = backgroundColor.darken(); + Color fontColor = Colors.black; + + if (letter == '' || displayed == false) { + backgroundColor = Theme.of(context).colorScheme.background; + borderColor = Theme.of(context).colorScheme.background; + fontColor = Theme.of(context).colorScheme.background; + } + + return Container( + margin: const EdgeInsets.all(spacingWidth), + padding: const EdgeInsets.all(spacingWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: Text( + ' $letter ', + style: TextStyle( + fontSize: 35.0 - (7 * position), + fontWeight: FontWeight.w600, + color: fontColor, + ), + ), + ); + } +} diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart new file mode 100644 index 0000000000000000000000000000000000000000..431c9f1b6ae126d7a1699c0956a22216d31783e6 --- /dev/null +++ b/lib/ui/widgets/settings_form.dart @@ -0,0 +1,77 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/config/default_settings.dart'; +import 'package:petitbac/cubit/settings_cubit.dart'; + +class SettingsForm extends StatefulWidget { + const SettingsForm({super.key}); + + @override + State<SettingsForm> createState() => _SettingsFormState(); +} + +class _SettingsFormState extends State<SettingsForm> { + int timerValue = DefaultSettings.defaultTimerValue; + + List<bool> _selectedTimerValue = []; + + @override + void dispose() { + super.dispose(); + } + + @override + void didChangeDependencies() { + SettingsCubit settings = BlocProvider.of<SettingsCubit>(context); + + timerValue = settings.getTimerValue(); + + _selectedTimerValue = + DefaultSettings.allowedTimerValues.map((e) => (e == timerValue)).toList(); + + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + void saveSettings() { + BlocProvider.of<SettingsCubit>(context).setValues( + timerValue: timerValue, + ); + } + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + // Timer value + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text('settings_label_timer_value').tr(), + ToggleButtons( + onPressed: (int index) { + setState(() { + timerValue = DefaultSettings.allowedTimerValues[index]; + for (int i = 0; i < _selectedTimerValue.length; i++) { + _selectedTimerValue[i] = i == index; + } + }); + saveSettings(); + }, + borderRadius: const BorderRadius.all(Radius.circular(8)), + constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0), + isSelected: _selectedTimerValue, + children: + DefaultSettings.allowedTimerValues.map((e) => Text(e.toString())).toList(), + ), + ], + ), + ], + ); + } +} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart new file mode 100644 index 0000000000000000000000000000000000000000..e206a93dfcfe878f9a96c1291f824b2824e6c3ac --- /dev/null +++ b/lib/utils/game_utils.dart @@ -0,0 +1,37 @@ +import 'package:petitbac/provider/data.dart'; +import 'package:petitbac/utils/random_pick_category.dart'; +import 'package:petitbac/utils/random_pick_letter.dart'; + +class GameUtils { + static Future<void> pickLetter(Data myProvider) async { + myProvider.setSearchingLetter(true); + RandomPickLetter randomPickLetter; + int attempts = 0; + do { + randomPickLetter = RandomPickLetter(); + await randomPickLetter.init(); + if (!myProvider.isLetterRecentlyPicked(randomPickLetter.letter)) { + myProvider.updateLetter(randomPickLetter.letter); + myProvider.setSearchingLetter(false); + break; + } + attempts++; + } while (attempts < 10); + } + + static Future<void> pickCategory(Data myProvider) async { + myProvider.setSearchingCategory(true); + RandomPickCategory randomPickCategory; + int attempts = 0; + do { + randomPickCategory = RandomPickCategory(); + await randomPickCategory.init(); + if (!myProvider.isCategoryRecentlyPicked(randomPickCategory.category)) { + myProvider.updateCategory(randomPickCategory.category); + myProvider.setSearchingCategory(false); + break; + } + attempts++; + } while (attempts < 10); + } +} diff --git a/pubspec.lock b/pubspec.lock index b15ebfbb8fc4b11d61a5733db2b95e116d18c681..53bc85dec4bae56188e56f74edcd0c91e60c1f18 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + url: "https://pub.dev" + source: hosted + version: "8.1.3" characters: dependency: transitive description: @@ -9,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" collection: dependency: transitive description: @@ -17,11 +49,67 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + url: "https://pub.dev" + source: hosted + version: "3.0.5" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + url: "https://pub.dev" + source: hosted + version: "8.1.4" flutter_lints: dependency: "direct dev" description: @@ -30,6 +118,64 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_swipe: + dependency: "direct main" + description: + name: flutter_swipe + sha256: dc6541bac3a0545ce15a3fa15913f6250532062960bf6b0ad4562d02f14a8545 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + http: + dependency: transitive + description: + name: http + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + url: "https://pub.dev" + source: hosted + version: "1.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + hydrated_bloc: + dependency: "direct main" + description: + name: hydrated_bloc + sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d" + url: "https://pub.dev" + source: hosted + version: "9.1.4" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" lints: dependency: transitive description: @@ -62,6 +208,94 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + path: + dependency: "direct main" + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: "direct main" description: @@ -70,11 +304,115 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + unicons: + dependency: "direct main" + description: + name: unicons + sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670 + url: "https://pub.dev" + source: hosted + version: "2.1.1" vector_math: dependency: transitive description: @@ -83,6 +421,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + url: "https://pub.dev" + source: hosted + version: "0.4.2" + win32: + dependency: transitive + description: + name: win32 + sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" + url: "https://pub.dev" + source: hosted + version: "5.3.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=1.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index f8880cc164add0ffd02269a5b56e37c92b9b988b..540b2ae38963714d9183c5d11d3eff88b5c64c8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,8 @@ name: petitbac description: A PetitBac game application. + publish_to: 'none' -version: 1.2.28+34 +version: 1.2.29+35 environment: sdk: '^3.0.0' @@ -9,12 +10,37 @@ environment: dependencies: flutter: sdk: flutter + + easy_localization: ^3.0.1 + equatable: ^2.0.5 + flutter_bloc: ^8.1.1 + flutter_swipe: ^1.0.1 + hive: ^2.2.3 + hydrated_bloc: ^9.0.0 + package_info_plus: ^5.0.1 + path: ^1.9.0 + path_provider: ^2.0.11 provider: ^6.0.5 + unicons: ^2.1.1 dev_dependencies: flutter_lints: ^3.0.1 flutter: - uses-material-design: true + uses-material-design: false assets: - assets/files/ + - assets/translations/ + + fonts: + - family: Nunito + fonts: + - asset: assets/fonts/Nunito-Bold.ttf + weight: 700 + - asset: assets/fonts/Nunito-Medium.ttf + weight: 500 + - asset: assets/fonts/Nunito-Regular.ttf + weight: 400 + - asset: assets/fonts/Nunito-Light.ttf + weight: 300 +