diff --git a/android/app/build.gradle b/android/app/build.gradle index c5483a3d5d4fd7481451c7bec063b2d88b258e69..5919a87f39f30c3b088c9c2b8c02bb835f7ff714 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 namespace "org.benoitharrault.petitbac" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index b33cdc8cb304c3e2e8e24f97f7e1fff87cf93379..000015d7277e11bcd5b31cb63f53b2fb406e6661 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.30 -app.versionCode=36 +app.versionName=1.2.31 +app.versionCode=37 diff --git a/assets/files/categories-fr.json b/assets/files/categories-fr.json deleted file mode 100644 index 4205a8c64d35933845353eff75494cb607a6f56d..0000000000000000000000000000000000000000 --- a/assets/files/categories-fr.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "categories": [ - "Pays", - "Prénom fille", - "Prénom garçon", - "Animal", - "Métier", - "Villes", - "Dessin animé", - "Film", - "Auteur de littérature", - "Acteur ou actrice", - "Chanteur ou chanteuse", - "Chose ou objet", - "Fruit ou légume", - "Couleur", - "Marque", - "Moyen de transport", - "Outil", - "Capitale", - "Instrument de musique", - "Boisson", - "Fleur", - "Plat", - "Personnage historique", - "Vêtement", - - "Minéral ou pierre précieuse", - "Étoile, planète ou constellation", - "Fleuve, cours d'eau ou océan", - "Partie du corps humain", - "Oiseau", - "Poisson", - "Qualité ou défaut", - "Arbre", - "Bande dessinée", - "Département français", - "Insecte", - "Dessert", - "Mammifère", - "Épice", - "Héros de mythologie", - "Héros fictif", - "Fromage", - "Jeu", - "Élément de véhicules", - "Site internet", - - "Mot de plus de 8 lettres", - "Cadeau de Noël", - "Mot en anglais", - "Mot en espagnol", - "Métier dont rêvent les enfants", - "Chose qui se trouve dans une voiture", - "Chose qui se trouve dans un camping", - "Chose qui se trouve dans un cartable", - "Chose qui se trouve dans une maison", - "Chose qui se trouve dans une forêt", - "Chose qui se trouve dans la mer", - "Ville française", - - "Qui sent mauvais", - "Qui fait plaisir", - "Mauvais pour la santé", - "Mauvais pour l'environement" - ] -} diff --git a/assets/icons/button_back.png b/assets/icons/button_back.png new file mode 100644 index 0000000000000000000000000000000000000000..cc48ffb1dbb653d9a996f139dfbe02969724bfa5 Binary files /dev/null and b/assets/icons/button_back.png differ diff --git a/assets/icons/button_start.png b/assets/icons/button_start.png new file mode 100644 index 0000000000000000000000000000000000000000..6845e2f5c21598ab61f1684d2075aeec0334bf23 Binary files /dev/null and b/assets/icons/button_start.png differ diff --git a/assets/icons/placeholder.png b/assets/icons/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..814df31be6ddc4275ebe4490c79365578dbef1f0 Binary files /dev/null and b/assets/icons/placeholder.png differ diff --git a/fastlane/metadata/android/en-US/changelogs/37.txt b/fastlane/metadata/android/en-US/changelogs/37.txt new file mode 100644 index 0000000000000000000000000000000000000000..9dc5d6607e58b7f51562cda67edeb8054845a6ef --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/37.txt @@ -0,0 +1 @@ +Improve game architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/37.txt b/fastlane/metadata/android/fr-FR/changelogs/37.txt new file mode 100644 index 0000000000000000000000000000000000000000..33a094d46216a48c3915aa0fd4adba69553fb580 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/37.txt @@ -0,0 +1 @@ +Amélioration globale de la conception du jeu. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh new file mode 100755 index 0000000000000000000000000000000000000000..7368dc4543ff5b92568d683f3d0fb144364bbe04 --- /dev/null +++ b/icons/build_game_icons.sh @@ -0,0 +1,78 @@ +#! /bin/bash + +# Check dependencies +command -v inkscape >/dev/null 2>&1 || { echo >&2 "I require inkscape but it's not installed. Aborting."; exit 1; } +command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not installed. Aborting."; exit 1; } +command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +BASE_DIR="$(dirname "${CURRENT_DIR}")" +ASSETS_DIR="${BASE_DIR}/assets" + +OPTIPNG_OPTIONS="-preserve -quiet -o7" +ICON_SIZE=192 + +####################################################### + +# Game images +AVAILABLE_GAME_IMAGES=" + button_back + button_start + placeholder +" + +####################################################### + +# optimize svg +function optimize_svg() { + SOURCE="$1" + + cp ${SOURCE} ${SOURCE}.tmp + scour \ + --remove-descriptive-elements \ + --enable-id-stripping \ + --enable-viewboxing \ + --enable-comment-stripping \ + --nindent=4 \ + --quiet \ + -i ${SOURCE}.tmp \ + -o ${SOURCE} + rm ${SOURCE}.tmp +} + +# build icons +function build_icon() { + SOURCE="$1" + TARGET="$2" + + echo "Building ${TARGET}" + + if [ ! -f "${SOURCE}" ]; then + echo "Missing file: ${SOURCE}" + exit 1 + fi + + optimize_svg "${SOURCE}" + + inkscape \ + --export-width=${ICON_SIZE} \ + --export-height=${ICON_SIZE} \ + --export-filename=${TARGET} \ + ${SOURCE} + + optipng ${OPTIPNG_OPTIONS} ${TARGET} +} + +####################################################### + +# Create output folders +mkdir -p ${ASSETS_DIR}/icons + +# Delete existing generated images +find ${ASSETS_DIR}/icons -type f -name "*.png" -delete + +# build game images +for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} +do + build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png +done diff --git a/icons/build_icons.sh b/icons/build_icons.sh deleted file mode 100755 index fefc393e2f601cd671938068d23247d6bfb1682b..0000000000000000000000000000000000000000 --- a/icons/build_icons.sh +++ /dev/null @@ -1,48 +0,0 @@ -#! /bin/bash - -# Check dependencies -command -v inkscape >/dev/null 2>&1 || { echo >&2 "I require inkscape but it's not installed. Aborting."; exit 1; } -command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not installed. Aborting."; exit 1; } -command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; } -command -v convert >/dev/null 2>&1 || { echo >&2 "I require convert (imagemagick) but it's not installed. Aborting."; exit 1; } - -CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" - -SOURCE="${CURRENT_DIR}/icon.svg" -OPTIPNG_OPTIONS="-preserve -quiet -o7" - -# optimize svg -cp ${SOURCE} ${SOURCE}.tmp -scour \ - --remove-descriptive-elements \ - --enable-id-stripping \ - --enable-viewboxing \ - --enable-comment-stripping \ - --nindent=4 \ - -i ${SOURCE}.tmp \ - -o ${SOURCE} -rm ${SOURCE}.tmp - -# build icons -function build_icon() { - ICON_SIZE="$1" - TARGET="$2" - - TARGET_PNG="${TARGET}.png" - - inkscape \ - --export-width=${ICON_SIZE} \ - --export-height=${ICON_SIZE} \ - --export-filename=${TARGET_PNG} \ - ${SOURCE} - - optipng ${OPTIPNG_OPTIONS} ${TARGET_PNG} -} - - -build_icon 72 ${BASE_DIR}/android/app/src/main/res/mipmap-hdpi/ic_launcher -build_icon 48 ${BASE_DIR}/android/app/src/main/res/mipmap-mdpi/ic_launcher -build_icon 96 ${BASE_DIR}/android/app/src/main/res/mipmap-xhdpi/ic_launcher -build_icon 144 ${BASE_DIR}/android/app/src/main/res/mipmap-xxhdpi/ic_launcher -build_icon 192 ${BASE_DIR}/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher diff --git a/icons/button_back.svg b/icons/button_back.svg new file mode 100644 index 0000000000000000000000000000000000000000..2622a578dba53ce582afabfc587c2a85a1fb6eaa --- /dev/null +++ b/icons/button_back.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#e41578" stroke="#fff" stroke-width=".238"/><path d="m59.387 71.362c1.1248 1.1302 4.0012 1.1302 4.0012 0v-45.921c0-1.1316-2.8832-1.1316-4.0121 0l-37.693 20.918c-1.1289 1.1248-1.1479 2.9551-0.02171 4.084z" fill="#fefeff" stroke="#930e4e" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m57.857 68.048c0.96243 0.96706 3.4236 0.96706 3.4236 0v-39.292c0-0.96825-2.467-0.96825-3.4329 0l-32.252 17.898c-0.96594 0.96243-0.9822 2.5285-0.01858 3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg> diff --git a/icons/button_start.svg b/icons/button_start.svg new file mode 100644 index 0000000000000000000000000000000000000000..e9d49d2172b9a0305db82779971e3c1e12f34a70 --- /dev/null +++ b/icons/button_start.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m34.852 25.44c-1.1248-1.1302-4.0012-1.1302-4.0012 0v45.921c0 1.1316 2.8832 1.1316 4.0121 0l37.693-20.918c1.1289-1.1248 1.1479-2.9551 0.02171-4.084z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m36.382 28.754c-0.96243-0.96706-3.4236-0.96706-3.4236 0v39.292c0 0.96825 2.467 0.96825 3.4329 0l32.252-17.898c0.96594-0.96243 0.9822-2.5285 0.01858-3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg> diff --git a/icons/placeholder.svg b/icons/placeholder.svg new file mode 100644 index 0000000000000000000000000000000000000000..23ace81fbb82a8409cc0710c0f7bddd6381f7256 --- /dev/null +++ b/icons/placeholder.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"/> diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..a1556efb7d05ba21cf3e365a3a067c8c66dd9428 --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,51 @@ +import 'package:petitbac/utils/tools.dart'; + +class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeItemsCount = 'itemsCount'; + static const String parameterCodeTimerValue = 'timerValue'; + static const List<String> availableParameters = [ + parameterCodeItemsCount, + parameterCodeTimerValue, + ]; + + // items count: available values + static const int itemsCountValueNoLimit = 0; + static const int itemsCountValueShort = 5; + static const int itemsCountValueMedium = 10; + static const int itemsCountValueLong = 20; + static const List<int> allowedItemsCountValues = [ + itemsCountValueNoLimit, + itemsCountValueShort, + itemsCountValueMedium, + itemsCountValueLong, + ]; + // items count: default value + static const int defaultItemsCountValue = itemsCountValueMedium; + + // timer value: available values + static const int timerValueNoTimer = 0; + static const int timerValueLow = 10; + static const int timerValueMedium = 30; + static const int timerValueHigh = 60; + static const List<int> allowedTimerValues = [ + timerValueNoTimer, + timerValueLow, + timerValueMedium, + timerValueHigh, + ]; + // timer value: default value + static const int defaultTimerValue = timerValueMedium; + + static List<int> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case 'itemsCount': + return DefaultGameSettings.allowedItemsCountValues; + case 'timerValue': + return DefaultGameSettings.allowedTimerValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..363c2a181a8a2c4799f4235145e072f7cae8153d --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,10 @@ +import 'package:petitbac/utils/tools.dart'; + +class DefaultGlobalSettings { + static const List<String> availableParameters = []; + + static List<int> getAvailableValues(String parameterCode) { + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/default_settings.dart b/lib/config/default_settings.dart deleted file mode 100644 index fa6914950abd4edacadc6209c65cc769aceabf97..0000000000000000000000000000000000000000 --- a/lib/config/default_settings.dart +++ /dev/null @@ -1,12 +0,0 @@ -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 index 4ba90b267d401a8c9ddfb8a611e88a7c0662f3c1..d875b6bb588512633fe1f389f17bec8fb35db1e4 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,10 +1,9 @@ -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'; +import 'package:petitbac/ui/screens/page_about.dart'; +import 'package:petitbac/ui/screens/page_game.dart'; +import 'package:petitbac/ui/screens/page_settings.dart'; class MenuItem { final String code; @@ -19,35 +18,39 @@ class MenuItem { } 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 const indexGame = 0; + static const menuItemGame = MenuItem( + code: 'bottom_nav_game', + icon: Icon(UniconsLine.home), + page: PageGame(), + ); - static Widget getPageWidget(int pageIndex) { - return Menu.items.elementAt(pageIndex).page; + static const indexSettings = 1; + static const menuItemSettings = MenuItem( + code: 'bottom_nav_settings', + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + code: 'bottom_nav_about', + icon: Icon(UniconsLine.info_circle), + page: PageAbout(), + ); + + static Map<int, MenuItem> items = { + indexGame: menuItemGame, + indexSettings: menuItemSettings, + indexAbout: menuItemAbout, + }; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); } - static List<BottomNavigationBarItem> getMenuItems() { - return Menu.items - .map((MenuItem item) => BottomNavigationBarItem( - icon: item.icon, - label: tr(item.code), - )) - .toList(); + static Widget getPageWidget(int pageIndex) { + return items[pageIndex]?.page ?? menuItemGame.page; } static int itemsCount = Menu.items.length; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart index bfe94d2013ffa3bbf089f6a836373856c3fadb6c..d310d36a3d885a2d6299b55f755db9a59de02b8a 100644 --- a/lib/cubit/game_cubit.dart +++ b/lib/cubit/game_cubit.dart @@ -1,35 +1,148 @@ +import 'dart:async'; +import 'dart:math'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:petitbac/models/game/game.dart'; +import 'package:petitbac/data/fetch_data_helper.dart'; +import 'package:petitbac/models/data/game_item.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/models/settings_game.dart'; +import 'package:petitbac/models/settings_global.dart'; part 'game_state.dart'; class GameCubit extends HydratedCubit<GameState> { - GameCubit() : super(const GameState()); + GameCubit() + : super(GameState( + currentGame: Game.createNull(), + )); + + void updateState(Game game) { + emit(GameState( + currentGame: game, + )); + } + + void refresh() { + final Game game = Game( + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + items: state.currentGame.items, + isRunning: state.currentGame.isRunning, + isFinished: state.currentGame.isFinished, + countdown: state.currentGame.countdown, + position: state.currentGame.position, + ); + // game.dump(); - void getData(GameState gameState) { - emit(gameState); + updateState(game); } - void updateGameState(Game gameData) { - emit(GameState(game: gameData)); + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + Game newGame = Game.createNew( + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + startTimer(); + } + + void quitGame() { + state.currentGame.isRunning = false; + refresh(); + } + + void next() { + if (state.currentGame.position < (state.currentGame.gameSettings.itemsCount - 1)) { + state.currentGame.position++; + startTimer(); + } else { + state.currentGame.isFinished = true; + } + refresh(); + } + + void startTimer() { + int timerValue = state.currentGame.gameSettings.timerValue; + + if (timerValue != 0) { + state.currentGame.countdown = timerValue; + + const Duration interval = Duration(seconds: 1); + Timer.periodic( + interval, + (Timer timer) { + if (state.currentGame.countdown != 0) { + state.currentGame.countdown = max(state.currentGame.countdown - 1, 0); + refresh(); + } + + if (state.currentGame.countdown == 0) { + timer.cancel(); + if (state.currentGame.position == + (state.currentGame.gameSettings.itemsCount - 1)) { + state.currentGame.isFinished = true; + } + } + + refresh(); + }, + ); + } else { + if (state.currentGame.position == (state.currentGame.gameSettings.itemsCount - 1)) { + state.currentGame.isFinished = true; + } + } + + refresh(); + } + + void pickNewItem() { + state.currentGame.items[state.currentGame.position] = FetchDataHelper().getRandomItem(); + refresh(); + } + + void pickNewCategory() { + GameItem newItem = GameItem( + letter: state.currentGame.letter, + category: FetchDataHelper().getRandomItem().category, + ); + + state.currentGame.items[state.currentGame.position] = newItem; + refresh(); + } + + void pickNewLetter() { + GameItem newItem = GameItem( + letter: FetchDataHelper().getRandomItem().letter, + category: state.currentGame.category, + ); + + state.currentGame.items[state.currentGame.position] = newItem; + refresh(); } @override GameState? fromJson(Map<String, dynamic> json) { - Game game = json['game'] as Game; + Game currentGame = json['currentGame'] as Game; return GameState( - game: game, + currentGame: currentGame, ); } @override Map<String, dynamic>? toJson(GameState state) { return <String, dynamic>{ - 'game': state.game?.toJson(), + 'currentGame': state.currentGame.toJson(), }; } } diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart index 3e4d0d092cf5da751fd66f72dedea0501878acbd..8581d72fe5cae6d6b78362f118af54090f443499 100644 --- a/lib/cubit/game_state.dart +++ b/lib/cubit/game_state.dart @@ -3,13 +3,13 @@ part of 'game_cubit.dart'; @immutable class GameState extends Equatable { const GameState({ - this.game, + required this.currentGame, }); - final Game? game; + final Game currentGame; @override List<Object?> get props => <Object?>[ - game, + currentGame, ]; } diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/nav_cubit.dart similarity index 51% rename from lib/cubit/bottom_nav_cubit.dart rename to lib/cubit/nav_cubit.dart index ac26ae9efa1973afc7bbc10f413d59f0fa2155d8..c01599b749bc89dc735fa778c6624624253c1c5b 100644 --- a/lib/cubit/bottom_nav_cubit.dart +++ b/lib/cubit/nav_cubit.dart @@ -2,26 +2,32 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:petitbac/config/menu.dart'; -class BottomNavCubit extends HydratedCubit<int> { - BottomNavCubit() : super(0); +class NavCubit extends HydratedCubit<int> { + NavCubit() : super(0); void updateIndex(int index) { - if (isIndexAllowed(index)) { + if (Menu.isIndexAllowed(index)) { emit(index); } else { - goToHomePage(); + goToGamePage(); } } - bool isIndexAllowed(int index) { - return (index >= 0) && (index < Menu.itemsCount); + void goToGamePage() { + emit(Menu.indexGame); } - void goToHomePage() => emit(0); + void goToSettingsPage() { + emit(Menu.indexSettings); + } + + void goToAboutPage() { + emit(Menu.indexAbout); + } @override int fromJson(Map<String, dynamic> json) { - return 0; + return Menu.indexGame; } @override diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart deleted file mode 100644 index d105a3fd9046cfcd57ad9cd9cda694698cb6f94a..0000000000000000000000000000000000000000 --- a/lib/cubit/settings_cubit.dart +++ /dev/null @@ -1,47 +0,0 @@ -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_game_cubit.dart b/lib/cubit/settings_game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..b31cae31818e04e1e456a6977e9647e0e7dc3fea --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,70 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:petitbac/models/settings_game.dart'; +import 'package:petitbac/utils/tools.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + int? itemsCount, + int? timerValue, + }) { + emit( + GameSettingsState( + settings: GameSettings( + itemsCount: itemsCount ?? state.settings.itemsCount, + timerValue: timerValue ?? state.settings.timerValue, + ), + ), + ); + } + + int getParameterValue(String code) { + switch (code) { + case 'itemsCount': + return GameSettings.getItemsCountValueFromUnsafe(state.settings.itemsCount); + case 'timerValue': + return GameSettings.getTimerValueFromUnsafe(state.settings.timerValue); + } + return 0; + } + + void setParameterValue(String code, int value) { + printlog('GameSettingsCubit.setParameterValue'); + printlog('code: $code / value: $value'); + + int itemsCount = code == 'itemsCount' ? value : getParameterValue('itemsCount'); + int timerValue = code == 'timerValue' ? value : getParameterValue('timerValue'); + + setValues( + itemsCount: itemsCount, + timerValue: timerValue, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + int itemsCount = json['itemsCount'] as int; + int timerValue = json['timerValue'] as int; + + return GameSettingsState( + settings: GameSettings( + itemsCount: itemsCount, + timerValue: timerValue, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + 'itemsCount': state.settings.itemsCount, + 'timerValue': state.settings.timerValue, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..b773dc69be12673b158e880e2d7e6e7bec465506 --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,19 @@ +part of 'settings_game_cubit.dart'; + +@immutable +class GameSettingsState extends Equatable { + const GameSettingsState({ + required this.settings, + }); + + final GameSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..43f64fe3934d1fd79ea5c53b156aaf22b26d2846 --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,44 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:petitbac/models/settings_global.dart'; +import 'package:petitbac/utils/tools.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues() { + emit( + GlobalSettingsState( + settings: GlobalSettings(), + ), + ); + } + + int getParameterValue(String code) { + switch (code) {} + return 0; + } + + void setParameterValue(String code, int value) { + printlog('GlobalSettingsCubit.setParameterValue'); + printlog('code: $code / value: $value'); + + setValues(); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + return GlobalSettingsState( + settings: GlobalSettings(), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{}; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e4fbdf707b4e805f2092d0ca6a68a2de1c957c6 --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,19 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart deleted file mode 100644 index 6048256f247aeb898ba1b3b10ca2daae3468a7c9..0000000000000000000000000000000000000000 --- a/lib/cubit/settings_state.dart +++ /dev/null @@ -1,19 +0,0 @@ -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/data/fetch_data_helper.dart b/lib/data/fetch_data_helper.dart new file mode 100644 index 0000000000000000000000000000000000000000..66931e9e939d088ce2a18d2e26b61479641b23ce --- /dev/null +++ b/lib/data/fetch_data_helper.dart @@ -0,0 +1,66 @@ +import 'dart:math'; + +import 'package:petitbac/data/game_data.dart'; +import 'package:petitbac/models/data/category.dart'; +import 'package:petitbac/models/data/game_item.dart'; +import 'package:petitbac/models/data/letter.dart'; +import 'package:petitbac/utils/tools.dart'; + +class FetchDataHelper { + FetchDataHelper(); + + final List<Category> _categories = []; + List<Category> get categories => _categories; + + final List<Letter> _letters = []; + List<Letter> get letters => _letters; + + void init() { + try { + final List<dynamic> rawCategories = GameData.data['categories'] as List<dynamic>; + for (var rawElement in rawCategories) { + final categoryCode = rawElement.toString(); + _categories.add(Category( + key: categoryCode, + text: categoryCode, + )); + } + } catch (e) { + printlog("$e"); + } + + const String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + for (var char in chars.split('')) { + _letters.add(Letter(key: char, text: char)); + } + } + + List<GameItem> getRandomItems(int count) { + if (_categories.isEmpty || _letters.isEmpty) { + init(); + } + + // will pick at least one item + int realCount = max(count, 1); + + List<Category> categories = _categories; + categories.shuffle(); + + List<Letter> letters = _letters; + letters.shuffle(); + + List<GameItem> items = []; + for (var i = 0; i < realCount; i++) { + items.add(GameItem( + letter: letters.elementAt(i), + category: categories.elementAt(i), + )); + } + + return items; + } + + GameItem getRandomItem() { + return getRandomItems(1).first; + } +} diff --git a/lib/data/game_data.dart b/lib/data/game_data.dart new file mode 100644 index 0000000000000000000000000000000000000000..563adb1f5766f32f3ddeb05dc8e66f2949285f4c --- /dev/null +++ b/lib/data/game_data.dart @@ -0,0 +1,66 @@ +class GameData { + static const Map<String, dynamic> data = { + "categories": [ + "Pays", + "Prénom fille", + "Prénom garçon", + "Animal", + "Métier", + "Villes", + "Dessin animé", + "Film", + "Auteur de littérature", + "Acteur ou actrice", + "Chanteur ou chanteuse", + "Chose ou objet", + "Fruit ou légume", + "Couleur", + "Marque", + "Moyen de transport", + "Outil", + "Capitale", + "Instrument de musique", + "Boisson", + "Fleur", + "Plat", + "Personnage historique", + "Vêtement", + "Minéral ou pierre précieuse", + "Étoile, planète ou constellation", + "Fleuve, cours d'eau ou océan", + "Partie du corps humain", + "Oiseau", + "Poisson", + "Qualité ou défaut", + "Arbre", + "Bande dessinée", + "Département français", + "Insecte", + "Dessert", + "Mammifère", + "Épice", + "Héros de mythologie", + "Héros fictif", + "Fromage", + "Jeu", + "Élément de véhicules", + "Site internet", + "Mot de plus de 8 lettres", + "Cadeau de Noël", + "Mot en anglais", + "Mot en espagnol", + "Métier dont rêvent les enfants", + "Chose qui se trouve dans une voiture", + "Chose qui se trouve dans un camping", + "Chose qui se trouve dans un cartable", + "Chose qui se trouve dans une maison", + "Chose qui se trouve dans une forêt", + "Chose qui se trouve dans la mer", + "Ville française", + "Qui sent mauvais", + "Qui fait plaisir", + "Mauvais pour la santé", + "Mauvais pour l'environement", + ] + }; +} diff --git a/lib/main.dart b/lib/main.dart index 462abf004e0928dbe200e87b7b187e560660ed48..4c088e7566c889ebbe109cee71def46769267f0b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,18 +6,17 @@ 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:petitbac/cubit/theme_cubit.dart'; -import 'package:provider/provider.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/cubit/nav_cubit.dart'; +import 'package:petitbac/cubit/settings_game_cubit.dart'; +import 'package:petitbac/cubit/settings_global_cubit.dart'; +import 'package:petitbac/cubit/theme_cubit.dart'; import 'package:petitbac/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -47,32 +46,31 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), - BlocProvider<GameCubit>(create: (context) => GameCubit()), - BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), + 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: BlocBuilder<ThemeCubit, ThemeModeState>( - builder: (BuildContext context, ThemeModeState state) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>( - builder: (context, data, child) { - return MaterialApp( - title: 'Petit Bac', - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, - home: const SkeletonScreen(), - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ); - }, - ), - ); - }), + builder: (BuildContext context, ThemeModeState state) { + return MaterialApp( + title: 'Petit Bac', + home: const SkeletonScreen(), + + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, + + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ); + }, + ), ); } } diff --git a/lib/models/data/category.dart b/lib/models/data/category.dart new file mode 100644 index 0000000000000000000000000000000000000000..ffe80be8eaabd65f24daecf6e6fe7d2f9de8fdf7 --- /dev/null +++ b/lib/models/data/category.dart @@ -0,0 +1,21 @@ +class Category { + final String key; + final String text; + + const Category({ + required this.key, + required this.text, + }); + + @override + String toString() { + return '$Category(${toJson()})'; + } + + Map<String, dynamic> toJson() { + return { + 'key': key, + 'text': text, + }; + } +} diff --git a/lib/models/data/game_item.dart b/lib/models/data/game_item.dart new file mode 100644 index 0000000000000000000000000000000000000000..fb0aa31446e3a39f66132d30e8da8ef89fd4044e --- /dev/null +++ b/lib/models/data/game_item.dart @@ -0,0 +1,24 @@ +import 'package:petitbac/models/data/category.dart'; +import 'package:petitbac/models/data/letter.dart'; + +class GameItem { + final Letter letter; + final Category category; + + GameItem({ + required this.letter, + required this.category, + }); + + @override + String toString() { + return '$GameItem(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'letter': letter.toJson(), + 'category': category.toJson(), + }; + } +} diff --git a/lib/models/data/letter.dart b/lib/models/data/letter.dart new file mode 100644 index 0000000000000000000000000000000000000000..7827fe59277ac87d8f5ba9b74ea860c256c518d8 --- /dev/null +++ b/lib/models/data/letter.dart @@ -0,0 +1,21 @@ +class Letter { + final String key; + final String text; + + const Letter({ + required this.key, + required this.text, + }); + + @override + String toString() { + return '$Letter(${toJson()})'; + } + + Map<String, dynamic> toJson() { + return { + 'key': key, + 'text': text, + }; + } +} diff --git a/lib/models/game.dart b/lib/models/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..9cdb1756b505d6ce5a8b64d05938a8205834d9e3 --- /dev/null +++ b/lib/models/game.dart @@ -0,0 +1,100 @@ +import 'package:petitbac/data/fetch_data_helper.dart'; +import 'package:petitbac/models/data/category.dart'; +import 'package:petitbac/models/data/game_item.dart'; +import 'package:petitbac/models/data/letter.dart'; +import 'package:petitbac/models/settings_game.dart'; +import 'package:petitbac/models/settings_global.dart'; +import 'package:petitbac/utils/tools.dart'; + +class Game { + final List<GameItem> items; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + int countdown = 0; + int position = 0; + bool isRunning = false; + bool isFinished = false; + + Game({ + required this.items, + required this.gameSettings, + required this.globalSettings, + required this.countdown, + required this.position, + this.isRunning = false, + this.isFinished = false, + }); + + factory Game.createNull() { + return Game( + items: [], + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + countdown: 0, + position: 0, + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + List<GameItem> items = FetchDataHelper().getRandomItems(newGameSettings.itemsCount); + + return Game( + items: items, + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + countdown: 0, + position: 0, + isRunning: true, + isFinished: false, + ); + } + + void stop() { + isRunning = false; + } + + GameItem get item => items[position]; + Category get category => item.category; + Letter get letter => item.letter; + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + gameSettings.dump(); + globalSettings.dump(); + printlog(''); + printlog('items:'); + printlog(items.toString()); + printlog(''); + printlog('Game: '); + printlog(' isRunning: $isRunning'); + printlog(' isFinished: $isFinished'); + printlog(' position: $position'); + printlog(' countdown: $countdown'); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'items': items, + 'gameSettings': gameSettings, + 'globalSettings': globalSettings, + 'countdown': countdown, + 'position': position, + 'isRunning': isRunning, + 'isFinished': isFinished, + }; + } +} diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart deleted file mode 100644 index ebe3d57f78a1ea5ed0b817ff40a1e293cde65ee7..0000000000000000000000000000000000000000 --- a/lib/models/game/game.dart +++ /dev/null @@ -1,38 +0,0 @@ -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/models/settings_game.dart b/lib/models/settings_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..526254604c88d699e27895b779b157741cf68f31 --- /dev/null +++ b/lib/models/settings_game.dart @@ -0,0 +1,53 @@ +import 'package:petitbac/config/default_game_settings.dart'; +import 'package:petitbac/utils/tools.dart'; + +class GameSettings { + final int itemsCount; + final int timerValue; + + GameSettings({ + required this.itemsCount, + required this.timerValue, + }); + + static int getItemsCountValueFromUnsafe(int itemsCount) { + if (DefaultGameSettings.allowedItemsCountValues.contains(itemsCount)) { + return itemsCount; + } + + return DefaultGameSettings.defaultItemsCountValue; + } + + static int getTimerValueFromUnsafe(int timerValue) { + if (DefaultGameSettings.allowedTimerValues.contains(timerValue)) { + return timerValue; + } + + return DefaultGameSettings.defaultTimerValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + itemsCount: DefaultGameSettings.defaultItemsCountValue, + timerValue: DefaultGameSettings.defaultTimerValue, + ); + } + + void dump() { + printlog('Game settings: '); + printlog(' itemsCount: $itemsCount'); + printlog(' timerValue: $timerValue'); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'itemsCount': itemsCount, + 'timerValue': timerValue, + }; + } +} diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..2448620a1be496b829a29f0f1f3a81f9bb554769 --- /dev/null +++ b/lib/models/settings_global.dart @@ -0,0 +1,23 @@ +import 'package:petitbac/utils/tools.dart'; + +class GlobalSettings { + GlobalSettings(); + + factory GlobalSettings.createDefault() { + return GlobalSettings(); + } + + void dump() { + printlog('Global settings: '); + printlog(' (none)'); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{}; + } +} diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index ebbe39723ff511713c8a73eb83a8e2107a873fa4..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter/foundation.dart'; - -class Data extends ChangeNotifier { - bool _searchingCategory = false; - bool _searchingLetter = false; - - String _category = ''; - String _letter = ''; - - int _countdown = -1; - - final int _recentCategoriesCount = 15; - final int _recentLettersCount = 10; - List<String> _recentCategories = []; - List<String> _recentLetters = []; - - bool get searchingCategory => _searchingCategory; - void setSearchingCategory(bool value) { - _searchingCategory = value; - notifyListeners(); - } - - bool get searchingLetter => _searchingLetter; - void setSearchingLetter(bool value) { - _searchingLetter = value; - notifyListeners(); - } - - String get category => _category; - void updateCategory(String value) { - _category = value; - if (value != '') { - _recentCategories.insert(0, value); - _recentCategories = _recentCategories.take(_recentCategoriesCount).toList(); - } - notifyListeners(); - } - - String get letter => _letter; - void updateLetter(String value) { - _letter = value; - if (value != '') { - _recentLetters.insert(0, value); - _recentLetters = _recentLetters.take(_recentLettersCount).toList(); - } - notifyListeners(); - } - - String recentlyPickedLetter(int count) { - if (_recentLetters.length > count) { - return _recentLetters[count]; - } - return ''; - } - - bool isCategoryRecentlyPicked(String category) { - return _recentCategories.contains(category); - } - - bool isLetterRecentlyPicked(String letter) { - return _recentLetters.contains(letter); - } - - int get countdown => _countdown; - void updateCountdown(int value) { - _countdown = value; - notifyListeners(); - } - - void resetGame() { - _category = ''; - _letter = ''; - _countdown = 0; - notifyListeners(); - } -} diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart new file mode 100644 index 0000000000000000000000000000000000000000..2509c75aa44f0d7557ea02a1ac08394b76fb7d8c --- /dev/null +++ b/lib/ui/painters/parameter_painter.dart @@ -0,0 +1,225 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:petitbac/config/default_game_settings.dart'; +import 'package:petitbac/models/settings_game.dart'; +import 'package:petitbac/models/settings_global.dart'; +import 'package:petitbac/utils/tools.dart'; + +class ParameterPainter extends CustomPainter { + const ParameterPainter({ + required this.code, + required this.value, + required this.isSelected, + required this.gameSettings, + required this.globalSettings, + }); + + final String code; + final int value; + final bool isSelected; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + @override + void paint(Canvas canvas, Size size) { + // force square + final double canvasSize = min(size.width, size.height); + + const Color borderColorEnabled = Colors.blue; + const Color borderColorDisabled = Colors.white; + + // "enabled/disabled" border + final paint = Paint(); + paint.style = PaintingStyle.stroke; + paint.color = isSelected ? borderColorEnabled : borderColorDisabled; + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 10; + canvas.drawRect( + Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); + + // content + switch (code) { + case DefaultGameSettings.parameterCodeItemsCount: + paintItemsCountParameterItem(value, canvas, canvasSize); + break; + case DefaultGameSettings.parameterCodeTimerValue: + paintTimerParameterItem(value, canvas, canvasSize); + break; + default: + printlog('Unknown parameter: $code/$value'); + paintUnknownParameterItem(value, canvas, canvasSize); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } + + // "unknown" parameter -> simple block with text + void paintUnknownParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + paint.color = Colors.grey; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + final textSpan = TextSpan( + text: '?\n$value', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintItemsCountParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { + const itemCountEmoji = '💬\n'; + + Color backgroundColor = Colors.grey; + String text = ''; + + switch (value) { + case DefaultGameSettings.itemsCountValueNoLimit: + backgroundColor = Colors.grey; + text = '⭐'; + break; + case DefaultGameSettings.itemsCountValueShort: + backgroundColor = Colors.green; + text = itemCountEmoji + DefaultGameSettings.itemsCountValueShort.toString(); + break; + case DefaultGameSettings.itemsCountValueMedium: + backgroundColor = Colors.orange; + text = itemCountEmoji + DefaultGameSettings.itemsCountValueMedium.toString(); + break; + case DefaultGameSettings.itemsCountValueLong: + backgroundColor = Colors.red; + text = itemCountEmoji + DefaultGameSettings.itemsCountValueLong.toString(); + break; + default: + printlog('Wrong value for itemsCount parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // centered text value + final textSpan = TextSpan( + text: text, + style: TextStyle( + color: Colors.black, + fontSize: size / 2.6, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintTimerParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { + const timerEmoji = '⏲️\n'; + + Color backgroundColor = Colors.grey; + String text = ''; + + switch (value) { + case DefaultGameSettings.timerValueNoTimer: + backgroundColor = Colors.grey; + text = '⭐'; + break; + case DefaultGameSettings.timerValueLow: + backgroundColor = Colors.green; + text = '$timerEmoji${DefaultGameSettings.timerValueLow}"'; + break; + case DefaultGameSettings.timerValueMedium: + backgroundColor = Colors.orange; + text = '$timerEmoji${DefaultGameSettings.timerValueMedium}"'; + break; + case DefaultGameSettings.timerValueHigh: + backgroundColor = Colors.red; + text = '$timerEmoji${DefaultGameSettings.timerValueHigh}"'; + break; + default: + printlog('Wrong value for itemsCount parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // centered text value + final textSpan = TextSpan( + text: text, + style: TextStyle( + color: Colors.black, + fontSize: size / 2.6, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } +} diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart deleted file mode 100644 index 125ea9568037cb27654abf48c21263f653899b30..0000000000000000000000000000000000000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,34 +0,0 @@ -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/about_page.dart b/lib/ui/screens/page_about.dart similarity index 86% rename from lib/ui/screens/about_page.dart rename to lib/ui/screens/page_about.dart index dd5c1767d113be9f7f3ef7c9b37c66e77b1650de..1720556b5c6125b00a97fef989e3d2d98fb26d63 100644 --- a/lib/ui/screens/about_page.dart +++ b/lib/ui/screens/page_about.dart @@ -2,10 +2,10 @@ 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'; +import 'package:petitbac/ui/widgets/helpers/app_header.dart'; -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); @override Widget build(BuildContext context) { @@ -14,7 +14,6 @@ class AboutPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: <Widget>[ - const SizedBox(height: 8), const AppHeader(text: 'about_title'), const Text('about_content').tr(), FutureBuilder<PackageInfo>( diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..933478a77bf2eb739ebed011d2e855cdd0d3d4f5 --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/ui/widgets/game/game.dart'; +import 'package:petitbac/ui/widgets/parameters.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); + }); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/page_settings.dart similarity index 64% rename from lib/ui/screens/settings_page.dart rename to lib/ui/screens/page_settings.dart index 216fad9ec8786385142e21dc3aea08866e6b4d02..d8690b98187e8df9d7286a92dda66a194b2600c6 100644 --- a/lib/ui/screens/settings_page.dart +++ b/lib/ui/screens/page_settings.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:petitbac/ui/widgets/header_app.dart'; -import 'package:petitbac/ui/widgets/settings_form.dart'; +import 'package:petitbac/ui/widgets/helpers/app_header.dart'; +import 'package:petitbac/ui/widgets/settings/settings_form.dart'; -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); @override Widget build(BuildContext context) { @@ -13,7 +13,6 @@ class SettingsPage extends StatelessWidget { 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 index f5fc21f57e76940a071bdc1ad2abdc4369e2adff..789d18b9fa29c47b66fa8dde8c51078b94a591a3 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,43 +1,32 @@ 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'; +import 'package:petitbac/cubit/nav_cubit.dart'; +import 'package:petitbac/ui/widgets/global_app_bar.dart'; -class SkeletonScreen extends StatefulWidget { +class SkeletonScreen extends StatelessWidget { const SkeletonScreen({super.key}); - @override - State<SkeletonScreen> createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { return Scaffold( + appBar: const GlobalAppBar(), 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); - }, - ), + body: Material( + color: Theme.of(context).colorScheme.background, + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), + child: Menu.getPageWidget(pageIndex), + ); + }, ), - 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 deleted file mode 100644 index 2e76fc6cb263f14b38699cc41299cebc31d50a03..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index d9f18eea081cb830b659f6d582be1e6255ef74c2..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ /dev/null @@ -1,40 +0,0 @@ -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/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart new file mode 100644 index 0000000000000000000000000000000000000000..2b0ca95a3d3087cd2d00073bad066810ab2806da --- /dev/null +++ b/lib/ui/widgets/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/cubit/settings_game_cubit.dart'; +import 'package:petitbac/cubit/settings_global_cubit.dart'; + +class StartNewGameButton extends StatelessWidget { + const StartNewGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () => gameCubit.startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game.dart b/lib/ui/widgets/game/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..b7384eb42fd6190bf62c9202762bc0ac4bd07440 --- /dev/null +++ b/lib/ui/widgets/game/game.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/ui/widgets/game/game_countdown.dart'; +import 'package:petitbac/ui/widgets/game/game_position_indicator.dart'; +import 'package:petitbac/ui/widgets/game/widget_category.dart'; +import 'package:petitbac/ui/widgets/game/widget_letter.dart'; + +class GameWidget extends StatelessWidget { + const GameWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + currentGame.gameSettings.itemsCount != 0 + ? const GamePositionIndicator() + : const SizedBox.shrink(), + const WidgetLetter(), + const GameButtonNextWithCountdown(), + const WidgetCategory(), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game_countdown.dart b/lib/ui/widgets/game/game_countdown.dart new file mode 100644 index 0000000000000000000000000000000000000000..508f35fd509971da0e885085280ebd826fa52126 --- /dev/null +++ b/lib/ui/widgets/game/game_countdown.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/cubit/settings_game_cubit.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/utils/color_extensions.dart'; + +class GameButtonNextWithCountdown extends StatelessWidget { + const GameButtonNextWithCountdown({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + const Color backgroundColor = Colors.blue; + const double borderWidth = 8.0; + final Color borderColor = backgroundColor.darken(); + + const Color textColor = Colors.black; + + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (context, settingsSate) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 5), + Container( + margin: const EdgeInsets.all(borderWidth), + padding: const EdgeInsets.all(borderWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: TextButton( + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + if (currentGame.isFinished) { + gameCubit.quitGame(); + } else { + if (currentGame.gameSettings.itemsCount == 0) { + gameCubit.pickNewItem(); + gameCubit.startTimer(); + } else { + if (currentGame.countdown == 0) { + gameCubit.next(); + } + } + } + }, + child: Text( + currentGame.isFinished + ? '🎆' + : ((currentGame.countdown == 0) + ? '🎲' + : currentGame.countdown.toString()), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 50, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ), + ), + const SizedBox(height: 5), + ], + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game_position_indicator.dart b/lib/ui/widgets/game/game_position_indicator.dart new file mode 100644 index 0000000000000000000000000000000000000000..84b3af59e82845f2253d70a01bf21ae10d94b24e --- /dev/null +++ b/lib/ui/widgets/game/game_position_indicator.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/ui/widgets/helpers/outlined_text_widget.dart'; +import 'package:petitbac/utils/color_extensions.dart'; + +class GamePositionIndicator extends StatelessWidget { + const GamePositionIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final int currentPosition = currentGame.position + 1; + final int maxPosition = currentGame.gameSettings.itemsCount; + + // Normalized [0..1] value + final double barValue = currentPosition / maxPosition; + + const barHeight = 30.0; + const Color baseColor = Colors.grey; + const Color textColor = Color.fromARGB(255, 238, 238, 238); + const Color outlineColor = Color.fromARGB(255, 200, 200, 200); + + return Stack( + alignment: Alignment.center, + children: [ + LinearProgressIndicator( + value: barValue, + color: baseColor, + backgroundColor: baseColor.darken(), + minHeight: barHeight, + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + OutlinedText( + text: '$currentPosition/$maxPosition', + fontSize: barHeight * 0.7, + textColor: textColor, + outlineColor: outlineColor, + ), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/widget_category.dart b/lib/ui/widgets/game/widget_category.dart new file mode 100644 index 0000000000000000000000000000000000000000..a54a2c13f0efad2c721610cbfa66d6697de55c0a --- /dev/null +++ b/lib/ui/widgets/game/widget_category.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/utils/color_extensions.dart'; + +class WidgetCategory extends StatelessWidget { + const WidgetCategory({super.key}); + + @override + Widget build(BuildContext context) { + const Color backgroundColor = Colors.green; + const double borderWidth = 8.0; + + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final Color borderColor = backgroundColor.darken(); + + return Container( + margin: const EdgeInsets.all(borderWidth), + padding: const EdgeInsets.all(borderWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: TextButton( + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.pickNewCategory(); + }, + child: Text( + currentGame.category.text, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 30, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/widget_letter.dart b/lib/ui/widgets/game/widget_letter.dart new file mode 100644 index 0000000000000000000000000000000000000000..9b7832dfb63d6ad0835a106de0c2378ec446db3d --- /dev/null +++ b/lib/ui/widgets/game/widget_letter.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/utils/color_extensions.dart'; + +class WidgetLetter extends StatelessWidget { + const WidgetLetter({super.key}); + + @override + Widget build(BuildContext context) { + const Color backgroundColor = Colors.orange; + const double borderWidth = 8.0; + + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final Color borderColor = backgroundColor.darken(); + + return Container( + margin: const EdgeInsets.all(borderWidth), + padding: const EdgeInsets.all(borderWidth), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderWidth), + border: Border.all( + color: borderColor, + width: borderWidth, + ), + ), + child: TextButton( + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.pickNewLetter(); + }, + child: Text( + currentGame.letter.text, + style: const TextStyle( + fontSize: 40, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..512717ac62b4b6e828fbafb35625a64a1ba047ba --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/config/menu.dart'; +import 'package:petitbac/cubit/game_cubit.dart'; +import 'package:petitbac/cubit/nav_cubit.dart'; +import 'package:petitbac/models/game.dart'; +import 'package:petitbac/ui/widgets/helpers/app_title.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning) { + menuActions.add(TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () {}, + onLongPress: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.quitGame(); + }, + )); + } else { + if (pageIndex == Menu.indexGame) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToSettingsPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToAboutPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemAbout.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 AppTitle(text: 'app_name'), + actions: menuActions, + ); + }, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/helpers/app_header.dart similarity index 77% rename from lib/ui/widgets/header_app.dart rename to lib/ui/widgets/helpers/app_header.dart index bf54b77375fbd0260f876f2885d0572b71715383..b5c5be05f6636cf488dcdb5bbc4d6f049b98de11 100644 --- a/lib/ui/widgets/header_app.dart +++ b/lib/ui/widgets/helpers/app_header.dart @@ -8,15 +8,16 @@ class AppHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( + return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( tr(text), textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), ), + const SizedBox(height: 8), ], ); } diff --git a/lib/ui/widgets/helpers/app_title.dart b/lib/ui/widgets/helpers/app_title.dart new file mode 100644 index 0000000000000000000000000000000000000000..7cbbb2030419047b3dcf093a2195a498bd8e8ce9 --- /dev/null +++ b/lib/ui/widgets/helpers/app_title.dart @@ -0,0 +1,17 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/widgets/helpers/outlined_text_widget.dart b/lib/ui/widgets/helpers/outlined_text_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..c495cdb82ffd0bb22ac82acda1e50ed43bd9861a --- /dev/null +++ b/lib/ui/widgets/helpers/outlined_text_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:petitbac/utils/color_extensions.dart'; + +class OutlinedText extends StatelessWidget { + const OutlinedText({ + super.key, + required this.text, + required this.fontSize, + required this.textColor, + this.outlineColor, + }); + + final String text; + final double fontSize; + final Color textColor; + final Color? outlineColor; + + @override + Widget build(BuildContext context) { + final double delta = fontSize / 30; + + return Text( + text, + style: TextStyle( + inherit: true, + fontSize: fontSize, + fontWeight: FontWeight.w600, + color: textColor, + shadows: [ + Shadow( + offset: Offset(-delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, -delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(delta, delta), + color: outlineColor ?? textColor.darken(), + ), + Shadow( + offset: Offset(-delta, delta), + color: outlineColor ?? textColor.darken(), + ), + ], + ), + ); + } +} diff --git a/lib/ui/widgets/mini_game.dart b/lib/ui/widgets/mini_game.dart deleted file mode 100644 index 3734c5f92597dc44c6ded3ab416437bf5c915a4c..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/mini_game.dart +++ /dev/null @@ -1,107 +0,0 @@ -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/parameters.dart b/lib/ui/widgets/parameters.dart new file mode 100644 index 0000000000000000000000000000000000000000..c13ccba6ded4cd33dac45559bc412b583392eb35 --- /dev/null +++ b/lib/ui/widgets/parameters.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/config/default_game_settings.dart'; +import 'package:petitbac/config/default_global_settings.dart'; +import 'package:petitbac/cubit/settings_game_cubit.dart'; +import 'package:petitbac/cubit/settings_global_cubit.dart'; +import 'package:petitbac/ui/painters/parameter_painter.dart'; +import 'package:petitbac/ui/widgets/button_game_start_new.dart'; + +class Parameters extends StatelessWidget { + const Parameters({super.key}); + + final double separatorHeight = 8.0; + + @override + Widget build(BuildContext context) { + final List<Widget> lines = []; + + // Game settings + for (String code in DefaultGameSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: false, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + lines.add(SizedBox(height: separatorHeight)); + lines.add(const Expanded(child: StartNewGameButton())); + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + return Column( + children: lines, + ); + } + + List<Widget> buildParametersLine({ + required String code, + required bool isGlobal, + }) { + final List<Widget> parameterButtons = []; + + final List<int> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultGameSettings.getAvailableValues(code); + + if (availableValues.length <= 1) { + return []; + } + + for (int value in availableValues) { + final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameSettingsCubit gameSettingsCubit = + BlocProvider.of<GameSettingsCubit>(context); + final GlobalSettingsCubit globalSettingsCubit = + BlocProvider.of<GlobalSettingsCubit>(context); + + final int currentValue = isGlobal + ? globalSettingsCubit.getParameterValue(code) + : gameSettingsCubit.getParameterValue(code); + + final bool isActive = (value == currentValue); + + final double displayWidth = MediaQuery.of(context).size.width; + final double itemWidth = displayWidth / availableValues.length - 26; + + return TextButton( + child: CustomPaint( + size: Size(itemWidth, itemWidth), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + isSelected: isActive, + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + isComplex: true, + ), + onPressed: () => isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value), + ); + }, + ); + }, + ); + + parameterButtons.add(parameterButton); + } + + return parameterButtons; + } +} diff --git a/lib/ui/widgets/picked_category.dart b/lib/ui/widgets/picked_category.dart deleted file mode 100644 index de5781b4cf3cfa2ded6ad0064c6c35f1eb0ce93b..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/picked_category.dart +++ /dev/null @@ -1,55 +0,0 @@ -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 deleted file mode 100644 index 0c0e009301cbea545baf3762a1b5813990215c36..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/picked_letter.dart +++ /dev/null @@ -1,101 +0,0 @@ -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 deleted file mode 100644 index 558f27ccad159e8636aa5d1db189f65b33a9389f..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/previous_letter.dart +++ /dev/null @@ -1,52 +0,0 @@ -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/settings_form.dart b/lib/ui/widgets/settings/settings_form.dart new file mode 100644 index 0000000000000000000000000000000000000000..eef343a0381e08ef03bc704a5012c583f6fe0aa2 --- /dev/null +++ b/lib/ui/widgets/settings/settings_form.dart @@ -0,0 +1,63 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:petitbac/ui/widgets/settings/theme_card.dart'; + +class SettingsForm extends StatefulWidget { + const SettingsForm({super.key}); + + @override + State<SettingsForm> createState() => _SettingsFormState(); +} + +class _SettingsFormState extends State<SettingsForm> { + @override + void dispose() { + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + // Light/dark theme + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const Text('settings_label_theme').tr(), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ThemeCard( + mode: ThemeMode.system, + icon: UniconsLine.cog, + ), + ThemeCard( + mode: ThemeMode.light, + icon: UniconsLine.sun, + ), + ThemeCard( + mode: ThemeMode.dark, + icon: UniconsLine.moon, + ) + ], + ), + ], + ), + + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/ui/widgets/settings/theme_card.dart b/lib/ui/widgets/settings/theme_card.dart new file mode 100644 index 0000000000000000000000000000000000000000..4fe730a436861525286a7efed051f94ec07f2c3e --- /dev/null +++ b/lib/ui/widgets/settings/theme_card.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:petitbac/cubit/theme_cubit.dart'; + +class ThemeCard extends StatelessWidget { + const ThemeCard({ + super.key, + required this.mode, + required this.icon, + }); + + final IconData icon; + final ThemeMode mode; + + @override + Widget build(BuildContext context) { + return BlocBuilder<ThemeCubit, ThemeModeState>( + builder: (BuildContext context, ThemeModeState state) { + return Card( + elevation: 2, + shadowColor: Theme.of(context).colorScheme.shadow, + color: state.themeMode == mode + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.surface, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + margin: const EdgeInsets.all(5), + child: InkWell( + onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme( + ThemeModeState(themeMode: mode), + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Icon( + icon, + size: 32, + color: state.themeMode != mode + ? Theme.of(context).colorScheme.primary + : Colors.white, + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart deleted file mode 100644 index 6251f79e1eded14fcdba7291e5713e1cee4f4931..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/settings_form.dart +++ /dev/null @@ -1,108 +0,0 @@ -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'; -import 'package:petitbac/ui/widgets/theme_card.dart'; -import 'package:unicons/unicons.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>[ - // Light/dark theme - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: <Widget>[ - const Text('settings_label_theme').tr(), - const Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ThemeCard( - mode: ThemeMode.system, - icon: UniconsLine.cog, - ), - ThemeCard( - mode: ThemeMode.light, - icon: UniconsLine.sun, - ), - ThemeCard( - mode: ThemeMode.dark, - icon: UniconsLine.moon, - ) - ], - ), - ], - ), - - const SizedBox(height: 16), - - // 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/ui/widgets/theme_card.dart b/lib/ui/widgets/theme_card.dart deleted file mode 100644 index dd9970868b8a36ee0ea017e22e5103773597a6cd..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/theme_card.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'package:petitbac/cubit/theme_cubit.dart'; - -class ThemeCard extends StatelessWidget { - const ThemeCard({ - super.key, - required this.mode, - required this.icon, - }); - - final IconData icon; - final ThemeMode mode; - - @override - Widget build(BuildContext context) { - return BlocBuilder<ThemeCubit, ThemeModeState>( - builder: (BuildContext context, ThemeModeState state) { - return Card( - elevation: 2, - shadowColor: Theme.of(context).colorScheme.shadow, - color: state.themeMode == mode - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.surface, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - ), - margin: const EdgeInsets.all(5), - child: InkWell( - onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme( - ThemeModeState(themeMode: mode), - ), - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: Icon( - icon, - size: 32, - color: - state.themeMode != mode ? Theme.of(context).colorScheme.primary : Colors.white, - ), - ), - ); - }); - } -} diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index e206a93dfcfe878f9a96c1291f824b2824e6c3ac..0000000000000000000000000000000000000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,37 +0,0 @@ -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/lib/utils/random_pick_category.dart b/lib/utils/random_pick_category.dart deleted file mode 100644 index 0132446881c27cd62748e053ee2890322d062a50..0000000000000000000000000000000000000000 --- a/lib/utils/random_pick_category.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:math' show Random; - -import 'package:flutter/services.dart'; - -class RandomPickCategory { - RandomPickCategory(); - - String? _category; - final random = Random(); - - init() async { - await categoryFromLocalFile(); - } - - Future<void> categoryFromLocalFile() async { - String jsonString; - try { - jsonString = await rootBundle.loadString('assets/files/categories-fr.json'); - final jsonResponse = await json.decode(jsonString); - var categoryList = jsonResponse[jsonResponse.keys.toList().join()]; - _category = categoryList[random.nextInt(categoryList.length)]; - } catch (e) { - _category = 'UNEXPECTED ERROR'; - } - } - - String get category => (_category != null) ? _category.toString() : ''; -} diff --git a/lib/utils/random_pick_letter.dart b/lib/utils/random_pick_letter.dart deleted file mode 100644 index 32caf82cdb7fa6368cff067cd59be398bad379d6..0000000000000000000000000000000000000000 --- a/lib/utils/random_pick_letter.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:async'; -import 'dart:math' show Random; - -class RandomPickLetter { - RandomPickLetter(); - - String? _letter; - final random = Random(); - final String _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - init() async { - await letterFromLocalFile(); - } - - Future<void> letterFromLocalFile() async { - _letter = _chars[random.nextInt(_chars.length)]; - } - - String get letter => (_letter != null) ? _letter.toString() : ''; -} diff --git a/pubspec.lock b/pubspec.lock index 53bc85dec4bae56188e56f74edcd0c91e60c1f18..b5a356dfeb8febcb7fbb85d3d013b567f3a279d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" characters: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" easy_logger: dependency: transitive description: @@ -106,31 +106,23 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.1.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" 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 @@ -148,10 +140,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_parser: dependency: transitive description: @@ -164,10 +156,10 @@ packages: dependency: "direct main" description: name: hydrated_bloc - sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d" + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.1.5" intl: dependency: transitive description: @@ -212,20 +204,20 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "8.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -236,26 +228,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -297,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -308,26 +300,26 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_linux: dependency: transitive description: @@ -348,10 +340,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -425,18 +417,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" win32: dependency: transitive description: name: win32 - sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -447,4 +439,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.16.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 45c8369c7f5369fc66e6f1b558759df333ec4e63..e7d76b48be1ca514ce32c6f916f1334d57212855 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,12 @@ name: petitbac description: A PetitBac game application. -publish_to: 'none' -version: 1.2.30+36 +publish_to: "none" + +version: 1.2.31+37 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: @@ -14,22 +15,19 @@ dependencies: 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 + package_info_plus: ^8.0.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: false + uses-material-design: true assets: - - assets/files/ + - assets/icons/ - assets/translations/ fonts: @@ -43,4 +41,3 @@ flutter: weight: 400 - asset: assets/fonts/Nunito-Light.ttf weight: 300 -