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

Add "top artists" chart

parent b06813fc
No related branches found
No related tags found
1 merge request!25Resolve "Add "top artists" chart"
Pipeline #4570 passed
This commit is part of merge request !25. Comments created here will be created in the context of that merge request.
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
app.versionName=0.0.24
app.versionCode=24
app.versionName=0.0.25
app.versionCode=25
......@@ -19,6 +19,8 @@
"discoveries_title": "Discoveries ({daysCount} days)",
"top_artists_title": "Top artists ({daysCount} days)",
"MON": "MON",
"TUE": "TUE",
"WED": "WED",
......
......@@ -19,6 +19,8 @@
"discoveries_title": "Découvertes ({daysCount} jours)",
"top_artists_title": "Top artistes ({daysCount} jours)",
"MON": "LUN",
"TUE": "MAR",
"WED": "MER",
......
Add "top artists" chart.
Ajout du graphique "top artistes".
import 'dart:convert';
class TopArtistsDataValue {
final String artistName;
final int count;
const TopArtistsDataValue({required this.artistName, required this.count});
factory TopArtistsDataValue.fromJson(Map<String, dynamic> json) {
return TopArtistsDataValue(
artistName: json['artistName'] as String,
count: json['count'] as int,
);
}
}
class TopArtistsData {
final List<TopArtistsDataValue> topArtists;
const TopArtistsData({
required this.topArtists,
});
factory TopArtistsData.fromJson(Map<String, dynamic> json) {
List<TopArtistsDataValue> topArtists = [];
json['top-artists'].forEach((element) {
TopArtistsDataValue value = TopArtistsDataValue(
artistName: element['artistName'] as String,
count: element['count'] as int,
);
topArtists.add(value);
});
return TopArtistsData(
topArtists: topArtists,
);
}
factory TopArtistsData.createEmpty() {
return TopArtistsData.fromJson({
'top-artists': [],
});
}
String toString() {
List<Map<String, Object>> listArtists = [];
this.topArtists.forEach((TopArtistsDataValue? item) {
listArtists.add({
'artistName': item != null ? item.artistName : '',
'count': item != null ? item.count : 0,
});
});
return jsonEncode({
'top-artists': listArtists,
});
}
}
......@@ -7,6 +7,7 @@ import '../models/discoveries.dart';
import '../models/statistics_global.dart';
import '../models/statistics_recent.dart';
import '../models/timeline.dart';
import '../models/topartists.dart';
class ScrobblesApi {
static String baseUrl = 'https://scrobble.harrault.fr';
......@@ -82,4 +83,16 @@ class ScrobblesApi {
throw Exception('Failed to get data from API.');
}
}
static Future<TopArtistsData> fetchTopArtists(int daysCount) async {
final String url = baseUrl + '/data/' + daysCount.toString() + '/top-artists';
print('fetching ' + url);
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
return TopArtistsData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
throw Exception('Failed to get data from API.');
}
}
}
......@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import '../widgets/main_screen/statistics_global_card.dart';
import '../widgets/main_screen/statistics_recent_card.dart';
import '../widgets/main_screen/timeline_card.dart';
import '../widgets/main_screen/topartists_card.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
......@@ -26,6 +27,8 @@ class _HomeScreenState extends State<HomeScreen> {
const StatisticsRecentCard(),
const SizedBox(height: 6),
const ChartTimelineCard(),
const SizedBox(height: 6),
const ChartTopArtistsCard(),
const SizedBox(height: 36),
],
),
......
import 'dart:convert';
import 'package:flutter/material.dart';
import '../../../models/topartists.dart';
import '../../../network/scrobbles_api.dart';
import '../../../ui/widgets/error.dart';
import '../../../ui/widgets/main_screen/topartists_content.dart';
class ChartTopArtistsCard extends StatelessWidget {
const ChartTopArtistsCard({super.key});
@override
Widget build(BuildContext context) {
final int daysCount = 14;
late Future<TopArtistsData> futureTimeline = ScrobblesApi.fetchTopArtists(daysCount);
return FutureBuilder<TopArtistsData>(
future: futureTimeline,
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.surface,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ChartTopArtistsCardContent(
daysCount: daysCount,
chartData: snapshot.hasData
? TopArtistsData.fromJson(jsonDecode(snapshot.data.toString()))
: TopArtistsData.createEmpty(),
isLoading: !snapshot.hasData,
),
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../config/app_colors.dart';
import '../../../models/topartists.dart';
import '../../../utils/color_extensions.dart';
class ChartTopArtists extends StatelessWidget {
final TopArtistsData chartData;
ChartTopArtists({super.key, required this.chartData});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 2.1,
child: Row(
children: <Widget>[
Expanded(
child: AspectRatio(
aspectRatio: 1,
child: PieChart(
PieChartData(
sections: getPieChartData(),
sectionsSpace: 1,
centerSpaceRadius: 40,
startDegreeOffset: -45,
pieTouchData: PieTouchData(enabled: false),
borderData: FlBorderData(show: false),
),
),
),
),
buildLegendWidget(),
],
),
);
}
Color getColorIndex(int index) {
const List<int> hexValues = [
0x8dd3c7,
0xffffb3,
0xbebada,
0xfb8072,
0x80b1d3,
0xfdb462,
0xb3de69,
0xfccde5,
0xd9d9d9,
0xbc80bd,
0xccebc5,
0xffed6f,
];
return Color(hexValues[index] + 0xff000000);
}
List<PieChartSectionData> getPieChartData() {
List<PieChartSectionData> items = [];
final radius = 40.0;
final fontSize = 11.0;
const shadows = [Shadow(color: Colors.black, blurRadius: 2)];
int index = 0;
this.chartData.topArtists.forEach((element) {
items.add(PieChartSectionData(
value: element.count.toDouble(),
title: element.artistName,
color: getColorIndex(index++).darken(20),
radius: radius,
titleStyle: TextStyle(
fontSize: fontSize,
color: AppColors.mainTextColor1,
shadows: shadows,
),
));
});
return items;
}
Widget buildLegendWidget() {
const double itemSize = 12;
List<Widget> items = [];
int index = 0;
this.chartData.topArtists.forEach((element) {
items.add(Row(
children: <Widget>[
Container(
width: itemSize,
height: itemSize,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: getColorIndex(index++).darken(20),
),
),
const SizedBox(
width: 4,
),
Text(
element.artistName + ' (' + element.count.toString() + ')',
style: TextStyle(
fontSize: itemSize - 2,
fontWeight: FontWeight.bold,
),
)
],
));
});
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: items,
);
}
}
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import '../../../models/topartists.dart';
import '../../../ui/widgets/main_screen/topartists_chart.dart';
class ChartTopArtistsCardContent extends StatelessWidget {
final int daysCount;
final TopArtistsData chartData;
final bool isLoading;
const ChartTopArtistsCardContent(
{super.key, required this.daysCount, required this.chartData, 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(
'top_artists_title',
style: textTheme.titleLarge!.apply(fontWeightDelta: 2),
).tr(
namedArgs: {
'daysCount': this.daysCount.toString(),
},
),
const SizedBox(height: 8),
this.isLoading
? Text(placeholder)
: ChartTopArtists(
chartData: TopArtistsData.fromJson(jsonDecode(this.chartData.toString())),
),
],
);
}
}
......@@ -3,7 +3,7 @@ description: Display scrobbles data and charts
publish_to: 'none'
version: 0.0.24+24
version: 0.0.25+25
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