From 8dfb50dfb012bfe3412cfb1ff6df9007c2d744d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Mon, 1 Jan 2024 17:59:28 +0100
Subject: [PATCH] Add (minimal) settings page

---
 android/gradle.properties                     |  4 +-
 assets/translations/en.json                   |  5 ++
 assets/translations/fr.json                   |  5 ++
 .../metadata/android/en-US/changelogs/11.txt  |  1 +
 .../metadata/android/fr-FR/changelogs/11.txt  |  1 +
 lib/config/default_settings.dart              |  9 +++
 lib/cubit/settings_cubit.dart                 | 39 +++++++++
 lib/cubit/settings_state.dart                 | 19 +++++
 lib/main.dart                                 |  2 +
 lib/ui/screens/settings.dart                  | 29 +++++++
 lib/ui/skeleton.dart                          |  3 +-
 lib/ui/widgets/bottom_nav_bar.dart            |  5 +-
 lib/ui/widgets/settings_form.dart             | 81 +++++++++++++++++++
 pubspec.yaml                                  |  2 +-
 14 files changed, 199 insertions(+), 6 deletions(-)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/11.txt
 create mode 100644 fastlane/metadata/android/fr-FR/changelogs/11.txt
 create mode 100644 lib/config/default_settings.dart
 create mode 100644 lib/cubit/settings_cubit.dart
 create mode 100644 lib/cubit/settings_state.dart
 create mode 100644 lib/ui/screens/settings.dart
 create mode 100644 lib/ui/widgets/settings_form.dart

diff --git a/android/gradle.properties b/android/gradle.properties
index 6bf54a6..f0be9fb 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.10
-app.versionCode=10
+app.versionName=0.0.11
+app.versionCode=11
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 655cd96..435402a 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -2,6 +2,11 @@
   "app_name": "Twister",
 
   "bottom_nav_game": "Game",
+  "bottom_nav_settings": "Settings",
+
+  "settings_title": "Settings",
+  "settings_title_game": "Game settings",
+  "settings_label_game_timer_value": "Timer value: ",
 
   "lang_prefix": "en"
 }
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
index c7fb863..45fb897 100644
--- a/assets/translations/fr.json
+++ b/assets/translations/fr.json
@@ -2,6 +2,11 @@
   "app_name": "Twister",
 
   "bottom_nav_game": "Jeu",
+  "bottom_nav_settings": "Réglages",
+
+  "settings_title": "Réglages",
+  "settings_title_game": "Paramètres du jeu",
+  "settings_label_game_timer_value": "Durée du chrono : ",
 
   "lang_prefix": "fr"
 }
