From 66edad8fbf40734dd4adebe9e007c615694a5613 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Thu, 26 Oct 2023 13:32:25 +0200
Subject: [PATCH] Improve global statistics widget

---
 android/gradle.properties                     |  4 +-
 assets/translations/en.json                   | 10 +--
 assets/translations/fr.json                   | 10 +--
 .../metadata/android/en-US/changelogs/6.txt   |  1 +
 .../metadata/android/fr-FR/changelogs/6.txt   |  1 +
 lib/models/statistics.dart                    | 41 +++++++--
 lib/ui/screens/main_screen.dart               |  4 +-
 lib/ui/widgets/main_screen/statistics.dart    | 83 -------------------
 .../widgets/main_screen/statistics_card.dart  | 46 ++++++++++
 .../main_screen/statistics_content.dart       | 77 +++++++++++++++++
 pubspec.yaml                                  |  2 +-
 11 files changed, 171 insertions(+), 108 deletions(-)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/6.txt
 create mode 100644 fastlane/metadata/android/fr-FR/changelogs/6.txt
 delete mode 100644 lib/ui/widgets/main_screen/statistics.dart
 create mode 100644 lib/ui/widgets/main_screen/statistics_card.dart
 create mode 100644 lib/ui/widgets/main_screen/statistics_content.dart

diff --git a/android/gradle.properties b/android/gradle.properties
index aa51064..135006f 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.5
-app.versionCode=5
+app.versionName=0.0.6
+app.versionCode=6
diff --git a/assets/translations/en.json b/assets/translations/en.json
index d829a40..31c1591 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -2,11 +2,9 @@
   "app_name": "Scrobbles",
 
   "global_statistics": "Global statistics",
-  "statistics_total_scrobbles_count": "Total scrobbles count:",
-  "statistics_last_scrobble": "Last scrobble: ",
+  "statistics_total_scrobbles_count": "Total scrobbles count: {count}",
+  "statistics_last_scrobble": "Last scrobble: {datetime}",
   "statistics_selected_period": "On last {daysCount} days:",
-  "statistics_recent_scrobbles_count": "Scrobbles:",
-  "statistics_discoveries": "Discoveries:",
-  "statistics_discoveries_artists": "artists",
-  "statistics_discoveries_tracks": "tracks"
+  "statistics_recent_scrobbles_count": "Scrobbles: {count}",
+  "statistics_discoveries": "Discoveries: {artistsCount} artists / {tracksCount} tracks"
 }
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
index d6bb8b0..e1594f2 100644
--- a/assets/translations/fr.json
+++ b/assets/translations/fr.json
@@ -2,11 +2,9 @@
   "app_name": "Scrobbles",
 
   "global_statistics": "Statistiques globales d'écoutes",
-  "statistics_total_scrobbles_count": "Nombre total d'écoutes :",
-  "statistics_last_scrobble": "Dernière écoute :",
+  "statistics_total_scrobbles_count": "Nombre total d'écoutes : {count}",
+  "statistics_last_scrobble": "Dernière écoute : {datetime}",
   "statistics_selected_period": "Sur les {daysCount} derniers jours:",
-  "statistics_recent_scrobbles_count": "Écoutes :",
-  "statistics_discoveries": "Découvertes :",
-  "statistics_discoveries_artists": "artistes",
-  "statistics_discoveries_tracks": "morceaux"
+  "statistics_recent_scrobbles_count": "Écoutes : {count}",
+  "statistics_discoveries": "Découvertes : {artistsCount} artistes / {tracksCount} morceaux"
 }
