feat: show class grades

This commit is contained in:
checkedear
2026-04-27 15:49:31 +02:00
parent 780aaee1dd
commit ddba6ad888
8 changed files with 174 additions and 26 deletions

View File

@@ -495,6 +495,51 @@ class KretaClient {
return (resp, statusCode, null, false);
}
ApiResponse<List<ClassGroupSubjectAverage>>? classGroupAveragesCache;
Future<ApiResponse<List<ClassGroupSubjectAverage>>> getClassGroupAverages(
ClassGroup classGroup, {
bool forceCache = true,
}) async {
String? err;
if (classGroup.studyTask == null) {
err = "classGroup.studyTask is null";
logger.warning(err);
return ApiResponse([], 0, err, false);
}
if (!forceCache) {
classGroupAveragesCache = null;
} else if (classGroupAveragesCache != null) {
return classGroupAveragesCache!;
}
var studyTaskUid = classGroup.studyTask!.uid.toString().split(",").first;
var (resp, status, ex, cached) = await _cachingGet(
CacheId.getClassGroupAvg,
KretaEndpoints.getClassGroupAvg(model.iss!, studyTaskUid),
forceCache,
0,
);
var items = List<ClassGroupSubjectAverage>.empty(growable: true);
try {
List<dynamic> rawItems = resp;
for (var item in rawItems) {
items.add(ClassGroupSubjectAverage.fromJson(item));
}
} catch (ex) {
err = ex.toString();
}
if (ex != null) {
err = ex.toString();
}
if (ex == null) {
classGroupAveragesCache = ApiResponse(items, 200, null, true);
}
return ApiResponse(items, status, err, cached);
}
ApiResponse<Student>? studentCache;
Future<ApiResponse<Student>> getStudent({bool forceCache = true}) async {

View File

@@ -84,6 +84,9 @@ class KretaEndpoints {
static String getClassGroups(String iss) =>
ka.KretaEndpoints.getClassGroups(iss);
static String getClassGroupAvg(String iss, String studyGroupId) =>
ka.KretaEndpoints.getClassGroupAvg(iss, studyGroupId);
static String getNoticeBoard(String iss) =>
ka.KretaEndpoints.getNoticeBoard(iss);

View File

@@ -13,6 +13,7 @@ enum CacheId {
getSubjectAvg,
getLessons,
getHomework,
getClassGroupAvg,
}
@collection

View File

@@ -344,7 +344,7 @@ Future<void> showGradeBottomSheet(
style: appStyle.fonts.B_14R.apply(color: appStyle.colors.textSecondary),
),
SizedBox(height: 20),
GradeSmallCard([], grade.subject),
GradeSmallCard([], null, grade.subject),
SizedBox(height: 10),
FirkaCard(
margin: EdgeInsets.all(0),
@@ -384,7 +384,6 @@ Future<void> showGradeBottomSheet(
color: appStyle.colors.buttonSecondaryFill,
),
onTap: () {
Navigator.pop(context);
context.go('/grades/subject', extra: grade.subject);
},
),
@@ -407,7 +406,7 @@ Future<void> showHomeworkBottomSheet(
style: appStyle.fonts.B_14R.apply(color: appStyle.colors.textSecondary),
),
SizedBox(height: 20),
GradeSmallCard([], homework.subject),
GradeSmallCard([], null, homework.subject),
SizedBox(height: 20),
Flexible(
fit: FlexFit.loose,

View File

@@ -34,6 +34,7 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
ApiResponse<List<Lesson>>? week;
ApiResponse<List<ClassGroup>>? classGroups;
ApiResponse<List<SubjectAverage>>? lessons;
ApiResponse<List<ClassGroupSubjectAverage>>? classAvgs;
void _onRefreshRequested(BuildContext context) async {
final cubit = context.read<HomeRefreshCubit>();
@@ -50,6 +51,10 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
group,
forceCache: false,
);
classAvgs = await widget.data.client.getClassGroupAverages(
group,
forceCache: false,
);
await Future.delayed(Duration(milliseconds: 100));
}
if (mounted) {
@@ -73,6 +78,7 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
if (classGroups?.response?.isNotEmpty ?? false) {
var group = classGroups!.response!.first;
lessons = await widget.data.client.getSubjectAverage(group);
classAvgs = await widget.data.client.getClassGroupAverages(group);
await Future.delayed(Duration(milliseconds: 100));
}
if (mounted) setState(() {});
@@ -92,7 +98,10 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
}
Widget _buildContent(BuildContext context) {
if (grades == null || week == null) {
if (grades == null ||
lessons == null ||
classAvgs == null ||
week == null) {
return SizedBox(
height: MediaQuery.of(context).size.height / 1.35,
child: Column(
@@ -105,6 +114,13 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
final allLessons = lessons!.response!;
final subjectAverage = allGrades.getSubjectAverage();
final classAverages = classAvgs!.response!
.map((c) => c.classGroupAverage)
.nonNulls;
double? classAverage = classAverages.isNotEmpty
? classAverages.reduce((f, s) => f + s)
: null;
final Set<Subject> subjects = HashSet(
hashCode: (s) => s.uid.hashCode,
@@ -119,7 +135,13 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
in subjects.toList()..sort((s1, s2) => s1.name.compareTo(s2.name))) {
gradeCards.add(
GestureDetector(
child: GradeSmallCard(allGrades, subject),
child: GradeSmallCard(
allGrades,
classAvgs!.response!
.firstWhereOrNull((s) => s.subject.uid == subject.uid)
?.classGroupAverage,
subject,
),
onTap: () {
context.go('/grades/subject', extra: subject);
},
@@ -209,6 +231,30 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
),
),
],
right: [
if (classAverage != null)
Container(
width: 48,
height: 26,
decoration: ShapeDecoration(
color: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(
color: getGradeColor(classAverage),
),
borderRadius: BorderRadius.circular(12),
),
),
child: Center(
child: Text(
classAverage.toStringAsFixed(2),
style: appStyle.fonts.B_16R.apply(
color: getGradeColor(classAverage),
),
),
),
),
],
),
FirkaCard(
left: [

View File

@@ -7,11 +7,16 @@ import 'package:firka_common/ui/shared/class_icon.dart';
import 'package:firka_common/ui/theme/style.dart';
class GradeSmallCard extends StatelessWidget {
final double? average;
final double? studentAverage;
final double? classAverage;
final Subject subject;
GradeSmallCard(List<Grade> grades, this.subject, {super.key})
: average = grades.getAverageBySubject(subject);
GradeSmallCard(
List<Grade> grades,
this.classAverage,
this.subject, {
super.key,
}) : studentAverage = grades.getAverageBySubject(subject);
@override
Widget build(BuildContext context) {
@@ -38,26 +43,45 @@ class GradeSmallCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
),
average == null
? const SizedBox()
: Container(
width: 48,
height: 26,
decoration: ShapeDecoration(
color: getGradeColor(average!).withAlpha(38),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Center(
child: Text(
average!.toStringAsFixed(2),
style: appStyle.fonts.B_16R.apply(
color: getGradeColor(average!),
),
),
if (classAverage != null)
Container(
width: 48,
height: 26,
decoration: ShapeDecoration(
color: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(color: getGradeColor(classAverage!)),
borderRadius: BorderRadius.circular(12),
),
),
child: Center(
child: Text(
classAverage!.toStringAsFixed(2),
style: appStyle.fonts.B_16R.apply(
color: getGradeColor(classAverage!),
),
),
),
),
if (studentAverage != null)
Container(
width: 48,
height: 26,
decoration: ShapeDecoration(
color: getGradeColor(studentAverage!).withAlpha(38),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Center(
child: Text(
studentAverage!.toStringAsFixed(2),
style: appStyle.fonts.B_16R.apply(
color: getGradeColor(studentAverage!),
),
),
),
),
],
),
);

View File

@@ -29,6 +29,9 @@ class KretaEndpoints {
static String getSubjectAvg(String iss, String studyGroupId) =>
"${kreta(iss)}/ellenorzo/v3/sajat/Ertekelesek/Atlagok/TantargyiAtlagok?oktatasiNevelesiFeladatUid=$studyGroupId&oktatasiNevelesiFeladatUid=$studyGroupId";
static String getClassGroupAvg(String iss, String studyGroupId) =>
"${kreta(iss)}/ellenorzo/v3/sajat/Ertekelesek/Atlagok/OsztalyAtlagok?oktatasiNevelesiFeladatUid=$studyGroupId&oktatasiNevelesiFeladatUid=$studyGroupId";
static String getTimeTable(String iss) =>
"${kreta(iss)}/ellenorzo/v3/sajat/OrarendElemek";

View File

@@ -84,3 +84,30 @@ class SubjectAverage extends UidObj {
return 'SubjectAverage(uid: "$uid", name: "${subject.name}", category: "${subject.category.name}", average: $average)';
}
}
class ClassGroupSubjectAverage extends UidObj {
final Subject subject;
final double? studentAverage;
final double? classGroupAverage;
ClassGroupSubjectAverage({
required super.uid,
required this.subject,
this.classGroupAverage,
this.studentAverage,
});
factory ClassGroupSubjectAverage.fromJson(Map<String, dynamic> json) {
return ClassGroupSubjectAverage(
uid: json['Uid'],
subject: Subject.fromJson(json['Tantargy']),
studentAverage: json.dbl('TanuloAtlag'),
classGroupAverage: json.dbl('OsztalyCsoportAtlag'),
);
}
@override
String toString() {
return 'ClassGroupSubjectAverage(uid: "$uid", subject: $subject)';
}
}