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
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.
Please register or to comment