Skip to content

Commit 13fb4bd

Browse files
Indicate evaluations that are ignored in the calculation of the grade (#1297)
* Indicate which evaluations are ignored in the calculation * Move ignored evaluation to expansion tile * Revert formatting * [BOT] Applying version. * [BOT] Applying format. * Changed title * Guard with mounted check * Move evaluation sorting to viewmodel * [BOT] Applying format. * Make member private --------- Co-authored-by: clubapplets-server <1958869+clubapplets-server@users.noreply.github.com>
1 parent 0919109 commit 13fb4bd

File tree

5 files changed

+115
-4
lines changed

5 files changed

+115
-4
lines changed

lib/l10n/intl_en.arb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,5 +455,15 @@
455455
"type": "String"
456456
}
457457
}
458-
}
458+
},
459+
460+
"ignored_evaluations_section_title": "{count,plural, =1{1 ignored grade} other{{count} ignored grades}}",
461+
"@ignored_evaluations_section_title": {
462+
"placeholders": {
463+
"count": {
464+
"type": "int"
465+
}
466+
}
467+
},
468+
"ignored_evaluations_section_description_text": "The following elements are ignored in the final grade calculation."
459469
}

lib/l10n/intl_fr.arb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,5 +454,15 @@
454454
"type": "String"
455455
}
456456
}
457-
}
457+
},
458+
459+
"ignored_evaluations_section_title": "{count,plural, =1{1 note ignorée} other{{count} notes ignorées}}",
460+
"@ignored_evaluations_section_title": {
461+
"placeholders": {
462+
"count": {
463+
"type": "int"
464+
}
465+
}
466+
},
467+
"ignored_evaluations_section_description_text": "Les éléments suivants sont ignorés dans le calcul de la note finale."
458468
}

lib/ui/student/grades/grade_details/view_model/grades_details_viewmodel.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:stacked/stacked.dart';
55
// Project imports:
66
import 'package:notredame/data/repositories/course_repository.dart';
77
import 'package:notredame/data/services/signets-api/models/course.dart';
8+
import 'package:notredame/data/services/signets-api/models/course_evaluation.dart';
89
import 'package:notredame/data/services/signets-api/models/signets_errors.dart';
910
import 'package:notredame/l10n/app_localizations.dart';
1011
import 'package:notredame/locator.dart';
@@ -22,6 +23,10 @@ class GradesDetailsViewModel extends FutureViewModel<Course> {
2223

2324
GradesDetailsViewModel({required this.course, required AppIntl intl}) : _appIntl = intl;
2425

26+
List<CourseEvaluation> get _allEvaluations => course.summary?.evaluations ?? [];
27+
List<CourseEvaluation> get ignoredEvaluations => _allEvaluations.where((e) => e.ignore).toList();
28+
List<CourseEvaluation> get nonIgnoredEvaluations => _allEvaluations.where((e) => !e.ignore).toList();
29+
2530
@override
2631
Future<Course> futureToRun() async {
2732
try {

lib/ui/student/grades/grade_details/widgets/grade_details_view.dart

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Dart imports:
2+
import 'dart:math';
3+
14
// Flutter imports:
25
import 'package:flutter/material.dart';
36

@@ -28,7 +31,11 @@ class GradesDetailsView extends StatefulWidget {
2831

2932
class _GradesDetailsViewState extends State<GradesDetailsView> with TickerProviderStateMixin {
3033
late AnimationController _controller;
34+
late AnimationController _ignoredEvaluationsAnimationController;
3135
bool _completed = false;
36+
final GlobalKey _expansionTileKey = GlobalKey();
37+
late Animation<double> _rotateAnimation;
38+
bool _isIgnoredEvaluationsExpanded = false;
3239

3340
@override
3441
void initState() {
@@ -42,6 +49,25 @@ class _GradesDetailsViewState extends State<GradesDetailsView> with TickerProvid
4249
});
4350
}
4451
});
52+
53+
_ignoredEvaluationsAnimationController = AnimationController(
54+
vsync: this,
55+
duration: const Duration(milliseconds: 200),
56+
value: 1.0,
57+
);
58+
_ignoredEvaluationsAnimationController.forward();
59+
60+
_rotateAnimation = Tween(
61+
begin: pi,
62+
end: 0.0,
63+
).animate(CurvedAnimation(parent: _ignoredEvaluationsAnimationController, curve: Curves.easeIn));
64+
}
65+
66+
@override
67+
void dispose() {
68+
_controller.dispose();
69+
_ignoredEvaluationsAnimationController.dispose();
70+
super.dispose();
4571
}
4672

