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

Merge branch '14-add-discoveries-charts' into 'master'

Resolve "Add "discoveries" charts"

Closes #14

See merge request !21
parents 80c9217c 88a9dbb0
Branches
Tags Release_0.0.26_26
1 merge request!21Resolve "Add "discoveries" charts"
Pipeline #4529 passed
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.
Please register or to comment