diff --git a/fastlane/metadata/android/en-US/changelogs/11.txt b/fastlane/metadata/android/en-US/changelogs/11.txt
new file mode 100644
index 0000000..e88f519
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/11.txt
@@ -0,0 +1 @@
+Add (minimal) application settings page.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/11.txt b/fastlane/metadata/android/fr-FR/changelogs/11.txt
new file mode 100644
index 0000000..0a44797
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/11.txt
@@ -0,0 +1 @@
+Ajout d'une page (minimale) de paramétrage de l'application.
diff --git a/lib/config/default_settings.dart b/lib/config/default_settings.dart
new file mode 100644
index 0000000..c327148
--- /dev/null
+++ b/lib/config/default_settings.dart
@@ -0,0 +1,9 @@
+class DefaultSettings {
+  static const int defaultTimerValue = 20;
+
+  static const List<int> allowedTimerValues = [
+    10,
+    defaultTimerValue,
+    30,
+  ];
+}
diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart
new file mode 100644
index 0000000..200abf9
--- /dev/null
+++ b/lib/cubit/settings_cubit.dart
@@ -0,0 +1,39 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:twister/config/default_settings.dart';
+
+part 'settings_state.dart';
+
+class SettingsCubit extends HydratedCubit<SettingsState> {
+  SettingsCubit() : super(const SettingsState());
+
+  int getTimerValue() {
+    return state.timerValue ?? DefaultSettings.defaultTimerValue;
+  }
+
+  void setValues({
+    int? timerValue,
+  }) {
+    emit(SettingsState(
+      timerValue: timerValue ?? state.timerValue,
+    ));
+  }
+
+  @override
+  SettingsState? fromJson(Map<String, dynamic> json) {
+    int timerValue = json['timerValue'] as int;
+
+    return SettingsState(
+      timerValue: timerValue,
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(SettingsState state) {
+    return <String, dynamic>{
+      'timerValue': state.timerValue ?? DefaultSettings.defaultTimerValue,
+    };
+  }
+}
diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart
new file mode 100644
index 0000000..3811631
--- /dev/null
+++ b/lib/cubit/settings_state.dart
@@ -0,0 +1,19 @@
+part of 'settings_cubit.dart';
+
+@immutable
+class SettingsState extends Equatable {
+  const SettingsState({
+    this.timerValue,
+  });
+
+  final int? timerValue;
+
+  @override
+  List<dynamic> get props => <dynamic>[
+        timerValue,
+      ];
+
+  Map<String, dynamic> get values => <String, dynamic>{
+        'discoveriesDaysCount': timerValue,
+      };
+}
diff --git a/lib/main.dart b/lib/main.dart
index 4d188e4..d30fd3e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -9,6 +9,7 @@ import 'package:path_provider/path_provider.dart';
 
 import 'package:twister/config/theme.dart';
 import 'package:twister/cubit/bottom_nav_cubit.dart';
+import 'package:twister/cubit/settings_cubit.dart';
 import 'package:twister/ui/skeleton.dart';
 
 void main() async {
@@ -43,6 +44,7 @@ class MyApp extends StatelessWidget {
     return MultiBlocProvider(
       providers: [
         BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()),
+        BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()),
       ],
       child: MaterialApp(
         title: 'Twister',
diff --git a/lib/ui/screens/settings.dart b/lib/ui/screens/settings.dart
new file mode 100644
index 0000000..6b21d25
--- /dev/null
+++ b/lib/ui/screens/settings.dart
@@ -0,0 +1,29 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+import 'package:twister/ui/widgets/app_titles.dart';
+import 'package:twister/ui/widgets/settings_form.dart';
+import 'package:unicons/unicons.dart';
+
+class ScreenSettings extends StatelessWidget {
+  const ScreenSettings({super.key});
+
+  static Icon navBarIcon = const Icon(UniconsLine.setting);
+  static String navBarText = 'bottom_nav_settings';
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+      color: Theme.of(context).colorScheme.background,
+      child: ListView(
+        padding: const EdgeInsets.symmetric(horizontal: 4),
+        physics: const BouncingScrollPhysics(),
+        children: <Widget>[
+          SizedBox(height: 8),
+          AppTitle1(text: tr('settings_title')),
+          SettingsForm(),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
index 4d56b23..ca33599 100644
--- a/lib/ui/skeleton.dart
+++ b/lib/ui/skeleton.dart
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'package:twister/cubit/bottom_nav_cubit.dart';
 import 'package:twister/ui/screens/home.dart';
+import 'package:twister/ui/screens/settings.dart';
 import 'package:twister/ui/widgets/app_bar.dart';
 import 'package:twister/ui/widgets/bottom_nav_bar.dart';
 
@@ -18,7 +19,7 @@ class _SkeletonScreenState extends State<SkeletonScreen> {
   Widget build(BuildContext context) {
     List<Widget> pageNavigation = <Widget>[
       const ScreenHome(),
-      const ScreenHome(),
+      const ScreenSettings(),
     ];
 
     return Scaffold(
diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart
index 5181bb9..24e9a83 100644
--- a/lib/ui/widgets/bottom_nav_bar.dart
+++ b/lib/ui/widgets/bottom_nav_bar.dart
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'package:twister/cubit/bottom_nav_cubit.dart';
+import 'package:twister/ui/screens/settings.dart';
 import 'package:twister/ui/screens/home.dart';
 
 class BottomNavBar extends StatelessWidget {
@@ -36,8 +37,8 @@ class BottomNavBar extends StatelessWidget {
               label: tr(ScreenHome.navBarText),
             ),
             BottomNavigationBarItem(
-              icon: ScreenHome.navBarIcon,
-              label: tr(ScreenHome.navBarText),
+              icon: ScreenSettings.navBarIcon,
+              label: tr(ScreenSettings.navBarText),
             ),
           ],
         );
diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart
new file mode 100644
index 0000000..fc668fe
--- /dev/null
+++ b/lib/ui/widgets/settings_form.dart
@@ -0,0 +1,81 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:twister/config/default_settings.dart';
+import 'package:twister/cubit/settings_cubit.dart';
+import 'package:twister/ui/widgets/app_titles.dart';
+
+class SettingsForm extends StatefulWidget {
+  const SettingsForm({super.key});
+
+  @override
+  State<SettingsForm> createState() => _SettingsFormState();
+}
+
+class _SettingsFormState extends State<SettingsForm> {
+  int timerValue = DefaultSettings.defaultTimerValue;
+
+  List<bool> _selectedTimerValue = [];
+
+  @override
+  void didChangeDependencies() {
+    SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
+
+    timerValue = settings.getTimerValue();
+
+    _selectedTimerValue =
+        DefaultSettings.allowedTimerValues.map((e) => (e == timerValue)).toList();
+
+    super.didChangeDependencies();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    void saveSettings() {
+      BlocProvider.of<SettingsCubit>(context).setValues(
+        timerValue: timerValue,
+      );
+    }
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.start,
+      mainAxisSize: MainAxisSize.max,
+      children: <Widget>[
+        SizedBox(height: 8),
+        AppTitle2(text: tr('settings_title_game')),
+
+        // Timer value
+        Row(
+          mainAxisAlignment: MainAxisAlignment.end,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: [
+            Text('settings_label_game_timer_value').tr(),
+            ToggleButtons(
+              onPressed: (int index) {
+                setState(() {
+                  timerValue = DefaultSettings.allowedTimerValues[index];
+                  for (int i = 0; i < _selectedTimerValue.length; i++) {
+                    _selectedTimerValue[i] = i == index;
+                  }
+                });
+                saveSettings();
+              },
+              borderRadius: const BorderRadius.all(Radius.circular(8)),
+              constraints: const BoxConstraints(minHeight: 30.0, minWidth: 30.0),
+              isSelected: _selectedTimerValue,
+              children:
+                  DefaultSettings.allowedTimerValues.map((e) => Text(e.toString())).toList(),
+            ),
+          ],
+        ),
+      ],
+    );
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 9b820cb..ac2c248 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: twister game companion
 
 publish_to: 'none'
 
-version: 0.0.10+10
+version: 0.0.11+11
 
 environment:
   sdk: '^3.0.0'
-- 
GitLab