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

Add "discoveries" chart

parent 80c9217c
No related branches found
No related tags found
1 merge request!21Resolve "Add "discoveries" charts"
Pipeline #4526 passed
This commit is part of merge request !21. Comments created here will be created in the context of that merge request.
Showing
with 274 additions and 17 deletions
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
app.versionName=0.0.19 app.versionName=0.0.20
app.versionCode=19 app.versionCode=20
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
"counts_by_day": "Counts by day ({daysCount} days)", "counts_by_day": "Counts by day ({daysCount} days)",
"counts_by_hour": "Counts by hour ({daysCount} days)", "counts_by_hour": "Counts by hour ({daysCount} days)",
"discoveries_title": "Discoveries ({daysCount} days)",
"MON": "MON", "MON": "MON",
"TUE": "TUE", "TUE": "TUE",
"WED": "WED", "WED": "WED",
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
"counts_by_day": "Écoutes par jour ({daysCount} jours)", "counts_by_day": "Écoutes par jour ({daysCount} jours)",
"counts_by_hour": "Écoutes par heure ({daysCount} jours)", "counts_by_hour": "Écoutes par heure ({daysCount} jours)",
"discoveries_title": "Découvertes ({daysCount} jours)",
"MON": "LUN", "MON": "LUN",
"TUE": "MAR", "TUE": "MAR",
"WED": "MER", "WED": "MER",
......
Add discoveries chart.
Ajout du graphique des découvertes.
import 'dart:convert';
class DiscoveriesDataValue {
final int newArtistsCount;
final int newTracksCount;
const DiscoveriesDataValue({required this.newArtistsCount, required this.newTracksCount});
factory DiscoveriesDataValue.fromJson(Map<String, dynamic> json) {
return DiscoveriesDataValue(
newArtistsCount: json['new-artists'] as int,
newTracksCount: json['new-tracks'] as int,
);
}
}
class DiscoveriesData {
final Map<String, DiscoveriesDataValue> data;
const DiscoveriesData({
required this.data,
});
factory DiscoveriesData.fromJson(Map<String, dynamic> json) {
Map<String, DiscoveriesDataValue> data = {};
json.keys.forEach((date) {
DiscoveriesDataValue value = DiscoveriesDataValue(
newArtistsCount: json[date]['new-artists'] as int,
newTracksCount: json[date]['new-tracks'] as int,
);
data[date] = value;
});
return DiscoveriesData(data: data);
}
factory DiscoveriesData.createEmpty() {
return DiscoveriesData.fromJson({});
}
String toString() {
Map<String, Map<String, int>> map = {};
this.data.keys.forEach((element) {
DiscoveriesDataValue? item = this.data[element];
map[element] = {
'new-artists': item != null ? item.newArtistsCount : 0,
'new-tracks': item != null ? item.newTracksCount : 0,
};
});
return jsonEncode(map);
}
}
...@@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; ...@@ -3,6 +3,7 @@ import 'package:http/http.dart' as http;
import '../models/counts_by_day.dart'; import '../models/counts_by_day.dart';
import '../models/counts_by_hour.dart'; import '../models/counts_by_hour.dart';
import '../models/discoveries.dart';
import '../models/statistics.dart'; import '../models/statistics.dart';
import '../models/timeline.dart'; import '../models/timeline.dart';
...@@ -56,4 +57,16 @@ class ScrobblesApi { ...@@ -56,4 +57,16 @@ class ScrobblesApi {
throw Exception('Failed to get data from API.'); throw Exception('Failed to get data from API.');
} }
} }
static Future<DiscoveriesData> fetchDiscoveries(int daysCount) async {
final String url = baseUrl + '/data/' + daysCount.toString() + '/news';
print('fetching ' + url);
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
return DiscoveriesData.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
throw Exception('Failed to get data from API.');
}
}
} }
...@@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; ...@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import '../widgets/main_screen/counts_by_day_card.dart'; import '../widgets/main_screen/counts_by_day_card.dart';
import '../widgets/main_screen/counts_by_hour_card.dart'; import '../widgets/main_screen/counts_by_hour_card.dart';
import '../widgets/main_screen/discoveries_card.dart';
import '../widgets/main_screen/statistics_card.dart'; import '../widgets/main_screen/statistics_card.dart';
import '../widgets/main_screen/timeline_card.dart'; import '../widgets/main_screen/timeline_card.dart';
...@@ -21,6 +22,8 @@ class MainScreen extends StatelessWidget { ...@@ -21,6 +22,8 @@ class MainScreen extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
const ChartTimelineCard(), const ChartTimelineCard(),
const SizedBox(height: 8), const SizedBox(height: 8),
const ChartDiscoveriesCard(),
const SizedBox(height: 8),
const ChartCountsByDayCard(), const ChartCountsByDayCard(),
const SizedBox(height: 8), const SizedBox(height: 8),
const ChartCountsByHourCard(), const ChartCountsByHourCard(),
......
...@@ -54,15 +54,19 @@ class CustomBarChart extends StatelessWidget { ...@@ -54,15 +54,19 @@ class CustomBarChart extends StatelessWidget {
BarChartGroupData getBarItem( BarChartGroupData getBarItem(
{required int x, {required int x,
required double value, required List<double> values,
required Color barColor, required List<Color> barColors,
required double barWidth}) { required double barWidth}) {
final gradient = this.getGradient(barColor, value, this.getMaxCountsValue()); List<BarChartRodData> barRods = [];
final borderColor = barColor.darken(20);
return BarChartGroupData( for (int i = 0; i < values.length; i++) {
x: x, double value = values[i];
barRods: [ Color barColor = barColors[i];
final gradient = this.getGradient(barColor, value, this.getMaxCountsValue());
final borderColor = barColor.darken(20);
barRods.add(
BarChartRodData( BarChartRodData(
toY: value, toY: value,
color: barColor, color: barColor,
...@@ -73,7 +77,12 @@ class CustomBarChart extends StatelessWidget { ...@@ -73,7 +77,12 @@ class CustomBarChart extends StatelessWidget {
color: borderColor, color: borderColor,
), ),
), ),
], );
}
return BarChartGroupData(
x: x,
barRods: barRods,
); );
} }
......
...@@ -82,8 +82,8 @@ class CountsByDayCardContentChart extends CustomBarChart { ...@@ -82,8 +82,8 @@ class CountsByDayCardContentChart extends CustomBarChart {
data.add(this.getBarItem( data.add(this.getBarItem(
x: day, x: day,
value: counts, values: [counts],
barColor: barColor, barColors: [barColor],
barWidth: barWidth, barWidth: barWidth,
)); ));
} }
......
...@@ -53,8 +53,8 @@ class CountsByHourCardContentChart extends CustomBarChart { ...@@ -53,8 +53,8 @@ class CountsByHourCardContentChart extends CustomBarChart {
if (counts != null) { if (counts != null) {
data.add(getBarItem( data.add(getBarItem(
x: day, x: day,
value: counts, values: [counts],
barColor: barColor, barColors: [barColor],
barWidth: barWidth, barWidth: barWidth,
)); ));
} }
......
import 'dart:convert';
import 'package:flutter/material.dart';
import '../../../models/discoveries.dart';
import '../../../network/scrobbles_api.dart';
import '../../../ui/widgets/error.dart';
import '../../../ui/widgets/main_screen/discoveries_content.dart';
class ChartDiscoveriesCard extends StatelessWidget {
const ChartDiscoveriesCard({super.key});
@override
Widget build(BuildContext context) {
final int daysCount = 14;
late Future<DiscoveriesData> futureTimeline = ScrobblesApi.fetchDiscoveries(daysCount);
return FutureBuilder<DiscoveriesData>(
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(12),
),
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: ChartDiscoveriesCardContent(
daysCount: daysCount,
chartData: snapshot.hasData
? DiscoveriesData.fromJson(jsonDecode(snapshot.data.toString()))
: DiscoveriesData.createEmpty(),
isLoading: !snapshot.hasData,
),
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../config/app_colors.dart';
import '../../../models/discoveries.dart';
import '../../../utils/color_extensions.dart';
import '../../../ui/widgets/charts/custom_bar_chart.dart';
class ChartDiscoveries extends CustomBarChart {
final DiscoveriesData chartData;
ChartDiscoveries({super.key, required this.chartData});
final double chartHeight = 120.0;
final double verticalTicksInterval = 10;
@override
Widget build(BuildContext context) {
return Container(
height: this.chartHeight,
child: LayoutBuilder(builder: (context, constraints) {
return getBarChart(
barWidth: this.getBarWidth(constraints.maxWidth, this.chartData.data.keys.length),
backgroundColor: Theme.of(context).colorScheme.onSurface,
);
}),
);
}
double getMaxCountsValue() {
double maxValue = 0;
this.chartData.data.keys.forEach((key) {
DiscoveriesDataValue? value = this.chartData.data[key];
if (value != null) {
double newArtistsCount = value.newArtistsCount.toDouble();
double newTracksCount = value.newTracksCount.toDouble();
if (newArtistsCount > maxValue) {
maxValue = newArtistsCount;
}
if (newTracksCount > maxValue) {
maxValue = newTracksCount;
}
}
});
return maxValue;
}
List<BarChartGroupData> getDataCounts(double barWidth) {
List<BarChartGroupData> data = [];
final newArtistsBarColor = AppColors.contentColorGreen.darken(20);
final newTracksBarColor = AppColors.contentColorBlue.darken(20);
this.chartData.data.keys.forEach((key) {
DiscoveriesDataValue? value = this.chartData.data[key];
if (value != null) {
final int date = DateTime.parse(key).millisecondsSinceEpoch;
data.add(this.getBarItem(
x: date,
values: [
value.newArtistsCount.toDouble(),
value.newTracksCount.toDouble(),
],
barColors: [
newArtistsBarColor,
newTracksBarColor,
],
barWidth: barWidth / 2.5,
));
}
});
return data;
}
}
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import '../../../models/discoveries.dart';
import '../../../ui/widgets/main_screen/discoveries_chart.dart';
class ChartDiscoveriesCardContent extends StatelessWidget {
final int daysCount;
final DiscoveriesData chartData;
final bool isLoading;
const ChartDiscoveriesCardContent(
{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(
'discoveries_title',
style: textTheme.titleLarge!.apply(fontWeightDelta: 2),
).tr(
namedArgs: {
'daysCount': this.daysCount.toString(),
},
),
const SizedBox(height: 8),
this.isLoading
? Text(placeholder)
: ChartDiscoveries(
chartData: DiscoveriesData.fromJson(jsonDecode(this.chartData.toString())),
),
],
);
}
}
...@@ -55,8 +55,8 @@ class ChartTimelineCounts extends CustomBarChart { ...@@ -55,8 +55,8 @@ class ChartTimelineCounts extends CustomBarChart {
data.add(this.getBarItem( data.add(this.getBarItem(
x: date, x: date,
value: counts, values: [counts],
barColor: barColor, barColors: [barColor],
barWidth: barWidth, barWidth: barWidth,
)); ));
} }
......
...@@ -3,7 +3,7 @@ description: Display scrobbles data and charts ...@@ -3,7 +3,7 @@ description: Display scrobbles data and charts
publish_to: 'none' publish_to: 'none'
version: 0.0.19+19 version: 0.0.20+20
environment: environment:
sdk: '^3.0.0' 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