From e50ca462f959de89c5d0c133b723650ed70631fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Fri, 5 Apr 2024 22:26:26 +0200
Subject: [PATCH] Improve layout

---
 android/gradle.properties                     |  4 +-
 .../metadata/android/en-US/changelogs/30.txt  |  1 +
 .../metadata/android/fr-FR/changelogs/30.txt  |  1 +
 lib/ui/screens/game.dart                      | 13 ++--
 lib/ui/widgets/game_top_indicator.dart        | 61 +++++--------------
 lib/ui/widgets/global_app_bar.dart            |  2 +-
 lib/ui/widgets/{ => helpers}/app_titles.dart  |  0
 .../widgets/helpers/outlined_text_widget.dart | 49 +++++++++++++++
 .../indicator_available_blocks.dart           | 51 ++++++++++++++++
 .../indicators/indicator_moves_count.dart     | 24 ++++++++
 .../widgets/indicators/indicator_score.dart   | 24 ++++++++
 .../indicators/indicator_shuffle_button.dart  | 32 ++++++++++
 lib/ui/widgets/parameters.dart                | 34 ++---------
 pubspec.lock                                  | 28 +++------
 pubspec.yaml                                  |  3 +-
 15 files changed, 221 insertions(+), 106 deletions(-)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/30.txt
 create mode 100644 fastlane/metadata/android/fr-FR/changelogs/30.txt
 rename lib/ui/widgets/{ => helpers}/app_titles.dart (100%)
 create mode 100644 lib/ui/widgets/helpers/outlined_text_widget.dart
 create mode 100644 lib/ui/widgets/indicators/indicator_available_blocks.dart
 create mode 100644 lib/ui/widgets/indicators/indicator_moves_count.dart
 create mode 100644 lib/ui/widgets/indicators/indicator_score.dart
 create mode 100644 lib/ui/widgets/indicators/indicator_shuffle_button.dart

