From 36ca3573927da0c79aca84de555b80a254ceb44f Mon Sep 17 00:00:00 2001 From: balint1414 Date: Wed, 22 Oct 2025 20:49:49 +0200 Subject: [PATCH] =?UTF-8?q?chart=20megjelen=C3=ADt=C3=A9se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/ui/phone/pages/home/home_grades.dart | 4 +- firka/lib/ui/phone/widgets/grade_chart.dart | 214 ++++++++++++++++++ firka/pubspec.yaml | 6 +- 3 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 firka/lib/ui/phone/widgets/grade_chart.dart diff --git a/firka/lib/ui/phone/pages/home/home_grades.dart b/firka/lib/ui/phone/pages/home/home_grades.dart index b7185c1..a5d4659 100644 --- a/firka/lib/ui/phone/pages/home/home_grades.dart +++ b/firka/lib/ui/phone/pages/home/home_grades.dart @@ -2,6 +2,7 @@ import 'package:firka/helpers/api/client/kreta_client.dart'; import 'package:firka/helpers/api/model/generic.dart'; import 'package:firka/helpers/ui/firka_card.dart'; import 'package:firka/helpers/ui/grade_helpers.dart'; +import 'package:firka/ui/phone/widgets/grade_chart.dart'; import 'package:firka/ui/widget/grade_small_card.dart'; import 'package:flutter/material.dart'; @@ -220,7 +221,8 @@ void updateListener() async { ) ], ), - SizedBox(height: 16), // TODO: Add graphs here + // SizedBox(height: 16), // TODO: Add graphs here + GradeChart(grades: grades?.response ?? []), // ...gradeCards, Expanded( child: ListView( diff --git a/firka/lib/ui/phone/widgets/grade_chart.dart b/firka/lib/ui/phone/widgets/grade_chart.dart new file mode 100644 index 0000000..ed9f17c --- /dev/null +++ b/firka/lib/ui/phone/widgets/grade_chart.dart @@ -0,0 +1,214 @@ +import 'package:firka/helpers/api/model/grade.dart'; +import 'package:firka/ui/model/style.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class GradeChart extends StatefulWidget { + final List grades; + const GradeChart({super.key, required this.grades}); + + @override + State createState() => _GradeChartState(); +} + +class _GradeChartState extends State { + List gradientColors = [ + appStyle.colors.grade5, + appStyle.colors.grade4, + appStyle.colors.grade3, + appStyle.colors.grade2, + appStyle.colors.grade1, + ]; + + late final List spots; + + @override + void initState() { + super.initState(); + + final sortedGrades = List.from(widget.grades) + ..sort((a, b) => a.creationDate.compareTo(b.creationDate)); + + if (sortedGrades.isEmpty) { + spots = [const FlSpot(0, 0)]; + return; + } + + double sum = 0; + double count = 0; + spots = []; + for (int i = 0; i < sortedGrades.length; i++) { + final grade = sortedGrades[i]; + if (grade.numericValue != null) { + sum += grade.numericValue!.toDouble(); + count += 1; + spots.add(FlSpot(i.toDouble(), double.parse((sum / count).toStringAsFixed(2)))); + } + } + if (spots.isEmpty) { + spots = [const FlSpot(0, 0)]; + } + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AspectRatio( + aspectRatio: 1.70, + child: Padding( + padding: const EdgeInsets.only( + right: 18, + left: 12, + top: 24, + bottom: 12, + ), + child: LineChart(avgData()), + ), + ), + ], + ); + } + + Widget bottomTitleWidgets(double value, TitleMeta meta) { + final style = TextStyle( + fontFamily: appStyle.fonts.B_16R.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 16, + color: appStyle.colors.textSecondary, + ); + + final firstX = spots.first.x.toInt(); + final lastX = spots.last.x.toInt(); + String text = ''; + const epsilon = 0.01; + + if ((value - firstX).abs() < epsilon) { + text = 'Szeptember'; + } else if ((value - lastX).abs() < epsilon) { + text = 'Most'; + } + + return SideTitleWidget( + meta: meta, + child: Text(text, style: style), + ); + } + + Widget leftTitleWidgets(double value, TitleMeta meta) { + final style = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + color: appStyle.colors.textSecondary, + ); + String text = switch (value.toInt()) { + 1 => '1', + 2 => '2', + 3 => '3', + 4 => '4', + 5 => '5', + _ => '', + }; + return Text(text, style: style, textAlign: TextAlign.left); + } + + LineChartData avgData() { + final firstX = spots.first.x; + final lastX = spots.last.x; + + Color colorForY(double y) { + switch (y.round()) { + case 1: + return appStyle.colors.grade1; + case 2: + return appStyle.colors.grade2; + case 3: + return appStyle.colors.grade3; + case 4: + return appStyle.colors.grade4; + case 5: + return appStyle.colors.grade5; + default: + return appStyle.colors.grade1; + } + } + + return LineChartData( + lineTouchData: const LineTouchData(enabled: true), + backgroundColor: appStyle.colors.card, + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + drawVerticalLine: false, + horizontalInterval: 1, + getDrawingHorizontalLine: (value) { + if (value != 5) { + return FlLine( + color: appStyle.colors.a15p, + strokeWidth: 1, + dashArray: [8, 12], + ); + } else { + return FlLine( + color: appStyle.colors.a15p, + strokeWidth: 1.3, + ); + } + }, + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + getTitlesWidget: bottomTitleWidgets, + interval: 1, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: leftTitleWidgets, + reservedSize: 42, + interval: 1, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + borderData: FlBorderData(show: false), + + minX: firstX, + maxX: lastX, + minY: 0, + maxY: 6, + + lineBarsData: [ + LineChartBarData( + spots: spots, + isCurved: false, + gradient: LinearGradient( + colors: [for (final s in spots) colorForY(s.y)], + ), + barWidth: 5, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + for (final s in spots) + colorForY(s.y).withValues(alpha: 0.1), + ], + ), + ), + ), + ], + ); + } +} diff --git a/firka/pubspec.yaml b/firka/pubspec.yaml index 5d13a97..773e925 100644 --- a/firka/pubspec.yaml +++ b/firka/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: xml: ^6.6.1 confetti: ^0.8.0 flutter_html: ^3.0.0 + fl_chart: ^1.1.1 dev_dependencies: flutter_test: @@ -96,4 +97,7 @@ flutter: - family: RobotoMono fonts: - asset: assets/fonts/RobotoMono-VariableFont_wght.ttf - style: normal \ No newline at end of file + style: normal + +dependency_overrides: + vector_math: ^2.2.0