From db4ee0cf0fc2c80bdad1041952a560076dc7c63a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Tue, 13 Apr 2021 15:53:04 +0200
Subject: [PATCH] Store categories in JSON file, improve app conception

---
 android/gradle.properties           |   4 +-
 assets/files/categories-fr.json     |  94 ++++++++++
 lib/main.dart                       | 270 +++-------------------------
 lib/provider/data.dart              |  53 ++++++
 lib/screens/home.dart               | 161 +++++++++++++++++
 lib/utils/random_pick_category.dart |  31 ++++
 lib/utils/random_pick_letter.dart   |  22 +++
 pubspec.lock                        |  15 ++
 pubspec.yaml                        |   3 +
 9 files changed, 401 insertions(+), 252 deletions(-)
 create mode 100644 assets/files/categories-fr.json
 create mode 100644 lib/provider/data.dart
 create mode 100644 lib/screens/home.dart
 create mode 100644 lib/utils/random_pick_category.dart
 create mode 100644 lib/utils/random_pick_letter.dart

diff --git a/android/gradle.properties b/android/gradle.properties
index 2aadffb..ad752d3 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=1.2.1
-app.versionCode=7
+app.versionName=1.2.2
+app.versionCode=8
diff --git a/assets/files/categories-fr.json b/assets/files/categories-fr.json
new file mode 100644
index 0000000..230043a
--- /dev/null
+++ b/assets/files/categories-fr.json
@@ -0,0 +1,94 @@
+{
+  "categories": [
+    "Pays",
+    "Prénoms fille",
+    "Prénoms garçon",
+    "Animaux",
+    "Métiers",
+    "Villes",
+    "Dessins animés",
+    "Séries",
+    "Films",
+    "Auteurs (littéraire)",
+    "Acteurs / Actices",
+    "Chanteurs / Chanteuses",
+    "Choses / Objets",
+    "Fruits et/ou légumes",
+    "Couleurs",
+    "Marques",
+    "Moyens de transport",
+    "Outils",
+    "Capitales",
+    "Instruments de musique",
+    "Boissons",
+    "Fleurs",
+    "Plats",
+    "Personnages historiques",
+    "Vêtements",
+
+    "Minéraux et pierres précieuses",
+    "Étoiles, planètes et constellations",
+    "Fleuves, cours d'eau et océans",
+    "Partie du corps humain",
+    "Oiseaux",
+    "Poissons",
+    "Qualités et défauts",
+    "Arbres",
+    "Bandes dessinées",
+    "Départements français",
+    "Insectes",
+    "Desserts",
+    "Mammifères",
+    "Epices",
+    "Héros de mythologie",
+    "Héros fictifs",
+    "Fromages",
+    "Jeux",
+    "Eléments de véhicules",
+    "Ustensils de ménage",
+    "Sites internet",
+    "Sportifs",
+
+    "Félins",
+    "Sculpteurs",
+    "Monnaies du monde",
+    "Mots de plus de 8 lettres",
+    "Cadeaux de Noël",
+    "Marques de voiture",
+    "Titres de magazines",
+    "Mots en anglais",
+    "Mots en espagnol",
+    "Compositeurs de musiques classiques",
+    "Footballeurs",
+    "Pays d'Afrique",
+    "Pays d'Amérique",
+    "Pays d'Asie",
+    "Pays d'Europe",
+    "Races de chiens",
+    "Races de chats",
+    "Héros de comics",
+    "Métiers dont rêvent les enfants",
+    "Tennisman",
+    "Sports collectifs",
+    "Humoristes",
+    "Objets/Choses qui se trouvent dans une voiture",
+    "Objets/Choses qui se trouvent dans un camping",
+    "Choses qui se trouvent dans un cartable d'écolier",
+    "Villes françaises",
+    "Emissions de TV",
+    "Peintres célèbres",
+    "Contes de fées",
+    "Titres de chansons",
+
+    "Qui sent mauvais",
+    "Qui fait plaisir",
+    "Se trouve dans un sac à main",
+    "Noms que l'on donne à un doudou",
+    "Mauvais pour la santé",
+    "Mauvais pour l'environement",
+    "Phobies",
+    "Plats que l'on peut manger à la cantine scolaire ou professionnelle",
+    "Emissions de télé-réalité",
+    "Choses qui grattent"
+  ]
+}
diff --git a/lib/main.dart b/lib/main.dart
index efb9aa0..a317c62 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,259 +1,29 @@
 import 'package:flutter/material.dart';
