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
Branches 44-implement-game-write-word-from-letters
Tags
1 merge request!25Resolve "Add "top artists" chart"
Pipeline #4570 passed
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.
Please register or to comment