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