-import 'dart:math';
-import 'dart:async';
+import 'package:provider/provider.dart';
 
-void main() {
-  runApp(MyApp());
-}
-
-class MyApp extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Petit bac',
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-      ),
-      home: MyHomePage(title: 'Petit bac'),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key, this.title}) : super(key: key);
-
-  final String title;
-  final Random _rnd = Random();
-  final String _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
-  final List _categories = [
-    'Pays',
-    'Prénoms fille',
-    'Prénoms garçon',
-    'Animaux',
-    'Métiers',
-    'Villes',
-    'Dessins animés',
-    'Séries',
-    'Films',
-    'Auteurs (littéraire)',
-    'Acteurs / Actices',
-    'Chanteurs / Chanteuses',
-    'Choses / Objets',
-    'Fruits et/ou légumes',
-    'Couleurs',
-    'Marques',
-    'Moyens de transport',
-    'Outils',
-    'Capitales',
-    'Instruments de musique',
-    'Boissons',
-    'Fleurs',
-    'Plats',
-    'Personnages historiques',
-    'Vêtements',
-
-    'Minéraux et pierres précieuses',
-    'Étoiles, planètes et constellations',
-    'Fleuves, cours d\'eau et océans',
-    'Partie du corps humain',
-    'Oiseaux',
-    'Poissons',
-    'Qualités et défauts',
-    'Arbres',
-    'Bandes dessinées',
-    'Départements français',
-    'Insectes',
-    'Desserts',
-    'Mammifères',
-    'Epices',
-    'Héros de mythologie',
-    'Héros fictifs',
-    'Fromages',
-    'Jeux',
-    'Eléments de véhicules',
-    'Ustensils de ménage',
-    'Sites internet',
-    'Sportifs',
-
-    'Félins',
-    'Sculpteurs',
-    'Monnaies du monde',
-    'Mots de plus de 8 lettres',
-    'Cadeaux de Noël',
-    'Marques de voiture',
-    'Titres de magazines',
-    'Mots en anglais',
-    'Mots en espagnol',
-    'Compositeurs de musiques classiques',
-    'Footballeurs',
-    'Pays d\'Afrique',
-    'Pays d\'Amérique',
-    'Pays d\'Asie',
-    'Pays d\'Europe',
-    'Races de chiens',
-    'Races de chats',
-    'Héros de comics',
-    'Métiers dont rêvent les enfants',
-    'Tennisman',
-    'Sports collectifs',
-    'Humoristes',
-    'Objets/Choses qui se trouvent dans une voiture',
-    'Objets/Choses qui se trouvent dans un camping',
-    'Choses qui se trouvent dans un cartable d\'écolier',
-    'Villes françaises',
-    'Emissions de TV',
-    'Peintres célèbres',
-    'Contes de fées',
-    'Titres de chansons',
-
-    'Qui sent mauvais',
-    'Qui fait plaisir',
-    'Se trouve dans un sac à main',
-    'Noms que l\'on donne à un doudou',
-    'Mauvais pour la santé',
-    'Mauvais pour l\'environement',
-    'Phobies',
-    'Plats que l\'on peut manger à la cantine scolaire ou professionnelle',
-    'Emissions de télé-réalité',
-    'Choses qui grattent',
-  ];
-
-  @override
-  _MyHomePageState createState() => _MyHomePageState();
-}
+import 'provider/data.dart';
+import 'screens/home.dart';
 