diff --git a/fastlane/metadata/android/en-US/changelogs/6.txt b/fastlane/metadata/android/en-US/changelogs/6.txt
new file mode 100644
index 0000000..0205c6b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/6.txt
@@ -0,0 +1 @@
+Improve global statistic bloc
diff --git a/fastlane/metadata/android/fr-FR/changelogs/6.txt b/fastlane/metadata/android/fr-FR/changelogs/6.txt
new file mode 100644
index 0000000..e2050cd
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/6.txt
@@ -0,0 +1 @@
+Amélioration du bloc de statistiques globales
diff --git a/lib/models/statistics.dart b/lib/models/statistics.dart
index aebb596..0654213 100644
--- a/lib/models/statistics.dart
+++ b/lib/models/statistics.dart
@@ -1,3 +1,5 @@
+import 'dart:convert';
+
 class StatisticsData {
   final int totalCount;
   final int recentCount;
@@ -17,14 +19,37 @@ class StatisticsData {
 
   factory StatisticsData.fromJson(Map<String, dynamic> json) {
     return StatisticsData(
-      totalCount: json['totalCount'] as int,
-      recentCount: json['recentCount'] as int,
-      firstPlayedArtistsCount: json['firstPlayedArtistsCount'] as int,
-      firstPlayedTracksCount: json['firstPlayedTracksCount'] as int,
-      selectedPeriod: json['selectedPeriod'] as int,
-      lastScrobble: DateTime.parse(
-        json['lastScrobble']['date'],
-      ),
+      totalCount: json['totalCount'] != null ? json['totalCount'] as int : 0,
+      recentCount: json['recentCount'] != null ? json['recentCount'] as int : 0,
+      firstPlayedArtistsCount:
+          json['firstPlayedArtistsCount'] != null ? json['firstPlayedArtistsCount'] as int : 0,
+      firstPlayedTracksCount:
+          json['firstPlayedTracksCount'] != null ? json['firstPlayedTracksCount'] as int : 0,
+      selectedPeriod: json['selectedPeriod'] != null ? json['selectedPeriod'] as int : 0,
+      lastScrobble: (json['lastScrobble'] != null && json['lastScrobble']['date'] != null)
+          ? DateTime.parse(
+              json['lastScrobble']['date'],
+            )
+          : DateTime.now(),
     );
   }
+
+  factory StatisticsData.createEmpty() {
+    return StatisticsData.fromJson({});
+  }
+
+  String toString() {
+    Map<String, dynamic> map = {
+      'totalCount': this.totalCount,
+      'recentCount': this.recentCount,
+      'firstPlayedArtistsCount': this.firstPlayedArtistsCount,
+      'firstPlayedTracksCount': this.firstPlayedTracksCount,
+      'selectedPeriod': this.selectedPeriod,
+      'lastScrobble': {
+        'date': this.lastScrobble.toString(),
+      },
+    };
+
+    return jsonEncode(map);
+  }
 }
diff --git a/lib/ui/screens/main_screen.dart b/lib/ui/screens/main_screen.dart
index 04f8d90..94275ca 100644
--- a/lib/ui/screens/main_screen.dart
+++ b/lib/ui/screens/main_screen.dart
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 
 import '../widgets/header.dart';
-import '../widgets/main_screen/statistics.dart';
+import '../widgets/main_screen/statistics_card.dart';
 
 class MainScreen extends StatelessWidget {
   const MainScreen({super.key});
@@ -15,7 +15,7 @@ class MainScreen extends StatelessWidget {
         physics: const BouncingScrollPhysics(),
         children: <Widget>[
           const Header(text: 'app_name'),
-          const Statistics(),
+          const StatisticsCard(),
           const SizedBox(height: 36),
         ],
       ),
diff --git a/lib/ui/widgets/main_screen/statistics.dart b/lib/ui/widgets/main_screen/statistics.dart
deleted file mode 100644
index 0dd1aeb..0000000
--- a/lib/ui/widgets/main_screen/statistics.dart
+++ /dev/null
@@ -1,83 +0,0 @@
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
-
-import '../../../models/statistics.dart';
-import '../../../network/scrobbles_api.dart';
-import '../../../ui/widgets/error.dart';
-
-class Statistics extends StatelessWidget {
-  const Statistics({super.key});
-
-  @override
-  Widget build(BuildContext context) {
-    late Future<StatisticsData> futureAlbum = ScrobblesApi.fetchStatistics();
-
-    return FutureBuilder<StatisticsData>(
-      future: futureAlbum,
-      builder: (context, snapshot) {
-        if (snapshot.hasData) {
-          final TextTheme textTheme = Theme.of(context).primaryTextTheme;
-
-          return Card(
-            elevation: 2,
-            shadowColor: Theme.of(context).colorScheme.shadow,
-            color: Theme.of(context).colorScheme.primary,
-            shape: const RoundedRectangleBorder(
-                borderRadius: BorderRadius.all(Radius.circular(12))),
-            child: Padding(
-              padding: const EdgeInsets.all(12.0),
-              child: Column(
-                mainAxisSize: MainAxisSize.min,
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: <Widget>[
-                  Text(
-                    tr('global_statistics'),
-                    style: textTheme.titleLarge!.apply(fontWeightDelta: 2),
-                  ),
-                  Text(
-                    tr('statistics_total_scrobbles_count') +
-                        ' ' +
-                        snapshot.data!.totalCount.toString(),
-                    style: textTheme.bodyMedium,
-                  ),
-                  Text(
-                    tr('statistics_last_scrobble') +
-                        ' ' +
-                        DateFormat().format(snapshot.data!.lastScrobble),
-                    style: textTheme.bodyMedium,
-                  ),
-                  Text(
-                    'statistics_selected_period',
-                    style: textTheme.bodyMedium,
-                  ).tr(namedArgs: {'daysCount': snapshot.data!.selectedPeriod.toString()}),
-                  Text(
-                    tr('statistics_recent_scrobbles_count') +
-                        ' ' +
-                        snapshot.data!.recentCount.toString(),
-                    style: textTheme.bodyMedium,
-                  ),
-                  Text(
-                    tr('statistics_discoveries') +
-                        ' ' +
-                        snapshot.data!.firstPlayedArtistsCount.toString() +
-                        ' ' +
-                        tr('statistics_discoveries_artists') +
-                        ' / ' +
-                        snapshot.data!.firstPlayedTracksCount.toString() +
-                        ' ' +
-                        tr('statistics_discoveries_tracks'),
-                    style: textTheme.bodyMedium,
-                  ),
-                ],
-              ),
-            ),
-          );
-        } else if (snapshot.hasError) {
-          return ShowErrorWidget(message: '${snapshot.error}');
-        }
-
-        return const CircularProgressIndicator();
-      },
-    );
-  }
-}
diff --git a/lib/ui/widgets/main_screen/statistics_card.dart b/lib/ui/widgets/main_screen/statistics_card.dart
new file mode 100644
index 0000000..ae0a4e9
--- /dev/null
+++ b/lib/ui/widgets/main_screen/statistics_card.dart
@@ -0,0 +1,46 @@
+import 'dart:convert';
+
+import 'package:flutter/material.dart';
+
+import '../../../models/statistics.dart';
+import '../../../network/scrobbles_api.dart';
+import '../../../ui/widgets/error.dart';
+import '../../../ui/widgets/main_screen/statistics_content.dart';
+
+class StatisticsCard extends StatelessWidget {
+  const StatisticsCard({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    late Future<StatisticsData> futureAlbum = ScrobblesApi.fetchStatistics();
+
+    return FutureBuilder<StatisticsData>(
+      future: futureAlbum,
+      builder: (context, snapshot) {
+        if (snapshot.hasError) {
+          return ShowErrorWidget(message: '${snapshot.error}');
+        }
+
+        return Card(
+          elevation: 2,
+          shadowColor: Theme.of(context).colorScheme.shadow,
+          color: Theme.of(context).colorScheme.primary,
+          shape: const RoundedRectangleBorder(
+            borderRadius: BorderRadius.all(
+              Radius.circular(12),
+            ),
+          ),
+          child: Padding(
+            padding: const EdgeInsets.all(12.0),
+            child: StatisticsContent(
+              statistics: snapshot.hasData
+                  ? StatisticsData.fromJson(jsonDecode(snapshot.data.toString()))
+                  : StatisticsData.createEmpty(),
+              isLoading: !snapshot.hasData,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/main_screen/statistics_content.dart b/lib/ui/widgets/main_screen/statistics_content.dart
new file mode 100644
index 0000000..c4659e4
--- /dev/null
+++ b/lib/ui/widgets/main_screen/statistics_content.dart
@@ -0,0 +1,77 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+import '../../../models/statistics.dart';
+
+class StatisticsContent extends StatelessWidget {
+  final StatisticsData statistics;
+  final bool isLoading;
+
+  const StatisticsContent({super.key, required this.statistics, required this.isLoading});
+
+  @override
+  Widget build(BuildContext context) {
+    final TextTheme textTheme = Theme.of(context).primaryTextTheme;
+
+    final String placeholder = '⏳';
+
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: <Widget>[
+        Text(
+          'global_statistics',
+          style: textTheme.titleLarge!.apply(fontWeightDelta: 2),
+        ).tr(),
+        Text(
+          'statistics_total_scrobbles_count',
+          style: textTheme.bodyMedium,
+        ).tr(
+          namedArgs: {
+            'count': this.isLoading ? placeholder : this.statistics.totalCount.toString(),
+          },
+        ),
+        Text(
+          'statistics_last_scrobble',
+          style: textTheme.bodyMedium,
+        ).tr(
+          namedArgs: {
+            'datetime': this.isLoading
+                ? placeholder
+                : DateFormat().format(this.statistics.lastScrobble),
+          },
+        ),
+        Text(
+          'statistics_selected_period',
+          style: textTheme.bodyMedium,
+        ).tr(
+          namedArgs: {
+            'daysCount':
+                this.isLoading ? placeholder : this.statistics.selectedPeriod.toString(),
+          },
+        ),
+        Text(
+          'statistics_recent_scrobbles_count',
+          style: textTheme.bodyMedium,
+        ).tr(
+          namedArgs: {
+            'count': this.isLoading ? placeholder : this.statistics.recentCount.toString(),
+          },
+        ),
+        Text(
+          'statistics_discoveries',
+          style: textTheme.bodyMedium,
+        ).tr(
+          namedArgs: {
+            'artistsCount': this.isLoading
+                ? placeholder
+                : this.statistics.firstPlayedArtistsCount.toString(),
+            'tracksCount': this.isLoading
+                ? placeholder
+                : this.statistics.firstPlayedTracksCount.toString(),
+          },
+        ),
+      ],
+    );
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index e80d1d1..3443b82 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: Display scrobbles data and charts
 
 publish_to: 'none'
 
-version: 0.0.5+5
+version: 0.0.6+6
 
 environment:
   sdk: '^3.0.0'
-- 
GitLab