4773
@override
@@ -229,12 +255,15 @@ class _GradesDetailsViewState extends State<GradesDetailsView> with TickerProvid
229255
const SizedBox(height: 8.0),
230256
Column(
231257
children: <Widget>[
232-
for (final CourseEvaluation evaluation in model.course.summary?.evaluations ?? [])
258+
for (final CourseEvaluation evaluation in model.nonIgnoredEvaluations)
233259
GradeEvaluationTile(
234260
evaluation,
235261
completed: _completed,
236262
key: Key("GradeEvaluationTile_${evaluation.title}"),
237263
),
264+
if (model.ignoredEvaluations.isNotEmpty) ...[
265+
_buildIgnoredEvaluationsExpansionTile(model.ignoredEvaluations),
266+
],
238267
const SizedBox(height: 24),
239268
],
240269
),
@@ -314,4 +343,61 @@ class _GradesDetailsViewState extends State<GradesDetailsView> with TickerProvid
314343
),
315344
);
316345
}
346+
347+
Widget _buildIgnoredEvaluationsExpansionTile(List<CourseEvaluation> ignoredEvaluations) {
348+
return Theme(
349+
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
350+
child: ExpansionTile(
351+
key: _expansionTileKey,
352+
onExpansionChanged: (value) {
353+
_isIgnoredEvaluationsExpanded = !_isIgnoredEvaluationsExpanded;
354+
if (value) {
355+
_ignoredEvaluationsAnimationController.reverse(from: pi);
356+
_scrollToSelectedContent(expansionTileKey: _expansionTileKey);
357+
} else {
358+
_ignoredEvaluationsAnimationController.forward(from: 0.0);
359+
}
360+
},
361+
trailing: AnimatedBuilder(
362+
animation: _rotateAnimation,
363+
builder: (BuildContext context, Widget? child) {
364+
return Transform.rotate(
365+
angle: _rotateAnimation.value,
366+
child: const Icon(Icons.keyboard_arrow_down_sharp, color: AppPalette.etsLightRed),
367+
);
368+
},
369+
child: const Icon(Icons.keyboard_arrow_down_sharp, color: AppPalette.etsLightRed),
370+
),
371+
tilePadding: EdgeInsets.only(left: 8.0, right: 28),
372+
title: Row(
373+
children: [
374+
Text(
375+
AppIntl.of(context)!.ignored_evaluations_section_title(ignoredEvaluations.length),
376+
style: Theme.of(context).textTheme.titleMedium,
377+
),
378+
],
379+
),
380+
children: [
381+
Padding(
382+
padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 8.0),
383+
child: Text(AppIntl.of(context)!.ignored_evaluations_section_description_text),
384+
),
385+
for (final CourseEvaluation evaluation in ignoredEvaluations)
386+
GradeEvaluationTile(evaluation, completed: _completed, key: Key("GradeEvaluationTile_${evaluation.title}")),
387+
const SizedBox(height: 24),
388+
],
389+
),
390+
);
391+
}
392+
393+
void _scrollToSelectedContent({required GlobalKey expansionTileKey}) {
394+
final keyContext = expansionTileKey.currentContext;
395+
if (keyContext != null) {
396+
Future.delayed(Duration(milliseconds: 200)).then((value) {
397+
if (keyContext.mounted) {
398+
Scrollable.ensureVisible(keyContext, duration: Duration(milliseconds: 200));
399+
}
400+
});
401+
}
402+
}
317403
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: The 4th generation of ÉTSMobile, the main gateway between the Éco
55
# pub.dev using `pub publish`. This is preferred for private packages.
66
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
77

8-
version: 4.60.10
8+
version: 4.61.10
99

1010
environment:
1111
sdk: '>=3.9.0 <4.0.0'

0 commit comments

Comments
 (0)