-class _MyHomePageState extends State<MyHomePage> {
-  String _randomLetter = '';
-  String _randomCategory = '';
-  Timer _timer;
-  int _start = 10;
-
-  void _pickRandomLetter() {
-    setState(() {
-      _randomLetter = widget._chars[widget._rnd.nextInt(widget._chars.length)];
-    });
-  }
-
-  void _pickRandomCategory() {
-    setState(() {
-      _randomCategory = widget._categories[widget._rnd.nextInt(widget._categories.length)];
-    });
-  }
-
-  void _startMiniGame() {
-    _pickRandomLetter();
-    _pickRandomCategory();
-    startTimer();
-  }
-
-  @override
-  void initState() {
-    _pickRandomLetter();
-    _pickRandomCategory();
-  }
-
-  void startTimer() {
-    const oneSec = const Duration(seconds: 1);
-    if (_timer != null) {
-      _timer.cancel();
-    }
-    _start = 10;
-    _timer = new Timer.periodic(
-      oneSec,
-      (Timer timer) {
-        if (_start == 0) {
-          setState(() {
-            timer.cancel();
-          });
-        } else {
-          setState(() {
-            _start--;
-          });
-        }
-      },
-    );
-  }
-
-  @override
-  void dispose() {
-    _timer.cancel();
-    super.dispose();
-  }
+void main() => runApp(MyApp());
 
+class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(widget.title),
-      ),
-      body: Center(
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            Column(
-              mainAxisAlignment: MainAxisAlignment.center,
-              children: <Widget>[
-                Text(
-                  '$_randomLetter',
-                  style: Theme.of(context).textTheme.headline2,
-                ),
-                RaisedButton(
-                  onPressed: _pickRandomLetter,
-                  color: Colors.orange,
-                  padding: EdgeInsets.all(10.0),
-                  child: Row(
-                    children: <Widget>[
-                      Icon(Icons.shuffle),
-                      Text("Piocher une lettre")
-                    ],
-                  ),
-                ),
-              ],
-            ),
-            SizedBox(height: 20),
-            Column(
-              mainAxisAlignment: MainAxisAlignment.center,
-              children: <Widget>[
-                Text(
-                  '$_randomCategory',
-                  style: Theme.of(context).textTheme.headline3,
-                ),
-                FlatButton(
-                  onPressed: _pickRandomCategory,
-                  color: Colors.orange,
-                  padding: EdgeInsets.all(10.0),
-                  child: Row(
-                    children: <Widget>[
-                      Icon(Icons.shuffle),
-                      Text("Piocher une catégorie")
-                    ],
-                  ),
-                ),
-              ],
-            ),
-            SizedBox(height: 40),
-            Column(
-              mainAxisAlignment: MainAxisAlignment.center,
-              children: <Widget>[
-                FlatButton(
-                  onPressed: _startMiniGame,
-                  color: Colors.blue,
-                  padding: EdgeInsets.all(10.0),
-                  child: Row(
-                    mainAxisAlignment: MainAxisAlignment.center,
-                    children: <Widget>[
-                      Text(
-                        '$_start',
-                        style: Theme.of(context).textTheme.headline2,
-                      ),
-                    ],
-                  ),
-                ),
-              ],
-            ),
-          ],
-        ),
-      ),
+    return ChangeNotifierProvider(
+      create: (BuildContext context) => Data(),
+      child: Consumer<Data>(builder: (context, data, child) {
+        return MaterialApp(
+          debugShowCheckedModeBanner: false,
+          theme: ThemeData(
+            primaryColor: Colors.blue,
+            visualDensity: VisualDensity.adaptivePlatformDensity,
+          ),
+          home: Home(),
+          routes: {
+            Home.id: (context) => Home(),
+          },
+        );
+      }),
     );
   }
 }
