diff --git a/firka/lib/core/average_helper.dart b/firka/lib/core/average_helper.dart deleted file mode 100644 index 58cdc0f..0000000 --- a/firka/lib/core/average_helper.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:kreta_api/kreta_api.dart'; - -bool _isPercentageGrade(Grade grade) { - final name = grade.valueType.name?.toLowerCase() ?? ''; - return name.contains('szazalek') || name.contains('percent'); -} - -bool shouldIgnoreInAverage(Grade grade) { - if (_isPercentageGrade(grade)) { - return true; - } - - final typeName = grade.type.name?.toLowerCase() ?? ''; - if (typeName == 'felevi_jegy_ertekeles' || - typeName == 'evvegi_jegy_ertekeles') { - return true; - } - - return false; -} - -double calculateAverage(List sortedGrades, {bool applyIgnoreFilter = true}) { - double totalWeight = 0.0; - double weightedSum = 0.0; - - if (applyIgnoreFilter && - sortedGrades.isNotEmpty && - sortedGrades.where((g) => !shouldIgnoreInAverage(g)).isEmpty) { - final grades = sortedGrades.where( - (g) => g.numericValue != null && g.numericValue! > 0, - ); - - if (grades.isNotEmpty) { - return grades.last.numericValue!.toDouble(); - } - } - - for (final grade in sortedGrades) { - if (applyIgnoreFilter && shouldIgnoreInAverage(grade)) continue; - - final value = grade.numericValue; - final weight = grade.weightPercentage; - - if (value != null && weight != null) { - weightedSum += value * weight; - totalWeight += weight; - } - } - - if (totalWeight == 0) { - return double.parse(0.0.toStringAsFixed(2)); - } - - final avg = weightedSum / totalWeight; - return double.parse(avg.toStringAsFixed(2)); -} diff --git a/firka/lib/routing/app_router.dart b/firka/lib/routing/app_router.dart index 6965fc8..51407e8 100644 --- a/firka/lib/routing/app_router.dart +++ b/firka/lib/routing/app_router.dart @@ -24,6 +24,16 @@ import 'package:go_router/go_router.dart'; import 'package:kreta_api/kreta_api.dart'; GoRouter createAppRouter() { + final subjectRoute = GoRoute( + path: 'subject', + builder: (context, state) { + return DefaultAssetBundle( + bundle: FirkaBundle(), + child: HomeGradesSubjectScreen(state.extra as Subject, initData), + ); + }, + ); + return GoRouter( navigatorKey: navigatorKey, initialLocation: _initialLocation, @@ -124,19 +134,7 @@ GoRouter createAppRouter() { child: HomeMainScreen(initData), ), ), - routes: [ - GoRoute( - path: 'subject/:uid', - builder: (context, state) { - final uid = state.pathParameters['uid'] ?? ''; - activeSubjectUid = uid; - return DefaultAssetBundle( - bundle: FirkaBundle(), - child: HomeGradesSubjectScreen(initData), - ); - }, - ), - ], + routes: [subjectRoute], ), ], ), @@ -151,19 +149,7 @@ GoRouter createAppRouter() { child: HomeGradesScreen(initData), ), ), - routes: [ - GoRoute( - path: 'subject/:uid', - builder: (context, state) { - final uid = state.pathParameters['uid'] ?? ''; - activeSubjectUid = uid; - return DefaultAssetBundle( - bundle: FirkaBundle(), - child: HomeGradesSubjectScreen(initData), - ); - }, - ), - ], + routes: [subjectRoute], ), ], ), @@ -186,17 +172,7 @@ GoRouter createAppRouter() { child: HomeTimetableMonthlyScreen(initData), ), ), - GoRoute( - path: 'subject/:uid', - builder: (context, state) { - final uid = state.pathParameters['uid'] ?? ''; - activeSubjectUid = uid; - return DefaultAssetBundle( - bundle: FirkaBundle(), - child: HomeGradesSubjectScreen(initData), - ); - }, - ), + subjectRoute, ], ), ], diff --git a/firka/lib/ui/components/common_bottom_sheets.dart b/firka/lib/ui/components/common_bottom_sheets.dart index 7cc748e..d862350 100644 --- a/firka/lib/ui/components/common_bottom_sheets.dart +++ b/firka/lib/ui/components/common_bottom_sheets.dart @@ -128,10 +128,8 @@ Future showLessonBottomSheet( FilledCircle( diameter: 40, color: bgColor, - child: ClassIconWidget( - uid: lesson.uid, - className: lesson.name, - category: subjectName, + child: ClassIconWidget.subject( + subject: lesson.subject!, color: accent, size: 26, ), @@ -218,13 +216,7 @@ Future showLessonBottomSheet( color: appStyle.colors.buttonSecondaryFill, ), onTap: () { - activeSubjectUid = lesson.subject!.uid; - subjectName = lesson.subject!.name; - subjectId = lesson.subject!.uid; - subjectCategory = lesson.subject!.category.name; - subjectInfo = []; - Navigator.pop(context); - context.push('/timetable/subject/${lesson.subject!.uid}'); + context.go('/timetable/subject', extra: lesson.subject); }, ), ]); @@ -392,13 +384,8 @@ Future showGradeBottomSheet( color: appStyle.colors.buttonSecondaryFill, ), onTap: () { - activeSubjectUid = grade.subject.uid; - subjectName = grade.subject.name; - subjectId = grade.subject.uid; - subjectCategory = grade.subject.category.name; - subjectInfo = []; Navigator.pop(context); - context.go('/grades/subject/${grade.subject.uid}'); + context.go('/grades/subject', extra: grade.subject); }, ), ]); @@ -490,13 +477,7 @@ Future showHomeworkBottomSheet( color: appStyle.colors.buttonSecondaryFill, ), onTap: () { - activeSubjectUid = homework.subject.uid; - subjectName = homework.subjectName; - subjectId = homework.subject.uid; - subjectCategory = ""; - subjectInfo = []; - Navigator.pop(context); - context.push('/home/subject/${homework.subject.uid}'); + context.go('/home/subject', extra: homework.subject); }, ), ]); diff --git a/firka/lib/ui/phone/pages/home/home_grades.dart b/firka/lib/ui/phone/pages/home/home_grades.dart index e93a0fa..b38d7cc 100644 --- a/firka/lib/ui/phone/pages/home/home_grades.dart +++ b/firka/lib/ui/phone/pages/home/home_grades.dart @@ -1,5 +1,7 @@ +import 'dart:collection'; + +import 'package:firka/core/extensions.dart'; import 'package:kreta_api/kreta_api.dart'; -import 'package:firka/core/average_helper.dart'; import 'package:firka/ui/components/firka_card.dart'; import 'package:firka/ui/components/grade_helpers.dart'; import 'package:firka/ui/phone/widgets/grade_chart.dart'; @@ -27,12 +29,6 @@ class HomeGradesScreen extends StatefulWidget { State createState() => _HomeGradesScreen(); } -String activeSubjectUid = ""; -String subjectName = ""; -String subjectId = ""; -String subjectCategory = ""; -List subjectInfo = []; - class _HomeGradesScreen extends FirkaState { ApiResponse>? grades; ApiResponse>? week; @@ -105,165 +101,32 @@ class _HomeGradesScreen extends FirkaState { ), ); } else { - var subjectAvg = 0.00; - var subjectCount = 0; - var subjectAvgRounded = 0.00; final allGrades = grades!.response!; - final bySubject = >{}; - for (final g in allGrades) { - bySubject.putIfAbsent(g.subject.uid, () => []).add(g); - } - final gradesForCalculation = []; - for (final subjectGrades in bySubject.values) { - final feleviOrEvvegi = subjectGrades.where((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; - return typeName == 'felevi_jegy_ertekeles' || - typeName == 'evvegi_jegy_ertekeles'; - }).toList(); - final hasOtherType = subjectGrades.any((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; - return typeName != 'felevi_jegy_ertekeles' && - typeName != 'evvegi_jegy_ertekeles'; - }); - if (!hasOtherType && feleviOrEvvegi.isNotEmpty) { - final withValue = feleviOrEvvegi - .where((g) => g.numericValue != null && g.numericValue! > 0) - .toList(); - if (withValue.isNotEmpty) { - withValue.sort((a, b) => a.recordDate.compareTo(b.recordDate)); - gradesForCalculation.add(withValue.last); - } - } else { - gradesForCalculation.addAll( - subjectGrades.where((g) => !shouldIgnoreInAverage(g)), - ); - } - } + final allLessons = lessons!.response!; - final summaryAvg2 = calculateAverage( - gradesForCalculation, - applyIgnoreFilter: false, + final subjectAverage = allGrades.getSubjectAverage(); + + final Set subjects = HashSet( + hashCode: (s) => s.uid.hashCode, + equals: (s, s2) => s.uid == s2.uid, ); - final List subjects = List.empty(growable: true); final List gradeCards = []; - for (var grade in allGrades) { - if (subjects.where((s) => s.uid == grade.subject.uid).isEmpty) { - subjects.add(grade.subject); - } + allGrades.map((g) => g.subject).forEach(subjects.add); + allLessons.map((l) => l.subject).forEach(subjects.add); + + for (var subject + in subjects.toList()..sort((s1, s2) => s1.name.compareTo(s2.name))) { + gradeCards.add( + GestureDetector( + child: GradeSmallCard(allGrades, subject), + onTap: () { + context.go('/grades/subject', extra: subject); + }, + ), + ); } - if (lessons != null && lessons!.response != null) { - for (var lesson in lessons!.response!) { - if (subjects.where((s) => s.uid == lesson.uid).isEmpty) { - subjects.add(lesson.subject); - } - } - } - - subjects.sort((s1, s2) => s1.name.compareTo(s2.name)); - - for (var subject in subjects) { - final subjectGrades = allGrades - .where((g) => g.subject.uid == subject.uid) - .toList(); - - double avg = double.nan; - if (subjectGrades.isNotEmpty) { - final feleviOrEvvegi = subjectGrades.where((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; - return typeName == 'felevi_jegy_ertekeles' || - typeName == 'evvegi_jegy_ertekeles'; - }).toList(); - final hasOtherType = subjectGrades.any((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; - return typeName != 'felevi_jegy_ertekeles' && - typeName != 'evvegi_jegy_ertekeles'; - }); - if (!hasOtherType && feleviOrEvvegi.isNotEmpty) { - final withValue = feleviOrEvvegi - .where((g) => g.numericValue != null && g.numericValue! > 0) - .toList(); - if (withValue.isNotEmpty) { - withValue.sort((a, b) => a.recordDate.compareTo(b.recordDate)); - avg = withValue.last.numericValue!.toDouble(); - } - } else { - avg = subjectGrades.getAverageBySubject(subject); - } - } - - if (avg.isNaN) { - gradeCards.add( - GestureDetector( - child: GradeSmallCard(allGrades, subject), - onTap: () { - activeSubjectUid = subject.uid; - subjectName = subject.name; - subjectId = subject.uid; - subjectCategory = subject.category.name!; - subjectInfo = subjects - .where((s) => s.uid == subject.uid) - .toList(); - context.go('/grades/subject/${subject.uid}'); - }, - ), - ); - } else { - gradeCards.add( - GestureDetector( - child: GradeSmallCard(allGrades, subject), - onTap: () { - activeSubjectUid = subject.uid; - subjectName = subject.name; - subjectId = subject.uid; - subjectCategory = subject.category.name!; - subjectInfo = subjects - .where((s) => s.uid == subject.uid) - .toList(); - context.go('/grades/subject/${subject.uid}'); - }, - ), - ); - } - - if (!avg.isNaN && avg > 0) { - subjectCount++; - subjectAvg += avg; - final rounding = widget.data.settings - .group("settings") - .subGroup("application") - .subGroup("rounding"); - subjectAvgRounded += roundGrade( - avg, - t1: rounding.dbl("1"), - t2: rounding.dbl("2"), - t3: rounding.dbl("3"), - t4: rounding.dbl("4"), - ); - } - } - - subjectAvg /= subjectCount; - subjectAvgRounded /= subjectCount; - - if (subjectCount == 0) { - subjectAvg = 0.00; - subjectAvgRounded = 0.00; - } - - final rounding = widget.data.settings - .group("settings") - .subGroup("application") - .subGroup("rounding"); - var subjectAvgColor = getGradeColor( - subjectAvg, - t1: rounding.dbl("1"), - t2: rounding.dbl("2"), - t3: rounding.dbl("3"), - t4: rounding.dbl("4"), - ); - return Padding( padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 12.0), child: Column( @@ -279,12 +142,9 @@ class _HomeGradesScreen extends FirkaState { ), ], ), - GradeChartWithInteraction(grades: gradesForCalculation), + GradeChartWithInteraction(grades: allGrades), SizedBox(height: 2), - GradeSummaryBar( - grades: gradesForCalculation, - l10n: widget.data.l10n, - ), + GradeSummaryBar(grades: allGrades, l10n: widget.data.l10n), SizedBox(height: 20), Expanded( child: ListView( @@ -319,84 +179,25 @@ class _HomeGradesScreen extends FirkaState { ), ], right: [ - Card( - shadowColor: Colors.transparent, - color: subjectAvgColor.withAlpha(38), - child: Padding( - padding: EdgeInsets.only( - left: 8, - right: 8, - top: 4, - bottom: 4, + if (subjectAverage != null) + Container( + width: 48, + height: 26, + decoration: ShapeDecoration( + color: getGradeColor(subjectAverage).withAlpha(38), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), - child: Text( - subjectAvg.toStringAsFixed(2), - style: appStyle.fonts.B_16SB.apply( - color: subjectAvgColor, + child: Center( + child: Text( + subjectAverage.toStringAsFixed(2), + style: appStyle.fonts.B_16R.apply( + color: getGradeColor(subjectAverage), + ), ), ), ), - ), - ], - ), - FirkaCard( - left: [ - Text( - widget.data.l10n.subject_avg_rounded, - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary, - ), - ), - ], - right: [ - Card( - shadowColor: Colors.transparent, - color: subjectAvgColor.withAlpha(38), - child: Padding( - padding: EdgeInsets.only( - left: 8, - right: 8, - top: 4, - bottom: 4, - ), - child: Text( - subjectAvgRounded.toStringAsFixed(2), - style: appStyle.fonts.B_16SB.apply( - color: subjectAvgColor, - ), - ), - ), - ), - ], - ), - FirkaCard( - left: [ - Text( - widget.data.l10n.overall_avg, - style: appStyle.fonts.B_16SB.apply( - color: appStyle.colors.textPrimary, - ), - ), - ], - right: [ - Card( - shadowColor: Colors.transparent, - color: subjectAvgColor.withAlpha(38), - child: Padding( - padding: EdgeInsets.only( - left: 8, - right: 8, - top: 4, - bottom: 4, - ), - child: Text( - summaryAvg2.toStringAsFixed(2), - style: appStyle.fonts.B_16SB.apply( - color: subjectAvgColor, - ), - ), - ), - ), ], ), FirkaCard( diff --git a/firka/lib/ui/phone/pages/home/home_grades_subject.dart b/firka/lib/ui/phone/pages/home/home_grades_subject.dart index c9c1ccc..e82637c 100644 --- a/firka/lib/ui/phone/pages/home/home_grades_subject.dart +++ b/firka/lib/ui/phone/pages/home/home_grades_subject.dart @@ -23,8 +23,9 @@ import 'package:firka/ui/theme/style.dart'; class HomeGradesSubjectScreen extends StatefulWidget { final AppInitialization data; + final Subject subject; - const HomeGradesSubjectScreen(this.data, {super.key}); + const HomeGradesSubjectScreen(this.subject, this.data, {super.key}); @override State createState() => _HomeGradesSubjectScreen(); @@ -36,9 +37,9 @@ class _HomeGradesSubjectScreen extends FirkaState { void _onRefreshRequested(BuildContext context) async { final cubit = context.read(); - grades = (await widget.data.client.getGrades(forceCache: false)).response! - .where((grade) => grade.subject.uid == activeSubjectUid) - .where((grade) => grade.type.name != "felevi_jegy_ertekeles"); + grades = (await widget.data.client.getGrades( + forceCache: false, + )).response!.where((grade) => grade.subject.uid == widget.subject.uid); if (mounted) { setState(() {}); @@ -51,9 +52,9 @@ class _HomeGradesSubjectScreen extends FirkaState { super.initState(); (() async { - grades = (await widget.data.client.getGrades()).response! - .where((grade) => grade.subject.uid == activeSubjectUid) - .where((grade) => grade.type.name != "felevi_jegy_ertekeles"); + grades = (await widget.data.client.getGrades()).response!.where( + (grade) => grade.subject.uid == widget.subject.uid, + ); if (mounted) setState(() {}); })(); @@ -107,7 +108,7 @@ class _HomeGradesSubjectScreen extends FirkaState { } Widget _buildContent(BuildContext context) { - if (grades == null || grades!.isEmpty || activeSubjectUid == "") { + if (grades == null || grades!.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( @@ -152,17 +153,15 @@ class _HomeGradesSubjectScreen extends FirkaState { ), child: Padding( padding: EdgeInsetsGeometry.all(6), - child: ClassIconWidget( - uid: subjectId, - className: subjectName, - category: subjectCategory, + child: ClassIconWidget.subject( + subject: widget.subject, color: appStyle.colors.accent, ), ), ), SizedBox(height: 8), Text( - subjectName, + widget.subject.name, style: appStyle.fonts.H_H2.apply( color: appStyle.colors.textPrimary, ), @@ -289,10 +288,8 @@ class _HomeGradesSubjectScreen extends FirkaState { FilledCircle( diameter: 36, color: appStyle.colors.a15p, - child: ClassIconWidget( - uid: aGrade.subject.uid, - className: aGrade.subject.name, - category: aGrade.subject.category.name!, + child: ClassIconWidget.subject( + subject: aGrade.subject, color: appStyle.colors.accent, size: 24, ), diff --git a/firka/lib/ui/phone/widgets/grade_chart.dart b/firka/lib/ui/phone/widgets/grade_chart.dart index 7d3b80c..3b1d089 100644 --- a/firka/lib/ui/phone/widgets/grade_chart.dart +++ b/firka/lib/ui/phone/widgets/grade_chart.dart @@ -5,8 +5,6 @@ import 'package:firka_common/firka_common.dart'; import 'package:intl/intl.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:firka/routing/chart_interaction_scope.dart'; -import 'package:firka/ui/components/grade_helpers.dart'; -import 'package:firka/ui/theme/style.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; @@ -31,46 +29,6 @@ class _GradeChartState extends State { late List spots; - double? _subjectAverageInList(List grades, String subjectUid) { - double weightedSum = 0; - double totalWeight = 0; - for (final g in grades) { - if (g.subject.uid != subjectUid) continue; - final name = g.valueType.name?.toLowerCase() ?? ''; - final isPercentage = - name.contains('szazalek') || name.contains('percent'); - if (isPercentage) continue; - final v = g.numericValue; - final w = g.weightPercentage; - if (v != null && w != null) { - final effectiveValue = g.valueType.name == "Szazalekos" - ? percentageToGrade(v).toDouble() - : v.toDouble(); - weightedSum += effectiveValue * w; - totalWeight += w; - } - } - return totalWeight > 0 ? weightedSum / totalWeight : null; - } - - double _runningSubjectAverage(List sortedGrades, int upToInclusive) { - final sublist = sortedGrades.sublist( - 0, - (upToInclusive + 1).clamp(0, sortedGrades.length), - ); - final subjectUids = sublist.map((g) => g.subject.uid).toSet(); - double sum = 0; - int count = 0; - for (final uid in subjectUids) { - final avg = _subjectAverageInList(sublist, uid); - if (avg != null) { - sum += avg; - count++; - } - } - return count > 0 ? sum / count : 0; - } - @override void initState() { super.initState(); @@ -79,12 +37,7 @@ class _GradeChartState extends State { void _computeSpots() { final sortedGrades = - widget.grades - .where( - (grade) => - grade.numericValue != null && grade.weightPercentage != null, - ) - .toList() + widget.grades.where((grade) => grade.shouldIncludeInAverage()).toList() ..sort((a, b) => a.recordDate.compareTo(b.recordDate)); if (sortedGrades.isEmpty) { @@ -98,8 +51,10 @@ class _GradeChartState extends State { spots = []; for (var i = 0; i < sortedGrades.length; i++) { - final partialAvg = _runningSubjectAverage(sortedGrades, i); - spots.add(DateSpot(i.toDouble(), partialAvg, sortedGrades[i].recordDate)); + final partialAvg = sortedGrades.take(i + 1).getSubjectAverage(); + spots.add( + DateSpot(i.toDouble(), partialAvg!, sortedGrades[i].recordDate), + ); } } diff --git a/firka/lib/ui/phone/widgets/grade_summary_bar.dart b/firka/lib/ui/phone/widgets/grade_summary_bar.dart index fb8bb76..2894bb6 100644 --- a/firka/lib/ui/phone/widgets/grade_summary_bar.dart +++ b/firka/lib/ui/phone/widgets/grade_summary_bar.dart @@ -1,4 +1,3 @@ -import 'package:firka/core/average_helper.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:firka/ui/components/grade.dart'; import 'package:firka/ui/components/grade_helpers.dart'; @@ -30,17 +29,9 @@ class _GradeSummaryBarState extends State { @override Widget build(BuildContext context) { - final (total, countsByGrade) = getGradeDistribution(widget.grades); - final gradeColors = [ - appStyle.colors.grade1, - appStyle.colors.grade2, - appStyle.colors.grade3, - appStyle.colors.grade4, - appStyle.colors.grade5, - ]; - final totalCounted = countsByGrade.reduce((a, b) => a + b); + final (total, countsByGrade) = widget.grades.getGradeDistribution(); final averageText = widget.showAverage - ? calculateAverage(widget.grades).toStringAsFixed(2) + ? (widget.grades.getAverage() ?? 0).toStringAsFixed(2) : ''; return Card( @@ -72,10 +63,13 @@ class _GradeSummaryBarState extends State { borderRadius: BorderRadius.circular(4), child: Row( children: List.generate(5, (i) { - final flex = totalCounted > 0 ? countsByGrade[i] : 1; + final flex = total > 0 ? countsByGrade[i] : 1; return Expanded( flex: flex, - child: Container(height: 10, color: gradeColors[i]), + child: Container( + height: 10, + color: getGradeColor(i + 1), + ), ); }), ), diff --git a/firka/lib/ui/phone/widgets/info_card.dart b/firka/lib/ui/phone/widgets/info_card.dart index e0fb940..0a4059d 100644 --- a/firka/lib/ui/phone/widgets/info_card.dart +++ b/firka/lib/ui/phone/widgets/info_card.dart @@ -38,13 +38,7 @@ class InfoCard extends StatelessWidget { return FilledCircle( diameter: 32, color: color.withAlpha(38), - child: ClassIconWidget( - uid: subject.uid, - className: subject.name, - category: subject.category.name!, - color: color, - size: 20, - ), + child: ClassIconWidget.subject(subject: subject, color: color, size: 20), ); } diff --git a/firka_common/lib/ui/components/grade.dart b/firka_common/lib/ui/components/grade.dart index 3188e25..88310de 100644 --- a/firka_common/lib/ui/components/grade.dart +++ b/firka_common/lib/ui/components/grade.dart @@ -44,7 +44,7 @@ class GradeWidget extends StatelessWidget { } if (g.valueType.name == 'Szazalekos') { - final gradeColor = getGradeColor(percentageToGrade(g.numericValue!)); + final gradeColor = appStyle.colors.accent; return FilledCircle( diameter: size, color: gradeColor.withAlpha(38), diff --git a/firka_common/lib/ui/components/grade_helpers.dart b/firka_common/lib/ui/components/grade_helpers.dart index ccbc295..d0cdddf 100644 --- a/firka_common/lib/ui/components/grade_helpers.dart +++ b/firka_common/lib/ui/components/grade_helpers.dart @@ -1,7 +1,8 @@ +import 'package:kreta_api/kreta_api.dart'; + import 'dart:ui'; import 'package:firka_common/ui/theme/style.dart'; -import 'package:kreta_api/kreta_api.dart'; int roundGrade( num grade, { @@ -26,23 +27,6 @@ int roundGrade( return 5; } -int percentageToGrade(int grade) { - if (grade < 50) { - return 1; - } - if (grade < 60) { - return 2; - } - if (grade < 70) { - return 3; - } - if (grade < 80) { - return 4; - } - - return 5; -} - Color getGradeColor( num grade, { double t1 = 1, @@ -64,77 +48,80 @@ Color getGradeColor( } } -(int total, List countsByGrade) getGradeDistribution(List grades) { - final filtered = grades.where((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; +extension GradeListExtension on Iterable { + (int total, List countsByGrade) getGradeDistribution() { + final filtered = where((g) => g.shouldIncludeInAverage()); + final counts = [0, 0, 0, 0, 0]; + for (final g in filtered) { + counts[g.numericValue! - 1]++; + } + return (filtered.length, counts); + } + + double? getAverageBySubject(Subject subject) { + return where( + (g) => g.subject.uid == subject.uid && g.shouldIncludeInAverage(), + ).getAverage(); + } + + double? getSubjectAverage() { + final averages = map( + (g) => g.subject, + ).toSet().map((subject) => getAverageBySubject(subject)).nonNulls; + + if (averages.isEmpty) { + return null; + } + + return averages.reduce((sum, avg) => sum + avg) / averages.length; + } + + double? getAverage() { + if (isEmpty) { + return null; + } + + double weightTotal = 0; + double sum = 0; + for (Grade grade in this) { + if (!grade.shouldIncludeInAverage()) { + continue; + } + + double weight = (grade.weightPercentage ?? 100) / 100.0; + weightTotal += weight; + sum += grade.numericValue! * weight; + } + + if (sum == 0) { + return null; + } + + return sum / weightTotal; + } +} + +extension GradeExtension on Grade { + bool isInPercentage() { + final name = valueType.name.toLowerCase(); + return name.contains('szazalek') || name.contains('percent'); + } + + bool shouldIncludeInAverage() { + if (isInPercentage()) { + return false; + } + + final typeName = type.name.toLowerCase(); if (typeName == 'felevi_jegy_ertekeles' || typeName == 'evvegi_jegy_ertekeles') { return false; } - final valueTypeName = g.valueType.name?.toLowerCase() ?? ''; - final isPercentage = - valueTypeName.contains('szazalek') || valueTypeName.contains('percent'); - if (isPercentage) { + if (numericValue == null) { return false; } return true; - }).toList(); - final counts = [0, 0, 0, 0, 0]; - for (final g in filtered) { - if (g.numericValue == null) continue; - final value = g.valueType.name == "Szazalekos" - ? percentageToGrade(g.numericValue!.round()) - : g.numericValue!.round().clamp(1, 5); - counts[value - 1]++; - } - return (filtered.length, counts); -} - -extension GradeListExtension on List { - double getAverageBySubject(Subject subject) { - final subjectGrades = where((g) => g.subject.uid == subject.uid).toList(); - if (subjectGrades.isEmpty) return double.nan; - - final feleviOrEvvegi = subjectGrades.where((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; - return typeName == 'felevi_jegy_ertekeles' || - typeName == 'evvegi_jegy_ertekeles'; - }).toList(); - final hasOtherType = subjectGrades.any((g) { - final typeName = g.type.name?.toLowerCase() ?? ''; - return typeName != 'felevi_jegy_ertekeles' && - typeName != 'evvegi_jegy_ertekeles'; - }); - - if (!hasOtherType && feleviOrEvvegi.isNotEmpty) { - final withValue = feleviOrEvvegi - .where((g) => g.numericValue != null && g.numericValue! > 0) - .toList(); - if (withValue.isEmpty) return double.nan; - withValue.sort((a, b) => a.recordDate.compareTo(b.recordDate)); - return withValue.last.numericValue!.toDouble(); - } - - var weightTotal = 0.00; - var sum = 0.00; - for (var grade in subjectGrades) { - final valueTypeName = grade.valueType.name?.toLowerCase() ?? ''; - final isPercentage = - valueTypeName.contains('szazalek') || - valueTypeName.contains('percent'); - final typeName = grade.type.name?.toLowerCase() ?? ''; - final isHalfYear = typeName == 'felevi_jegy_ertekeles'; - final isEndYear = typeName == 'evvegi_jegy_ertekeles'; - if (isPercentage || isHalfYear || isEndYear) continue; - if (grade.numericValue != null) { - var weight = (grade.weightPercentage ?? 100) / 100.0; - weightTotal += weight; - sum += grade.numericValue! * weight; - } - } - if (weightTotal == 0) return double.nan; - return sum / weightTotal; } } diff --git a/firka_common/lib/ui/shared/class_icon.dart b/firka_common/lib/ui/shared/class_icon.dart index 1ee71aa..3d5a159 100644 --- a/firka_common/lib/ui/shared/class_icon.dart +++ b/firka_common/lib/ui/shared/class_icon.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:firka_common/core/icon_helper.dart'; import 'package:firka_common/ui/shared/firka_icon.dart'; +import 'package:kreta_api/kreta_api.dart'; + class ClassIconWidget extends StatelessWidget { final String _uid; final String _className; @@ -10,6 +12,15 @@ class ClassIconWidget extends StatelessWidget { final Color color; final double? size; + ClassIconWidget.subject({ + super.key, + required Subject subject, + this.color = Colors.white, + this.size, + }) : _className = subject.name, + _uid = subject.uid, + _category = subject.category.name; + const ClassIconWidget({ super.key, required String uid, diff --git a/firka_common/lib/ui/shared/grade_small_card.dart b/firka_common/lib/ui/shared/grade_small_card.dart index b2a2ec7..dac5e6d 100644 --- a/firka_common/lib/ui/shared/grade_small_card.dart +++ b/firka_common/lib/ui/shared/grade_small_card.dart @@ -6,13 +6,12 @@ import 'package:firka_common/ui/components/grade_helpers.dart'; import 'package:firka_common/ui/shared/class_icon.dart'; import 'package:firka_common/ui/theme/style.dart'; -import '../../firka_common.dart'; - class GradeSmallCard extends StatelessWidget { - final List grades; + final double? average; final Subject subject; - GradeSmallCard(this.grades, this.subject, {super.key}); + GradeSmallCard(List grades, this.subject, {super.key}) + : average = grades.getAverageBySubject(subject); @override Widget build(BuildContext context) { @@ -39,26 +38,22 @@ class GradeSmallCard extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - grades.getAverageBySubject(subject).isNaN + average == null ? const SizedBox() : Container( width: 48, height: 26, decoration: ShapeDecoration( - color: getGradeColor( - grades.getAverageBySubject(subject), - ).withAlpha(38), + color: getGradeColor(average!).withAlpha(38), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Center( child: Text( - grades.getAverageBySubject(subject).toStringAsFixed(2), + average!.toStringAsFixed(2), style: appStyle.fonts.B_16R.apply( - color: getGradeColor( - grades.getAverageBySubject(subject), - ), + color: getGradeColor(average!), ), ), ),