diff --git a/android/gradle.properties b/android/gradle.properties index c8bbff9bd599a7c3ffbe76de5a61ead3de90631a..a22054ee50f7b42877fcabc148c89078687808c9 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.0.39 -app.versionCode=39 +app.versionName=0.1.0 +app.versionCode=40 diff --git a/assets/translations/en.json b/assets/translations/en.json index 92299163019c59771191b4b86fb4a41f0ece93f1..f44610149e211750a2eb1877ce42da67d7b8ce98 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,14 +1,12 @@ { "app_name": "Colors", - "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": "Colors, a colorful flood game.", - "about_version": "Version: {version}" + "about_version": "Version: {version}", + + "": "" } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index dc7d503692165e64de23d0191442b6ada8bacb6d..312f2f2999310b07279ecda043f30508dd6711cf 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,14 +1,12 @@ { - "app_name": "Colors", - - "bottom_nav_home": "Jeu", - "bottom_nav_settings": "Réglages", - "bottom_nav_about": "Infos", + "app_name": "Couleurs", "settings_title": "Réglages", "settings_label_theme": "Thème de couleurs", "about_title": "Informations", "about_content": "Colors, un jeu de remplissage haut en couleurs.", - "about_version": "Version : {version}" + "about_version": "Version : {version}", + + "": "" } diff --git a/assets/icons/button_back.png b/assets/ui/button_back.png similarity index 100% rename from assets/icons/button_back.png rename to assets/ui/button_back.png 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/icons/button_start.png b/assets/ui/button_start.png similarity index 100% rename from assets/icons/button_start.png rename to assets/ui/button_start.png diff --git a/assets/icons/game_fail.png b/assets/ui/game_fail.png similarity index 100% rename from assets/icons/game_fail.png rename to assets/ui/game_fail.png diff --git a/assets/icons/game_win.png b/assets/ui/game_win.png similarity index 100% rename from assets/icons/game_win.png rename to assets/ui/game_win.png 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/fastlane/metadata/android/en-US/changelogs/40.txt b/fastlane/metadata/android/en-US/changelogs/40.txt new file mode 100644 index 0000000000000000000000000000000000000000..d4afd512e55b3fd8ffbfd795adb9b00832e5aaef --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40.txt @@ -0,0 +1 @@ +Improve/normalize game architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/40.txt b/fastlane/metadata/android/fr-FR/changelogs/40.txt new file mode 100644 index 0000000000000000000000000000000000000000..6a9871a5eb8eb3c6e9106520f1cbf1f39f9e5ef7 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40.txt @@ -0,0 +1 @@ +Amélioration/normalisation de l'architecture du jeu. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh deleted file mode 100755 index 7ea6ed5d23526aa94c9626826ffd4545141a1674..0000000000000000000000000000000000000000 --- a/icons/build_game_icons.sh +++ /dev/null @@ -1,80 +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; } - -CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -BASE_DIR="$(dirname "${CURRENT_DIR}")" -ASSETS_DIR="${BASE_DIR}/assets" - -OPTIPNG_OPTIONS="-preserve -quiet -o7" -ICON_SIZE=192 - -####################################################### - -# Game images -AVAILABLE_GAME_IMAGES=" - button_back - button_start - game_fail - game_win -" - -####################################################### - -# optimize svg -function optimize_svg() { - SOURCE="$1" - - cp ${SOURCE} ${SOURCE}.tmp - scour \ - --remove-descriptive-elements \ - --enable-id-stripping \ - --enable-viewboxing \ - --enable-comment-stripping \ - --nindent=4 \ - --quiet \ - -i ${SOURCE}.tmp \ - -o ${SOURCE} - rm ${SOURCE}.tmp -} - -# build icons -function build_icon() { - SOURCE="$1" - TARGET="$2" - - echo "Building ${TARGET}" - - if [ ! -f "${SOURCE}" ]; then - echo "Missing file: ${SOURCE}" - exit 1 - fi - - optimize_svg "${SOURCE}" - - inkscape \ - --export-width=${ICON_SIZE} \ - --export-height=${ICON_SIZE} \ - --export-filename=${TARGET} \ - ${SOURCE} - - optipng ${OPTIPNG_OPTIONS} ${TARGET} -} - -####################################################### - -# Create output folders -mkdir -p ${ASSETS_DIR}/icons - -# Delete existing generated images -find ${ASSETS_DIR}/icons -type f -name "*.png" -delete - -# build game images -for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} -do - build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png -done - diff --git a/lib/utils/color_theme.dart b/lib/config/color_theme.dart similarity index 100% rename from lib/utils/color_theme.dart rename to lib/config/color_theme.dart diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart index db8841295f53570f90f2a50ea1e6abbca2ac2403..de6450104bf828e5e6ade5e037e4c5e27c0638fd 100644 --- a/lib/config/default_game_settings.dart +++ b/lib/config/default_game_settings.dart @@ -1,4 +1,4 @@ -import 'package:colors/utils/tools.dart'; +import 'package:colors/utils/tools.dart'; class DefaultGameSettings { // available game parameters codes @@ -78,4 +78,9 @@ class DefaultGameSettings { return values[parameterLevel] ?? getMovesCountLimitDeltaFromLevelCode(DefaultGameSettings.defaultDifficultyLevelValue); } + + // parameters displayed with assets (instead of painter) + static List<String> displayedWithAssets = [ + // + ]; } diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart index 3ec1474a9af61c014e5791a793d7205343070dfb..d49140267e05b12c1de03dae5698ec4ecc7548bf 100644 --- a/lib/config/default_global_settings.dart +++ b/lib/config/default_global_settings.dart @@ -1,4 +1,4 @@ -import 'package:colors/utils/tools.dart'; +import 'package:colors/utils/tools.dart'; class DefaultGlobalSettings { // available global parameters codes @@ -25,4 +25,9 @@ class DefaultGlobalSettings { 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 2f9395d83564baa03622ecebe7fdc74e7a68c08c..37dd0750d2aba417738c0b2371becb23c3b3355a 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -6,12 +6,10 @@ import 'package:colors/ui/screens/page_game.dart'; import 'package:colors/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, }); @@ -20,21 +18,18 @@ class MenuItem { class Menu { static const indexGame = 0; static const menuItemGame = MenuItem( - code: 'bottom_nav_game', icon: Icon(UniconsLine.home), page: PageGame(), ); static const indexSettings = 1; static const menuItemSettings = MenuItem( - code: 'bottom_nav_settings', icon: Icon(UniconsLine.setting), page: PageSettings(), ); static const indexAbout = 2; static const menuItemAbout = MenuItem( - code: 'bottom_nav_about', icon: Icon(UniconsLine.info_circle), page: PageAbout(), ); 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 index 16775c36cca0ee88c6ef2a7470959c51a80dac08..798724273b3c0d7f19a7e5a8f59c180f246571a1 100644 --- a/lib/cubit/game_cubit.dart +++ b/lib/cubit/game_cubit.dart @@ -6,17 +6,16 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:colors/models/game.dart'; -import 'package:colors/models/settings_game.dart'; -import 'package:colors/models/settings_global.dart'; -import 'package:colors/utils/tools.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/models/settings/settings_game.dart'; +import 'package:colors/models/settings/settings_global.dart'; part 'game_state.dart'; class GameCubit extends HydratedCubit<GameState> { GameCubit() : super(GameState( - currentGame: Game.createNull(), + currentGame: Game.createEmpty(), )); void updateState(Game game) { @@ -27,19 +26,25 @@ class GameCubit extends HydratedCubit<GameState> { void refresh() { final Game game = Game( - board: state.currentGame.board, + // Settings gameSettings: state.currentGame.gameSettings, globalSettings: state.currentGame.globalSettings, + // State isRunning: state.currentGame.isRunning, + isStarted: state.currentGame.isStarted, isFinished: state.currentGame.isFinished, - gameWon: state.currentGame.gameWon, - movesCount: state.currentGame.movesCount, - maxMovesCount: state.currentGame.maxMovesCount, animationInProgress: state.currentGame.animationInProgress, + // Base data + board: state.currentGame.board, + // Game data + maxMovesCount: state.currentGame.maxMovesCount, + movesCount: state.currentGame.movesCount, progress: state.currentGame.progress, progressTotal: state.currentGame.progressTotal, progressDelta: state.currentGame.progressDelta, + gameWon: state.currentGame.gameWon, ); + // game.dump(); updateState(game); } @@ -48,12 +53,8 @@ class GameCubit extends HydratedCubit<GameState> { required GameSettings gameSettings, required GlobalSettings globalSettings, }) { - printlog('Starting new game:'); - printlog('- level: ${gameSettings.difficultyLevel}'); - printlog('- size: ${gameSettings.parameterSize}'); - printlog('- colors: ${gameSettings.parameterColorsCount}'); - - Game newGame = Game.createNew( + final Game newGame = Game.createNew( + // Settings gameSettings: gameSettings, globalSettings: globalSettings, ); @@ -62,6 +63,8 @@ class GameCubit extends HydratedCubit<GameState> { updateState(newGame); updateGameIsRunning(true); + + refresh(); } void quitGame() { @@ -69,6 +72,17 @@ class GameCubit extends HydratedCubit<GameState> { refresh(); } + void resumeSavedGame() { + state.currentGame.isRunning = true; + refresh(); + } + + void deleteSavedGame() { + state.currentGame.isRunning = false; + state.currentGame.isFinished = true; + refresh(); + } + void updateCellValue(int col, int row, int value) { state.currentGame.board.cells[row][col].value = value; refresh(); @@ -99,6 +113,11 @@ class GameCubit extends HydratedCubit<GameState> { refresh(); } + void updateGameIsStarted(bool gameIsStarted) { + state.currentGame.isStarted = gameIsStarted; + refresh(); + } + void updateGameIsFinished(bool gameIsFinished) { state.currentGame.isFinished = gameIsFinished; refresh(); @@ -110,6 +129,8 @@ class GameCubit extends HydratedCubit<GameState> { } fillBoardFromFirstCell(int value) { + updateGameIsStarted(true); + List<List<int>> cellsToFill = state.currentGame.board.getSiblingFillableCells(0, 0, []); final int progressBeforeMove = cellsToFill.length; @@ -154,7 +175,7 @@ class GameCubit extends HydratedCubit<GameState> { @override GameState? fromJson(Map<String, dynamic> json) { - Game currentGame = json['currentGame'] as Game; + final Game currentGame = json['currentGame'] as Game; return GameState( currentGame: currentGame, diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart index 3fd161a0915313722b7a15c55c7cf538a7e3b6e1..00e211668c3269255926939324355792abd61c41 100644 --- a/lib/cubit/game_state.dart +++ b/lib/cubit/game_state.dart @@ -12,8 +12,4 @@ class GameState extends Equatable { List<dynamic> get props => <dynamic>[ currentGame, ]; - - Map<String, dynamic> get values => <String, dynamic>{ - 'currentGame': currentGame, - }; } diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart index f70f2be39c047a76c59c76d016035007a48f8e11..8b61a56a1f8a041b97cf4d20abb9ed78a28e31e6 100644 --- a/lib/cubit/settings_game_cubit.dart +++ b/lib/cubit/settings_game_cubit.dart @@ -1,10 +1,9 @@ import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:colors/config/default_game_settings.dart'; -import 'package:colors/models/settings_game.dart'; +import 'package:colors/models/settings/settings_game.dart'; part 'settings_game_state.dart'; @@ -36,6 +35,7 @@ class GameSettingsCubit extends HydratedCubit<GameSettingsState> { case DefaultGameSettings.parameterCodeColorsCount: return GameSettings.getColorsValueFromUnsafe(state.settings.parameterColorsCount); } + return ''; } diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart index b773dc69be12673b158e880e2d7e6e7bec465506..5acd85b44ba541e1c5e9c26af1c4be26a385b9ed 100644 --- a/lib/cubit/settings_game_state.dart +++ b/lib/cubit/settings_game_state.dart @@ -12,8 +12,4 @@ class GameSettingsState extends Equatable { List<dynamic> get props => <dynamic>[ settings, ]; - - Map<String, dynamic> get values => <String, dynamic>{ - 'settings': settings, - }; } diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart index 41d3f191c94ff81119902bab88d3c8b376a11dff..360d535b818b56bb35f930da0d0ca8091ae93293 100644 --- a/lib/cubit/settings_global_cubit.dart +++ b/lib/cubit/settings_global_cubit.dart @@ -1,10 +1,9 @@ import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:colors/config/default_global_settings.dart'; -import 'package:colors/models/settings_global.dart'; +import 'package:colors/models/settings/settings_global.dart'; part 'settings_global_state.dart'; diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart index 4e4fbdf707b4e805f2092d0ca6a68a2de1c957c6..ebcddd700f252257223ca8e16c85202b04f3ff24 100644 --- a/lib/cubit/settings_global_state.dart +++ b/lib/cubit/settings_global_state.dart @@ -12,8 +12,4 @@ class GlobalSettingsState extends Equatable { List<dynamic> get props => <dynamic>[ settings, ]; - - Map<String, dynamic> get values => <String, dynamic>{ - 'settings': settings, - }; } diff --git a/lib/main.dart b/lib/main.dart index 4ffcc22f036fee392607c109e0a71a7098963cea..e9c7a92cec06c586428a291485fd750c2030731e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,19 +1,20 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter_bloc/flutter_bloc.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:colors/ui/skeleton.dart'; import 'package:colors/config/theme.dart'; import 'package:colors/cubit/game_cubit.dart'; import 'package:colors/cubit/nav_cubit.dart'; import 'package:colors/cubit/settings_game_cubit.dart'; import 'package:colors/cubit/settings_global_cubit.dart'; import 'package:colors/cubit/theme_cubit.dart'; -import 'package:colors/ui/skeleton.dart'; void main() async { // Initialize packages @@ -25,18 +26,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 { @@ -44,6 +44,11 @@ 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<NavCubit>(create: (context) => NavCubit()), @@ -73,4 +78,24 @@ class MyApp extends StatelessWidget { ), ); } + + 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 image in gameImages) { + assets.add('assets/ui/$image.png'); + } + + return assets; + } } diff --git a/lib/models/board.dart b/lib/models/game/board.dart similarity index 95% rename from lib/models/board.dart rename to lib/models/game/board.dart index 5d6ccd91949853ddce74c7965e82722c2e694fb1..8b7ff01a8c7fe388a3703326d85f9d6c4c7b2379 100644 --- a/lib/models/board.dart +++ b/lib/models/game/board.dart @@ -1,10 +1,11 @@ import 'dart:math'; -import 'package:colors/models/cell.dart'; -import 'package:colors/models/settings_game.dart'; -import 'package:colors/models/types.dart'; +import 'package:colors/models/game/cell.dart'; +import 'package:colors/models/settings/settings_game.dart'; import 'package:colors/utils/tools.dart'; +typedef BoardCells = List<List<Cell>>; + class Board { final BoardCells cells; @@ -12,7 +13,7 @@ class Board { required this.cells, }); - factory Board.createNull() { + factory Board.createEmpty() { return Board(cells: []); } diff --git a/lib/models/cell.dart b/lib/models/game/cell.dart similarity index 100% rename from lib/models/cell.dart rename to lib/models/game/cell.dart diff --git a/lib/models/game.dart b/lib/models/game/game.dart similarity index 56% rename from lib/models/game.dart rename to lib/models/game/game.dart index 28b61ead100e1d896811f67e9752e19fc79a9f82..23ffeb5efd6ea2d10a6e576ee32080ba2ea3ca64 100644 --- a/lib/models/game.dart +++ b/lib/models/game/game.dart @@ -1,46 +1,61 @@ import 'package:colors/config/default_game_settings.dart'; -import 'package:colors/models/board.dart'; -import 'package:colors/models/settings_game.dart'; -import 'package:colors/models/settings_global.dart'; +import 'package:colors/models/game/board.dart'; +import 'package:colors/models/settings/settings_game.dart'; +import 'package:colors/models/settings/settings_global.dart'; import 'package:colors/utils/tools.dart'; class Game { - final Board board; - final GameSettings gameSettings; - final GlobalSettings globalSettings; - bool isRunning = false; - bool isFinished = false; - bool gameWon = false; - - int maxMovesCount = 0; - int movesCount = 0; - - int progress = 0; - int progressTotal = 0; - int progressDelta = 0; - - bool animationInProgress = false; - Game({ - required this.board, + // Settings required this.gameSettings, required this.globalSettings, + + // State this.isRunning = false, + this.isStarted = false, this.isFinished = false, - this.gameWon = false, + this.animationInProgress = false, + + // Base data + required this.board, + + // Game data this.maxMovesCount = 0, this.movesCount = 0, this.progress = 0, this.progressTotal = 0, this.progressDelta = 0, - this.animationInProgress = false, + this.gameWon = false, }); - factory Game.createNull() { + // Settings + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + // State + bool isRunning; + bool isStarted; + bool isFinished; + bool animationInProgress; + + // Base data + final Board board; + + // Game data + int maxMovesCount; + int movesCount; + int progress; + int progressTotal; + int progressDelta; + bool gameWon; + + factory Game.createEmpty() { return Game( - board: Board.createNull(), + // Settings gameSettings: GameSettings.createDefault(), globalSettings: GlobalSettings.createDefault(), + // Base data + board: Board.createEmpty(), ); } @@ -48,8 +63,8 @@ class Game { GameSettings? gameSettings, GlobalSettings? globalSettings, }) { - GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); - GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); final int baseMaxMovesCount = (30 * (newGameSettings.boardSize * newGameSettings.colorsCount) / (17 * 6)).round(); @@ -58,36 +73,44 @@ class Game { newGameSettings.difficultyLevel); return Game( - board: Board.createRandom(newGameSettings), + // Settings gameSettings: newGameSettings, globalSettings: newGlobalSettings, + // State isRunning: true, - isFinished: false, - gameWon: false, + // Base data + board: Board.createRandom(newGameSettings), + // Game data progress: 1, progressTotal: newGameSettings.boardSize * newGameSettings.boardSize, maxMovesCount: baseMaxMovesCount + deltaMovesCountFromLevel, ); } + bool get canBeResumed => isStarted && !isFinished; + void dump() { printlog(''); printlog('## Current game dump:'); printlog(''); + printlog('$Game:'); + printlog(' Settings'); gameSettings.dump(); globalSettings.dump(); - printlog(''); + printlog(' State'); + printlog(' isRunning: $isRunning'); + printlog(' isStarted: $isStarted'); + printlog(' isFinished: $isFinished'); + printlog(' animationInProgress: $animationInProgress'); + printlog(' Base data'); board.dump(); - printlog(''); - printlog('Game: '); - printlog(' isRunning: $isRunning'); - printlog(' isFinished: $isFinished'); - printlog(' gameWon: $gameWon'); - printlog(' movesCount: $movesCount'); - printlog(' maxMovesCount: $maxMovesCount'); - printlog(' progress: $progress'); - printlog(' progressTotal: $progressTotal'); - printlog(' progressDelta: $progressDelta'); + printlog(' Game data'); + printlog(' maxMovesCount: $maxMovesCount'); + printlog(' movesCount: $movesCount'); + printlog(' progress: $progress'); + printlog(' progressTotal: $progressTotal'); + printlog(' progressDelta: $progressDelta'); + printlog(' gameWon: $gameWon'); printlog(''); } @@ -98,17 +121,23 @@ class Game { Map<String, dynamic>? toJson() { return <String, dynamic>{ - 'board': board.toJson(), + // Settings 'gameSettings': gameSettings.toJson(), 'globalSettings': globalSettings.toJson(), + // State 'isRunning': isRunning, + 'isStarted': isStarted, 'isFinished': isFinished, - 'gameWon': gameWon, - 'movesCount': movesCount, + 'animationInProgress': animationInProgress, + // Base data + 'board': board.toJson(), + // Game data 'maxMovesCount': maxMovesCount, + 'movesCount': movesCount, 'progress': progress, 'progressTotal': progressTotal, 'progressDelta': progressDelta, + 'gameWon': gameWon, }; } } diff --git a/lib/models/settings_game.dart b/lib/models/settings/settings_game.dart similarity index 98% rename from lib/models/settings_game.dart rename to lib/models/settings/settings_game.dart index b5227e80ccf1589646a06dc5e7e186fbc4336e26..2c5cc8e9a222f2cae56b5622030ac1d024f78845 100644 --- a/lib/models/settings_game.dart +++ b/lib/models/settings/settings_game.dart @@ -1,5 +1,5 @@ import 'package:colors/config/default_game_settings.dart'; -import 'package:colors/utils/tools.dart'; +import 'package:colors/utils/tools.dart'; class GameSettings { String difficultyLevel; diff --git a/lib/models/settings_global.dart b/lib/models/settings/settings_global.dart similarity index 94% rename from lib/models/settings_global.dart rename to lib/models/settings/settings_global.dart index 09994f4124e0761b2da152295f837a0d535514ec..4dd791da096642331f4741c50e50a70734707a02 100644 --- a/lib/models/settings_global.dart +++ b/lib/models/settings/settings_global.dart @@ -1,5 +1,5 @@ import 'package:colors/config/default_global_settings.dart'; -import 'package:colors/utils/tools.dart'; +import 'package:colors/utils/tools.dart'; class GlobalSettings { String skin; diff --git a/lib/models/types.dart b/lib/models/types.dart deleted file mode 100644 index 339bfe66331369eb92a4113be046f7a36785b9c6..0000000000000000000000000000000000000000 --- a/lib/models/types.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:colors/models/cell.dart'; - -typedef BoardCells = List<List<Cell>>; diff --git a/lib/ui/game/game_bottom.dart b/lib/ui/game/game_bottom.dart new file mode 100644 index 0000000000000000000000000000000000000000..309ee2b4fc189d544b39efa70a0eed05ae0c3af6 --- /dev/null +++ b/lib/ui/game/game_bottom.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/ui/widgets/game/select_color_bar.dart'; + +class GameBottomWidget extends StatelessWidget { + const GameBottomWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return !currentGame.isFinished ? const SelectColorBar() : const SizedBox.shrink(); + }, + ); + } +} diff --git a/lib/ui/widgets/game/message_game_end.dart b/lib/ui/game/game_end.dart similarity index 51% rename from lib/ui/widgets/game/message_game_end.dart rename to lib/ui/game/game_end.dart index 2a5cae825069b95e1c4cc9865c1c6acf678516ee..c5847410d75818a2acdce36315a20fd1e84b0fcf 100644 --- a/lib/ui/widgets/game/message_game_end.dart +++ b/lib/ui/game/game_end.dart @@ -1,12 +1,12 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/models/game.dart'; -import 'package:colors/ui/widgets/game/button_game_restart.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/ui/widgets/actions/button_game_quit.dart'; -class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key}); +class GameEndWidget extends StatelessWidget { + const GameEndWidget({super.key}); @override Widget build(BuildContext context) { @@ -14,15 +14,9 @@ class EndGameMessage extends StatelessWidget { builder: (BuildContext context, GameState gameState) { final Game currentGame = gameState.currentGame; - String decorationImageAssetName = ''; - if (currentGame.gameWon) { - decorationImageAssetName = 'assets/icons/game_win.png'; - } else { - decorationImageAssetName = 'assets/icons/game_fail.png'; - } - final Image decorationImage = Image( - image: AssetImage(decorationImageAssetName), + image: AssetImage( + currentGame.gameWon ? 'assets/ui/game_win.png' : 'assets/ui/game_fail.png'), fit: BoxFit.fill, ); @@ -34,11 +28,19 @@ class EndGameMessage extends StatelessWidget { children: [ TableRow( children: [ - Column(children: [decorationImage]), - Column(children: [decorationImage]), - const Column(children: [RestartGameButton()]), - Column(children: [decorationImage]), - Column(children: [decorationImage]), + 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..b9d6970944d7fc3cf6b580a42432f08f9796326c --- /dev/null +++ b/lib/ui/game/game_top.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +import 'package:colors/ui/widgets/indicators/indicator_max_moves.dart'; +import 'package:colors/ui/widgets/indicators/indicator_moves_count.dart'; +import 'package:colors/ui/widgets/indicators/indicator_progress.dart'; + +class GameTopWidget extends StatelessWidget { + const GameTopWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Table( + children: const [ + TableRow( + children: [ + Column( + children: [ + GameIndicatorMovesCount(), + ], + ), + Column( + children: [ + GameIndicatorMaxMovesCount(), + GameIndicatorProgress(), + ], + ), + ], + ), + ], + ); + } +} 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/widgets/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart similarity index 69% rename from lib/ui/widgets/helpers/outlined_text_widget.dart rename to lib/ui/helpers/outlined_text_widget.dart index 8e33709bd71085ea7a64084a231f692953af203d..7f5d58b79526f42799105fbee3b37d8c1ec1d983 100644 --- a/lib/ui/widgets/helpers/outlined_text_widget.dart +++ b/lib/ui/helpers/outlined_text_widget.dart @@ -1,22 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:colors/utils/color_extensions.dart'; + class OutlinedText extends StatelessWidget { const OutlinedText({ super.key, required this.text, required this.fontSize, required this.textColor, - required this.outlineColor, + this.outlineColor, }); final String text; final double fontSize; final Color textColor; - final Color outlineColor; + final Color? outlineColor; @override Widget build(BuildContext context) { - final double delta = fontSize / 35; + final double delta = fontSize / 30; return Text( text, @@ -28,19 +30,19 @@ class OutlinedText extends StatelessWidget { shadows: [ Shadow( offset: Offset(-delta, -delta), - color: outlineColor, + color: outlineColor ?? textColor.darken(), ), Shadow( offset: Offset(delta, -delta), - color: outlineColor, + color: outlineColor ?? textColor.darken(), ), Shadow( offset: Offset(delta, delta), - color: outlineColor, + color: outlineColor ?? textColor.darken(), ), Shadow( offset: Offset(-delta, delta), - color: outlineColor, + 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..2645e80aaf465bc04ebc357a65a99571e476bdfb --- /dev/null +++ b/lib/ui/layouts/game_layout.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/ui/game/game_bottom.dart'; +import 'package:colors/ui/game/game_end.dart'; +import 'package:colors/ui/game/game_top.dart'; +import 'package:colors/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 SizedBox(height: 8), + const GameBottomWidget(), + const Expanded(child: SizedBox.shrink()), + currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/layouts/parameters_layout.dart similarity index 55% rename from lib/ui/widgets/parameters.dart rename to lib/ui/layouts/parameters_layout.dart index 55525a9b2402805ed513f789958c568493f28089..a1bbda5d4eb8ef04e4d7349124b9433241fcf2f4 100644 --- a/lib/ui/widgets/parameters.dart +++ b/lib/ui/layouts/parameters_layout.dart @@ -1,15 +1,20 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:colors/config/default_game_settings.dart'; import 'package:colors/config/default_global_settings.dart'; import 'package:colors/cubit/settings_game_cubit.dart'; import 'package:colors/cubit/settings_global_cubit.dart'; -import 'package:colors/ui/painters/parameter_painter.dart'; -import 'package:colors/ui/widgets/button_game_start_new.dart'; +import 'package:colors/ui/parameters/parameter_image.dart'; +import 'package:colors/ui/parameters/parameter_painter.dart'; +import 'package:colors/ui/widgets/actions/button_delete_saved_game.dart'; +import 'package:colors/ui/widgets/actions/button_game_start_new.dart'; +import 'package:colors/ui/widgets/actions/button_resume_saved_game.dart'; + +class ParametersLayout extends StatelessWidget { + const ParametersLayout({super.key, required this.canResume}); -class Parameters extends StatelessWidget { - const Parameters({super.key}); + final bool canResume; final double separatorHeight = 8.0; @@ -17,8 +22,6 @@ class Parameters extends StatelessWidget { Widget build(BuildContext context) { final List<Widget> lines = []; - lines.add(SizedBox(height: separatorHeight)); - // Game settings for (String code in DefaultGameSettings.availableParameters) { lines.add(Row( @@ -33,7 +36,24 @@ class Parameters extends StatelessWidget { } lines.add(SizedBox(height: separatorHeight)); - lines.add(const Expanded(child: StartNewGameButton())); + + 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 @@ -85,28 +105,41 @@ class Parameters extends StatelessWidget { final bool isActive = (value == currentValue); final double displayWidth = MediaQuery.of(context).size.width; - final double itemWidth = displayWidth / availableValues.length - 30; + final double itemWidth = displayWidth / availableValues.length - 26; + + final bool displayedWithAssets = + DefaultGlobalSettings.displayedWithAssets.contains(code) || + DefaultGameSettings.displayedWithAssets.contains(code); return TextButton( child: Container( - margin: const EdgeInsets.all(0), - padding: const EdgeInsets.all(0), - child: CustomPaint( - size: Size(itemWidth, itemWidth), - willChange: false, - painter: ParameterPainter( - code: code, - value: value, - isSelected: isActive, - gameSettings: gameSettingsState.settings, - globalSettings: globalSettingsState.settings, - ), - isComplex: true, - ), + 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), + onPressed: () { + isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value); + }, ); }, ); diff --git a/lib/ui/painters/board_painter.dart b/lib/ui/painters/board_painter.dart index 42bb2a2a5f7e284f9bc2bd9fb6cda486ea07fe50..2dfb282ee6b100ee2166d539a362dc5a4b521ac6 100644 --- a/lib/ui/painters/board_painter.dart +++ b/lib/ui/painters/board_painter.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:colors/models/cell.dart'; -import 'package:colors/models/game.dart'; -import 'package:colors/models/types.dart'; -import 'package:colors/utils/color_theme.dart'; +import 'package:colors/config/color_theme.dart'; +import 'package:colors/models/game/board.dart'; +import 'package:colors/models/game/cell.dart'; +import 'package:colors/models/game/game.dart'; class BoardPainter extends CustomPainter { const BoardPainter({ @@ -19,6 +19,7 @@ class BoardPainter extends CustomPainter { final int boardSize = game.gameSettings.boardSize; final BoardCells cells = game.board.cells; final double cellSize = size.width / boardSize; + const double borderSize = 3; // background for (var row = 0; row < boardSize; row++) { @@ -34,17 +35,17 @@ class BoardPainter extends CustomPainter { cellPaintBackground.color = Color(colorCode); cellPaintBackground.style = PaintingStyle.fill; - final Rect cellBackground = - Rect.fromPoints(Offset(x - 1, y - 1), Offset(x + cellSize + 2, y + cellSize + 2)); + final Rect cellBackground = Rect.fromPoints( + Offset(x - (borderSize / 2), y - (borderSize / 2)), + Offset(x + (borderSize / 2) + cellSize, y + (borderSize / 2) + cellSize)); canvas.drawRect(cellBackground, cellPaintBackground); } } // borders - const double borderSize = 2; final cellPaintBorder = Paint(); - cellPaintBorder.color = colorScheme.onBackground; + cellPaintBorder.color = colorScheme.onSurface; cellPaintBorder.strokeWidth = borderSize; cellPaintBorder.strokeCap = StrokeCap.round; 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/painters/parameter_painter.dart b/lib/ui/parameters/parameter_painter.dart similarity index 96% rename from lib/ui/painters/parameter_painter.dart rename to lib/ui/parameters/parameter_painter.dart index fb216ed2c3bd02cb57c27c8121ba47de02e637d7..4d9273cf11baedc2f8703e60044f86a9a476b6f3 100644 --- a/lib/ui/painters/parameter_painter.dart +++ b/lib/ui/parameters/parameter_painter.dart @@ -2,12 +2,12 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:colors/config/color_theme.dart'; import 'package:colors/config/default_game_settings.dart'; import 'package:colors/config/default_global_settings.dart'; -import 'package:colors/models/settings_game.dart'; -import 'package:colors/models/settings_global.dart'; +import 'package:colors/models/settings/settings_game.dart'; +import 'package:colors/models/settings/settings_global.dart'; import 'package:colors/utils/color_extensions.dart'; -import 'package:colors/utils/color_theme.dart'; import 'package:colors/utils/tools.dart'; class ParameterPainter extends CustomPainter { @@ -38,7 +38,7 @@ class ParameterPainter extends CustomPainter { paint.style = PaintingStyle.stroke; paint.color = isSelected ? borderColorEnabled : borderColorDisabled; paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 20 / 100 * canvasSize; + paint.strokeWidth = 10; canvas.drawRect( Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); @@ -75,7 +75,7 @@ class ParameterPainter extends CustomPainter { ) { final paint = Paint(); paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 3 / 100 * size; + paint.strokeWidth = 3; paint.color = Colors.grey; paint.style = PaintingStyle.fill; @@ -92,6 +92,7 @@ class ParameterPainter extends CustomPainter { final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, + textAlign: TextAlign.center, ); textPainter.layout(); textPainter.paint( @@ -160,6 +161,7 @@ class ParameterPainter extends CustomPainter { final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, + textAlign: TextAlign.center, ); textPainter.layout(); @@ -261,7 +263,7 @@ class ParameterPainter extends CustomPainter { final paint = Paint(); paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 3 / 100 * size; + paint.strokeWidth = 3; // Colored background paint.color = backgroundColor; @@ -318,6 +320,7 @@ class ParameterPainter extends CustomPainter { final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, + textAlign: TextAlign.center, ); textPainter.layout(); textPainter.paint( @@ -339,7 +342,7 @@ class ParameterPainter extends CustomPainter { final paint = Paint(); paint.strokeJoin = StrokeJoin.round; - paint.strokeWidth = 3 / 100 * size; + paint.strokeWidth = 3; // Colored background paint.color = backgroundColor; diff --git a/lib/ui/screens/page_about.dart b/lib/ui/screens/page_about.dart index a8602ef37637e6f640625560da6a31e461f11238..0a67bb7eaefeef5ae0f20d0a3b203b32577943cc 100644 --- a/lib/ui/screens/page_about.dart +++ b/lib/ui/screens/page_about.dart @@ -2,37 +2,40 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:colors/ui/widgets/helpers/header_app.dart'; +import 'package:colors/ui/helpers/app_titles.dart'; class PageAbout extends StatelessWidget { const PageAbout({super.key}); @override Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - const SizedBox(height: 8), - const AppHeader(text: 'about_title'), - const Text('about_content').tr(), - FutureBuilder<PackageInfo>( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.done: - return const Text('about_version').tr( - namedArgs: { - 'version': snapshot.data!.version, - }, - ); - default: - return const SizedBox(); - } - }, - ), - ], + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + const SizedBox(height: 8), + const AppTitle(text: 'about_title'), + const Text('about_content').tr(), + FutureBuilder<PackageInfo>( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return const Text('about_version').tr( + namedArgs: { + 'version': snapshot.data!.version, + }, + ); + default: + return const SizedBox(); + } + }, + ), + ], + ), ); } } diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart index d3a64ac427c48bfbffc62f0f93e4f2793aaadcd1..78a33e0315250296f10d591780838974c7cac6ff 100644 --- a/lib/ui/screens/page_game.dart +++ b/lib/ui/screens/page_game.dart @@ -1,9 +1,10 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/ui/widgets/game/game.dart'; -import 'package:colors/ui/widgets/parameters.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/ui/layouts/game_layout.dart'; +import 'package:colors/ui/layouts/parameters_layout.dart'; class PageGame extends StatelessWidget { const PageGame({super.key}); @@ -11,8 +12,13 @@ class PageGame extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); - }); + 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/page_settings.dart b/lib/ui/screens/page_settings.dart index d559f9566662c9f3bffaaf35a184e6b79a04feda..c1771c7a53821bfb96d8db81f15cecf818770c39 100644 --- a/lib/ui/screens/page_settings.dart +++ b/lib/ui/screens/page_settings.dart @@ -1,23 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:colors/ui/widgets/helpers/header_app.dart'; -import 'package:colors/ui/widgets/settings/settings_form.dart'; +import 'package:colors/ui/helpers/app_titles.dart'; +import 'package:colors/ui/settings/settings_form.dart'; class PageSettings extends StatelessWidget { const PageSettings({super.key}); @override Widget build(BuildContext context) { - return const Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - SizedBox(height: 8), - AppHeader(text: 'settings_title'), - SizedBox(height: 8), - SettingsForm(), - ], + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + SizedBox(height: 8), + 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 69d9cebaa83e6ac8d4a222ff5bdaa08a900c3fa7..9c360cc67be68635eb3957d0709f73a15e986f53 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:colors/ui/widgets/settings/theme_card.dart'; +import 'package:colors/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 index 834c67dbd317b84767c411f3a6b1822cfa1ed9f1..5fd7bd79e680c32a419bc9bea0c80e1fdec49348 100644 --- a/lib/ui/widgets/settings/theme_card.dart +++ b/lib/ui/settings/theme_card.dart @@ -1,5 +1,5 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:colors/cubit/theme_cubit.dart'; diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index eaec6f6314be0af2e0ecb1a955db10b90b76f4d8..182e5fc0cc1cf09fc4086c0e957c082f7a2dde85 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,5 +1,5 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:colors/config/menu.dart'; import 'package:colors/cubit/nav_cubit.dart'; @@ -14,17 +14,21 @@ class SkeletonScreen extends StatelessWidget { appBar: const GlobalAppBar(), extendBodyBehindAppBar: false, body: Material( - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: BlocBuilder<NavCubit, int>( builder: (BuildContext context, int pageIndex) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + ), child: Menu.getPageWidget(pageIndex), ); }, ), ), - 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..6fc2c16f62fe5275b80f733273a49613e4b49d30 --- /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:colors/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..7d07274378b9b71588dfda2e23f71c4256212482 --- /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:colors/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/button_game_start_new.dart b/lib/ui/widgets/actions/button_game_start_new.dart similarity index 72% rename from lib/ui/widgets/button_game_start_new.dart rename to lib/ui/widgets/actions/button_game_start_new.dart index 613d2fb7cbfca7241b2c7fbf788825a92d412141..2a94abe91c33186c930d2181d98956b4ef803d2d 100644 --- a/lib/ui/widgets/button_game_start_new.dart +++ b/lib/ui/widgets/actions/button_game_start_new.dart @@ -14,17 +14,17 @@ class StartNewGameButton extends StatelessWidget { builder: (BuildContext context, GameSettingsState gameSettingsState) { return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( builder: (BuildContext context, GlobalSettingsState globalSettingsState) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - return TextButton( child: const Image( - image: AssetImage('assets/icons/button_start.png'), + image: AssetImage('assets/ui/button_start.png'), fit: BoxFit.fill, ), - onPressed: () => gameCubit.startNewGame( - gameSettings: gameSettingsState.settings, - globalSettings: globalSettingsState.settings, - ), + 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..fe603c1c1012fe8429f796a8d884a8e2f46310ae --- /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:colors/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/game/button_game_restart.dart b/lib/ui/widgets/game/button_game_restart.dart deleted file mode 100644 index dce403b3cfb2690b2f5abcf0bd9e4559654779c6..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/game/button_game_restart.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; - -import 'package:colors/cubit/game_cubit.dart'; - -class RestartGameButton extends StatelessWidget { - const RestartGameButton({super.key}); - - @override - Widget build(BuildContext context) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - - return TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all<EdgeInsets>(const EdgeInsets.all(0)), - ), - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => gameCubit.quitGame(), - ); - } -} diff --git a/lib/ui/widgets/game/cell.dart b/lib/ui/widgets/game/cell.dart new file mode 100644 index 0000000000000000000000000000000000000000..2120b645a789d5c320a94b63f1f790e8614a8904 --- /dev/null +++ b/lib/ui/widgets/game/cell.dart @@ -0,0 +1,56 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/config/color_theme.dart'; +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game/cell.dart'; +import 'package:colors/models/game/game.dart'; + +class CellWidget extends StatelessWidget { + const CellWidget({ + super.key, + required this.cell, + required this.itemWidth, + }); + + final Cell cell; + final double itemWidth; + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + final String skin = currentGame.globalSettings.skin; + final squareColor = Color(ColorTheme.getColorCode(skin, cell.value)); + + return Container( + decoration: BoxDecoration( + border: Border.all( + color: colorScheme.onSurface, + width: 4, + ), + ), + child: GestureDetector( + child: Container( + color: squareColor, + padding: const EdgeInsets.all(3), + child: SizedBox.square( + dimension: itemWidth - 19, + ), + ), + onTap: () { + if (!currentGame.animationInProgress && + currentGame.board.getFirstCellValue() != cell.value) { + BlocProvider.of<GameCubit>(context).fillBoardFromFirstCell(cell.value); + } + }, + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/cell_interactive.dart b/lib/ui/widgets/game/cell_interactive.dart deleted file mode 100644 index 40b67d01db9fb21a8b080f5d7ebc3f40bf6dc29d..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/game/cell_interactive.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; - -import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/models/cell.dart'; -import 'package:colors/models/game.dart'; -import 'package:colors/utils/color_theme.dart'; - -class InteractiveCell extends StatelessWidget { - const InteractiveCell({ - super.key, - required this.cell, - required this.colorScheme, - required this.currentGame, - required this.itemWidth, - }); - - final Cell cell; - final ColorScheme colorScheme; - final Game currentGame; - final double itemWidth; - - @override - Widget build(BuildContext context) { - final String skin = currentGame.globalSettings.skin; - final squareColor = Color(ColorTheme.getColorCode(skin, cell.value)); - - return Container( - margin: const EdgeInsets.all(2), - decoration: BoxDecoration( - border: Border.all( - color: colorScheme.onSurface, - width: 3, - ), - ), - child: GestureDetector( - child: Container( - color: squareColor, - padding: const EdgeInsets.all(4), - child: SizedBox.square( - dimension: itemWidth - 19, - ), - ), - onTap: () { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - - if (!currentGame.animationInProgress && - currentGame.board.getFirstCellValue() != cell.value) { - gameCubit.fillBoardFromFirstCell(cell.value); - } - }, - ), - ); - } -} diff --git a/lib/ui/widgets/game/game.dart b/lib/ui/widgets/game/game.dart deleted file mode 100644 index 870676c265cf9d68145f51223fbcb05494086d8e..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/game/game.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; - -import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/models/game.dart'; -import 'package:colors/ui/widgets/game/game_board.dart'; -import 'package:colors/ui/widgets/game/game_top_indicator.dart'; -import 'package:colors/ui/widgets/game/message_game_end.dart'; -import 'package:colors/ui/widgets/game/select_color_bar.dart'; - -class GameWidget extends StatelessWidget { - const GameWidget({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - final Game currentGame = gameState.currentGame; - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 8), - const GameTopIndicatorWidget(), - const SizedBox(height: 2), - const GameBoard(), - const Expanded( - child: SizedBox(height: 2), - ), - currentGame.isFinished ? const EndGameMessage() : const SelectColorBar(), - ], - ); - }, - ); - } -} diff --git a/lib/ui/widgets/game/game_board.dart b/lib/ui/widgets/game/game_board.dart index 385fc17de52368f835b2d83fe25531dc40ce523d..694b1be28e8596191a8a5c36d839a0f4efac1aa3 100644 --- a/lib/ui/widgets/game/game_board.dart +++ b/lib/ui/widgets/game/game_board.dart @@ -2,11 +2,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/models/game.dart'; +import 'package:colors/models/game/game.dart'; import 'package:colors/ui/painters/board_painter.dart'; -class GameBoard extends StatelessWidget { - const GameBoard({super.key}); +class GameBoardWidget extends StatelessWidget { + const GameBoardWidget({super.key}); @override Widget build(BuildContext context) { @@ -24,8 +24,7 @@ class GameBoard extends StatelessWidget { final int row = yTap ~/ (boardWidth / boardSize); final int cellValue = currentGame.board.getCellValue(col, row); - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); - gameCubit.fillBoardFromFirstCell(cellValue); + BlocProvider.of<GameCubit>(context).fillBoardFromFirstCell(cellValue); }, child: CustomPaint( size: Size(boardWidth, boardWidth), diff --git a/lib/ui/widgets/game/game_top_indicator.dart b/lib/ui/widgets/game/game_top_indicator.dart deleted file mode 100644 index 93948cd0e17629e81e6f7a90ac1b3de091b3969d..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/game/game_top_indicator.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; - -import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/models/game.dart'; - -class GameTopIndicatorWidget extends StatelessWidget { - const GameTopIndicatorWidget({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder<GameCubit, GameState>( - builder: (BuildContext context, GameState gameState) { - final Game currentGame = gameState.currentGame; - - String progressIndicator = '${currentGame.progress}/${currentGame.progressTotal}'; - - if (currentGame.movesCount > 0) { - progressIndicator += ' (+${currentGame.progressDelta})'; - } - - Color maxMovesCountColor = Colors.grey; - if (currentGame.movesCount > currentGame.maxMovesCount) { - maxMovesCountColor = Colors.red; - } - - return Table( - children: [ - TableRow( - children: [ - Column( - children: [ - Text( - currentGame.movesCount.toString(), - style: TextStyle( - fontSize: 35, - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.primary, - ), - ) - ], - ), - Column( - children: [ - Text( - '(max: ${currentGame.maxMovesCount})', - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: maxMovesCountColor, - ), - ), - Text( - progressIndicator, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Colors.green, - ), - ), - ], - ), - ], - ), - ], - ); - }, - ); - } -} diff --git a/lib/ui/widgets/game/select_color_bar.dart b/lib/ui/widgets/game/select_color_bar.dart index 3b8addb9f96cbe10f766008599da87eb98ad518a..7f927e70fc9f7ee5ca2c6a6fed3e9fb2b1806905 100644 --- a/lib/ui/widgets/game/select_color_bar.dart +++ b/lib/ui/widgets/game/select_color_bar.dart @@ -2,8 +2,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; import 'package:colors/cubit/game_cubit.dart'; -import 'package:colors/models/cell.dart'; -import 'package:colors/ui/widgets/game/cell_interactive.dart'; +import 'package:colors/models/game/cell.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/ui/widgets/game/cell.dart'; class SelectColorBar extends StatelessWidget { const SelectColorBar({super.key}); @@ -12,28 +13,21 @@ class SelectColorBar extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder<GameCubit, GameState>( builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + final int colorsCount = currentGame.gameSettings.colorsCount; final double blockWidth = MediaQuery.of(context).size.width; - final ColorScheme colorScheme = Theme.of(context).colorScheme; - final int maxValue = gameState.currentGame.gameSettings.colorsCount; + final double itemWidth = blockWidth / colorsCount; - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - TableRow( - children: [ - for (int value = 1; value <= maxValue; value++) - Column( - children: [ - InteractiveCell( - cell: Cell(value), - colorScheme: colorScheme, - currentGame: gameState.currentGame, - itemWidth: blockWidth / maxValue, - ), - ], - ), - ], - ), + for (int value = 1; value <= colorsCount; value++) + CellWidget( + cell: Cell(value), + itemWidth: itemWidth, + ), ], ); }, diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart index b3850bef24b8409f46d88e34ffc3907cff006bbc..227a3da94a022dad77380baf737da52cf2d0076f 100644 --- a/lib/ui/widgets/global_app_bar.dart +++ b/lib/ui/widgets/global_app_bar.dart @@ -1,11 +1,11 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:colors/config/menu.dart'; import 'package:colors/cubit/game_cubit.dart'; import 'package:colors/cubit/nav_cubit.dart'; -import 'package:colors/models/game.dart'; -import 'package:colors/ui/widgets/helpers/app_titles.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:colors/ui/helpers/app_titles.dart'; class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { const GlobalAppBar({super.key}); @@ -16,19 +16,20 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { builder: (BuildContext context, GameState gameState) { return BlocBuilder<NavCubit, int>( builder: (BuildContext context, int pageIndex) { - final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); final Game currentGame = gameState.currentGame; final List<Widget> menuActions = []; - if (currentGame.isRunning) { + if (currentGame.isRunning && !currentGame.isFinished) { menuActions.add(TextButton( - onPressed: null, - onLongPress: () => gameCubit.quitGame(), child: const Image( - image: AssetImage('assets/icons/button_back.png'), + image: AssetImage('assets/ui/button_back.png'), fit: BoxFit.fill, ), + onPressed: () {}, + onLongPress: () { + BlocProvider.of<GameCubit>(context).quitGame(); + }, )); } else { if (pageIndex == Menu.indexGame) { @@ -68,7 +69,7 @@ class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { } return AppBar( - title: const AppTitle(text: 'app_name'), + title: const AppHeader(text: 'app_name'), actions: menuActions, ); }, diff --git a/lib/ui/widgets/helpers/app_titles.dart b/lib/ui/widgets/helpers/app_titles.dart deleted file mode 100644 index 7cbbb2030419047b3dcf093a2195a498bd8e8ce9..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/helpers/app_titles.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -class AppTitle extends StatelessWidget { - const AppTitle({super.key, required this.text}); - - final String text; - - @override - Widget build(BuildContext context) { - return Text( - tr(text), - textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineLarge!.apply(fontWeightDelta: 2), - ); - } -} diff --git a/lib/ui/widgets/helpers/header_app.dart b/lib/ui/widgets/helpers/header_app.dart deleted file mode 100644 index b5c5be05f6636cf488dcdb5bbc4d6f049b98de11..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/helpers/header_app.dart +++ /dev/null @@ -1,24 +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 Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tr(text), - textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), - ), - const SizedBox(height: 8), - ], - ); - } -} diff --git a/lib/ui/widgets/indicators/indicator_max_moves.dart b/lib/ui/widgets/indicators/indicator_max_moves.dart new file mode 100644 index 0000000000000000000000000000000000000000..8af0991284c128fe8324f031e2ac853078e8af78 --- /dev/null +++ b/lib/ui/widgets/indicators/indicator_max_moves.dart @@ -0,0 +1,32 @@ +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + + +class GameIndicatorMaxMovesCount extends StatelessWidget { + const GameIndicatorMaxMovesCount({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + Color maxMovesCountColor = Colors.grey; + if (currentGame.movesCount > currentGame.maxMovesCount) { + maxMovesCountColor = Colors.red; + } + + return Text( + '(max: ${currentGame.maxMovesCount})', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: maxMovesCountColor, + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/indicators/indicator_moves_count.dart b/lib/ui/widgets/indicators/indicator_moves_count.dart new file mode 100644 index 0000000000000000000000000000000000000000..010ad60711c918af3d8d837c959bf0aec1a46ae5 --- /dev/null +++ b/lib/ui/widgets/indicators/indicator_moves_count.dart @@ -0,0 +1,27 @@ +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + + +class GameIndicatorMovesCount extends StatelessWidget { + const GameIndicatorMovesCount({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Text( + currentGame.movesCount.toString(), + style: TextStyle( + fontSize: 35, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/indicators/indicator_progress.dart b/lib/ui/widgets/indicators/indicator_progress.dart new file mode 100644 index 0000000000000000000000000000000000000000..1259fb0ccf51e3c844ad499dd9af3ace02d11a91 --- /dev/null +++ b/lib/ui/widgets/indicators/indicator_progress.dart @@ -0,0 +1,33 @@ +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game/game.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + + +class GameIndicatorProgress extends StatelessWidget { + const GameIndicatorProgress({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + String progressIndicator = '${currentGame.progress}/${currentGame.progressTotal}'; + + if (currentGame.movesCount > 0) { + progressIndicator += ' (+${currentGame.progressDelta})'; + } + + return Text( + progressIndicator, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.green, + ), + ); + }, + ); + } +} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart index 39c9322574b0fbfae3a7b9ba4eb964942b807d37..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 100644 --- a/lib/utils/color_extensions.dart +++ b/lib/utils/color_extensions.dart @@ -1,7 +1,5 @@ import 'dart:ui'; -import 'package:flutter/material.dart'; - extension ColorExtension on Color { Color darken([int percent = 40]) { assert(1 <= percent && percent <= 100); diff --git a/lib/utils/tools.dart b/lib/utils/tools.dart index 8ed7a5313f39ef5257deba3c3119c0f41ab10cd9..fd48b2b009b80b22248d6e7f08a63e96c4065bd7 100644 --- a/lib/utils/tools.dart +++ b/lib/utils/tools.dart @@ -1,15 +1,7 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; void printlog(String message) { if (!kReleaseMode) { debugPrint(message); } } - -Widget buildImageContainerWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/$imageAssetCode.png'), - fit: BoxFit.fill, - ); -} diff --git a/pubspec.lock b/pubspec.lock index 25b5be0248780b099e0f72fc5dab70bd5ce13f92..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,18 +106,18 @@ 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 @@ -164,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: @@ -188,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: @@ -236,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: @@ -276,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: @@ -308,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: @@ -425,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: @@ -438,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 e6b8af0dde4d69af11d8f3a645b1418b75662251..498d8abc1014e0a1eff0e5069391c3c8b31adc8f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,16 +1,18 @@ name: colors description: Fill the board, a colorfull game! -publish_to: 'none' -version: 0.0.39+39 +publish_to: "none" + +version: 0.1.0+40 environment: - sdk: '^3.0.0' + sdk: "^3.0.0" dependencies: flutter: sdk: flutter + # base easy_localization: ^3.0.1 equatable: ^2.0.5 flutter_bloc: ^8.1.1 @@ -20,13 +22,16 @@ dependencies: path_provider: ^2.0.11 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/icons/ + - assets/ui/ - assets/translations/ fonts: 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..659697a1c043cfe1c7654635cfaec3e4a0ff8a1a --- /dev/null +++ b/resources/build_resources.sh @@ -0,0 +1,7 @@ +#! /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/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/icons/button_back.svg b/resources/ui/images/button_back.svg similarity index 100% rename from icons/button_back.svg rename to resources/ui/images/button_back.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/icons/button_start.svg b/resources/ui/images/button_start.svg similarity index 100% rename from icons/button_start.svg rename to resources/ui/images/button_start.svg diff --git a/icons/game_fail.svg b/resources/ui/images/game_fail.svg similarity index 100% rename from icons/game_fail.svg rename to resources/ui/images/game_fail.svg diff --git a/icons/game_win.svg b/resources/ui/images/game_win.svg similarity index 100% rename from icons/game_win.svg rename to resources/ui/images/game_win.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"/>