diff --git a/android/gradle.properties b/android/gradle.properties index ed86f0f26922ea79f4d0c8e0e84f6d74259952bb..3487476dc637023e34426ce50caf3343f91e3038 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.22 -app.versionCode=22 +app.versionName=0.0.23 +app.versionCode=23 diff --git a/fastlane/metadata/android/en-US/changelogs/23.txt b/fastlane/metadata/android/en-US/changelogs/23.txt new file mode 100644 index 0000000000000000000000000000000000000000..cf2edcda6ef3e34843d546e8687be049ca3993c8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/23.txt @@ -0,0 +1 @@ +Add graphic themes. diff --git a/fastlane/metadata/android/fr-FR/changelogs/23.txt b/fastlane/metadata/android/fr-FR/changelogs/23.txt new file mode 100644 index 0000000000000000000000000000000000000000..36ea90e476ed28f45ca08c66cb054f40ba79823b --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/23.txt @@ -0,0 +1 @@ +Ajout de différents thèmes graphiques. diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart index 8d3f8be4e46d5ba48a5c318d677664272a691f3f..d8d7c9513a6bdc3ccf32c4df188466176b5731c7 100644 --- a/lib/config/default_game_settings.dart +++ b/lib/config/default_game_settings.dart @@ -38,6 +38,7 @@ class DefaultGameSettings { return DefaultGameSettings.allowedColorsCountValues; } + print('Did not find any available value for game parameter \"' + parameterCode + '\".'); return []; } diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart index 420faf0edd22548df0c7baafc2a07b55b9a54f86..773491a601114e5d5c4a21873d4ae893d7a1c41e 100644 --- a/lib/config/default_global_settings.dart +++ b/lib/config/default_global_settings.dart @@ -1,6 +1,7 @@ class DefaultGlobalSettings { static const List<String> availableParameters = [ 'colorsTheme', + 'graphicTheme', ]; static const int defaultColorsThemeValue = 1; @@ -17,12 +18,49 @@ class DefaultGlobalSettings { // 9, // 0x111323,0x374566,0x50785d,0x8497b3,0xe8dcd8,0xcfb463,0xb35447,0x692e4b, // https://lospec.com/palette-list/low-8 ]; + static const int graphicThemeSolidBackground = 0; + static const int graphicThemeGradientAndBorder = 1; + static const int graphicThemeEmojis = 2; + static const int graphicThemePatterns = 3; + + static const int defaultGraphicThemeValue = graphicThemeSolidBackground; + static const List<int> allowedGraphicThemeValues = [ + graphicThemeSolidBackground, + graphicThemeGradientAndBorder, + graphicThemeEmojis, + graphicThemePatterns, + ]; + + static const List<String> graphicThemeContentEmojiStrings = [ + '🍏', + '🤍', + '🦋', + '🐞', + '⭐', + '🍄', + '🍒', + '🐤', + ]; + static const List<String> graphicThemeContentPatternStrings = [ + '✖', + '✚', + '▲', + '■', + '●', + '◆', + '━', + '⧧', + ]; + static List<int> getAvailableValues(String parameterCode) { switch (parameterCode) { case 'colorsTheme': return DefaultGlobalSettings.allowedColorsThemeValues; + case 'graphicTheme': + return DefaultGlobalSettings.allowedGraphicThemeValues; } + print('Did not find any available value for global parameter \"' + parameterCode + '\".'); return []; } } diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart index d4a61c8c8f2b95df71eaef47859b9b0dabe0dc3d..94a99a3ca65d2d912eacdfe6eaaa1a3ff67c4586 100644 --- a/lib/cubit/settings_global_cubit.dart +++ b/lib/cubit/settings_global_cubit.dart @@ -11,11 +11,13 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { void setValues({ int? colorsTheme, + int? graphicTheme, }) { emit( GlobalSettingsState( settings: GlobalSettings( colorsTheme: colorsTheme ?? state.settings.colorsTheme, + graphicTheme: graphicTheme ?? state.settings.graphicTheme, ), ), ); @@ -25,6 +27,8 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { switch (code) { case 'colorsTheme': return GlobalSettings.getColorsThemeValueFromUnsafe(state.settings.colorsTheme); + case 'graphicTheme': + return GlobalSettings.getGraphicThemeValueFromUnsafe(state.settings.graphicTheme); } return 0; } @@ -34,19 +38,23 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { print('code: ' + code + ' / value: ' + value.toString()); int colorsTheme = code == 'colorsTheme' ? value : getParameterValue('colorsTheme'); + int graphicTheme = code == 'graphicTheme' ? value : getParameterValue('graphicTheme'); setValues( colorsTheme: colorsTheme, + graphicTheme: graphicTheme, ); } @override GlobalSettingsState? fromJson(Map<String, dynamic> json) { int colorsTheme = json['colorsTheme'] as int; + int graphicTheme = json['graphicTheme'] as int; return GlobalSettingsState( settings: GlobalSettings( colorsTheme: colorsTheme, + graphicTheme: graphicTheme, ), ); } @@ -55,6 +63,7 @@ class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { Map<String, dynamic>? toJson(GlobalSettingsState state) { return <String, dynamic>{ 'colorsTheme': state.settings.colorsTheme, + 'graphicTheme': state.settings.graphicTheme, }; } } diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart index 8156993df521018fa50f7522b5ee1d34705e7d90..eac1cf32fe955e0b91309c388d715f28a80b4a9a 100644 --- a/lib/models/settings_global.dart +++ b/lib/models/settings_global.dart @@ -2,9 +2,11 @@ import 'package:jeweled/config/default_global_settings.dart'; class GlobalSettings { final int colorsTheme; + final int graphicTheme; GlobalSettings({ required this.colorsTheme, + required this.graphicTheme, }); static int getColorsThemeValueFromUnsafe(int colorsTheme) { @@ -15,15 +17,25 @@ class GlobalSettings { return DefaultGlobalSettings.defaultColorsThemeValue; } + static int getGraphicThemeValueFromUnsafe(int graphicTheme) { + if (DefaultGlobalSettings.allowedGraphicThemeValues.contains(graphicTheme)) { + return graphicTheme; + } + + return DefaultGlobalSettings.defaultGraphicThemeValue; + } + factory GlobalSettings.createDefault() { return GlobalSettings( colorsTheme: DefaultGlobalSettings.defaultColorsThemeValue, + graphicTheme: DefaultGlobalSettings.defaultGraphicThemeValue, ); } void dump() { print('Settings: '); print(' colorsTheme: ' + colorsTheme.toString()); + print(' graphicTheme: ' + graphicTheme.toString()); } String toString() { @@ -33,6 +45,7 @@ class GlobalSettings { Map<String, dynamic>? toJson() { return <String, dynamic>{ 'colorsTheme': this.colorsTheme, + 'graphicTheme': this.graphicTheme, }; } } diff --git a/lib/ui/painters/game_board_painter.dart b/lib/ui/painters/game_board_painter.dart index 52ef2114ecd06e37972392f8d8c9f9bf61416c3a..428dbe8d9b06ee14027a5e4abcfabb0271798e00 100644 --- a/lib/ui/painters/game_board_painter.dart +++ b/lib/ui/painters/game_board_painter.dart @@ -1,60 +1,74 @@ import 'dart:math'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:jeweled/config/default_global_settings.dart'; import 'package:jeweled/models/game.dart'; import 'package:jeweled/models/cell_location.dart'; +import 'package:jeweled/utils/color_extensions.dart'; import 'package:jeweled/utils/color_theme.dart'; class GameBoardPainter extends CustomPainter { - const GameBoardPainter({required this.game, required this.animations}); + const GameBoardPainter({ + required this.game, + required this.animations, + }); final Game game; final List<List<Animation<double>?>> animations; @override void paint(Canvas canvas, Size size) { - final double canvasSize = min(size.width, size.height); - - final int cellsCountHorizontal = game.gameSettings.boardSize; - final int cellsCountVertical = game.gameSettings.boardSize; - - final double cellSize = canvasSize / max(cellsCountHorizontal, cellsCountVertical); - - const double overlapping = 1; - const double borderSize = 4; - - // background - for (int row = 0; row < cellsCountVertical; row++) { - final double yOrigin = cellSize * row; - for (int col = 0; col < cellsCountHorizontal; col++) { - final double x = cellSize * col; - - final CellLocation cellLocation = CellLocation.go(row, col); - final int? cellValue = game.getCellValueShuffled(cellLocation); - if (cellValue != null) { - final int colorCode = - ColorTheme.getColorCode(cellValue, game.globalSettings.colorsTheme); - - final Animation<double>? translation = this.animations[row][col]; - final double y = yOrigin + (translation?.value ?? 0) * cellSize; - - final cellPaintBackground = Paint(); - cellPaintBackground.color = Color(colorCode); - cellPaintBackground.style = PaintingStyle.fill; + int graphicTheme = game.globalSettings.graphicTheme; - final Rect cellBackground = Rect.fromPoints( - Offset(x, y), Offset(x + cellSize + overlapping, y + cellSize + overlapping)); + final double canvasSize = min(size.width, size.height); - canvas.drawRect(cellBackground, cellPaintBackground); - } - } + const double cellBorderWidth = 2; + const double boardBorderWidth = 3; + + switch (graphicTheme) { + case DefaultGlobalSettings.graphicThemeSolidBackground: + this.drawBoard( + canvas: canvas, + canvasSize: canvasSize, + game: game, + ); + break; + case DefaultGlobalSettings.graphicThemeGradientAndBorder: + this.drawBoard( + canvas: canvas, + canvasSize: canvasSize, + game: game, + borderWidth: cellBorderWidth, + gradientFrom: -10, + gradientTo: 10, + ); + break; + case DefaultGlobalSettings.graphicThemeEmojis: + this.drawBoard( + canvas: canvas, + canvasSize: canvasSize, + game: game, + contentStrings: DefaultGlobalSettings.graphicThemeContentEmojiStrings, + ); + break; + case DefaultGlobalSettings.graphicThemePatterns: + this.drawBoard( + canvas: canvas, + canvasSize: canvasSize, + game: game, + contentStrings: DefaultGlobalSettings.graphicThemeContentPatternStrings, + ); + break; + + default: } // board borders final boardPaintBorder = Paint(); boardPaintBorder.color = ColorTheme.getBorderColor(); - boardPaintBorder.strokeWidth = borderSize; + boardPaintBorder.strokeWidth = boardBorderWidth; boardPaintBorder.strokeCap = StrokeCap.round; final Offset boardTopLeft = Offset(0, 0); @@ -72,4 +86,126 @@ class GameBoardPainter extends CustomPainter { bool shouldRepaint(CustomPainter oldDelegate) { return true; } + + void drawBoard({ + required Canvas canvas, + required double canvasSize, + required Game game, + double overlapping = 1, + int gradientFrom = 0, + int gradientTo = 0, + double borderWidth = 0, + List<String>? contentStrings, + }) { + final int cellsCountHorizontal = game.gameSettings.boardSize; + final int cellsCountVertical = game.gameSettings.boardSize; + final int colorsTheme = game.globalSettings.colorsTheme; + + final double size = canvasSize / max(cellsCountHorizontal, cellsCountVertical); + + for (int row = 0; row < cellsCountVertical; row++) { + final double yOrigin = size * row; + for (int col = 0; col < cellsCountHorizontal; col++) { + final double x = size * col; + + final CellLocation cellLocation = CellLocation.go(row, col); + final int? cellValue = game.getCellValueShuffled(cellLocation); + if (cellValue != null) { + final Animation<double>? translation = this.animations[row][col]; + final double y = yOrigin + (translation?.value ?? 0) * size; + + this.drawCell( + canvas: canvas, + x: x, + y: y, + cellSize: size, + cellValue: cellValue, + colorsTheme: colorsTheme, + overlapping: overlapping, + gradientFrom: gradientFrom, + gradientTo: gradientTo, + borderWidth: borderWidth, + contentStrings: contentStrings, + ); + } + } + } + } + + void drawCell({ + required Canvas canvas, + required double x, + required double y, + required double cellSize, + required int cellValue, + required int colorsTheme, + required double overlapping, + required int gradientFrom, + required int gradientTo, + required double borderWidth, + required List<String>? contentStrings, + }) { + final Color baseColor = ColorTheme.getColor(cellValue, colorsTheme); + + final paint = Paint(); + + // draw background + final Rect rect = Rect.fromLTWH(x, y, cellSize + overlapping, cellSize + overlapping); + + // solid or gradient + if (gradientFrom == 0 && gradientTo == 0) { + paint.color = baseColor; + } else { + paint.shader = ui.Gradient.linear( + rect.topLeft, + rect.bottomCenter, + [ + (gradientFrom < 0) + ? baseColor.lighten(-gradientFrom) + : baseColor.darken(gradientFrom), + (gradientTo < 0) ? baseColor.lighten(-gradientTo) : baseColor.darken(gradientTo), + ], + ); + } + paint.style = PaintingStyle.fill; + canvas.drawRect(rect, paint); + + // draw border + if (borderWidth != 0) { + final Rect border = Rect.fromLTWH(x + borderWidth, y + borderWidth, + cellSize + overlapping - 2 * borderWidth, cellSize + overlapping - 2 * borderWidth); + + final paintBorder = Paint(); + paintBorder.color = baseColor.darken(20); + paintBorder.style = PaintingStyle.stroke; + + paintBorder.strokeWidth = borderWidth; + paintBorder.strokeJoin = StrokeJoin.round; + canvas.drawRect(border, paintBorder); + } + + // draw content + if (contentStrings != null) { + final textSpan = TextSpan( + text: contentStrings[cellValue - 1], + style: TextStyle( + color: Colors.black, + fontSize: 4 + cellSize / 2, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + x + (cellSize - textPainter.width) * 0.5, + y + (cellSize - textPainter.height) * 0.5, + ), + ); + } + } } diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart index 9bbfd47c691e7e32b81ec91e565af00fdc73660f..775e306d55006714379f4459f5bfbdebe148706c 100644 --- a/lib/ui/painters/parameter_painter.dart +++ b/lib/ui/painters/parameter_painter.dart @@ -1,8 +1,10 @@ import 'dart:math'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:jeweled/config/default_game_settings.dart'; +import 'package:jeweled/config/default_global_settings.dart'; import 'package:jeweled/models/settings_game.dart'; import 'package:jeweled/models/settings_global.dart'; import 'package:jeweled/utils/color_extensions.dart'; @@ -50,6 +52,9 @@ class ParameterPainter extends CustomPainter { case 'colorsTheme': paintColorsThemeParameterItem(value, canvas, canvasSize); break; + case 'graphicTheme': + paintGraphicThemeParameterItem(value, canvas, canvasSize); + break; default: print('Unknown parameter: ' + code + '/' + value.toString()); paintUnknownParameterItem(value, canvas, canvasSize); @@ -62,7 +67,19 @@ class ParameterPainter extends CustomPainter { } // "unknown" parameter -> simple bock with text - void paintUnknownParameterItem(final int value, final Canvas canvas, final double size) { + void paintUnknownParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + paint.color = Colors.grey; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(Offset(0, 0), Offset(size, size)), paint); + final textSpan = TextSpan( text: '?' + '\n' + value.toString(), style: const TextStyle( @@ -85,7 +102,11 @@ class ParameterPainter extends CustomPainter { ); } - void paintBoardSizeParameterItem(final int value, final Canvas canvas, final double size) { + void paintBoardSizeParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { Color backgroundColor = Colors.grey; int gridWidth = 1; @@ -144,7 +165,11 @@ class ParameterPainter extends CustomPainter { } } - void paintColorsCountParameterItem(final int value, final Canvas canvas, final double size) { + void paintColorsCountParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { Color backgroundColor = Colors.grey; switch (value) { @@ -232,7 +257,11 @@ class ParameterPainter extends CustomPainter { ); } - void paintColorsThemeParameterItem(final int value, final Canvas canvas, final double size) { + void paintColorsThemeParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { Color backgroundColor = Colors.grey; const int gridWidth = 4; @@ -268,4 +297,116 @@ class ParameterPainter extends CustomPainter { } } } + + void paintGraphicThemeParameterItem( + final int value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(Offset(0, 0), Offset(size, size)), paint); + + // cells preview + const List<Offset> positions = [ + Offset(0, 0), + Offset(1, 0), + Offset(2, 0), + Offset(2, 1), + Offset(2, 2), + Offset(1, 2), + Offset(0, 2), + Offset(0, 1), + ]; + + final double padding = 5 / 100 * size; + final double width = (size - 2 * padding) / 3; + + bool drawBorder = false; + bool gradientColor = false; + List<String> contentStrings = []; + + switch (value) { + case DefaultGlobalSettings.graphicThemeSolidBackground: + break; + case DefaultGlobalSettings.graphicThemeGradientAndBorder: + drawBorder = true; + gradientColor = true; + break; + case DefaultGlobalSettings.graphicThemeEmojis: + contentStrings = DefaultGlobalSettings.graphicThemeContentEmojiStrings; + break; + case DefaultGlobalSettings.graphicThemePatterns: + contentStrings = DefaultGlobalSettings.graphicThemeContentPatternStrings; + break; + default: + print('Wrong value for colorsCount parameter value: ' + value.toString()); + } + + for (int itemValue = 0; itemValue < positions.length; itemValue++) { + final Offset position = positions[itemValue]; + + final Offset topLeft = + Offset(padding + position.dx * width, padding + position.dy * width); + final Offset bottomRight = topLeft + Offset(width, width); + + final Rect itemBox = Rect.fromPoints(topLeft, bottomRight); + final itemColor = ColorTheme.getColor(itemValue, globalSettings.colorsTheme); + + paint.color = itemColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(itemBox, paint); + + // gradient background + if (gradientColor) { + paint.shader = ui.Gradient.linear(itemBox.topLeft, itemBox.bottomCenter, [ + itemColor.lighten(10), + itemColor.darken(10), + ]); + paint.style = PaintingStyle.fill; + canvas.drawRect(itemBox, paint); + } + + // cell border + if (drawBorder) { + final paintBorder = Paint(); + paintBorder.color = itemColor.darken(20); + paintBorder.style = PaintingStyle.stroke; + paintBorder.strokeWidth = 2; + + canvas.drawRect(itemBox, paintBorder); + } + + // centered text value + if (contentStrings.length != 0) { + final textSpan = TextSpan( + text: contentStrings[itemValue], + style: TextStyle( + color: Colors.black, + fontSize: width / 2, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + topLeft.dx + (width - textPainter.width) * 0.5, + topLeft.dy + (width - textPainter.height) * 0.5, + ), + ); + } + } + } } diff --git a/lib/utils/color_theme.dart b/lib/utils/color_theme.dart index 8579cbda7f81765b26eb99ba150f0a6e162ed2bb..86369215493c9cc01c0d639da0a4bd191dac5474 100644 --- a/lib/utils/color_theme.dart +++ b/lib/utils/color_theme.dart @@ -89,6 +89,10 @@ class ColorTheme { return defaultItemColor | 0xFF000000; } + static Color getColor(int? value, final int skin) { + return Color(getColorCode(value, skin)); + } + static Color getBorderColor() { return Colors.grey; } diff --git a/pubspec.yaml b/pubspec.yaml index dc067a44485505be72ce42a6bc0376409c87ab62..2f2f682b18806a5824607d9488e6982ed54b5b8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Jeweled Game publish_to: 'none' -version: 0.0.22+22 +version: 0.0.23+23 environment: sdk: '^3.0.0'