From cc298e0b75868f91c9fde9c9e2de0265ec1f78c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Mon, 6 May 2024 01:13:33 +0200 Subject: [PATCH] Improve game architecture/conception --- android/app/build.gradle | 2 +- android/gradle.properties | 4 +- assets/files/categories-fr.json | 67 ------ assets/icons/button_back.png | Bin 0 -> 3771 bytes assets/icons/button_start.png | Bin 0 -> 3999 bytes assets/icons/placeholder.png | Bin 0 -> 170 bytes .../metadata/android/en-US/changelogs/37.txt | 1 + .../metadata/android/fr-FR/changelogs/37.txt | 1 + icons/build_game_icons.sh | 78 ++++++ icons/build_icons.sh | 48 ---- icons/button_back.svg | 2 + icons/button_start.svg | 2 + icons/placeholder.svg | 2 + lib/config/default_game_settings.dart | 51 ++++ lib/config/default_global_settings.dart | 10 + lib/config/default_settings.dart | 12 - lib/config/menu.dart | 63 ++--- lib/cubit/game_cubit.dart | 131 +++++++++- lib/cubit/game_state.dart | 6 +- .../{bottom_nav_cubit.dart => nav_cubit.dart} | 22 +- lib/cubit/settings_cubit.dart | 47 ---- lib/cubit/settings_game_cubit.dart | 70 ++++++ lib/cubit/settings_game_state.dart | 19 ++ lib/cubit/settings_global_cubit.dart | 44 ++++ lib/cubit/settings_global_state.dart | 19 ++ lib/cubit/settings_state.dart | 19 -- lib/data/fetch_data_helper.dart | 66 +++++ lib/data/game_data.dart | 66 +++++ lib/main.dart | 56 +++-- lib/models/data/category.dart | 21 ++ lib/models/data/game_item.dart | 24 ++ lib/models/data/letter.dart | 21 ++ lib/models/game.dart | 100 ++++++++ lib/models/game/game.dart | 38 --- lib/models/settings_game.dart | 53 +++++ lib/models/settings_global.dart | 23 ++ lib/provider/data.dart | 76 ------ lib/ui/painters/parameter_painter.dart | 225 ++++++++++++++++++ lib/ui/screens/game_page.dart | 34 --- .../{about_page.dart => page_about.dart} | 7 +- lib/ui/screens/page_game.dart | 18 ++ ...{settings_page.dart => page_settings.dart} | 9 +- lib/ui/skeleton.dart | 45 ++-- lib/ui/widgets/app_bar.dart | 20 -- lib/ui/widgets/bottom_nav_bar.dart | 40 ---- lib/ui/widgets/button_game_start_new.dart | 34 +++ lib/ui/widgets/game/game.dart | 37 +++ lib/ui/widgets/game/game_countdown.dart | 82 +++++++ .../widgets/game/game_position_indicator.dart | 50 ++++ lib/ui/widgets/game/widget_category.dart | 52 ++++ lib/ui/widgets/game/widget_letter.dart | 51 ++++ lib/ui/widgets/global_app_bar.dart | 84 +++++++ .../app_header.dart} | 5 +- lib/ui/widgets/helpers/app_title.dart | 17 ++ .../widgets/helpers/outlined_text_widget.dart | 51 ++++ lib/ui/widgets/mini_game.dart | 107 --------- lib/ui/widgets/parameters.dart | 115 +++++++++ lib/ui/widgets/picked_category.dart | 55 ----- lib/ui/widgets/picked_letter.dart | 101 -------- lib/ui/widgets/previous_letter.dart | 52 ---- lib/ui/widgets/settings/settings_form.dart | 63 +++++ lib/ui/widgets/settings/theme_card.dart | 47 ++++ lib/ui/widgets/settings_form.dart | 108 --------- lib/ui/widgets/theme_card.dart | 45 ---- lib/utils/game_utils.dart | 37 --- lib/utils/random_pick_category.dart | 30 --- lib/utils/random_pick_letter.dart | 20 -- pubspec.lock | 86 +++---- pubspec.yaml | 17 +- 69 files changed, 1874 insertions(+), 1134 deletions(-) delete mode 100644 assets/files/categories-fr.json create mode 100644 assets/icons/button_back.png create mode 100644 assets/icons/button_start.png create mode 100644 assets/icons/placeholder.png create mode 100644 fastlane/metadata/android/en-US/changelogs/37.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/37.txt create mode 100755 icons/build_game_icons.sh delete mode 100755 icons/build_icons.sh create mode 100644 icons/button_back.svg create mode 100644 icons/button_start.svg create mode 100644 icons/placeholder.svg create mode 100644 lib/config/default_game_settings.dart create mode 100644 lib/config/default_global_settings.dart delete mode 100644 lib/config/default_settings.dart rename lib/cubit/{bottom_nav_cubit.dart => nav_cubit.dart} (51%) delete mode 100644 lib/cubit/settings_cubit.dart create mode 100644 lib/cubit/settings_game_cubit.dart create mode 100644 lib/cubit/settings_game_state.dart create mode 100644 lib/cubit/settings_global_cubit.dart create mode 100644 lib/cubit/settings_global_state.dart delete mode 100644 lib/cubit/settings_state.dart create mode 100644 lib/data/fetch_data_helper.dart create mode 100644 lib/data/game_data.dart create mode 100644 lib/models/data/category.dart create mode 100644 lib/models/data/game_item.dart create mode 100644 lib/models/data/letter.dart create mode 100644 lib/models/game.dart delete mode 100644 lib/models/game/game.dart create mode 100644 lib/models/settings_game.dart create mode 100644 lib/models/settings_global.dart delete mode 100644 lib/provider/data.dart create mode 100644 lib/ui/painters/parameter_painter.dart delete mode 100644 lib/ui/screens/game_page.dart rename lib/ui/screens/{about_page.dart => page_about.dart} (86%) create mode 100644 lib/ui/screens/page_game.dart rename lib/ui/screens/{settings_page.dart => page_settings.dart} (64%) delete mode 100644 lib/ui/widgets/app_bar.dart delete mode 100644 lib/ui/widgets/bottom_nav_bar.dart create mode 100644 lib/ui/widgets/button_game_start_new.dart create mode 100644 lib/ui/widgets/game/game.dart create mode 100644 lib/ui/widgets/game/game_countdown.dart create mode 100644 lib/ui/widgets/game/game_position_indicator.dart create mode 100644 lib/ui/widgets/game/widget_category.dart create mode 100644 lib/ui/widgets/game/widget_letter.dart create mode 100644 lib/ui/widgets/global_app_bar.dart rename lib/ui/widgets/{header_app.dart => helpers/app_header.dart} (77%) create mode 100644 lib/ui/widgets/helpers/app_title.dart create mode 100644 lib/ui/widgets/helpers/outlined_text_widget.dart delete mode 100644 lib/ui/widgets/mini_game.dart create mode 100644 lib/ui/widgets/parameters.dart delete mode 100644 lib/ui/widgets/picked_category.dart delete mode 100644 lib/ui/widgets/picked_letter.dart delete mode 100644 lib/ui/widgets/previous_letter.dart create mode 100644 lib/ui/widgets/settings/settings_form.dart create mode 100644 lib/ui/widgets/settings/theme_card.dart delete mode 100644 lib/ui/widgets/settings_form.dart delete mode 100644 lib/ui/widgets/theme_card.dart delete mode 100644 lib/utils/game_utils.dart delete mode 100644 lib/utils/random_pick_category.dart delete mode 100644 lib/utils/random_pick_letter.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index c5483a3..5919a87 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 b33cdc8..000015d 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 4205a8c..0000000 --- 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 GIT binary patch literal 3771 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 literal 0 HcmV?d00001 diff --git a/assets/icons/button_start.png b/assets/icons/button_start.png new file mode 100644 index 0000000000000000000000000000000000000000..6845e2f5c21598ab61f1684d2075aeec0334bf23 GIT binary patch literal 3999 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 literal 0 HcmV?d00001 diff --git a/assets/icons/placeholder.png b/assets/icons/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..814df31be6ddc4275ebe4490c79365578dbef1f0 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0WCPk}I_+{y{! z3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)lFbDXAxc>kDfB6yV z6ATOtj3q&S!3+-1Zlp0VFi3m4IEF|_zCCEj$iTpGNa6SLI9~n)1_lO(0v(|P42<pU Q7vF(+p00i_>zopr0K3yKtN;K2 literal 0 HcmV?d00001 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 0000000..9dc5d66 --- /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 0000000..33a094d --- /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 0000000..7368dc4 --- /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 fefc393..0000000 --- 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 0000000..2622a57 --- /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 0000000..e9d49d2 --- /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 0000000..23ace81 --- /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 0000000..a1556ef --- /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 0000000..363c2a1 --- /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 fa69149..0000000 --- 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 4ba90b2..d875b6b 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 bfe94d2..d310d36 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 3e4d0d0..8581d72 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 ac26ae9..c01599b 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 d105a3f..0000000 --- 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 0000000..b31cae3 --- /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 0000000..b773dc6 --- /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 0000000..43f64fe --- /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 0000000..4e4fbdf --- /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 6048256..0000000 --- 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 0000000..66931e9 --- /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 0000000..563adb1 --- /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 462abf0..4c088e7 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 0000000..ffe80be --- /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 0000000..fb0aa31 --- /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 0000000..7827fe5 --- /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 0000000..9cdb175 --- /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 ebe3d57..0000000 --- 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 0000000..5262546 --- /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 0000000..2448620 --- /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 ebbe397..0000000 --- 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 0000000..2509c75 --- /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 125ea95..0000000 --- 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 dd5c176..1720556 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 0000000..933478a --- /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 216fad9..d8690b9 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 f5fc21f..789d18b 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 2e76fc6..0000000 --- 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 d9f18ee..0000000 --- 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 0000000..2b0ca95 --- /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 0000000..b7384eb --- /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 0000000..508f35f --- /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 0000000..84b3af5 --- /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 0000000..a54a2c1 --- /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 0000000..9b7832d --- /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 0000000..512717a --- /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 bf54b77..b5c5be0 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 0000000..7cbbb20 --- /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 0000000..c495cdb --- /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 3734c5f..0000000 --- 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 0000000..c13ccba --- /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 de5781b..0000000 --- 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 0c0e009..0000000 --- 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 558f27c..0000000 --- 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 0000000..eef343a --- /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 0000000..4fe730a --- /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 6251f79..0000000 --- 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 dd99708..0000000 --- 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 e206a93..0000000 --- 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 0132446..0000000 --- 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 32caf82..0000000 --- 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 53bc85d..b5a356d 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 45c8369..e7d76b4 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 - -- GitLab