diff --git a/android/gradle.properties b/android/gradle.properties index f7613f2f8c445194bd8345c26effa0131dfe211e..dfa6ea859d8324f76d1b0eccc01621db70e41cde 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=0.1.40 -app.versionCode=64 +app.versionName=0.2.0 +app.versionCode=65 diff --git a/assets/menu/game-pick-image.png b/assets/menu/game-pick-image.png deleted file mode 100644 index 4f98fea428136e093ec5ed41310bc18ad6bda71a..0000000000000000000000000000000000000000 Binary files a/assets/menu/game-pick-image.png and /dev/null differ diff --git a/assets/menu/game-pick-word.png b/assets/menu/game-pick-word.png deleted file mode 100644 index 0625f5ac0e3428e5c5959f0e66eebc7fced794da..0000000000000000000000000000000000000000 Binary files a/assets/menu/game-pick-word.png and /dev/null differ diff --git a/assets/placeholder.png b/assets/placeholder.png deleted file mode 100644 index 9c9e7153c4ac87b54574b24a2cef2d27678c6cd7..0000000000000000000000000000000000000000 Binary files a/assets/placeholder.png and /dev/null differ diff --git a/assets/translations/en.json b/assets/translations/en.json index 7a959c85f39256092adb9e8b230109e61283fb63..bee067fabfa853ccf17fe49031ee56c06a778293 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,14 +1,12 @@ { "app_name": "Word guess", - "bottom_nav_home": "Game", - "bottom_nav_settings": "Settings", - "bottom_nav_about": "About", - "settings_title": "Settings", "settings_label_theme": "Theme mode", "about_title": "About", "about_content": "Word guess game.", - "about_version": "Version: {version}" + "about_version": "Version: {version}", + + "": "" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index de1e73a173d57d55b362a6a9c92c6d5c88e4cd77..51afd63375a5f83f8579342d6886dbaa186a196e 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,14 +1,12 @@ { "app_name": "Trouve le mot", - "bottom_nav_home": "Jeu", - "bottom_nav_settings": "Réglages", - "bottom_nav_about": "Infos", - "settings_title": "Réglages", "settings_label_theme": "Thème de couleurs", "about_title": "Informations", "about_content": "Trouve le mot.", - "about_version": "Version : {version}" + "about_version": "Version : {version}", + + "": "" } diff --git a/assets/ui/button_back.png b/assets/ui/button_back.png new file mode 100644 index 0000000000000000000000000000000000000000..cc48ffb1dbb653d9a996f139dfbe02969724bfa5 Binary files /dev/null and b/assets/ui/button_back.png differ diff --git a/assets/ui/button_delete_saved_game.png b/assets/ui/button_delete_saved_game.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4f217689b11e444b7163557d7e5d68f3bbfe7d Binary files /dev/null and b/assets/ui/button_delete_saved_game.png differ diff --git a/assets/ui/button_resume_game.png b/assets/ui/button_resume_game.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ea0a02d05e42377eb551a4b51428b511a32f5d Binary files /dev/null and b/assets/ui/button_resume_game.png differ diff --git a/assets/ui/button_start.png b/assets/ui/button_start.png new file mode 100644 index 0000000000000000000000000000000000000000..6845e2f5c21598ab61f1684d2075aeec0334bf23 Binary files /dev/null and b/assets/ui/button_start.png differ diff --git a/assets/ui/game_fail.png b/assets/ui/game_fail.png new file mode 100644 index 0000000000000000000000000000000000000000..93f2801f9d6bb2ce508e1293cd64d6ff2e9970ec Binary files /dev/null and b/assets/ui/game_fail.png differ diff --git a/assets/ui/game_win.png b/assets/ui/game_win.png new file mode 100644 index 0000000000000000000000000000000000000000..876334279c1711b349a62131a33607eecf924eb6 Binary files /dev/null and b/assets/ui/game_win.png differ diff --git a/assets/ui/placeholder.png b/assets/ui/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..814df31be6ddc4275ebe4490c79365578dbef1f0 Binary files /dev/null and b/assets/ui/placeholder.png differ diff --git a/assets/ui/type_pick-image.png b/assets/ui/type_pick-image.png new file mode 100644 index 0000000000000000000000000000000000000000..216e7d24926beb60c9806f5404a313cb62778ffb Binary files /dev/null and b/assets/ui/type_pick-image.png differ diff --git a/assets/ui/type_pick-word.png b/assets/ui/type_pick-word.png new file mode 100644 index 0000000000000000000000000000000000000000..8c816e62f246aa7fc2762cae52090547b74cb1c2 Binary files /dev/null and b/assets/ui/type_pick-word.png differ diff --git a/fastlane/metadata/android/en-US/changelogs/65.txt b/fastlane/metadata/android/en-US/changelogs/65.txt new file mode 100644 index 0000000000000000000000000000000000000000..d4afd512e55b3fd8ffbfd795adb9b00832e5aaef --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/65.txt @@ -0,0 +1 @@ +Improve/normalize game architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/65.txt b/fastlane/metadata/android/fr-FR/changelogs/65.txt new file mode 100644 index 0000000000000000000000000000000000000000..6a9871a5eb8eb3c6e9106520f1cbf1f39f9e5ef7 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/65.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture du jeu. diff --git a/images/build_images.sh b/images/build_images.sh deleted file mode 100755 index c9639849e52cc4c8306171828fe670f147eb63f3..0000000000000000000000000000000000000000 --- a/images/build_images.sh +++ /dev/null @@ -1,67 +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}")" - -CONVERT_OPTIONS="-alpha off +dither -colors 256 -depth 4" -OPTIPNG_OPTIONS="-preserve -quiet -o7" - -# 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 \ - -i ${SOURCE}.tmp \ - -o ${SOURCE} - rm ${SOURCE}.tmp -} - -# build image -function build_image() { - SOURCE="$1" - TARGET_PNG="$2" - IMAGE_WIDTH="$3" - IMAGE_HEIGHT="$4" - - inkscape \ - --export-width=${IMAGE_WIDTH} \ - --export-height=${IMAGE_HEIGHT} \ - --export-filename=${TARGET_PNG} \ - ${SOURCE} -} - -# optimize image -function optimize_image() { - IMAGE_FILE="$1" - - convert "${IMAGE_FILE}" ${CONVERT_OPTIONS} "${IMAGE_FILE}" - optipng ${OPTIPNG_OPTIONS} ${IMAGE_FILE} -} - -# build menu image -function build_menu_image() { - IMAGE_NAME="$1" - - INPUT_SVG="${CURRENT_DIR}/menu/${IMAGE_NAME}.svg" - OUTPUT_PNG="${BASE_DIR}/assets/menu/${IMAGE_NAME}.png" - - optimize_svg "${INPUT_SVG}" - build_image "${INPUT_SVG}" "${OUTPUT_PNG}" 640 640 - optimize_image "${OUTPUT_PNG}" -} - - -build_menu_image "game-pick-image" -build_menu_image "game-pick-word" diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..f6ae267b5da3b0be46ecb25da461df11ebddf274 --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,52 @@ +import 'package:wordguessing/utils/tools.dart'; + +class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeGameType = 'type'; + static const String parameterCodeLangValue = 'lang'; + static const List<String> availableParameters = [ + parameterCodeGameType, + parameterCodeLangValue, + ]; + + // game type: available values + static const String gameTypeValuePickWord = 'pick-word'; + static const String gameTypeValuePickImage = 'pick-image'; + static const List<String> allowedGameTypeValues = [ + gameTypeValuePickWord, + gameTypeValuePickImage, + ]; + // game type: default value + static const String defaultGameTypeValue = gameTypeValuePickWord; + + // lang: available values + static const String langValueFr = 'fr'; + static const String langValueEn = 'en'; + static const List<String> allowedLangValues = [ + langValueFr, + langValueEn, + ]; + // lang: default value + static const String defaultLangValue = langValueFr; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeGameType: + return DefaultGameSettings.allowedGameTypeValues; + case parameterCodeLangValue: + return DefaultGameSettings.allowedLangValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + parameterCodeGameType, + ]; + + static const int itemsCount = 4; + static const int recentWordsCount = 20; +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..2b98f69b6fc166bd72410cb1e30bdb8f2bdfe36c --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,33 @@ +import 'package:wordguessing/utils/tools.dart'; + +class DefaultGlobalSettings { + // available global parameters codes + static const String parameterCodeSkin = 'skin'; + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + // skin: available values + static const String skinValueDefault = 'default'; + static const List<String> allowedSkinValues = [ + skinValueDefault, + ]; + // skin: default value + static const String defaultSkinValue = skinValueDefault; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart index 13081ea3b21b0c581d8dec4088186de0a98e3683..3d55d36f5ba813bc482957b2589dd3cf5c73423b 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,53 +1,51 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:wordguessing/ui/screens/about_page.dart'; -import 'package:wordguessing/ui/screens/game_page.dart'; -import 'package:wordguessing/ui/screens/settings_page.dart'; +import 'package:wordguessing/ui/screens/page_about.dart'; +import 'package:wordguessing/ui/screens/page_game.dart'; +import 'package:wordguessing/ui/screens/page_settings.dart'; class MenuItem { - final String code; final Icon icon; final Widget page; const MenuItem({ - required this.code, required this.icon, required this.page, }); } class Menu { - static List<MenuItem> items = [ - const MenuItem( - code: 'bottom_nav_home', - icon: Icon(UniconsLine.home), - page: GamePage(), - ), - const MenuItem( - code: 'bottom_nav_settings', - icon: Icon(UniconsLine.setting), - page: SettingsPage(), - ), - const MenuItem( - code: 'bottom_nav_about', - icon: Icon(UniconsLine.info_circle), - page: AboutPage(), - ), - ]; + static const indexGame = 0; + static const menuItemGame = MenuItem( + 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( + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + 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/config/theme.dart b/lib/config/theme.dart index be390348c7868e7c63387df13e13c46de43f8a23..74f532fd5abf693979118609564d29167e902009 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -39,11 +39,9 @@ final ColorScheme lightColorScheme = ColorScheme.light( secondary: primarySwatch.shade500, onSecondary: Colors.white, error: errorColor, - background: textSwatch.shade200, - onBackground: textSwatch.shade500, onSurface: textSwatch.shade500, surface: textSwatch.shade50, - surfaceVariant: Colors.white, + surfaceContainerHighest: Colors.white, shadow: textSwatch.shade900.withOpacity(.1), ); @@ -52,11 +50,9 @@ final ColorScheme darkColorScheme = ColorScheme.dark( secondary: primarySwatch.shade500, onSecondary: Colors.white, error: errorColor, - background: const Color(0xFF171724), - onBackground: textSwatch.shade400, onSurface: textSwatch.shade300, surface: const Color(0xFF262630), - surfaceVariant: const Color(0xFF282832), + surfaceContainerHighest: const Color(0xFF282832), shadow: textSwatch.shade900.withOpacity(.2), ); @@ -192,5 +188,3 @@ final ThemeData darkTheme = lightTheme.copyWith( ), ), ); - -final ThemeData appTheme = darkTheme; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..bd4ccf1393b22e97ff5f4b5a7ff66a3b70aaa3df --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,113 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/models/settings/settings_game.dart'; +import 'package:wordguessing/models/settings/settings_global.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() + : super(GameState( + currentGame: Game.createEmpty(), + )); + + void updateState(Game game) { + emit(GameState( + currentGame: game, + )); + } + + void refresh() { + final Game game = Game( + // Settings + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + // State + isRunning: state.currentGame.isRunning, + isStarted: state.currentGame.isStarted, + isFinished: state.currentGame.isFinished, + animationInProgress: state.currentGame.animationInProgress, + // Base data + word: state.currentGame.word, + otherWords: state.currentGame.otherWords, + images: state.currentGame.images, + // Game data + recentWordsKeys: state.currentGame.recentWordsKeys, + questionsCount: state.currentGame.questionsCount, + goodAnswers: state.currentGame.goodAnswers, + wrongAnswers: state.currentGame.wrongAnswers, + ); + // game.dump(); + + updateState(game); + } + + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + final Game newGame = Game.createNew( + // Settings + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + updateState(newGame); + nextWord(); + + newGame.dump(); + + updateState(newGame); + refresh(); + } + + void quitGame() { + state.currentGame.isRunning = false; + refresh(); + } + + void resumeSavedGame() { + state.currentGame.isRunning = true; + refresh(); + } + + void deleteSavedGame() { + state.currentGame.isRunning = false; + state.currentGame.isFinished = true; + refresh(); + } + + void nextWord() { + state.currentGame.pickNewWord(); + refresh(); + } + + void checkWord(word) { + if (state.currentGame.word.key == word.key) { + state.currentGame.goodAnswers++; + nextWord(); + } else { + state.currentGame.wrongAnswers++; + } + refresh(); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + final Game currentGame = json['currentGame'] as Game; + + return GameState( + currentGame: currentGame, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'currentGame': state.currentGame.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..00e211668c3269255926939324355792abd61c41 --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,15 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + required this.currentGame, + }); + + final Game currentGame; + + @override + List<dynamic> get props => <dynamic>[ + 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 96d5a6948ba1212c462227ef08403039b55e266d..889605d765c462238201538baf27ce4c929d2cc0 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:wordguessing/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_game_cubit.dart b/lib/cubit/settings_game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..d7d021010b5ae44542d23387045000eb2c7c9be0 --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,72 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/models/settings/settings_game.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + String? gameType, + String? lang, + }) { + emit( + GameSettingsState( + settings: GameSettings( + gameType: gameType ?? state.settings.gameType, + lang: lang ?? state.settings.lang, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGameSettings.parameterCodeGameType: + return GameSettings.getGameTypeValueFromUnsafe(state.settings.gameType); + case DefaultGameSettings.parameterCodeLangValue: + return GameSettings.getLangValueFromUnsafe(state.settings.lang); + } + + return ''; + } + + void setParameterValue(String code, String value) { + final String gameType = code == DefaultGameSettings.parameterCodeGameType + ? value + : getParameterValue(DefaultGameSettings.parameterCodeGameType); + final String lang = code == DefaultGameSettings.parameterCodeLangValue + ? value + : getParameterValue(DefaultGameSettings.parameterCodeLangValue); + + setValues( + gameType: gameType, + lang: lang, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + final String gameType = json[DefaultGameSettings.parameterCodeGameType] as String; + final String lang = json[DefaultGameSettings.parameterCodeLangValue] as String; + + return GameSettingsState( + settings: GameSettings( + gameType: gameType, + lang: lang, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeGameType: state.settings.gameType, + DefaultGameSettings.parameterCodeLangValue: state.settings.lang, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..5acd85b44ba541e1c5e9c26af1c4be26a385b9ed --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,15 @@ +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, + ]; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..9b3f66099e68097a9d905b838626157fa676bc4e --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:wordguessing/config/default_global_settings.dart'; +import 'package:wordguessing/models/settings/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..ebcddd700f252257223ca8e16c85202b04f3ff24 --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,15 @@ +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, + ]; +} diff --git a/lib/data/fetch_data_helper.dart b/lib/data/fetch_data_helper.dart index 848f8240f2399568a09f6684213370b6cc827bf3..7e27529b88b668563ce3920f5801756fa4cf1b3b 100644 --- a/lib/data/fetch_data_helper.dart +++ b/lib/data/fetch_data_helper.dart @@ -1,5 +1,5 @@ import 'package:wordguessing/data/game_data.dart'; -import 'package:wordguessing/models/word.dart'; +import 'package:wordguessing/models/data/word.dart'; import 'package:wordguessing/utils/tools.dart'; class FetchDataHelper { @@ -38,7 +38,7 @@ class FetchDataHelper { } } - List<Word> getWords(String lang, int count) { + List<Word> getWords({required String lang, required int count}) { if (_words.isEmpty || lang != _lang) { init(lang); } diff --git a/lib/main.dart b/lib/main.dart index 5dac5d3c52ce806d2155eae497dcae8f22810553..3a627ad614efa29a41b00535e1957f66f3fcc275 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,20 +2,23 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:provider/provider.dart'; +import 'package:wordguessing/config/default_game_settings.dart'; import 'package:wordguessing/config/theme.dart'; -import 'package:wordguessing/cubit/bottom_nav_cubit.dart'; +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/cubit/nav_cubit.dart'; +import 'package:wordguessing/cubit/settings_game_cubit.dart'; +import 'package:wordguessing/cubit/settings_global_cubit.dart'; import 'package:wordguessing/cubit/theme_cubit.dart'; -import 'package:wordguessing/provider/data.dart'; import 'package:wordguessing/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -24,18 +27,17 @@ void main() async { storageDirectory: tmpDir, ); - runApp( - EasyLocalization( - path: 'assets/translations', - supportedLocales: const <Locale>[ - Locale('en'), - Locale('fr'), - ], - fallbackLocale: const Locale('en'), - useFallbackTranslations: true, - child: const MyApp(), - ), - ); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) + .then((value) => runApp(EasyLocalization( + path: 'assets/translations', + supportedLocales: const <Locale>[ + Locale('en'), + Locale('fr'), + ], + fallbackLocale: const Locale('en'), + useFallbackTranslations: true, + child: const MyApp(), + ))); } class MyApp extends StatelessWidget { @@ -43,36 +45,62 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { + final List<String> assets = getImagesAssets(); + for (String asset in assets) { + precacheImage(AssetImage(asset), context); + } + return MultiBlocProvider( providers: [ - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + 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: 'Jeux de mots et lettres', - home: const SkeletonScreen(), + builder: (BuildContext context, ThemeModeState state) { + return MaterialApp( + title: 'Jeux de mots et lettres', + home: const SkeletonScreen(), - // Theme stuff - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, - // Localization stuff - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ); - }, - ), - ); - }), + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ); + }, + ), ); } + + List<String> getImagesAssets() { + final List<String> assets = []; + + final List<String> gameImages = [ + 'button_back', + 'button_delete_saved_game', + 'button_resume_game', + 'button_start', + 'game_fail', + 'game_win', + 'placeholder', + ]; + + for (String gameType in DefaultGameSettings.allowedGameTypeValues) { + gameImages.add('${DefaultGameSettings.parameterCodeGameType}_$gameType'); + } + + for (String image in gameImages) { + assets.add('assets/ui/$image.png'); + } + + return assets; + } } diff --git a/lib/models/word.dart b/lib/models/data/word.dart similarity index 53% rename from lib/models/word.dart rename to lib/models/data/word.dart index bd5dfc3c448102e563fb358f5e94ad8700adf45a..f6b29e955dc367fe2163d114b36bbbda03cff6fd 100644 --- a/lib/models/word.dart +++ b/lib/models/data/word.dart @@ -9,16 +9,24 @@ class Word { required this.images, }); - Map<String, dynamic> toJson() { - return { - 'key': key, - 'text': text, - 'images': images.toString(), - }; + factory Word.createEmpty() { + return const Word( + key: '', + text: '', + images: [], + ); } @override String toString() { - return toJson().toString(); + return '$Word(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'key': key, + 'text': text, + 'images': images, + }; } } diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..7ea682d127904ffdc13775537db49ab60ffb483c --- /dev/null +++ b/lib/models/game/game.dart @@ -0,0 +1,219 @@ +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/data/fetch_data_helper.dart'; +import 'package:wordguessing/models/data/word.dart'; +import 'package:wordguessing/models/settings/settings_game.dart'; +import 'package:wordguessing/models/settings/settings_global.dart'; +import 'package:wordguessing/utils/tools.dart'; + +class Game { + Game({ + // Settings + required this.gameSettings, + required this.globalSettings, + + // State + this.isRunning = false, + this.isStarted = false, + this.isFinished = false, + this.animationInProgress = false, + + // Base data + required this.word, + required this.otherWords, + required this.images, + + // Game data + this.recentWordsKeys = const [], + this.questionsCount = 0, + this.goodAnswers = 0, + this.wrongAnswers = 0, + }); + + // Settings + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + // State + bool isRunning; + bool isStarted; + bool isFinished; + bool animationInProgress; + + // Base data + Word word; + List<Word> otherWords; + List<Word> images; + + // Game data + List<String> recentWordsKeys; + int questionsCount; + int goodAnswers; + int wrongAnswers; + + factory Game.createEmpty() { + return Game( + // Settings + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + // Base data + word: Word.createEmpty(), + otherWords: [], + images: [], + // Game data + recentWordsKeys: [], + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + return Game( + // Settings + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + // State + isRunning: true, + // Base data + word: Word.createEmpty(), + otherWords: [], + images: [], + // Game data + recentWordsKeys: [], + ); + } + + bool get canBeResumed => isStarted && !isFinished; + + bool get gameWon => isRunning && isStarted && isFinished; + + void updateWord(Word newWord) { + word = newWord; + if (newWord.key != '') { + recentWordsKeys.insert(0, newWord.key); + recentWordsKeys = recentWordsKeys.take(DefaultGameSettings.recentWordsCount).toList(); + } + } + + void pickNewWord() { + switch (gameSettings.gameType) { + case DefaultGameSettings.gameTypeValuePickImage: + pickDataForGamePickImage(); + break; + case DefaultGameSettings.gameTypeValuePickWord: + pickDataForGamePickWord(); + break; + default: + } + + questionsCount++; + } + + bool isRecentlyPicked(String key) { + return recentWordsKeys.contains(key); + } + + void pickDataForGamePickImage() { + Word word; + + int attempts = 0; + do { + final List<Word> words = FetchDataHelper().getWords( + lang: gameSettings.lang, + count: DefaultGameSettings.itemsCount, + ); + + word = words.take(1).toList()[0]; + + if ((words.length == DefaultGameSettings.itemsCount) && !isRecentlyPicked(word.key)) { + updateWord(word); + + final List<Word> pickedImages = []; + for (var element in words) { + pickedImages.add(element); + } + + images = pickedImages; + } + + attempts++; + } while (word != word && attempts < 10); + } + + void pickDataForGamePickWord() { + Word word; + + int attempts = 0; + do { + final List<Word> words = FetchDataHelper().getWords( + lang: gameSettings.lang, + count: DefaultGameSettings.itemsCount, + ); + + word = words.take(1).toList()[0]; + + if ((words.length >= DefaultGameSettings.itemsCount) && !isRecentlyPicked(word.key)) { + updateWord(word); + otherWords = words.skip(1).toList(); + images = [word]; + } + + attempts++; + } while (word != word && attempts < 10); + } + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + printlog('$Game:'); + printlog(' Settings'); + gameSettings.dump(); + globalSettings.dump(); + printlog(' State'); + printlog(' isRunning: $isRunning'); + printlog(' isStarted: $isStarted'); + printlog(' isFinished: $isFinished'); + printlog(' animationInProgress: $animationInProgress'); + printlog(' Base data'); + printlog(' word: $word'); + printlog(' otherWords: $otherWords'); + printlog(' images: $images'); + printlog(' Game data'); + printlog(' recentWordsKeys: $recentWordsKeys'); + printlog(' questionsCount: $questionsCount'); + printlog(' goodAnswers: $goodAnswers'); + printlog(' wrongAnswers: $wrongAnswers'); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + // Settings + 'gameSettings': gameSettings.toJson(), + 'globalSettings': globalSettings.toJson(), + // State + 'isRunning': isRunning, + 'isStarted': isStarted, + 'isFinished': isFinished, + 'animationInProgress': animationInProgress, + // Base data + 'word': word.toJson(), + 'otherWords': otherWords, + 'images': images, + // Game data + 'recentWordsKeys': recentWordsKeys, + 'questionsCount': questionsCount, + 'goodAnswers': goodAnswers, + 'wrongAnswers': wrongAnswers, + }; + } +} diff --git a/lib/models/settings/settings_game.dart b/lib/models/settings/settings_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..09272f47cdcfddeed9b532630d0f08bf2908536a --- /dev/null +++ b/lib/models/settings/settings_game.dart @@ -0,0 +1,54 @@ +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/utils/tools.dart'; + +class GameSettings { + final String gameType; + final String lang; + + GameSettings({ + required this.gameType, + required this.lang, + }); + + static String getGameTypeValueFromUnsafe(String gameType) { + if (DefaultGameSettings.allowedGameTypeValues.contains(gameType)) { + return gameType; + } + + return DefaultGameSettings.defaultGameTypeValue; + } + + static String getLangValueFromUnsafe(String lang) { + if (DefaultGameSettings.allowedLangValues.contains(lang)) { + return lang; + } + + return DefaultGameSettings.defaultLangValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + gameType: DefaultGameSettings.defaultGameTypeValue, + lang: DefaultGameSettings.defaultLangValue, + ); + } + + void dump() { + printlog('$GameSettings:'); + printlog(' ${DefaultGameSettings.parameterCodeGameType}: $gameType'); + printlog(' ${DefaultGameSettings.parameterCodeLangValue}: $lang'); + printlog(''); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeGameType: gameType, + DefaultGameSettings.parameterCodeLangValue: lang, + }; + } +} diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..3072b1fbfd8c910e290c84c37feab6242b7bfbaa --- /dev/null +++ b/lib/models/settings/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:wordguessing/config/default_global_settings.dart'; +import 'package:wordguessing/utils/tools.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings:'); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 476ed718cdc77abe927802975ba9890cc9ff62a7..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter/foundation.dart'; - -import 'package:wordguessing/models/word.dart'; - -enum GameType { pickWord, pickImage } - -class Data extends ChangeNotifier { - GameType? _gameType; - - // Language - String _lang = ''; - - // randomization - Word? _word; - List<Word> _otherWords = []; - List<Word> _images = []; - final int _recentWordsCount = 20; - List<String> _recentWordsKeys = []; - - // game data - int _questionsCount = 0; - int _goodAnswers = 0; - int _wrongAnswers = 0; - - GameType? get gameType => _gameType; - - void updateGameType(GameType gameType) { - _gameType = gameType; - notifyListeners(); - } - - void resetGameType() { - _gameType = null; - notifyListeners(); - } - - String get lang => _lang; - - void updateLang(String value) { - _lang = value; - notifyListeners(); - } - - Word? get word => _word; - - void updateWord(Word? newWord) { - _word = newWord; - if ((newWord != null) && (newWord.key != '')) { - _recentWordsKeys.insert(0, newWord.key); - _recentWordsKeys = _recentWordsKeys.take(_recentWordsCount).toList(); - } - notifyListeners(); - } - - bool isRecentlyPicked(String word) { - return _recentWordsKeys.contains(word); - } - - List<Word> get otherWords => _otherWords; - - void updateOtherWords(List<Word> words) { - _otherWords = words; - notifyListeners(); - } - - List<Word> get images => _images; - - void updateImages(List<Word> images) { - _images = images; - notifyListeners(); - } - - void resetGame() { - resetGameType(); - updateLang(''); - updateQuestionsCount(0); - updateGoodAnswers(0); - updateWrongAnswers(0); - updateWord(null); - updateOtherWords([]); - updateImages([]); - notifyListeners(); - } - - int get questionsCount => _questionsCount; - - void updateQuestionsCount(int value) { - _questionsCount = value; - notifyListeners(); - } - - int get goodAnswers => _goodAnswers; - - void updateGoodAnswers(int value) { - _goodAnswers = value; - notifyListeners(); - } - - int get wrongAnswers => _wrongAnswers; - - void updateWrongAnswers(int value) { - _wrongAnswers = value; - notifyListeners(); - } -} diff --git a/lib/ui/game/game_end.dart b/lib/ui/game/game_end.dart new file mode 100644 index 0000000000000000000000000000000000000000..f760ccf5ec901b0fce95c33378004ef401c01ab2 --- /dev/null +++ b/lib/ui/game/game_end.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/widgets/actions/button_game_quit.dart'; + +class GameEndWidget extends StatelessWidget { + const GameEndWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final Image decorationImage = Image( + image: AssetImage( + currentGame.gameWon ? 'assets/ui/game_win.png' : 'assets/ui/game_fail.png'), + fit: BoxFit.fill, + ); + + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + TableRow( + children: [ + Column( + children: [decorationImage], + ), + Column( + children: [ + currentGame.animationInProgress == true + ? decorationImage + : const QuitGameButton() + ], + ), + Column( + children: [decorationImage], + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/game/game_top.dart b/lib/ui/game/game_top.dart new file mode 100644 index 0000000000000000000000000000000000000000..480f5c760751067cc0e3ee8e997620634ada7ef8 --- /dev/null +++ b/lib/ui/game/game_top.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +import 'package:wordguessing/ui/widgets/game/score_bar.dart'; + +class GameTopWidget extends StatelessWidget { + const GameTopWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const ScoreBarWidget(); + } +} diff --git a/lib/ui/games/abstract_game.dart b/lib/ui/games/abstract_game.dart deleted file mode 100644 index 84b45fea06b7002bd172d0961f5706dd02470aab..0000000000000000000000000000000000000000 --- a/lib/ui/games/abstract_game.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:wordguessing/provider/data.dart'; - -class GameAbstract extends StatelessWidget { - const GameAbstract({super.key}); - - final int countWords = 4; - - void startGame(Data myProvider, String lang) { - myProvider.updateLang(lang); - nextWord(myProvider); - } - - void nextWord(Data myProvider) { - pickData(myProvider); - myProvider.updateQuestionsCount(myProvider.questionsCount + 1); - } - - void pickData(Data myProvider) {} - - void checkWord(Data myProvider, word) { - if (myProvider.word?.key == word.key) { - myProvider.updateGoodAnswers(myProvider.goodAnswers + 1); - nextWord(myProvider); - } else { - myProvider.updateWrongAnswers(myProvider.wrongAnswers + 1); - } - } - - Widget buildScoreItemContainer(String text, Color blockColor) { - // Darken block color to get border color - const double amount = 0.2; - final hsl = HSLColor.fromColor(blockColor); - final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); - final Color borderColor = hslDark.toColor(); - - return Container( - margin: const EdgeInsets.symmetric(horizontal: 15), - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: blockColor, - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: borderColor, - width: 4, - ), - ), - child: Text( - text, - style: const TextStyle( - fontSize: 25, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - ); - } - - String getGoodAnswersString(Data myProvider) { - final int count = myProvider.questionsCount - 1; - return '👍 ${myProvider.goodAnswers}/$count'; - } - - String getWrongAnswersString(Data myProvider) { - final int count = myProvider.questionsCount - 1; - return '🚩 ${myProvider.wrongAnswers}/$count'; - } - - Widget buildScoreContainer(Data myProvider) { - return Container( - padding: const EdgeInsets.all(5), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildScoreItemContainer( - getGoodAnswersString(myProvider), - Colors.green, - ), - const SizedBox(width: 20), - buildScoreItemContainer( - getWrongAnswersString(myProvider), - Colors.orange, - ), - ], - ), - ); - } - - Widget buildStartGameBlock(Data myProvider) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Colors.teal, - backgroundColor: Colors.teal, - padding: const EdgeInsets.all(35), - ), - child: const Text( - "🇫🇷", - style: TextStyle( - fontSize: 60, - color: Colors.black, - ), - ), - onPressed: () => startGame(myProvider, 'fr'), - ), - const SizedBox(height: 15), - ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Colors.teal, - backgroundColor: Colors.teal, - padding: const EdgeInsets.all(35), - ), - child: const Text( - "🇬🇧", - style: TextStyle( - fontSize: 60, - color: Colors.black, - ), - ), - onPressed: () => startGame(myProvider, 'en'), - ), - ], - ); - } - - List<Widget> buildPageContent(Data myProvider) { - return <Widget>[]; - } - - Widget buildPage(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - - return SizedBox.expand( - child: FittedBox( - fit: BoxFit.contain, - alignment: Alignment.center, - child: SizedBox( - height: (MediaQuery.of(context).size.height), - width: (MediaQuery.of(context).size.width), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: buildPageContent(myProvider), - ), - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return buildPage(context); - } -} diff --git a/lib/ui/games/game_pick_image.dart b/lib/ui/games/game_pick_image.dart deleted file mode 100644 index d6530fedf2135302714699817da9fa3d130435b5..0000000000000000000000000000000000000000 --- a/lib/ui/games/game_pick_image.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:wordguessing/data/fetch_data_helper.dart'; -import 'package:wordguessing/models/word.dart'; -import 'package:wordguessing/provider/data.dart'; -import 'package:wordguessing/ui/games/abstract_game.dart'; - -class GamePickImagePage extends GameAbstract { - const GamePickImagePage({super.key}); - - @override - pickData(Data myProvider) { - Word word; - - int attempts = 0; - do { - final List<Word> words = FetchDataHelper().getWords(myProvider.lang, countWords); - - word = words.take(1).toList()[0]; - - if ((words.length == countWords) && !myProvider.isRecentlyPicked(word.key)) { - myProvider.updateWord(word); - - final List<Word> images = []; - for (var element in words) { - images.add(element); - } - - myProvider.updateImages(images); - } - - attempts++; - } while (myProvider.word != word && attempts < 10); - } - - Widget buildImageContainer(Data myProvider, Word word) { - const double imageSize = 130; - - String imageAsset = 'assets/placeholder.png'; - if ((word.images.isNotEmpty)) { - imageAsset = 'assets/images/${word.images[0]}'; - } - - return TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.blue.shade200, - width: 8, - ), - ), - margin: const EdgeInsets.all(2), - child: Image( - image: AssetImage(imageAsset), - width: imageSize, - height: imageSize, - fit: BoxFit.fill, - ), - ), - onPressed: () { - checkWord(myProvider, word); - }, - ); - } - - Widget buildImageItemsBlock(Data myProvider) { - final List<Word> images = myProvider.images; - - if (images.length != countWords) { - return const SizedBox.shrink(); - } - - images.sort((a, b) => a.key.compareTo(b.key)); - - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildImageContainer(myProvider, images[0]), - buildImageContainer(myProvider, images[1]), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildImageContainer(myProvider, images[2]), - buildImageContainer(myProvider, images[3]), - ], - ) - ], - ); - } - - Widget buildTextItemBlock(Data myProvider) { - final Word? word = myProvider.word; - - if (word == null) { - return const SizedBox.shrink(); - } - - return Container( - decoration: BoxDecoration( - color: Colors.blue.shade800, - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: Colors.white, - width: 6, - ), - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - word.text, - style: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ); - } - - @override - List<Widget> buildPageContent(Data myProvider) { - return <Widget>[ - buildTextItemBlock(myProvider), - const SizedBox(height: 2), - ((myProvider.word == null) || (myProvider.word?.key == '')) - ? buildStartGameBlock(myProvider) - : buildScoreContainer(myProvider), - const SizedBox(height: 2), - buildImageItemsBlock(myProvider), - ]; - } -} diff --git a/lib/ui/games/game_pick_word.dart b/lib/ui/games/game_pick_word.dart deleted file mode 100644 index 6d68235c7195d41cf2c1fb1db4451f83007c12ea..0000000000000000000000000000000000000000 --- a/lib/ui/games/game_pick_word.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:wordguessing/data/fetch_data_helper.dart'; -import 'package:wordguessing/models/word.dart'; -import 'package:wordguessing/provider/data.dart'; -import 'package:wordguessing/ui/games/abstract_game.dart'; - -class GamePickWordPage extends GameAbstract { - const GamePickWordPage({super.key}); - - @override - void pickData(Data myProvider) { - Word word; - - int attempts = 0; - do { - final List<Word> words = FetchDataHelper().getWords(myProvider.lang, countWords); - - word = words.take(1).toList()[0]; - - if ((words.length >= countWords) && !myProvider.isRecentlyPicked(word.key)) { - myProvider.updateWord(word); - myProvider.updateOtherWords(words.skip(1).toList()); - myProvider.updateImages([word]); - } - - attempts++; - } while (myProvider.word != word && attempts < 10); - } - - Widget buildImageContainer(String image) { - const double imageSize = 130; - - String imageAsset = 'assets/placeholder.png'; - if (image != '') { - imageAsset = 'assets/images/$image'; - } - - return Container( - margin: const EdgeInsets.all(2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.blue.shade200, - width: 8, - ), - ), - child: Image( - image: AssetImage(imageAsset), - width: imageSize, - height: imageSize, - fit: BoxFit.fill, - ), - ); - } - - Widget buildImageItemsBlock(Word? currentWord) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildImageContainer(currentWord?.images[0] ?? ''), - buildImageContainer(currentWord?.images[1] ?? ''), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildImageContainer(currentWord?.images[2] ?? ''), - buildImageContainer(currentWord?.images[3] ?? ''), - ], - ) - ], - ); - } - - Widget buildTextContainer(Data myProvider, Word word) { - return Container( - margin: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: Colors.blue[800], - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: Colors.white, - width: 6, - ), - ), - child: TextButton( - style: TextButton.styleFrom( - padding: const EdgeInsets.all(15), - ), - child: Text( - word.text, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - onPressed: () { - checkWord(myProvider, word); - }, - ), - ); - } - - Widget buildTextItemsBlock(Data myProvider) { - Word? word = myProvider.word; - List<Word> otherWords = myProvider.otherWords; - - if ((word == null) || (otherWords.length != (countWords - 1))) { - return const Column(); - } - - List<Word> words = [ - word, - otherWords[0], - otherWords[1], - otherWords[2], - ]; - - words.sort((a, b) => a.key.compareTo(b.key)); - - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildTextContainer(myProvider, words[0]), - const SizedBox(width: 10), - buildTextContainer(myProvider, words[1]), - ], - ), - const SizedBox(height: 5), - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildTextContainer(myProvider, words[2]), - const SizedBox(width: 10), - buildTextContainer(myProvider, words[3]), - ], - ) - ], - ); - } - - @override - List<Widget> buildPageContent(Data myProvider) { - return <Widget>[ - (myProvider.images.isNotEmpty) - ? buildImageItemsBlock(myProvider.images[0]) - : const SizedBox.shrink(), - const SizedBox(height: 2), - ((myProvider.word == null) || (myProvider.word?.key == '')) - ? buildStartGameBlock(myProvider) - : buildScoreContainer(myProvider), - const SizedBox(height: 2), - buildTextItemsBlock(myProvider), - ]; - } -} diff --git a/lib/ui/games/game_selector.dart b/lib/ui/games/game_selector.dart deleted file mode 100644 index 882d9521f02c8d42368f1b51d34456b50465ff6a..0000000000000000000000000000000000000000 --- a/lib/ui/games/game_selector.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:wordguessing/provider/data.dart'; - -class GameSelector extends StatelessWidget { - const GameSelector({super.key}); - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - - Widget buildMenuItemContainer(String code, Color color) { - const double imageSize = 150; - - final String imageAsset = 'assets/menu/$code.png'; - - return Container( - margin: const EdgeInsets.all(2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: color, - width: 12, - ), - ), - child: GestureDetector( - child: Image( - image: AssetImage(imageAsset), - width: imageSize, - height: imageSize, - fit: BoxFit.fill, - ), - onTap: () { - myProvider.resetGame(); - switch (code) { - case 'game-pick-word': - myProvider.updateGameType(GameType.pickWord); - break; - case 'game-pick-image': - myProvider.updateGameType(GameType.pickImage); - break; - default: - } - }, - ), - ); - } - - Widget content = Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - buildMenuItemContainer('game-pick-word', Colors.pink), - buildMenuItemContainer('game-pick-image', Colors.yellow), - ], - ), - ); - - return content; - } -} diff --git a/lib/ui/helpers/app_titles.dart b/lib/ui/helpers/app_titles.dart new file mode 100644 index 0000000000000000000000000000000000000000..b98107b12fabc3114ebfbec994166b588abcf1ad --- /dev/null +++ b/lib/ui/helpers/app_titles.dart @@ -0,0 +1,32 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppHeader extends StatelessWidget { + const AppHeader({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + ); + } +} + +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.titleLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..bcc628aff9437c9e448df828c763a2e6c1a565d5 --- /dev/null +++ b/lib/ui/helpers/outlined_text_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +import 'package:wordguessing/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/layouts/game_layout.dart b/lib/ui/layouts/game_layout.dart new file mode 100644 index 0000000000000000000000000000000000000000..e4db92c4e0e71c32e8c60ceeb2fe0b3d538a7b97 --- /dev/null +++ b/lib/ui/layouts/game_layout.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/game/game_end.dart'; +import 'package:wordguessing/ui/game/game_top.dart'; +import 'package:wordguessing/ui/widgets/game/game_board.dart'; + +class GameLayout extends StatelessWidget { + const GameLayout({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Container( + alignment: AlignmentDirectional.topCenter, + padding: const EdgeInsets.all(4), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const GameTopWidget(), + const SizedBox(height: 8), + const GameBoardWidget(), + const Expanded(child: SizedBox.shrink()), + currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/layouts/parameters_layout.dart b/lib/ui/layouts/parameters_layout.dart new file mode 100644 index 0000000000000000000000000000000000000000..ff114f36908b371bdd9be1721a844c2be3f6764d --- /dev/null +++ b/lib/ui/layouts/parameters_layout.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/config/default_global_settings.dart'; +import 'package:wordguessing/cubit/settings_game_cubit.dart'; +import 'package:wordguessing/cubit/settings_global_cubit.dart'; +import 'package:wordguessing/ui/parameters/parameter_image.dart'; +import 'package:wordguessing/ui/parameters/parameter_painter.dart'; +import 'package:wordguessing/ui/widgets/actions/button_delete_saved_game.dart'; +import 'package:wordguessing/ui/widgets/actions/button_game_start_new.dart'; +import 'package:wordguessing/ui/widgets/actions/button_resume_saved_game.dart'; + +class ParametersLayout extends StatelessWidget { + const ParametersLayout({super.key, required this.canResume}); + + final bool canResume; + + 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)); + + if (canResume == false) { + // Start new game + lines.add(const Expanded( + child: StartNewGameButton(), + )); + } else { + // Resume game + lines.add(const Expanded( + child: ResumeSavedGameButton(), + )); + // Delete saved game + lines.add(SizedBox.square( + dimension: MediaQuery.of(context).size.width / 4, + child: const DeleteSavedGameButton(), + )); + } + + 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<String> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultGameSettings.getAvailableValues(code); + + if (availableValues.length <= 1) { + return []; + } + + for (String 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 String 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; + + final bool displayedWithAssets = + DefaultGlobalSettings.displayedWithAssets.contains(code) || + DefaultGameSettings.displayedWithAssets.contains(code); + + return TextButton( + child: Container( + child: displayedWithAssets + ? SizedBox.square( + dimension: itemWidth, + child: ParameterImage( + code: code, + value: value, + isSelected: isActive, + ), + ) + : 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/parameters/parameter_image.dart b/lib/ui/parameters/parameter_image.dart new file mode 100644 index 0000000000000000000000000000000000000000..fc4b576f85b01158b74548400d11a4d027c57fbe --- /dev/null +++ b/lib/ui/parameters/parameter_image.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +class ParameterImage extends StatelessWidget { + const ParameterImage({ + super.key, + required this.code, + required this.value, + required this.isSelected, + }); + + final String code; + final String value; + final bool isSelected; + + static const Color buttonBackgroundColor = Colors.white; + static const Color buttonBorderColorActive = Colors.blue; + static const Color buttonBorderColorInactive = Colors.white; + static const double buttonBorderWidth = 8.0; + static const double buttonBorderRadius = 8.0; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: buttonBackgroundColor, + borderRadius: BorderRadius.circular(buttonBorderRadius), + border: Border.all( + color: isSelected ? buttonBorderColorActive : buttonBorderColorInactive, + width: buttonBorderWidth, + ), + ), + child: Image( + image: AssetImage('assets/ui/${code}_$value.png'), + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/ui/parameters/parameter_painter.dart b/lib/ui/parameters/parameter_painter.dart new file mode 100644 index 0000000000000000000000000000000000000000..9fe8daed2b2b33dff467d21e9cdf4404ae3167db --- /dev/null +++ b/lib/ui/parameters/parameter_painter.dart @@ -0,0 +1,148 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/models/settings/settings_game.dart'; +import 'package:wordguessing/models/settings/settings_global.dart'; +import 'package:wordguessing/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 String 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.parameterCodeLangValue: + paintLangParameterItem(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 String 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 paintLangParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + String text = ''; + + switch (value) { + case DefaultGameSettings.langValueFr: + text = '🇫🇷'; + backgroundColor = Colors.teal; + break; + case DefaultGameSettings.langValueEn: + text = '🇬🇧'; + backgroundColor = Colors.teal; + break; + default: + printlog('Wrong value for lang parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3; + + // 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 df3c84376e9d37a3040d09e0665f19d3abca2165..0000000000000000000000000000000000000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:wordguessing/provider/data.dart'; -import 'package:wordguessing/ui/games/game_pick_image.dart'; -import 'package:wordguessing/ui/games/game_pick_word.dart'; -import 'package:wordguessing/ui/games/game_selector.dart'; - -class GamePage extends StatelessWidget { - const GamePage({super.key}); - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - - switch (myProvider.gameType) { - case GameType.pickImage: - return const GamePickImagePage(); - case GameType.pickWord: - return const GamePickWordPage(); - default: - return const GameSelector(); - } - } -} 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 05d19322774be26931233ecd40beb1adffe27b55..9ea18e98836e10870e541327731974eac5ea7172 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:wordguessing/ui/widgets/header_app.dart'; +import 'package:wordguessing/ui/helpers/app_titles.dart'; -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); @override Widget build(BuildContext context) { @@ -17,7 +17,7 @@ class AboutPage extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: <Widget>[ const SizedBox(height: 8), - const AppHeader(text: 'about_title'), + const AppTitle(text: 'about_title'), const Text('about_content').tr(), FutureBuilder<PackageInfo>( future: PackageInfo.fromPlatform(), diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..402d3c0a7a4186483154dc8d655deab11ef8931e --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/layouts/game_layout.dart'; +import 'package:wordguessing/ui/layouts/parameters_layout.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return currentGame.isRunning + ? const GameLayout() + : ParametersLayout(canResume: currentGame.canBeResumed); + }, + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/page_settings.dart similarity index 65% rename from lib/ui/screens/settings_page.dart rename to lib/ui/screens/page_settings.dart index df50c147d84f6b0b8358bdc18fc6898cadefc1fb..a72773940501e2a7fae3324ff4894fdd4ce88efc 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:wordguessing/ui/widgets/header_app.dart'; -import 'package:wordguessing/ui/widgets/settings/settings_form.dart'; +import 'package:wordguessing/ui/helpers/app_titles.dart'; +import 'package:wordguessing/ui/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) { @@ -16,7 +16,7 @@ class SettingsPage extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: <Widget>[ SizedBox(height: 8), - AppHeader(text: 'settings_title'), + AppTitle(text: 'settings_title'), SizedBox(height: 8), SettingsForm(), ], diff --git a/lib/ui/widgets/settings/settings_form.dart b/lib/ui/settings/settings_form.dart similarity index 96% rename from lib/ui/widgets/settings/settings_form.dart rename to lib/ui/settings/settings_form.dart index cb59277121264dace76f59d41290687c575f3a4f..bb30d8d437ff7968f32669f312a7875a0bc85ac2 100644 --- a/lib/ui/widgets/settings/settings_form.dart +++ b/lib/ui/settings/settings_form.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:wordguessing/ui/widgets/settings/theme_card.dart'; +import 'package:wordguessing/ui/settings/theme_card.dart'; class SettingsForm extends StatefulWidget { const SettingsForm({super.key}); diff --git a/lib/ui/widgets/settings/theme_card.dart b/lib/ui/settings/theme_card.dart similarity index 100% rename from lib/ui/widgets/settings/theme_card.dart rename to lib/ui/settings/theme_card.dart diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index bc0a8a0bffaf7382bcf8473fd6ecfd12aff9b37a..f3f33c41559decac392bd0a9a4ec097823adbed4 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,49 +1,34 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_swipe/flutter_swipe.dart'; -import 'package:provider/provider.dart'; import 'package:wordguessing/config/menu.dart'; -import 'package:wordguessing/cubit/bottom_nav_cubit.dart'; -import 'package:wordguessing/provider/data.dart'; -import 'package:wordguessing/ui/widgets/app_bar.dart'; -import 'package:wordguessing/ui/widgets/bottom_nav_bar.dart'; +import 'package:wordguessing/cubit/nav_cubit.dart'; +import 'package:wordguessing/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) { - final Data myProvider = Provider.of<Data>(context); - return Scaffold( + appBar: const GlobalAppBar(), extendBodyBehindAppBar: false, - appBar: StandardAppBar(myProvider: myProvider), - 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.surface, + 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, + backgroundColor: Theme.of(context).colorScheme.surface, ); } } diff --git a/lib/ui/widgets/actions/button_delete_saved_game.dart b/lib/ui/widgets/actions/button_delete_saved_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..0bf51b1734d40d31bec6ee6b3c05660b3d916b78 --- /dev/null +++ b/lib/ui/widgets/actions/button_delete_saved_game.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; + +class DeleteSavedGameButton extends StatelessWidget { + const DeleteSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_delete_saved_game.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).deleteSavedGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_quit.dart b/lib/ui/widgets/actions/button_game_quit.dart new file mode 100644 index 0000000000000000000000000000000000000000..db54a2b3ed81c05ab53d990f98d853848a86766a --- /dev/null +++ b/lib/ui/widgets/actions/button_game_quit.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; + +class QuitGameButton extends StatelessWidget { + const QuitGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).quitGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_game_start_new.dart b/lib/ui/widgets/actions/button_game_start_new.dart new file mode 100644 index 0000000000000000000000000000000000000000..dede701ccd80bc41b5cc3e14154694836cceb50f --- /dev/null +++ b/lib/ui/widgets/actions/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/cubit/settings_game_cubit.dart'; +import 'package:wordguessing/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) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ); + }, + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/actions/button_resume_saved_game.dart b/lib/ui/widgets/actions/button_resume_saved_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e26624a12339a7da2eaf8f801f667d068c049b8 --- /dev/null +++ b/lib/ui/widgets/actions/button_resume_saved_game.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; + +class ResumeSavedGameButton extends StatelessWidget { + const ResumeSavedGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return TextButton( + child: const Image( + image: AssetImage('assets/ui/button_resume_game.png'), + fit: BoxFit.fill, + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).resumeSavedGame(); + }, + ); + } +} diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index 73038ecffd02c00b457707a647a0b9b57ebdaa3f..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:wordguessing/provider/data.dart'; -import 'package:wordguessing/ui/widgets/header_app.dart'; - -class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { - const StandardAppBar({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final List<Widget> menuActions = []; - - if (myProvider.gameType != null) { - menuActions.add( - IconButton( - icon: const Icon(Icons.loop), - onPressed: () => myProvider.resetGame(), - ), - ); - } - - return AppBar( - title: const AppHeader(text: 'app_name'), - actions: menuActions, - ); - } - - @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 4e1d708cac14eb145f0934a92f552782d72ca978..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_swipe/flutter_swipe.dart'; - -import 'package:wordguessing/config/menu.dart'; -import 'package:wordguessing/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/game/game_board.dart b/lib/ui/widgets/game/game_board.dart new file mode 100644 index 0000000000000000000000000000000000000000..45ae0e82c7cd2a5fa848afbc940341a3aef0e69d --- /dev/null +++ b/lib/ui/widgets/game/game_board.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/widgets/game/game_pick_image.dart'; +import 'package:wordguessing/ui/widgets/game/game_pick_word.dart'; + +class GameBoardWidget extends StatelessWidget { + const GameBoardWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + switch (currentGame.gameSettings.gameType) { + case DefaultGameSettings.gameTypeValuePickImage: + return const GamePickImagePage(); + case DefaultGameSettings.gameTypeValuePickWord: + return const GamePickWordPage(); + default: + return const SizedBox.shrink(); + } + }, + ), + ); + } +} diff --git a/lib/ui/widgets/game/game_pick_image.dart b/lib/ui/widgets/game/game_pick_image.dart new file mode 100644 index 0000000000000000000000000000000000000000..d15b284c7f94593f5fdae736ef669f75503aba50 --- /dev/null +++ b/lib/ui/widgets/game/game_pick_image.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import 'package:wordguessing/ui/widgets/game/pick_image_game_items.dart'; +import 'package:wordguessing/ui/widgets/game/pick_image_game_word.dart'; + +class GamePickImagePage extends StatelessWidget { + const GamePickImagePage({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + PickImageGameWordContainer(), + SizedBox(height: 8), + PickImageGameItemsContainer(), + ], + ); + } +} diff --git a/lib/ui/widgets/game/game_pick_word.dart b/lib/ui/widgets/game/game_pick_word.dart new file mode 100644 index 0000000000000000000000000000000000000000..fc22b7b581d47b20151fc606c3a2950a064fb118 --- /dev/null +++ b/lib/ui/widgets/game/game_pick_word.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import 'package:wordguessing/ui/widgets/game/pick_word_game_image_items.dart'; +import 'package:wordguessing/ui/widgets/game/pick_word_game_word_items.dart'; + +class GamePickWordPage extends StatelessWidget { + const GamePickWordPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + PickWordGameImageItemsContainer(), + SizedBox(height: 8), + PickWordGameWordItemsContainer(), + ], + ); + } +} diff --git a/lib/ui/widgets/game/pick_image_game_image.dart b/lib/ui/widgets/game/pick_image_game_image.dart new file mode 100644 index 0000000000000000000000000000000000000000..dbf46fe7f2429e8d8b19d48776647c5c98923ce9 --- /dev/null +++ b/lib/ui/widgets/game/pick_image_game_image.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/data/word.dart'; + +class PickImageGameImageContainer extends StatelessWidget { + const PickImageGameImageContainer({super.key, required this.word}); + + final Word word; + + @override + Widget build(BuildContext context) { + const double imageSize = 130; + + String imageAsset = 'assets/ui/placeholder.png'; + if ((word.images.isNotEmpty)) { + imageAsset = 'assets/images/${word.images[0]}'; + } + + return TextButton( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.blue.shade200, + width: 8, + ), + ), + margin: const EdgeInsets.all(2), + child: Image( + image: AssetImage(imageAsset), + width: imageSize, + height: imageSize, + fit: BoxFit.fill, + ), + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).checkWord(word); + }, + ); + } +} diff --git a/lib/ui/widgets/game/pick_image_game_items.dart b/lib/ui/widgets/game/pick_image_game_items.dart new file mode 100644 index 0000000000000000000000000000000000000000..4ff8245649d178ef33300bb055850148124b0058 --- /dev/null +++ b/lib/ui/widgets/game/pick_image_game_items.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/data/word.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/widgets/game/pick_image_game_image.dart'; + +class PickImageGameItemsContainer extends StatelessWidget { + const PickImageGameItemsContainer({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + final List<Word> images = currentGame.images; + + if (images.length != DefaultGameSettings.itemsCount) { + return const SizedBox.shrink(); + } + + images.sort((a, b) => a.key.compareTo(b.key)); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PickImageGameImageContainer(word: images[0]), + PickImageGameImageContainer(word: images[1]), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PickImageGameImageContainer(word: images[2]), + PickImageGameImageContainer(word: images[3]), + ], + ) + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/pick_image_game_word.dart b/lib/ui/widgets/game/pick_image_game_word.dart new file mode 100644 index 0000000000000000000000000000000000000000..efc1aab1d9d451521baffad5b0398e580d24b173 --- /dev/null +++ b/lib/ui/widgets/game/pick_image_game_word.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; + +class PickImageGameWordContainer extends StatelessWidget { + const PickImageGameWordContainer({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Container( + decoration: BoxDecoration( + color: Colors.blue.shade800, + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: Colors.white, + width: 6, + ), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + currentGame.word.text, + style: const TextStyle( + fontSize: 30, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/pick_word_game_image.dart b/lib/ui/widgets/game/pick_word_game_image.dart new file mode 100644 index 0000000000000000000000000000000000000000..705450c37d087f6e4df655d39ce1f9c364bfc00e --- /dev/null +++ b/lib/ui/widgets/game/pick_word_game_image.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + + +class PickWordGameImageContainer extends StatelessWidget { + const PickWordGameImageContainer({super.key, required this.image}); + + final String image; + + @override + Widget build(BuildContext context) { + const double imageSize = 130; + + String imageAsset = 'assets/ui/placeholder.png'; + if (image != '') { + imageAsset = 'assets/images/$image'; + } + + return Container( + margin: const EdgeInsets.all(2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.blue.shade200, + width: 8, + ), + ), + child: Image( + image: AssetImage(imageAsset), + width: imageSize, + height: imageSize, + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/ui/widgets/game/pick_word_game_image_items.dart b/lib/ui/widgets/game/pick_word_game_image_items.dart new file mode 100644 index 0000000000000000000000000000000000000000..c0e91cfc0938f594060649707e880b99fa3ddb69 --- /dev/null +++ b/lib/ui/widgets/game/pick_word_game_image_items.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/data/word.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/widgets/game/pick_word_game_image.dart'; + +class PickWordGameImageItemsContainer extends StatelessWidget { + const PickWordGameImageItemsContainer({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + if (currentGame.images.isEmpty) { + return const SizedBox.shrink(); + } + + final Word word = currentGame.images[0]; + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PickWordGameImageContainer(image: word.images[0]), + PickWordGameImageContainer(image: word.images[1]), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PickWordGameImageContainer(image: word.images[2]), + PickWordGameImageContainer(image: word.images[3]), + ], + ) + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/pick_word_game_word.dart b/lib/ui/widgets/game/pick_word_game_word.dart new file mode 100644 index 0000000000000000000000000000000000000000..1a2ad03987fcd4811b527d6326e504ad304d80c1 --- /dev/null +++ b/lib/ui/widgets/game/pick_word_game_word.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/data/word.dart'; + +class PickWordGameWordContainer extends StatelessWidget { + const PickWordGameWordContainer({super.key, required this.word}); + + final Word word; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.blue[800], + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: Colors.white, + width: 6, + ), + ), + child: TextButton( + style: TextButton.styleFrom( + padding: const EdgeInsets.all(15), + ), + child: Text( + word.text, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + onPressed: () { + BlocProvider.of<GameCubit>(context).checkWord(word); + }, + ), + ); + } +} diff --git a/lib/ui/widgets/game/pick_word_game_word_items.dart b/lib/ui/widgets/game/pick_word_game_word_items.dart new file mode 100644 index 0000000000000000000000000000000000000000..8cc00278b20796ad9bd53b9881e368e705467de9 --- /dev/null +++ b/lib/ui/widgets/game/pick_word_game_word_items.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/config/default_game_settings.dart'; +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/data/word.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/widgets/game/pick_word_game_word.dart'; + +class PickWordGameWordItemsContainer extends StatelessWidget { + const PickWordGameWordItemsContainer({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + Word? word = currentGame.word; + List<Word> otherWords = currentGame.otherWords; + + if (otherWords.length != (DefaultGameSettings.itemsCount - 1)) { + return const Column(); + } + + List<Word> words = [ + word, + otherWords[0], + otherWords[1], + otherWords[2], + ]; + + words.sort((a, b) => a.key.compareTo(b.key)); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PickWordGameWordContainer(word: words[0]), + const SizedBox(width: 10), + PickWordGameWordContainer(word: words[1]), + ], + ), + const SizedBox(height: 5), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PickWordGameWordContainer(word: words[2]), + const SizedBox(width: 10), + PickWordGameWordContainer(word: words[3]), + ], + ) + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/score_bar.dart b/lib/ui/widgets/game/score_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..f194767d6b46d03c178223603391c407fb04c6fc --- /dev/null +++ b/lib/ui/widgets/game/score_bar.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/widgets/indicators/score_indicator.dart'; + +class ScoreBarWidget extends StatelessWidget { + const ScoreBarWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final int count = currentGame.questionsCount - 1; + + final String goodAnswersString = '👍 ${currentGame.goodAnswers}/$count'; + final String wrongAnswersString = '🚩 ${currentGame.wrongAnswers}/$count'; + + return Container( + padding: const EdgeInsets.all(5), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ScoreIndicator( + text: goodAnswersString, + blockColor: Colors.green, + ), + const SizedBox(width: 20), + ScoreIndicator( + text: wrongAnswersString, + blockColor: Colors.orange, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..540f87eeb8d54a47a4be17539dee60b213df0cb6 --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:wordguessing/config/menu.dart'; +import 'package:wordguessing/cubit/game_cubit.dart'; +import 'package:wordguessing/cubit/nav_cubit.dart'; +import 'package:wordguessing/models/game/game.dart'; +import 'package:wordguessing/ui/helpers/app_titles.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 && !currentGame.isFinished) { + menuActions.add(TextButton( + child: const Image( + image: AssetImage('assets/ui/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () {}, + onLongPress: () { + BlocProvider.of<GameCubit>(context).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 AppHeader(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/header_app.dart deleted file mode 100644 index bf54b77375fbd0260f876f2885d0572b71715383..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/header_app.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -class AppHeader extends StatelessWidget { - const AppHeader({super.key, required this.text}); - - final String text; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tr(text), - textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), - ), - ], - ); - } -} diff --git a/lib/ui/widgets/indicators/score_indicator.dart b/lib/ui/widgets/indicators/score_indicator.dart new file mode 100644 index 0000000000000000000000000000000000000000..b6e4ecd132d9356ff8797946b02a0f40f342dfed --- /dev/null +++ b/lib/ui/widgets/indicators/score_indicator.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import 'package:wordguessing/utils/color_extensions.dart'; + +class ScoreIndicator extends StatelessWidget { + const ScoreIndicator({ + super.key, + required this.text, + required this.blockColor, + }); + + final String text; + final Color blockColor; + + @override + Widget build(BuildContext context) { + final Color borderColor = blockColor.darken(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 15), + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: blockColor, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: borderColor, + width: 4, + ), + ), + child: Text( + text, + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ); + } +} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/pubspec.lock b/pubspec.lock index e9215a535c1e4d7b9a106294c437f72a288dd913..e0ab96ebb656b1260018d45af586d9ec14ba4a7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17" + sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" easy_logger: dependency: transitive description: @@ -106,31 +106,23 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a url: "https://pub.dev" source: hosted - version: "8.1.5" + version: "8.1.6" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "4.0.0" 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 @@ -172,18 +164,18 @@ packages: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" lints: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" material_color_utilities: dependency: transitive description: @@ -196,10 +188,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" nested: dependency: transitive description: @@ -225,7 +217,7 @@ packages: source: hosted version: "3.0.0" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -244,18 +236,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.5" 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: @@ -284,10 +276,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: 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 @@ -316,18 +308,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" 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: @@ -433,10 +425,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.1" xdg_directories: dependency: transitive description: @@ -446,5 +438,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index d93b5e6e43a1788a671e11fe1160cc3f0eecf5dc..749d20f0dff6f74571043563b650c6c42013ca0c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,8 @@ name: wordguessing description: A wordguessing game application. publish_to: "none" -version: 0.1.40+64 + +version: 0.2.0+65 environment: sdk: "^3.0.0" @@ -11,27 +12,27 @@ dependencies: flutter: sdk: flutter + # base 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: ^8.0.0 - path: ^1.9.0 path_provider: ^2.0.11 - provider: ^6.0.5 unicons: ^2.1.1 + # specific + # (none) + dev_dependencies: - flutter_lints: ^3.0.1 + flutter_lints: ^4.0.0 flutter: uses-material-design: true assets: - assets/images/ - - assets/menu/ - - assets/placeholder.png + - assets/ui/ - assets/translations/ fonts: @@ -45,3 +46,4 @@ flutter: weight: 400 - asset: assets/fonts/Nunito-Light.ttf weight: 300 + diff --git a/icons/build_application_icons.sh b/resources/app/build_application_resources.sh similarity index 98% rename from icons/build_application_icons.sh rename to resources/app/build_application_resources.sh index 27dbe2647fe4e6d562fbd99451716d1b7d448570..6d67b8f4f9eca701d1aed7331ef41dfb0bd44f20 100755 --- a/icons/build_application_icons.sh +++ b/resources/app/build_application_resources.sh @@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins 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}")" +BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")" SOURCE_ICON="${CURRENT_DIR}/icon.svg" SOURCE_FASTLANE="${CURRENT_DIR}/featureGraphic.svg" diff --git a/icons/featureGraphic.svg b/resources/app/featureGraphic.svg similarity index 100% rename from icons/featureGraphic.svg rename to resources/app/featureGraphic.svg diff --git a/icons/icon.svg b/resources/app/icon.svg similarity index 100% rename from icons/icon.svg rename to resources/app/icon.svg diff --git a/resources/build_resources.sh b/resources/build_resources.sh new file mode 100755 index 0000000000000000000000000000000000000000..774953c5b885aae73f710aaa9d8b55a0d8dcc2c0 --- /dev/null +++ b/resources/build_resources.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + +${CURRENT_DIR}/app/build_application_resources.sh +${CURRENT_DIR}/ui/build_ui_resources.sh diff --git a/scripts/.gitignore b/resources/data/.gitignore similarity index 100% rename from scripts/.gitignore rename to resources/data/.gitignore diff --git a/scripts/01_download_images.sh b/resources/data/01_download_images.sh similarity index 100% rename from scripts/01_download_images.sh rename to resources/data/01_download_images.sh diff --git a/scripts/02_prepare_images.sh b/resources/data/02_prepare_images.sh similarity index 100% rename from scripts/02_prepare_images.sh rename to resources/data/02_prepare_images.sh diff --git a/scripts/03_optimize_images.sh b/resources/data/03_optimize_images.sh similarity index 100% rename from scripts/03_optimize_images.sh rename to resources/data/03_optimize_images.sh diff --git a/scripts/04_build_images_board.sh b/resources/data/04_build_images_board.sh similarity index 100% rename from scripts/04_build_images_board.sh rename to resources/data/04_build_images_board.sh diff --git a/scripts/05_build_assets_list.sh b/resources/data/05_build_assets_list.sh similarity index 100% rename from scripts/05_build_assets_list.sh rename to resources/data/05_build_assets_list.sh diff --git a/scripts/words.csv b/resources/data/words.csv similarity index 100% rename from scripts/words.csv rename to resources/data/words.csv diff --git a/scripts/words.json b/resources/data/words.json similarity index 100% rename from scripts/words.json rename to resources/data/words.json diff --git a/resources/ui/build_ui_resources.sh b/resources/ui/build_ui_resources.sh new file mode 100755 index 0000000000000000000000000000000000000000..4f365ede7d83140ce6309a3083580f2662b30990 --- /dev/null +++ b/resources/ui/build_ui_resources.sh @@ -0,0 +1,110 @@ +#! /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 "$(dirname "${CURRENT_DIR}")")" +ASSETS_DIR="${BASE_DIR}/assets" + +OPTIPNG_OPTIONS="-preserve -quiet -o7" +ICON_SIZE=192 + +####################################################### + +# Game images (svg files found in `images` folder) +AVAILABLE_GAME_IMAGES="" +if [ -d "${CURRENT_DIR}/images" ]; then + AVAILABLE_GAME_IMAGES="$(find "${CURRENT_DIR}/images" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort)" +fi + +# Skins (subfolders found in `skins` folder) +AVAILABLE_SKINS="" +if [ -d "${CURRENT_DIR}/skins" ]; then + AVAILABLE_SKINS="$(find "${CURRENT_DIR}/skins" -mindepth 1 -type d | awk -F/ '{print $NF}')" +fi + +# Images per skin (svg files found recursively in `skins` folder and subfolders) +SKIN_IMAGES="" +if [ -d "${CURRENT_DIR}/skins" ]; then + SKIN_IMAGES="$(find "${CURRENT_DIR}/skins" -type f -name "*.svg" | awk -F/ '{print $NF}' | cut -d"." -f1 | sort | uniq)" +fi + +####################################################### + +# 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_image() { + SOURCE="$1" + TARGET="$2" + + echo "Building ${TARGET}" + + if [ ! -f "${SOURCE}" ]; then + echo "Missing file: ${SOURCE}" + exit 1 + fi + + optimize_svg "${SOURCE}" + + mkdir -p "$(dirname "${TARGET}")" + + inkscape \ + --export-width=${ICON_SIZE} \ + --export-height=${ICON_SIZE} \ + --export-filename=${TARGET} \ + "${SOURCE}" + + optipng ${OPTIPNG_OPTIONS} "${TARGET}" +} + +function build_image_for_skin() { + SKIN_CODE="$1" + + # skin images + for SKIN_IMAGE in ${SKIN_IMAGES} + do + build_image ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png + done +} + +####################################################### + +# Delete existing generated images +if [ -d "${ASSETS_DIR}/ui" ]; then + find ${ASSETS_DIR}/ui -type f -name "*.png" -delete +fi +if [ -d "${ASSETS_DIR}/skins" ]; then + find ${ASSETS_DIR}/skins -type f -name "*.png" -delete +fi + +# build game images +for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} +do + build_image ${CURRENT_DIR}/images/${GAME_IMAGE}.svg ${ASSETS_DIR}/ui/${GAME_IMAGE}.png +done + +# build skins images +for SKIN in ${AVAILABLE_SKINS} +do + build_image_for_skin "${SKIN}" +done + diff --git a/resources/ui/images/button_back.svg b/resources/ui/images/button_back.svg new file mode 100644 index 0000000000000000000000000000000000000000..2622a578dba53ce582afabfc587c2a85a1fb6eaa --- /dev/null +++ b/resources/ui/images/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/resources/ui/images/button_delete_saved_game.svg b/resources/ui/images/button_delete_saved_game.svg new file mode 100644 index 0000000000000000000000000000000000000000..ac7eefef476f761903fe781b8c86d0c94323550a --- /dev/null +++ b/resources/ui/images/button_delete_saved_game.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="#ee7d49" stroke="#fff" stroke-width=".238"/><path d="m61.07 35.601-1.7399 27.837c-0.13442 2.1535-1.9205 3.8312-4.0781 3.8312h-16.84c-2.1576 0-3.9437-1.6777-4.0781-3.8312l-1.7399-27.837h-2.6176c-0.84621 0-1.5323-0.68613-1.5323-1.5323 0-0.84655 0.68613-1.5323 1.5323-1.5323h33.711c0.84621 0 1.5323 0.68578 1.5323 1.5323 0 0.84621-0.68613 1.5323-1.5323 1.5323zm-3.2617 0h-21.953l1.4715 26.674c0.05985 1.0829 0.95531 1.9305 2.0403 1.9305h14.929c1.085 0 1.9804-0.84757 2.0403-1.9305zm-10.977 3.0647c0.78977 0 1.4301 0.6403 1.4301 1.4301v19.614c0 0.78977-0.6403 1.4301-1.4301 1.4301s-1.4301-0.6403-1.4301-1.4301v-19.614c0-0.78977 0.6403-1.4301 1.4301-1.4301zm-6.1293 0c0.80004 0 1.4588 0.62935 1.495 1.4286l0.89647 19.719c0.03182 0.70016-0.50998 1.2933-1.2101 1.3255-0.01915 7.02e-4 -0.03831 1e-3 -0.05781 1e-3 -0.74462 0-1.3596-0.58215-1.4003-1.3261l-1.0757-19.719c-0.0407-0.74701 0.53188-1.3852 1.2786-1.4259 0.02462-0.0014 0.04926-2e-3 0.07388-2e-3zm12.259 0c0.74804 0 1.3541 0.60609 1.3541 1.3541 0 0.02462-3.28e-4 0.04926-0.0017 0.07388l-1.0703 19.618c-0.04379 0.80106-0.70597 1.4281-1.5081 1.4281-0.74804 0-1.3541-0.60609-1.3541-1.3541 0-0.02462 3.49e-4 -0.04925 0.0017-0.07388l1.0703-19.618c0.04379-0.80106 0.70597-1.4281 1.5081-1.4281zm-10.216-12.259h8.1728c2.2567 0 4.086 1.8293 4.086 4.086v2.0433h-16.344v-2.0433c0-2.2567 1.8293-4.086 4.086-4.086zm0.20453 3.0647c-0.67725 0-1.2259 0.54863-1.2259 1.2259v1.8388h10.215v-1.8388c0-0.67725-0.54863-1.2259-1.2259-1.2259z" fill="#fff" fill-rule="evenodd" stroke="#bd4812" stroke-width=".75383"/></svg> diff --git a/resources/ui/images/button_resume_game.svg b/resources/ui/images/button_resume_game.svg new file mode 100644 index 0000000000000000000000000000000000000000..6ad8b64202d0e70f898c16c520e756fe8a934add --- /dev/null +++ b/resources/ui/images/button_resume_game.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="m39.211 31.236c-0.84086-0.84489-2.9911-0.84489-2.9911 0v34.329c0 0.84594 2.1554 0.84594 2.9993 0l28.178-15.637c0.84392-0.84086 0.85812-2.2091 0.01623-3.053z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="6.1726"/><path d="m40.355 33.714c-0.71948-0.72294-2.5594-0.72294-2.5594 0v29.373c0 0.72383 1.8442 0.72383 2.5663 0l24.11-13.38c0.7221-0.71948 0.73426-1.8902 0.01389-2.6124z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.225"/><path d="m28.369 66.919v-37.591" fill="#105ca2" stroke="#105ca2" stroke-linecap="round" stroke-width="4.0337"/></svg> diff --git a/resources/ui/images/button_start.svg b/resources/ui/images/button_start.svg new file mode 100644 index 0000000000000000000000000000000000000000..e9d49d2172b9a0305db82779971e3c1e12f34a70 --- /dev/null +++ b/resources/ui/images/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/resources/ui/images/game_fail.svg b/resources/ui/images/game_fail.svg new file mode 100644 index 0000000000000000000000000000000000000000..2922fd7adc2bd2e813836c728f095376c73d4143 --- /dev/null +++ b/resources/ui/images/game_fail.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="#d11717" stroke="#fff" stroke-width=".238"/><path d="m71.624 59.304c3.5089 3.5089 3.5089 9.0561 0 12.565-1.6976 1.6976-3.9623 2.6034-6.2261 2.6034s-4.5275-0.90569-6.2261-2.6034l-12.452-12.452-12.452 12.452c-1.6976 1.6976-3.9623 2.6034-6.2261 2.6034s-4.5275-0.90569-6.2261-2.6034c-3.5089-3.5089-3.5089-9.0561 0-12.565l12.452-12.452-12.452-12.452c-3.5089-3.5089-3.5089-9.0561 0-12.565s9.0561-3.5089 12.565 0l12.452 12.452 12.452-12.452c3.5089-3.5089 9.0561-3.5089 12.565 0s3.5089 9.0561 0 12.565l-12.452 12.452z" fill="#e7e7e7" stroke-width=".20213"/></svg> diff --git a/resources/ui/images/game_win.svg b/resources/ui/images/game_win.svg new file mode 100644 index 0000000000000000000000000000000000000000..fe20923864d0c5d39168eced03038b65106a596b --- /dev/null +++ b/resources/ui/images/game_win.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"><g transform="matrix(.17604 0 0 .17604 7.9341 1.7716)"><path d="m101.92 496.35c-1.8555 0-3.7109-0.69532-5.1484-2.0898-2.9297-2.8438-3-7.5234-0.15234-10.453l9.1875-9.4648c2.8438-2.9297 7.5234-3 10.453-0.15625s3 7.5234 0.15625 10.453l-9.1914 9.4648c-1.4492 1.4961-3.375 2.2461-5.3047 2.2461z" fill="#ff4e61"/><path d="m201.65 133.26c-1.8516 0-3.7109-0.69531-5.1445-2.0898-2.9297-2.8438-3-7.5234-0.15625-10.449l9.1914-9.4688c2.8438-2.9297 7.5195-3 10.449-0.15625s3 7.5234 0.15625 10.453l-9.1914 9.4688c-1.4492 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#ff4e61"/><path d="m413.8 100.39c-1.8555 0-3.7109-0.69141-5.1484-2.0859-2.9297-2.8438-3-7.5234-0.15625-10.453l9.1914-9.4688c2.8438-2.9258 7.5234-2.9961 10.453-0.15234 2.9297 2.8398 3 7.5234 0.15625 10.449l-9.1914 9.4688c-1.4492 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#5c73bc"/><path d="m413.8 463.77c-1.8555 0-3.7109-0.69532-5.1484-2.0859-2.9297-2.8438-3-7.5234-0.15625-10.453l9.1914-9.4688c2.8438-2.9258 7.5234-3 10.453-0.15625s3 7.5234 0.15625 10.453l-9.1914 9.4688c-1.4492 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#fa0"/><path d="m63.07 112.91c-1.8516 0-3.7109-0.69141-5.1445-2.0859-2.9297-2.8438-3-7.5234-0.15625-10.453l9.1914-9.4687c2.8438-2.9258 7.5234-2.9961 10.453-0.15234 2.9258 2.8438 2.9961 7.5234 0.15234 10.449l-9.1914 9.4688c-1.4492 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#fa0"/><path d="m12.309 278.82c-1.8516 0-3.7109-0.69141-5.1445-2.0859-2.9297-2.8438-3-7.5234-0.15625-10.453l9.1875-9.4688c2.8438-2.9297 7.5234-3 10.453-0.15625 2.9297 2.8438 3 7.5234 0.15625 10.453l-9.1914 9.4688c-1.4453 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#2dc471"/><path d="m216.29 278.49-23.996 12.996c-6.2226 3.3711-13.496-2.0742-12.309-9.2148l4.582-27.523c0.47266-2.8359-0.4375-5.7266-2.4375-7.7344l-19.414-19.496c-5.0352-5.0547-2.2578-13.863 4.7031-14.906l26.824-4.0156c2.7656-0.41407 5.1524-2.1992 6.3867-4.7812l12-25.043c3.1133-6.4922 12.102-6.4922 15.215 0l11.996 25.043c1.2383 2.582 3.625 4.3672 6.3867 4.7812l26.828 4.0156c6.957 1.043 9.7344 9.8516 4.6992 14.906l-19.41 19.496c-2 2.0078-2.9141 4.8984-2.4414 7.7344l4.582 27.523c1.1914 7.1406-6.082 12.586-12.305 9.2148l-23.996-12.996c-2.4727-1.3398-5.4258-1.3398-7.8945 0z" fill="#ffd02f"/><path d="m220.24 512c-4.082 0-7.3906-3.3086-7.3906-7.3906v-115.59c0-4.082 3.3086-7.3945 7.3906-7.3945s7.3906 3.3125 7.3906 7.3945v115.59c0 4.082-3.3086 7.3906-7.3906 7.3906z" fill="#5c73bc"/><path d="m220.3 357.42h-0.11328c-4.082 0-7.3945-3.3125-7.3945-7.3945s3.3086-7.3906 7.3945-7.3906h0.11328c4.082 0 7.3906 3.3086 7.3906 7.3906s-3.3086 7.3945-7.3906 7.3945z" fill="#5c73bc"/><path d="m220.3 332h-0.14838c-4.082-0.0156-7.375-3.3398-7.3594-7.4219 0.0195-4.0742 3.3242-7.3594 7.3906-7.3594h0.14848c4.082 0.0156 7.375 3.3398 7.3594 7.4219-0.0156 4.0703-3.3242 7.3594-7.3906 7.3594z" fill="#fa0"/><path d="m87.234 230.89c-1.9297 0-3.8555-0.75-5.3047-2.2422l-79.34-81.738c-2.8438-2.9297-2.7773-7.6094 0.15234-10.449 2.9297-2.8438 7.6094-2.7734 10.453 0.15235l79.344 81.738c2.8438 2.9258 2.7734 7.6094-0.15625 10.449-1.4375 1.3945-3.293 2.0898-5.1484 2.0898z" fill="#ff4e61"/><path d="m113.95 258.5c-1.8633 0-3.7266-0.69922-5.1641-2.1055-2.9219-2.8516-2.9766-7.5312-0.125-10.453l0.082-0.082c2.8516-2.918 7.5312-2.9766 10.453-0.12109 2.9219 2.8516 2.9766 7.5312 0.12109 10.453l-0.0781 0.082c-1.4492 1.4805-3.3672 2.2266-5.2891 2.2266z" fill="#fa0"/><path d="m131.4 276.48c-1.8555 0-3.7109-0.69531-5.1484-2.0898-2.9258-2.8438-2.9961-7.5234-0.15235-10.449l0.0781-0.0859c2.8476-2.9297 7.5273-2.9961 10.453-0.15235 2.9297 2.8438 3 7.5234 0.15625 10.453l-0.082 0.082c-1.4492 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#5c73bc"/><path d="m353.24 227.99c-1.8555 0-3.7109-0.69141-5.1445-2.0859-2.9297-2.8438-3-7.5234-0.15625-10.453l79.34-81.734c2.8438-2.9297 7.5234-3 10.453-0.15625 2.9297 2.8438 3 7.5234 0.15625 10.453l-79.344 81.734c-1.4492 1.4922-3.375 2.2422-5.3047 2.2422z" fill="#fa0"/><path d="m326.52 255.6c-1.9141 0-3.8242-0.73828-5.2695-2.2109l-0.082-0.082c-2.8633-2.9141-2.8203-7.5938 0.0899-10.453 2.9141-2.8633 7.5938-2.8203 10.453 0.0898l0.082 0.082c2.8594 2.9141 2.8203 7.5938-0.0937 10.453-1.4375 1.4141-3.3086 2.1211-5.1797 2.1211z" fill="#ff4e61"/><path d="m309.07 273.58c-1.9297 0-3.8555-0.75-5.3047-2.2422l-0.082-0.082c-2.8398-2.9297-2.7734-7.6094 0.15625-10.453s7.6094-2.7734 10.453 0.15234l0.082 0.082c2.8398 2.9297 2.7734 7.6094-0.15625 10.453-1.4375 1.3945-3.293 2.0898-5.1484 2.0898z" fill="#fa0"/><path d="m300.65 116.69c-1.2422 0-2.5-0.3125-3.6523-0.97266-3.5469-2.0234-4.7812-6.5391-2.7578-10.082l56.863-99.652c2.0234-3.543 6.5352-4.7773 10.082-2.7539 3.5469 2.0234 4.7812 6.5391 2.7578 10.082l-56.863 99.652c-1.3633 2.3867-3.8594 3.7266-6.4297 3.7266z" fill="#62d38f"/><path d="m281.52 150.33c-1.293 0-2.5977-0.33593-3.7891-1.0469l-0.0976-0.0586c-3.5-2.0938-4.6445-6.6328-2.5469-10.137 2.0938-3.5078 6.6328-4.6445 10.137-2.5508l0.0977 0.0586c3.5039 2.0938 4.6445 6.6328 2.5508 10.137-1.3867 2.3164-3.8359 3.5976-6.3516 3.5976z" fill="#fa0"/><path d="m269.02 172.25c-1.3008 0-2.6172-0.34375-3.8086-1.0625l-0.0977-0.0586c-3.4961-2.1094-4.6211-6.6523-2.5156-10.148 2.1094-3.4961 6.6523-4.6172 10.148-2.5117l0.0976 0.0586c3.4961 2.1094 4.6211 6.6523 2.5117 10.148-1.3867 2.3008-3.832 3.5742-6.3359 3.5742z" fill="#2dc471"/><path d="m139.96 116.69c-2.5703 0-5.0664-1.3398-6.4297-3.7305l-56.863-99.648c-2.0234-3.5469-0.78906-8.0586 2.7539-10.082 3.5469-2.0234 8.0625-0.79297 10.086 2.7539l56.863 99.648c2.0234 3.5469 0.78906 8.0625-2.7539 10.086-1.1562 0.66016-2.4141 0.97266-3.6562 0.97266z" fill="#5c73bc"/><path d="m159.09 150.33c-2.5078 0-4.957-1.2773-6.3438-3.582-2.1016-3.5-0.96875-8.043 2.5273-10.145l0.10157-0.0586c3.5-2.1016 8.0391-0.97266 10.141 2.5273 2.1055 3.5 0.97266 8.0391-2.5273 10.145l-0.0977 0.0586c-1.1914 0.71484-2.5039 1.0547-3.8008 1.0547z" fill="#ff4e61"/><path d="m171.6 172.25c-2.5 0-4.9375-1.2656-6.3281-3.5625-2.1172-3.4922-1-8.0352 2.4883-10.152l0.0977-0.0586c3.4961-2.1133 8.0391-1 10.156 2.4922 2.1133 3.4922 1 8.0352-2.4922 10.152l-0.0977 0.0586c-1.1992 0.72656-2.5195 1.0703-3.8242 1.0703z" fill="#fa0"/><path d="m402.14 357.28-15.523 11.602c-4.0234 3.0117-9.6523-0.043-9.5234-5.1641l0.5039-19.75c0.0508-2.0352-0.87109-3.9648-2.4688-5.1641l-15.508-11.621c-4.0234-3.0156-2.9453-9.4726 1.8242-10.93l18.391-5.6094c1.8906-0.57812 3.3906-2.082 4-4.0156l5.9375-18.785c1.5391-4.875 7.8359-5.8125 10.652-1.5898l10.863 16.285c1.1211 1.6758 2.9688 2.6797 4.9414 2.6797l19.18 0.0117c4.9766 4e-3 7.7891 5.8828 4.7578 9.9492l-11.676 15.672c-1.2031 1.6172-1.5586 3.7383-0.94922 5.6719l5.918 18.797c1.5312 4.875-3.0273 9.4453-7.7148 7.7344l-18.078-6.5977c-1.8594-0.67969-3.9258-0.37109-5.5273 0.82422z" fill="#ffd02f"/><path d="m261.51 512c-4.082 0-7.3906-3.3086-7.3906-7.3906 0-57.23 22.832-95.922 41.984-118.3 20.828-24.332 41.613-35.023 42.488-35.469 3.6406-1.8477 8.0898-0.39063 9.9336 3.2539 1.8438 3.6367 0.39453 8.0781-3.2422 9.9297-0.3125 0.16016-19.5 10.164-38.367 32.395-25.227 29.719-38.016 66.121-38.016 108.2 0 4.082-3.3086 7.3906-7.3906 7.3906z" fill="#ff4e61"/><path d="m102.86 397.35 11.766 15.605c3.0547 4.0469 9.2852 2.7305 10.547-2.2266l4.8633-19.113c0.5-1.9648 1.9102-3.5547 3.7695-4.2461l18.039-6.707c4.6797-1.7383 5.3906-8.25 1.207-11.016l-16.141-10.672c-1.6602-1.1016-2.6914-2.9726-2.7578-5.0039l-0.61719-19.75c-0.15625-5.1211-5.9492-7.832-9.7969-4.5859l-14.84 12.516c-1.5312 1.2891-3.5781 1.7227-5.4726 1.1562l-18.422-5.5c-4.7773-1.4258-9.0703 3.4102-7.2617 8.1836l6.9688 18.41c0.71875 1.8945 0.48438 4.0352-0.625 5.7188l-10.77 16.348c-2.793 4.2422 0.34375 9.9375 5.3125 9.6445l19.145-1.1406c1.9727-0.11719 3.875 0.77343 5.0859 2.3789z" fill="#ffd02f"/><path d="m179.02 512c-4.082 0-7.3906-3.3086-7.3906-7.3906 0-30.059-6.6797-57.559-19.852-81.734-1.9531-3.5859-0.62891-8.0742 2.957-10.027 3.5859-1.9531 8.0742-0.62891 10.027 2.9531 14.363 26.375 21.648 56.254 21.648 88.809 0 4.082-3.3086 7.3906-7.3906 7.3906z" fill="#fa0"/><path d="m268.93 55.898c0-11.285-8.8828-20.434-19.836-20.434-10.957 0-19.836 9.1484-19.836 20.434 0 11.285 8.8789 20.438 19.836 20.438 10.953 0 19.836-9.1523 19.836-20.438z" fill="#ffd02f"/><path d="m373.08 446.81c0-11.285-8.8789-20.434-19.832-20.434-10.957 0-19.836 9.1484-19.836 20.434s8.8789 20.434 19.836 20.434c10.953 0 19.832-9.1484 19.832-20.434z" fill="#5c73bc"/><path d="m44.129 450.86c0-9.0508-7.1211-16.387-15.91-16.387-8.7852 0-15.906 7.3359-15.906 16.387 0 9.0547 7.1211 16.391 15.906 16.391 8.7891 0 15.91-7.3359 15.91-16.391z" fill="#62d38f"/><path d="m88.172 288.35c0-9.0508-7.1211-16.387-15.91-16.387-8.7852 0-15.906 7.3359-15.906 16.387s7.1211 16.391 15.906 16.391c8.7891 0 15.91-7.3398 15.91-16.391z" fill="#5c73bc"/><g fill="#ff4e61"><path d="m210.84 16.391c0-9.0547-7.1211-16.391-15.906-16.391-8.7891 0-15.91 7.3359-15.91 16.391 0 9.0508 7.1211 16.387 15.91 16.387 8.7852 0 15.906-7.3359 15.906-16.387z"/><path d="m365.23 152.88c0-9.0508-7.125-16.391-15.91-16.391-8.7852 0-15.91 7.3398-15.91 16.391s7.125 16.387 15.91 16.387c8.7852 0 15.91-7.3359 15.91-16.387z"/><path d="m139.96 32.746c-1.8555 0-3.7109-0.69141-5.1484-2.0898-2.9297-2.8438-3-7.5195-0.15625-10.449l9.1914-9.4688c2.8438-2.9297 7.5234-3 10.449-0.15625 2.9297 2.8438 3 7.5234 0.15625 10.453l-9.1875 9.4688c-1.4492 1.4922-3.3789 2.2422-5.3047 2.2422z"/></g></g></svg> diff --git a/resources/ui/images/placeholder.svg b/resources/ui/images/placeholder.svg new file mode 100644 index 0000000000000000000000000000000000000000..23ace81fbb82a8409cc0710c0f7bddd6381f7256 --- /dev/null +++ b/resources/ui/images/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/images/menu/game-pick-image.svg b/resources/ui/images/type_pick-image.svg similarity index 100% rename from images/menu/game-pick-image.svg rename to resources/ui/images/type_pick-image.svg diff --git a/images/menu/game-pick-word.svg b/resources/ui/images/type_pick-word.svg similarity index 100% rename from images/menu/game-pick-word.svg rename to resources/ui/images/type_pick-word.svg diff --git a/scripts/cache/.gitkeep b/scripts/cache/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/scripts/cache/download/.gitkeep b/scripts/cache/download/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/scripts/cache/optimized/.gitkeep b/scripts/cache/optimized/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/scripts/cache/selected/.gitkeep b/scripts/cache/selected/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000