Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • android/org.benoitharrault.scrobbles
1 result
Show changes
Commits on Source (2)
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'
......