From 6f7fa8bfdecd122af84935f8755a7f2da6bde9fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Fri, 10 Nov 2023 14:58:24 +0100
Subject: [PATCH] Split global / recent statistics

---
 android/gradle.properties                     |  4 +-
 assets/translations/en.json                   |  2 +
 assets/translations/fr.json                   |  2 +
 .../metadata/android/en-US/changelogs/24.txt  |  1 +
 .../metadata/android/fr-FR/changelogs/24.txt  |  1 +
 lib/models/statistics_global.dart             | 37 ++++++++++++++
 ...statistics.dart => statistics_recent.dart} | 26 +++-------
 lib/network/scrobbles_api.dart                | 19 ++++++--
 lib/ui/screens/home_screen.dart               |  7 ++-
 ..._card.dart => statistics_global_card.dart} | 21 ++++----
 .../statistics_global_content.dart            | 48 +++++++++++++++++++
 .../main_screen/statistics_recent_card.dart   | 48 +++++++++++++++++++
 ...nt.dart => statistics_recent_content.dart} | 29 +++--------
 pubspec.yaml                                  |  2 +-
 14 files changed, 185 insertions(+), 62 deletions(-)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/24.txt
 create mode 100644 fastlane/metadata/android/fr-FR/changelogs/24.txt
 create mode 100644 lib/models/statistics_global.dart
 rename lib/models/{statistics.dart => statistics_recent.dart} (58%)
 rename lib/ui/widgets/main_screen/{statistics_card.dart => statistics_global_card.dart} (61%)
 create mode 100644 lib/ui/widgets/main_screen/statistics_global_content.dart
 create mode 100644 lib/ui/widgets/main_screen/statistics_recent_card.dart
 rename lib/ui/widgets/main_screen/{statistics_content.dart => statistics_recent_content.dart} (63%)

diff --git a/android/gradle.properties b/android/gradle.properties
index 3487476..c2a871a 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.23
-app.versionCode=23
+app.versionName=0.0.24
+app.versionCode=24
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 9c047c5..8d3e0ce 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -8,6 +8,8 @@
   "global_statistics": "Global statistics",
   "statistics_total_scrobbles_count": "Total scrobbles count: {count}",
   "statistics_last_scrobble": "Last scrobble: {datetime}",
+
+  "recent_statistics": "Recent statistics",
   "statistics_selected_period": "On last {daysCount} days:",
   "statistics_recent_scrobbles_count_and_discoveries": "{count} scrobbles and {artistsCount} artists / {tracksCount} tracks discovered.",
 
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
index 6ec386f..b7788bb 100644
--- a/assets/translations/fr.json
+++ b/assets/translations/fr.json
@@ -8,6 +8,8 @@
   "global_statistics": "Statistiques globales d'écoutes",
   "statistics_total_scrobbles_count": "Nombre total d'écoutes : {count}",
   "statistics_last_scrobble": "Dernière écoute : {datetime}",
+
+  "recent_statistics": "Statistiques récentes",
   "statistics_selected_period": "Sur les {daysCount} derniers jours :",
   "statistics_recent_scrobbles_count_and_discoveries": "{count} écoutes et {artistsCount} artistes / {tracksCount} morceaux découverts.",
 
diff --git a/fastlane/metadata/android/en-US/changelogs/24.txt b/fastlane/metadata/android/en-US/changelogs/24.txt
new file mode 100644
index 0000000..0048603
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/24.txt
@@ -0,0 +1 @@
+Split global and recent statistics.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/24.txt b/fastlane/metadata/android/fr-FR/changelogs/24.txt
new file mode 100644
index 0000000..a2484a5
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/24.txt
@@ -0,0 +1 @@
+Séparation des statistiques globales et récentes.
diff --git a/lib/models/statistics_global.dart b/lib/models/statistics_global.dart
new file mode 100644
index 0000000..0610e27
--- /dev/null
+++ b/lib/models/statistics_global.dart
@@ -0,0 +1,37 @@
+import 'dart:convert';
+
+class StatisticsGlobalData {
+  final int totalCount;
+  final DateTime lastScrobble;
+
+  const StatisticsGlobalData({
+    required this.totalCount,
+    required this.lastScrobble,
+  });
+
+  factory StatisticsGlobalData.fromJson(Map<String, dynamic> json) {
+    return StatisticsGlobalData(
+      totalCount: json['totalCount'] != null ? json['totalCount'] as int : 0,
+      lastScrobble: (json['lastScrobble'] != null && json['lastScrobble']['date'] != null)
+          ? DateTime.parse(
+              json['lastScrobble']['date'],
+            )
+          : DateTime.now(),
+    );
+  }
+
+  factory StatisticsGlobalData.createEmpty() {
+    return StatisticsGlobalData.fromJson({});
+  }
+
+  String toString() {
+    Map<String, dynamic> map = {
+      'totalCount': this.totalCount,
+      'lastScrobble': {
+        'date': this.lastScrobble.toString(),
+      },
+    };
+
+    return jsonEncode(map);
+  }
+}
diff --git a/lib/models/statistics.dart b/lib/models/statistics_recent.dart
similarity index 58%
rename from lib/models/statistics.dart
rename to lib/models/statistics_recent.dart
index 0654213..218dd1f 100644
--- a/lib/models/statistics.dart
+++ b/lib/models/statistics_recent.dart
@@ -1,53 +1,39 @@
 import 'dart:convert';
 