diff --git a/lib/provider/data.dart b/lib/provider/data.dart
new file mode 100644
index 0000000..1c709ad
--- /dev/null
+++ b/lib/provider/data.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/foundation.dart';
+
+class Data extends ChangeNotifier {
+
+  // randomization
+  bool _searchingCategory = false;
+  bool _searchingLetter = false;
+  String _category = '';
+  String _letter = '';
+  int _timer = 0;
+
+  bool get searchingCategory => _searchingCategory;
+
+  set searchingCategory(bool value) {
+    _searchingCategory = value;
+    notifyListeners();
+  }
+
+  bool get searchingLetter => _searchingLetter;
+
+  set searchingLetter(bool value) {
+    _searchingLetter = value;
+    notifyListeners();
+  }
+
+  String get category => _category;
+
+  set updateCategory(String value) {
+    _category = value;
+    notifyListeners();
+  }
+
+  String get letter => _letter;
+
+  set updateLetter(String value) {
+    _letter = value;
+    notifyListeners();
+  }
+
+  int get timer => _timer;
+
+  set updateTimer(int value) {
+    _timer = value;
+    notifyListeners();
+  }
+
+  void resetGame() {
+    _category = '';
+    _letter = '';
+    _timer = 0;
+    notifyListeners();
+  }
+}
diff --git a/lib/screens/home.dart b/lib/screens/home.dart
new file mode 100644
index 0000000..5da13a0
--- /dev/null
+++ b/lib/screens/home.dart
@@ -0,0 +1,161 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+import 'dart:math';
+import 'dart:async';
+
+import '../provider/data.dart';
+import '../utils/random_pick_category.dart';
+import '../utils/random_pick_letter.dart';
+
+class Home extends StatelessWidget {
+  static const String id = 'home';
+
+  String _randomLetter = '';
+  String _randomCategory = '';
+  Timer _timer;
+  int _timerStart = 10;
+
+  Future<void> startMiniGame(BuildContext context, Data myProvider) async {
+    pickCategory(context, myProvider);
+    pickLetter(context, myProvider);
+    startTimer(context, myProvider);
+  }
+
+  Future<void> startTimer(BuildContext context, Data myProvider) async {
+    const oneSec = const Duration(seconds: 1);
+    if (_timer != null) {
+      _timer.cancel();
+    }
+    _timerStart = 10;
+    myProvider.updateTimer = _timerStart;
+    _timer = new Timer.periodic(
+      oneSec,
+      (Timer timer) {
+        if (_timerStart == 0) {
+          timer.cancel();
+        } else {
+          _timerStart--;
+          myProvider.updateTimer = _timerStart;
+        }
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    _timer.cancel();
+  }
+
+  Future<void> pickCategory(BuildContext context, Data myProvider) async {
+    myProvider.searchingCategory = true;
+    RandomPickCategory randomPickCategory;
+    int attempts = 0;
+    do {
+      randomPickCategory = RandomPickCategory();
+      await randomPickCategory.init();
+      if (randomPickCategory.category != null) {
+        myProvider.updateCategory = randomPickCategory.category;
+        myProvider.searchingCategory = false;
+        break;
+      }
+      attempts++;
+    } while (attempts < 3);
+  }
+
+  Future<void> pickLetter(BuildContext context, Data myProvider) async {
+    myProvider.searchingLetter = true;
+    RandomPickLetter randomPickLetter;
+    int attempts = 0;
+    do {
+      randomPickLetter = RandomPickLetter();
+      await randomPickLetter.init();
+      if (randomPickLetter.letter != null) {
+        myProvider.updateLetter = randomPickLetter.letter;
+        myProvider.searchingLetter = false;
+        break;
+      }
+      attempts++;
+    } while (attempts < 3);
+  }
+
+
+  @override
+  Widget build(BuildContext context) {
+    Data _myProvider = Provider.of<Data>(context);
+
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('Petit bac'),
+      ),
+      body: Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: <Widget>[
+            Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: <Widget>[
+                Text(
+                  _myProvider.letter,
+                  style: Theme.of(context).textTheme.headline2,
+                ),
+                RaisedButton(
+                  onPressed: () => pickLetter(context, _myProvider),
+                  color: Colors.orange,
+                  padding: EdgeInsets.all(10.0),
+                  child: Row(
+                    children: <Widget>[
+                      Icon(Icons.shuffle),
+                      Text("Piocher une lettre")
+                    ],
+                  ),
+                ),
+              ],
+            ),
+            SizedBox(height: 20),
+            Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: <Widget>[
+                Text(
+                  _myProvider.category,
+                  style: Theme.of(context).textTheme.headline3,
+                ),
+                FlatButton(
+                  onPressed: () => pickCategory(context, _myProvider),
+                  color: Colors.orange,
+                  padding: EdgeInsets.all(10.0),
+                  child: Row(
+                    children: <Widget>[
+                      Icon(Icons.shuffle),
+                      Text("Piocher une catégorie")
+                    ],
+                  ),
+                ),
+              ],
+            ),
+            SizedBox(height: 40),
+            Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: <Widget>[
+                FlatButton(
+                  onPressed: () => startMiniGame(context, _myProvider),
+                  color: Colors.blue,
+                  padding: EdgeInsets.all(10.0),
+                  child: Row(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: <Widget>[
+                      Text(
+                        _myProvider.timer.toString(),
+                        style: Theme.of(context).textTheme.headline2,
+                      ),
+                    ],
+                  ),
+                ),
+              ],
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/utils/random_pick_category.dart b/lib/utils/random_pick_category.dart
new file mode 100644
index 0000000..7ef32ba
--- /dev/null
+++ b/lib/utils/random_pick_category.dart
@@ -0,0 +1,31 @@
+import 'dart:async';
+import 'dart:convert';
+import 'package:flutter/services.dart';
+import 'dart:math' show Random;
+
+class RandomPickCategory {
+  RandomPickCategory();
+
+  String _category;
+  final random = Random();
+
+  init() async {
+    await categoryFromLocalFile();
+  }
+
+  Future<void> categoryFromLocalFile() async {
+    String jsonString;
+    try {
+      jsonString = await rootBundle.loadString('assets/files/categories-fr.json');
+      final jsonResponse = await json.decode(jsonString);
+      var categoryList = jsonResponse[jsonResponse.keys.toList().join()];
+      int randomCategoryIndex = random.nextInt(categoryList.length);
+      String category = categoryList[random.nextInt(categoryList.length)];
+      _category = category ?? 'UNEXPECTED ERROR';
+    } catch (e) {
+      _category = 'UNEXPECTED ERROR';
+    }
+  }
+
+  String get category => _category;
+}
diff --git a/lib/utils/random_pick_letter.dart b/lib/utils/random_pick_letter.dart
new file mode 100644
index 0000000..15635c9
--- /dev/null
+++ b/lib/utils/random_pick_letter.dart
@@ -0,0 +1,22 @@
+import 'dart:async';
+import 'dart:convert';
+import 'package:flutter/services.dart';
+import 'dart:math' show Random;
+
+class RandomPickLetter {
+  RandomPickLetter();
+
+  String _letter;
+  final random = Random();
+  final String _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+  init() async {
+    await letterFromLocalFile();
+  }
+
+  Future<void> letterFromLocalFile() async {
+    _letter = _chars[random.nextInt(_chars.length)];
+  }
+
+  String get letter => _letter;
+}
diff --git a/pubspec.lock b/pubspec.lock
index 9e492de..48d7003 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -74,6 +74,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.3.0"
+  nested:
+    dependency: transitive
+    description:
+      name: nested
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
   path:
     dependency: transitive
     description:
@@ -81,6 +88,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.8.0"
+  provider:
+    dependency: "direct main"
+    description:
+      name: provider
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.0.0"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -144,3 +158,4 @@ packages:
     version: "2.1.0"
 sdks:
   dart: ">=2.12.0-0.0 <3.0.0"
+  flutter: ">=1.16.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 9e1633f..ba60a9c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -9,6 +9,7 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
+  provider: ^5.0.0
 
 dev_dependencies:
   flutter_test:
@@ -16,3 +17,5 @@ dev_dependencies:
 
 flutter:
   uses-material-design: true
+  assets:
+    - assets/files/
-- 
GitLab