From e7099855e01653fbdaa1379ca354db98387683fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Thu, 23 Nov 2023 16:36:38 +0100
Subject: [PATCH] Add "interface type" setting

---
 assets/translations/en.json       |  3 +++
 assets/translations/fr.json       |  3 +++
 lib/cubit/settings_cubit.dart     | 32 +++++++++++++++++++++++++-
 lib/cubit/settings_state.dart     |  4 ++++
 lib/models/interface_type.dart    | 14 ++++++++++++
 lib/ui/screens/demo_page.dart     |  7 +++---
 lib/ui/skeleton.dart              |  2 ++
 lib/ui/widgets/app_bar.dart       | 10 ++++-----
 lib/ui/widgets/header_app.dart    | 26 ++++++++++++++++++----
 lib/ui/widgets/settings_form.dart | 37 +++++++++++++++++++++++++++++--
 pubspec.lock                      | 14 ++++++------
 11 files changed, 129 insertions(+), 23 deletions(-)
 create mode 100644 lib/models/interface_type.dart

diff --git a/assets/translations/en.json b/assets/translations/en.json
index 2347221..d90a462 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -9,6 +9,9 @@
   "settings_title": "Settings",
   "settings_label_api_url": "API URL:",
   "settings_label_security_token": "Security token:",
+  "settings_label_interface_type": "Interface type:",
+  "interface_type_basic": "basic",
+  "interface_type_expert": "expert",
   "settings_button_save": "Save",
 
   "about_title": "About",
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
index 332b150..67575f6 100644
--- a/assets/translations/fr.json
+++ b/assets/translations/fr.json
@@ -9,6 +9,9 @@
   "settings_title": "Paramètres",
   "settings_label_api_url": "URL de l'API :",
   "settings_label_security_token": "Jeton de sécurité :",
+  "settings_label_interface_type": "Type d'interface :",
+  "interface_type_basic": "simple",
+  "interface_type_expert": "expert",
   "settings_button_save": "Enregistrer",
 
   "about_title": "À propos",
diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart
index 8d13db0..01d3b94 100644
--- a/lib/cubit/settings_cubit.dart
+++ b/lib/cubit/settings_cubit.dart
@@ -2,12 +2,14 @@ import 'package:equatable/equatable.dart';
 import 'package:flutter/material.dart';
 import 'package:hydrated_bloc/hydrated_bloc.dart';
 
+import 'package:random/models/interface_type.dart';
+
 part 'settings_state.dart';
 
 class SettingsCubit extends HydratedCubit<SettingsState> {
   SettingsCubit() : super(const SettingsState());
 
-  String getSetting(String key, [String? defaultValue]) {
+  Object getSetting(String key, [String? defaultValue]) {
     if (state.values.keys.contains(key)) {
       return state.values[key] ?? defaultValue ?? '';
     }
@@ -15,13 +17,27 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
     return defaultValue ?? '';
   }
 
+  String getApiUrl() {
+    return state.apiUrl ?? '';
+  }
+
+  String getSecurityToken() {
+    return state.securityToken ?? '';
+  }
+
+  InterfaceType getInterfaceType() {
+    return state.interfaceType ?? InterfaceType.basic;
+  }
+
   void setValues({
     String? apiUrl,
     String? securityToken,
+    InterfaceType? interfaceType,
   }) {
     emit(SettingsState(
       apiUrl: apiUrl != null ? apiUrl : state.apiUrl,
       securityToken: securityToken != null ? securityToken : state.securityToken,
+      interfaceType: interfaceType != null ? interfaceType : state.interfaceType,
     ));
   }
 
@@ -29,10 +45,23 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
   SettingsState? fromJson(Map<String, dynamic> json) {
     String apiUrl = json['apiUrl'] as String;
     String securityToken = json['securityToken'] as String;
+    InterfaceType interfaceType;
+
+    switch (json['interfaceType'] as String) {
+      case 'InterfaceType.basic':
+        interfaceType = InterfaceType.basic;
+        break;
+      case 'InterfaceType.expert':
+        interfaceType = InterfaceType.expert;
+        break;
+      default:
+        interfaceType = InterfaceType.basic;
+    }
 
     return SettingsState(
       apiUrl: apiUrl,
       securityToken: securityToken,
+      interfaceType: interfaceType,
     );
   }
 
@@ -41,6 +70,7 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
     return <String, String>{
       'apiUrl': state.apiUrl ?? '',
       'securityToken': state.securityToken ?? '',
+      'interfaceType': state.interfaceType.toString(),
     };
   }
 }
diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart
index f435ed3..3f95d16 100644
--- a/lib/cubit/settings_state.dart
+++ b/lib/cubit/settings_state.dart
@@ -5,19 +5,23 @@ class SettingsState extends Equatable {
   const SettingsState({
     this.apiUrl,
     this.securityToken,
+    this.interfaceType,
   });
 
   final String? apiUrl;
   final String? securityToken;
+  final InterfaceType? interfaceType;
 
   @override
   List<String?> get props => <String?>[
         apiUrl,
         securityToken,
+        interfaceType.toString(),
       ];
 
   Map<String, String?> get values => <String, String?>{
         'apiUrl': apiUrl,
         'securityToken': securityToken,
+        'interfaceType': interfaceType.toString(),
       };
 }
diff --git a/lib/models/interface_type.dart b/lib/models/interface_type.dart
new file mode 100644
index 0000000..05dc342
--- /dev/null
+++ b/lib/models/interface_type.dart
@@ -0,0 +1,14 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+enum InterfaceType {
+  basic,
+  expert,
+}
+
+class InterfaceTypes {
+  static List<Widget> selectWidgets = <Widget>[
+    Text('interface_type_basic').tr(),
+    Text('interface_type_expert').tr(),
+  ];
+}
diff --git a/lib/ui/screens/demo_page.dart b/lib/ui/screens/demo_page.dart
index b488381..03afe0a 100644
--- a/lib/ui/screens/demo_page.dart
+++ b/lib/ui/screens/demo_page.dart
@@ -78,9 +78,10 @@ class DemoPage extends StatelessWidget {
             mainAxisAlignment: MainAxisAlignment.center,
             crossAxisAlignment: CrossAxisAlignment.center,
             children: [
-              Text('apiUrl: ' + settings.getSetting('apiUrl')),
-              Text('securityToken: ' + settings.getSetting('securityToken')),
-              Text('unknown: ' + settings.getSetting('unknown', 'undefined')),
+              Text('apiUrl: ' + settings.getApiUrl()),
+              Text('securityToken: ' + settings.getSecurityToken()),
+              Text('interfaceType: ' + settings.getInterfaceType().toString()),
+              Text('unknown: ' + settings.getSetting('unknown', 'undefined').toString()),
             ],
           );
         },
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
index 01f8cca..cf3414e 100644
--- a/lib/ui/skeleton.dart
+++ b/lib/ui/skeleton.dart
@@ -21,6 +21,8 @@ class SkeletonScreen extends StatefulWidget {
 class _SkeletonScreenState extends State<SkeletonScreen> {
   @override
   Widget build(BuildContext context) {
+    print('SkeletonScreen - build');
+
     const List<Widget> pageNavigation = <Widget>[
       DemoPage(),
       GraphPage(),
diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart
index ea07b61..3afc4bb 100644
--- a/lib/ui/widgets/app_bar.dart
+++ b/lib/ui/widgets/app_bar.dart
@@ -1,5 +1,4 @@
 import 'package:flutter/material.dart';
-import 'package:unicons/unicons.dart';
 
 import 'package:random/ui/widgets/header_app.dart';
 
@@ -8,13 +7,12 @@ class StandardAppBar extends StatelessWidget implements PreferredSizeWidget {
 
   @override
   Widget build(BuildContext context) {
+    print('StandardAppBar - build');
+
     return AppBar(
-      title: const AppHeader(text: 'app_name'),
+      title: AppHeader(text: 'app_name'),
       actions: [
-        IconButton(
-          onPressed: () {},
-          icon: const Icon(UniconsSolid.refresh),
-        ),
+        //
       ],
     );
   }
diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/header_app.dart
index 77b015b..a64053e 100644
--- a/lib/ui/widgets/header_app.dart
+++ b/lib/ui/widgets/header_app.dart
@@ -1,5 +1,9 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:random/cubit/settings_cubit.dart';
+import 'package:random/models/interface_type.dart';
 
 class AppHeader extends StatelessWidget {
   const AppHeader({super.key, required this.text});
@@ -8,10 +12,24 @@ class AppHeader extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Text(
-      tr(text),
-      textAlign: TextAlign.start,
-      style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
+    print('AppHeader - build (' + this.text + ')');
+
+    return BlocProvider<SettingsCubit>(
+      create: (BuildContext context) => SettingsCubit(),
+      child: BlocBuilder<SettingsCubit, SettingsState>(
+        builder: (BuildContext context, SettingsState state) {
+          SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
+
+          bool isExpert = settings.getInterfaceType() == InterfaceType.expert;
+          String titleSuffix = isExpert ? ' ⭐' : '';
+
+          return Text(
+            tr(text) + titleSuffix,
+            textAlign: TextAlign.start,
+            style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
+          );
+        },
+      ),
     );
   }
 }
diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart
index e0a6e85..fa8677a 100644
--- a/lib/ui/widgets/settings_form.dart
+++ b/lib/ui/widgets/settings_form.dart
@@ -5,6 +5,8 @@ import 'package:unicons/unicons.dart';
 
 import 'package:random/cubit/bottom_nav_cubit.dart';
 import 'package:random/cubit/settings_cubit.dart';
+import 'package:random/config/theme.dart';
+import 'package:random/models/interface_type.dart';
 
 class SettingsForm extends StatefulWidget {
   const SettingsForm({super.key});
@@ -16,6 +18,7 @@ class SettingsForm extends StatefulWidget {
 class _SettingsFormState extends State<SettingsForm> {
   final apiUrlController = TextEditingController();
   final securityTokenController = TextEditingController();
+  final List<Widget> interfaceTypesWidgets = InterfaceTypes.selectWidgets;
 
   @override
   void dispose() {
@@ -24,17 +27,26 @@ class _SettingsFormState extends State<SettingsForm> {
     super.dispose();
   }
 
+  List<bool> _selectedInterfaceType = <bool>[];
+
   @override
   Widget build(BuildContext context) {
     SettingsCubit settings = BlocProvider.of<SettingsCubit>(context);
 
-    apiUrlController.text = settings.getSetting('apiUrl');
-    securityTokenController.text = settings.getSetting('securityToken');
+    apiUrlController.text = settings.getApiUrl();
+    securityTokenController.text = settings.getSecurityToken();
+    if (_selectedInterfaceType.length != 2) {
+      _selectedInterfaceType = <bool>[
+        settings.getInterfaceType() == InterfaceType.basic,
+        settings.getInterfaceType() == InterfaceType.expert,
+      ];
+    }
 
     void saveSettings() {
       BlocProvider.of<SettingsCubit>(context).setValues(
         apiUrl: apiUrlController.text,
         securityToken: securityTokenController.text,
+        interfaceType: _selectedInterfaceType[0] ? InterfaceType.basic : InterfaceType.expert,
       );
 
       BlocProvider.of<BottomNavCubit>(context).getDemoPage();
@@ -61,6 +73,27 @@ class _SettingsFormState extends State<SettingsForm> {
           ),
         ),
         SizedBox(height: 16),
+        Text('settings_label_interface_type').tr(),
+        ToggleButtons(
+          direction: Axis.horizontal,
+          onPressed: (int index) {
+            setState(() {
+              _selectedInterfaceType = index == 0 ? [true, false] : [false, true];
+            });
+          },
+          borderRadius: const BorderRadius.all(Radius.circular(8)),
+          selectedBorderColor: appTheme.primaryColor,
+          selectedColor: appTheme.colorScheme.onSecondary,
+          fillColor: appTheme.colorScheme.secondary,
+          color: appTheme.primaryColor,
+          constraints: const BoxConstraints(
+            minHeight: 40.0,
+            minWidth: 80.0,
+          ),
+          isSelected: _selectedInterfaceType,
+          children: interfaceTypesWidgets,
+        ),
+        SizedBox(height: 16),
         ElevatedButton(
           child: Row(
             mainAxisAlignment: MainAxisAlignment.center,
diff --git a/pubspec.lock b/pubspec.lock
index 287e966..ced1c87 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -45,10 +45,10 @@ packages:
     dependency: transitive
     description:
       name: collection
-      sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
+      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
       url: "https://pub.dev"
     source: hosted
-    version: "1.17.2"
+    version: "1.18.0"
   crypto:
     dependency: transitive
     description:
@@ -172,10 +172,10 @@ packages:
     dependency: transitive
     description:
       name: meta
-      sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
+      sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
       url: "https://pub.dev"
     source: hosted
-    version: "1.9.1"
+    version: "1.10.0"
   nested:
     dependency: transitive
     description:
@@ -401,10 +401,10 @@ packages:
     dependency: transitive
     description:
       name: web
-      sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
+      sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
       url: "https://pub.dev"
     source: hosted
-    version: "0.1.4-beta"
+    version: "0.3.0"
   win32:
     dependency: transitive
     description:
@@ -422,5 +422,5 @@ packages:
     source: hosted
     version: "1.0.3"
 sdks:
-  dart: ">=3.1.0-185.0.dev <4.0.0"
+  dart: ">=3.2.0-194.0.dev <4.0.0"
   flutter: ">=3.7.0"
-- 
GitLab