-class StatisticsData {
-  final int totalCount;
+class StatisticsRecentData {
   final int recentCount;
   final int firstPlayedArtistsCount;
   final int firstPlayedTracksCount;
   final int selectedPeriod;
-  final DateTime lastScrobble;
 
-  const StatisticsData({
-    required this.totalCount,
+  const StatisticsRecentData({
     required this.recentCount,
     required this.firstPlayedArtistsCount,
     required this.firstPlayedTracksCount,
     required this.selectedPeriod,
-    required this.lastScrobble,
   });
 
-  factory StatisticsData.fromJson(Map<String, dynamic> json) {
-    return StatisticsData(
-      totalCount: json['totalCount'] != null ? json['totalCount'] as int : 0,
+  factory StatisticsRecentData.fromJson(Map<String, dynamic> json) {
+    return StatisticsRecentData(
       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({});
+  factory StatisticsRecentData.createEmpty() {
+    return StatisticsRecentData.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/network/scrobbles_api.dart b/lib/network/scrobbles_api.dart
index d2ba828..1638595 100644
--- a/lib/network/scrobbles_api.dart
+++ b/lib/network/scrobbles_api.dart
@@ -4,19 +4,32 @@ import 'package:http/http.dart' as http;
 import '../models/counts_by_day.dart';
 import '../models/counts_by_hour.dart';
 import '../models/discoveries.dart';
-import '../models/statistics.dart';
+import '../models/statistics_global.dart';
+import '../models/statistics_recent.dart';
 import '../models/timeline.dart';
 
 class ScrobblesApi {
   static String baseUrl = 'https://scrobble.harrault.fr';
 
-  static Future<StatisticsData> fetchStatistics(int daysCount) async {
+  static Future<StatisticsGlobalData> fetchGlobalStatistics() async {
+    final String url = baseUrl + '/stats';
+    print('fetching ' + url);
+    final response = await http.get(Uri.parse(url));
+
+    if (response.statusCode == 200) {
+      return StatisticsGlobalData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
+    } else {
+      throw Exception('Failed to get data from API.');
+    }
+  }
+
+  static Future<StatisticsRecentData> fetchRecentStatistics(int daysCount) async {
     final String url = baseUrl + '/' + daysCount.toString() + '/stats';
     print('fetching ' + url);
     final response = await http.get(Uri.parse(url));
 
     if (response.statusCode == 200) {
-      return StatisticsData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
+      return StatisticsRecentData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
     } else {
       throw Exception('Failed to get data from API.');
     }
diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart
index d640754..d05a634 100644
--- a/lib/ui/screens/home_screen.dart
+++ b/lib/ui/screens/home_screen.dart
@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 
-import '../widgets/main_screen/statistics_card.dart';
+import '../widgets/main_screen/statistics_global_card.dart';
+import '../widgets/main_screen/statistics_recent_card.dart';
 import '../widgets/main_screen/timeline_card.dart';
 
 class HomeScreen extends StatefulWidget {
@@ -20,7 +21,9 @@ class _HomeScreenState extends State<HomeScreen> {
         physics: const BouncingScrollPhysics(),
         children: <Widget>[
           const SizedBox(height: 8),
-          const StatisticsCard(),
+          const StatisticsGlobalCard(),
+          const SizedBox(height: 6),
+          const StatisticsRecentCard(),
           const SizedBox(height: 6),
           const ChartTimelineCard(),
           const SizedBox(height: 36),
diff --git a/lib/ui/widgets/main_screen/statistics_card.dart b/lib/ui/widgets/main_screen/statistics_global_card.dart
similarity index 61%
rename from lib/ui/widgets/main_screen/statistics_card.dart
rename to lib/ui/widgets/main_screen/statistics_global_card.dart
index 4285410..1a7474c 100644
--- a/lib/ui/widgets/main_screen/statistics_card.dart
+++ b/lib/ui/widgets/main_screen/statistics_global_card.dart
@@ -2,21 +2,20 @@ import 'dart:convert';
 
 import 'package:flutter/material.dart';
 
-import '../../../models/statistics.dart';
+import '../../../models/statistics_global.dart';
 import '../../../network/scrobbles_api.dart';
 import '../../../ui/widgets/error.dart';
-import '../../../ui/widgets/main_screen/statistics_content.dart';
+import 'statistics_global_content.dart';
 
-class StatisticsCard extends StatelessWidget {
-  const StatisticsCard({super.key});
+class StatisticsGlobalCard extends StatelessWidget {
+  const StatisticsGlobalCard({super.key});
 
   @override
   Widget build(BuildContext context) {
-    final int daysCount = 14;
-    late Future<StatisticsData> futureStatistics = ScrobblesApi.fetchStatistics(daysCount);
+    late Future<StatisticsGlobalData> future = ScrobblesApi.fetchGlobalStatistics();
 
-    return FutureBuilder<StatisticsData>(
-      future: futureStatistics,
+    return FutureBuilder<StatisticsGlobalData>(
+      future: future,
       builder: (context, snapshot) {
         if (snapshot.hasError) {
           return ShowErrorWidget(message: '${snapshot.error}');
@@ -33,10 +32,10 @@ class StatisticsCard extends StatelessWidget {
           ),
           child: Padding(
             padding: const EdgeInsets.all(8.0),
-            child: StatisticsContent(
+            child: StatisticsGlobalContent(
               statistics: snapshot.hasData
-                  ? StatisticsData.fromJson(jsonDecode(snapshot.data.toString()))
-                  : StatisticsData.createEmpty(),
+                  ? StatisticsGlobalData.fromJson(jsonDecode(snapshot.data.toString()))
+                  : StatisticsGlobalData.createEmpty(),
               isLoading: !snapshot.hasData,
             ),
           ),
diff --git a/lib/ui/widgets/main_screen/statistics_global_content.dart b/lib/ui/widgets/main_screen/statistics_global_content.dart
new file mode 100644
index 0000000..4459347
--- /dev/null
+++ b/lib/ui/widgets/main_screen/statistics_global_content.dart
@@ -0,0 +1,48 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+import '../../../models/statistics_global.dart';
+
+class StatisticsGlobalContent extends StatelessWidget {
+  final StatisticsGlobalData statistics;
+  final bool isLoading;
+
+  const StatisticsGlobalContent(
+      {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),
+          },
+        ),
+      ],
+    );
+  }
+}
diff --git a/lib/ui/widgets/main_screen/statistics_recent_card.dart b/lib/ui/widgets/main_screen/statistics_recent_card.dart
new file mode 100644
index 0000000..5f12d27
--- /dev/null
+++ b/lib/ui/widgets/main_screen/statistics_recent_card.dart
@@ -0,0 +1,48 @@
+import 'dart:convert';
+
+import 'package:flutter/material.dart';
+
+import '../../../models/statistics_recent.dart';
+import '../../../network/scrobbles_api.dart';
+import '../../../ui/widgets/error.dart';
+import 'statistics_recent_content.dart';
+
+class StatisticsRecentCard extends StatelessWidget {
+  const StatisticsRecentCard({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final int daysCount = 21;
+
+    late Future<StatisticsRecentData> future = ScrobblesApi.fetchRecentStatistics(daysCount);
+
+    return FutureBuilder<StatisticsRecentData>(
+      future: future,
+      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(8),
+            ),
+          ),
+          child: Padding(
+            padding: const EdgeInsets.all(8.0),
+            child: StatisticsRecentContent(
+              statistics: snapshot.hasData
+                  ? StatisticsRecentData.fromJson(jsonDecode(snapshot.data.toString()))
+                  : StatisticsRecentData.createEmpty(),
+              isLoading: !snapshot.hasData,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/main_screen/statistics_content.dart b/lib/ui/widgets/main_screen/statistics_recent_content.dart
similarity index 63%
rename from lib/ui/widgets/main_screen/statistics_content.dart
rename to lib/ui/widgets/main_screen/statistics_recent_content.dart
index bee5636..cd95b00 100644
--- a/lib/ui/widgets/main_screen/statistics_content.dart
+++ b/lib/ui/widgets/main_screen/statistics_recent_content.dart
@@ -1,13 +1,14 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 
-import '../../../models/statistics.dart';
+import '../../../models/statistics_recent.dart';
 
-class StatisticsContent extends StatelessWidget {
-  final StatisticsData statistics;
+class StatisticsRecentContent extends StatelessWidget {
+  final StatisticsRecentData statistics;
   final bool isLoading;
 
-  const StatisticsContent({super.key, required this.statistics, required this.isLoading});
+  const StatisticsRecentContent(
+      {super.key, required this.statistics, required this.isLoading});
 
   @override
   Widget build(BuildContext context) {
@@ -20,27 +21,9 @@ class StatisticsContent extends StatelessWidget {
       crossAxisAlignment: CrossAxisAlignment.start,
       children: <Widget>[
         Text(
-          'global_statistics',
+          'recent_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!.apply(fontWeightDelta: 2),
diff --git a/pubspec.yaml b/pubspec.yaml
index 4b8265d..f60d4ef 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: Display scrobbles data and charts
 
 publish_to: 'none'
 
-version: 0.0.23+23
+version: 0.0.24+24
 
 environment:
   sdk: '^3.0.0'
-- 
GitLab