diff --git a/android/gradle.properties b/android/gradle.properties
index d965699..cc4e56e 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.29
-app.versionCode=29
+app.versionName=0.0.30
+app.versionCode=30
diff --git a/fastlane/metadata/android/en-US/changelogs/30.txt b/fastlane/metadata/android/en-US/changelogs/30.txt
new file mode 100644
index 0000000..1c6fefb
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/30.txt
@@ -0,0 +1 @@
+Improve layout and design.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/30.txt b/fastlane/metadata/android/fr-FR/changelogs/30.txt
new file mode 100644
index 0000000..3a1b67c
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/30.txt
@@ -0,0 +1 @@
+Amélioration des graphismes.
diff --git a/lib/ui/screens/game.dart b/lib/ui/screens/game.dart
index 8877778..5f56240 100644
--- a/lib/ui/screens/game.dart
+++ b/lib/ui/screens/game.dart
@@ -12,15 +12,10 @@ class ScreenGame extends StatelessWidget {
   Widget build(BuildContext context) {
     return Material(
       color: Theme.of(context).colorScheme.background,
-      child: Column(
-        children: <Widget>[
-          const SizedBox(height: 8),
-          BlocBuilder<GameCubit, GameState>(
-            builder: (BuildContext context, GameState gameState) {
-              return gameState.currentGame.isRunning ? const GameWidget() : const Parameters();
-            },
-          ),
-        ],
+      child: BlocBuilder<GameCubit, GameState>(
+        builder: (BuildContext context, GameState gameState) {
+          return gameState.currentGame.isRunning ? const GameWidget() : const Parameters();
+        },
       ),
     );
   }
diff --git a/lib/ui/widgets/game_top_indicator.dart b/lib/ui/widgets/game_top_indicator.dart
index bdbbc85..3d639b7 100644
--- a/lib/ui/widgets/game_top_indicator.dart
+++ b/lib/ui/widgets/game_top_indicator.dart
@@ -1,70 +1,39 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:unicons/unicons.dart';
 
 import 'package:jeweled/cubit/game_cubit.dart';
 import 'package:jeweled/models/game.dart';
+import 'package:jeweled/ui/widgets/indicators/indicator_available_blocks.dart';
+import 'package:jeweled/ui/widgets/indicators/indicator_moves_count.dart';
+import 'package:jeweled/ui/widgets/indicators/indicator_score.dart';
+import 'package:jeweled/ui/widgets/indicators/indicator_shuffle_button.dart';
 
 class GameTopIndicatorWidget extends StatelessWidget {
   const GameTopIndicatorWidget({super.key});
 
   @override
   Widget build(BuildContext context) {
-    const Color scoreTextColor = Colors.white;
-    const Color movesCountTextColor = Colors.grey;
-    const Color availableBlocksCountTextColor = Colors.green;
-
-    const double scoreFontSize = 40;
-    const double movesCountFontSize = 15;
-    const double availableBlocksCountFontSize = 20;
-
     return BlocBuilder<GameCubit, GameState>(
       builder: (BuildContext context, GameState gameState) {
         final Game currentGame = gameState.currentGame;
 
-        return Table(
+        return Column(
           children: [
-            TableRow(
+            ScoreIndicator(game: currentGame),
+            Table(
+              defaultVerticalAlignment: TableCellVerticalAlignment.middle,
               children: [
-                Column(
+                TableRow(
                   children: [
-                    Text(
-                      currentGame.score.toString(),
-                      style: const TextStyle(
-                        fontSize: scoreFontSize,
-                        fontWeight: FontWeight.w600,
-                        color: scoreTextColor,
-                      ),
-                    ),
-                    Text(
-                      currentGame.movesCount.toString(),
-                      style: const TextStyle(
-                        fontSize: movesCountFontSize,
-                        fontWeight: FontWeight.w600,
-                        color: movesCountTextColor,
-                      ),
+                    Center(
+                      child: MovesCountsIndicator(game: currentGame),
                     ),
-                  ],
-                ),
-                Column(
-                  children: [
-                    Text(
-                      currentGame.availableBlocksCount.toString(),
-                      style: const TextStyle(
-                        fontSize: availableBlocksCountFontSize,
-                        fontWeight: FontWeight.w600,
-                        color: availableBlocksCountTextColor,
-                      ),
+                    AvailableBlocksCountIndicator(game: currentGame),
+                    Center(
+                      child: ShuffleButton(game: currentGame),
                     ),
-                    TextButton(
-                      child: const Icon(UniconsSolid.refresh),
-                      onPressed: () {
-                        final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
-                        gameCubit.shuffleColors(currentGame.globalSettings.colorsTheme);
-                      },
-                    )
                   ],
-                ),
+                )
               ],
             ),
           ],
diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart
index 58905ec..1d425fb 100644
--- a/lib/ui/widgets/global_app_bar.dart
+++ b/lib/ui/widgets/global_app_bar.dart
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'package:jeweled/cubit/game_cubit.dart';
 import 'package:jeweled/models/game.dart';
-import 'package:jeweled/ui/widgets/app_titles.dart';
+import 'package:jeweled/ui/widgets/helpers/app_titles.dart';
 
 class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
   const GlobalAppBar({super.key});
diff --git a/lib/ui/widgets/app_titles.dart b/lib/ui/widgets/helpers/app_titles.dart
similarity index 100%
rename from lib/ui/widgets/app_titles.dart
rename to lib/ui/widgets/helpers/app_titles.dart
diff --git a/lib/ui/widgets/helpers/outlined_text_widget.dart b/lib/ui/widgets/helpers/outlined_text_widget.dart
new file mode 100644
index 0000000..8e33709
--- /dev/null
+++ b/lib/ui/widgets/helpers/outlined_text_widget.dart
@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+
+class OutlinedText extends StatelessWidget {
+  const OutlinedText({
+    super.key,
+    required this.text,
+    required this.fontSize,
+    required this.textColor,
+    required this.outlineColor,
+  });
+
+  final String text;
+  final double fontSize;
+  final Color textColor;
+  final Color outlineColor;
+
+  @override
+  Widget build(BuildContext context) {
+    final double delta = fontSize / 35;
+
+    return Text(
+      text,
+      style: TextStyle(
+        inherit: true,
+        fontSize: fontSize,
+        fontWeight: FontWeight.w600,
+        color: textColor,
+        shadows: [
+          Shadow(
+            offset: Offset(-delta, -delta),
+            color: outlineColor,
+          ),
+          Shadow(
+            offset: Offset(delta, -delta),
+            color: outlineColor,
+          ),
+          Shadow(
+            offset: Offset(delta, delta),
+            color: outlineColor,
+          ),
+          Shadow(
+            offset: Offset(-delta, delta),
+            color: outlineColor,
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/widgets/indicators/indicator_available_blocks.dart b/lib/ui/widgets/indicators/indicator_available_blocks.dart
new file mode 100644
index 0000000..865b933
--- /dev/null
+++ b/lib/ui/widgets/indicators/indicator_available_blocks.dart
@@ -0,0 +1,51 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+import 'package:jeweled/models/game.dart';
+import 'package:jeweled/ui/widgets/helpers/outlined_text_widget.dart';
+import 'package:jeweled/utils/color_extensions.dart';
+
+class AvailableBlocksCountIndicator extends StatelessWidget {
+  const AvailableBlocksCountIndicator({super.key, required this.game});
+
+  final Game game;
+
+  @override
+  Widget build(BuildContext context) {
+    // Minimum available blocks count value to fill bar
+    const minBlocksCount = 6;
+
+    // Normalized [0..1] value
+    final double barValue = min(minBlocksCount, game.availableBlocksCount) / minBlocksCount;
+
+    // Bar color: red < orange < green
+    final Color baseColor = (barValue < 0.5
+            ? Color.lerp(Colors.red, Colors.orange, barValue * 2)
+            : Color.lerp(Colors.orange, Colors.green, (barValue - 0.5) * 2)) ??
+        Colors.grey;
+
+    const barHeight = 25.0;
+    const Color textColor = Color.fromARGB(255, 238, 238, 238);
+    const Color outlineColor = Color.fromARGB(255, 200, 200, 200);
+
+    return Stack(
+      alignment: Alignment.center,
+      children: [
+        LinearProgressIndicator(
+          value: barValue,
+          color: baseColor,
+          backgroundColor: baseColor.darken(),
+          minHeight: barHeight,
+          borderRadius: const BorderRadius.all(Radius.circular(15)),
+        ),
+        OutlinedText(
+          text: game.availableBlocksCount.toString(),
+          fontSize: barHeight,
+          textColor: textColor,
+          outlineColor: outlineColor,
+        ),
+      ],
+    );
+  }
+}
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 0000000..f6ebaa8
--- /dev/null
+++ b/lib/ui/widgets/indicators/indicator_moves_count.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+
+import 'package:jeweled/models/game.dart';
+import 'package:jeweled/ui/widgets/helpers/outlined_text_widget.dart';
+import 'package:jeweled/utils/color_extensions.dart';
+
+class MovesCountsIndicator extends StatelessWidget {
+  const MovesCountsIndicator({super.key, required this.game});
+
+  final Game game;
+
+  @override
+  Widget build(BuildContext context) {
+    const Color baseColor = Color.fromARGB(255, 215, 1, 133);
+    final Color outlineColor = baseColor.darken();
+
+    return OutlinedText(
+      text: game.movesCount.toString(),
+      fontSize: 40,
+      textColor: baseColor,
+      outlineColor: outlineColor,
+    );
+  }
+}
diff --git a/lib/ui/widgets/indicators/indicator_score.dart b/lib/ui/widgets/indicators/indicator_score.dart
new file mode 100644
index 0000000..6f7dc83
--- /dev/null
+++ b/lib/ui/widgets/indicators/indicator_score.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+
+import 'package:jeweled/models/game.dart';
+import 'package:jeweled/ui/widgets/helpers/outlined_text_widget.dart';
+import 'package:jeweled/utils/color_extensions.dart';
+
+class ScoreIndicator extends StatelessWidget {
+  const ScoreIndicator({super.key, required this.game});
+
+  final Game game;
+
+  @override
+  Widget build(BuildContext context) {
+    const Color baseColor = Color.fromARGB(255, 218, 218, 218);
+    final Color outlineColor = baseColor.darken();
+
+    return OutlinedText(
+      text: game.score.toString(),
+      fontSize: 70,
+      textColor: baseColor,
+      outlineColor: outlineColor,
+    );
+  }
+}
diff --git a/lib/ui/widgets/indicators/indicator_shuffle_button.dart b/lib/ui/widgets/indicators/indicator_shuffle_button.dart
new file mode 100644
index 0000000..5688bf1
--- /dev/null
+++ b/lib/ui/widgets/indicators/indicator_shuffle_button.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:jeweled/cubit/game_cubit.dart';
+import 'package:jeweled/models/game.dart';
+import 'package:jeweled/ui/widgets/helpers/outlined_text_widget.dart';
+import 'package:jeweled/utils/color_extensions.dart';
+
+class ShuffleButton extends StatelessWidget {
+  const ShuffleButton({super.key, required this.game});
+
+  final Game game;
+
+  @override
+  Widget build(BuildContext context) {
+    const Color textColor = Color.fromARGB(255, 215, 1, 133);
+    final Color outlineColor = textColor.darken();
+
+    return TextButton(
+      child: OutlinedText(
+        text: '🔄',
+        fontSize: 25,
+        textColor: textColor,
+        outlineColor: outlineColor,
+      ),
+      onPressed: () {
+        final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
+        gameCubit.shuffleColors(game.globalSettings.colorsTheme);
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/widgets/parameters.dart
index 21cc610..c8f858a 100644
--- a/lib/ui/widgets/parameters.dart
+++ b/lib/ui/widgets/parameters.dart
@@ -92,7 +92,7 @@ class Parameters extends StatelessWidget {
     }
 
     lines.add(SizedBox(height: separatorHeight));
-    lines.add(buildStartNewGameButton());
+    lines.add(Expanded(child: buildStartNewGameButton()));
     lines.add(SizedBox(height: separatorHeight));
 
     // Global settings
@@ -138,39 +138,17 @@ class Parameters extends StatelessWidget {
   }
 
   static Widget buildStartNewGameButton() {
-    const double blockMargin = 3.0;
-    const double blockPadding = 2.0;
-
     return BlocBuilder<GameSettingsCubit, GameSettingsState>(
       builder: (BuildContext context, GameSettingsState gameSettingsState) {
         return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
           builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
             final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
 
-            return Container(
-              margin: const EdgeInsets.all(blockMargin),
-              padding: const EdgeInsets.all(blockPadding),
-              child: Table(
-                defaultColumnWidth: const IntrinsicColumnWidth(),
-                children: [
-                  TableRow(
-                    children: [
-                      buildDecorationImageWidget(),
-                      Column(
-                        children: [
-                          TextButton(
-                            child: buildImageContainerWidget('button_start'),
-                            onPressed: () => gameCubit.startNewGame(
-                              gameSettings: gameSettingsState.settings,
-                              globalSettings: globalSettingsState.settings,
-                            ),
-                          ),
-                        ],
-                      ),
-                      buildDecorationImageWidget(),
-                    ],
-                  ),
-                ],
+            return TextButton(
+              child: buildImageContainerWidget('button_start'),
+              onPressed: () => gameCubit.startNewGame(
+                gameSettings: gameSettingsState.settings,
+                globalSettings: globalSettingsState.settings,
               ),
             );
           },
diff --git a/pubspec.lock b/pubspec.lock
index f689e87..47304c5 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -13,10 +13,10 @@ packages:
     dependency: transitive
     description:
       name: bloc
-      sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e
+      sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
       url: "https://pub.dev"
     source: hosted
-    version: "8.1.3"
+    version: "8.1.4"
   characters:
     dependency: transitive
     description:
@@ -98,18 +98,18 @@ packages:
     dependency: "direct main"
     description:
       name: flutter_bloc
-      sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1"
+      sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
       url: "https://pub.dev"
     source: hosted
-    version: "8.1.4"
+    version: "8.1.5"
   flutter_lints:
     dependency: "direct dev"
     description:
       name: flutter_lints
-      sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
+      sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
+    version: "3.0.2"
   flutter_localizations:
     dependency: transitive
     description: flutter
@@ -132,10 +132,10 @@ packages:
     dependency: "direct main"
     description:
       name: hydrated_bloc
-      sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d"
+      sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c
       url: "https://pub.dev"
     source: hosted
-    version: "9.1.4"
+    version: "9.1.5"
   intl:
     dependency: transitive
     description:
@@ -333,14 +333,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.2"
-  unicons:
-    dependency: "direct main"
-    description:
-      name: unicons
-      sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670
-      url: "https://pub.dev"
-    source: hosted
-    version: "2.1.1"
   vector_math:
     dependency: transitive
     description:
@@ -361,10 +353,10 @@ packages:
     dependency: transitive
     description:
       name: win32
-      sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
+      sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a"
       url: "https://pub.dev"
     source: hosted
-    version: "5.2.0"
+    version: "5.4.0"
   xdg_directories:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 4f9bb6c..a11b99e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: Jeweled Game
 
 publish_to: 'none'
 
-version: 0.0.29+29
+version: 0.0.30+30
 
 environment:
   sdk: '^3.0.0'
@@ -17,7 +17,6 @@ dependencies:
   hive: ^2.2.3
   hydrated_bloc: ^9.0.0
   path_provider: ^2.0.11
-  unicons: ^2.1.1
 
 dev_dependencies:
   flutter_lints: ^3.0.1
-- 
GitLab