Skip to content
Snippets Groups Projects
Commit 6f7fa8bf authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Split global / recent statistics

parent 13a8455b
No related branches found
No related tags found
1 merge request!26Resolve "Split global / period statistics"
Pipeline #4562 passed
This commit is part of merge request !26. Comments created here will be created in the context of that merge request.
Showing
with 185 additions and 62 deletions
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
......@@ -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.",
......
......@@ -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.",
......
Split global and recent statistics.
Séparation des statistiques globales et récentes.
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);
}
}
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);
......
......@@ -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.');
}
......
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),
......
......@@ -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,
),
),
......
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),
},
),
],
);
}
}
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,
),
),
);
},
);
}
}
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),
......
......@@ -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'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment