diff --git a/packages/ap_common_flutter_ui/lib/src/scaffold/score_scaffold.dart b/packages/ap_common_flutter_ui/lib/src/scaffold/score_scaffold.dart index c135874b..41efbf07 100644 --- a/packages/ap_common_flutter_ui/lib/src/scaffold/score_scaffold.dart +++ b/packages/ap_common_flutter_ui/lib/src/scaffold/score_scaffold.dart @@ -25,9 +25,11 @@ class ScoreScaffold extends StatefulWidget { this.finalScoreBuilder, this.customHint, this.isShowSearchButton = false, + this.showPRCard, this.bottom, this.customStateHint, this.semesterPickerController, + this.semesterPickerUiConfig, }); /// Creates a [ScoreScaffold] from a [DataState]. @@ -53,8 +55,10 @@ class ScoreScaffold extends StatefulWidget { this.middleScoreBuilder, this.finalScoreBuilder, this.isShowSearchButton = false, + this.showPRCard = true, this.bottom, this.semesterPickerController, + this.semesterPickerUiConfig, }) : state = dataState.when( loading: () => ScoreState.loading, loaded: (_, __) => ScoreState.finish, @@ -87,6 +91,7 @@ class ScoreScaffold extends StatefulWidget { final Widget Function(int index)? finalScoreBuilder; final bool isShowSearchButton; + final bool? showPRCard; final String? customHint; @@ -95,6 +100,9 @@ class ScoreScaffold extends StatefulWidget { /// Optional controller for the semester picker. final SemesterPickerController? semesterPickerController; + /// Optional UI config for the semester picker. + final SemesterUIConfig? semesterPickerUiConfig; + @override ScoreScaffoldState createState() => ScoreScaffoldState(); } @@ -139,36 +147,33 @@ class ScoreScaffoldState extends State { return Scaffold( backgroundColor: colorScheme.surface, appBar: AppBar( + centerTitle: false, titleSpacing: 0, - title: Row( - children: [ - Flexible( - child: Text( - widget.title ?? context.ap.score, - overflow: TextOverflow.ellipsis, - ), - ), - if (widget.itemPicker != null) ...[ - const SizedBox(width: 12), - widget.itemPicker!, - ], - if (widget.semesterData != null && - widget.itemPicker == null) ...[ - const SizedBox(width: 12), - SemesterPicker( - semesterData: widget.semesterData!, - currentIndex: widget.semesterData!.currentIndex, - onSelect: (Semester semester, int index) { - widget.onSelect?.call(index); - }, - featureTag: 'score', - controller: widget.semesterPickerController, - ), - ], - ], + title: Text( + widget.title ?? context.ap.score, + overflow: TextOverflow.ellipsis, ), bottom: widget.bottom as PreferredSizeWidget?, - actions: const [], + actions: [ + if (widget.itemPicker != null) ...[ + widget.itemPicker!, + const SizedBox(width: 12), + ], + if (widget.semesterData != null && + widget.itemPicker == null) ...[ + SemesterPicker( + semesterData: widget.semesterData!, + currentIndex: widget.semesterData!.currentIndex, + onSelect: (Semester semester, int index) { + widget.onSelect?.call(index); + }, + featureTag: 'score', + controller: widget.semesterPickerController, + uiConfig: widget.semesterPickerUiConfig, + ), + const SizedBox(width: 12), + ], + ], ), floatingActionButton: AnimatedScale( scale: _showFab ? 1.0 : 0.0, @@ -332,6 +337,7 @@ class ScoreScaffoldState extends State { finalScoreBuilder: widget.finalScoreBuilder, isAnalysisView: isLandscape || _isAnalysisView, scrollController: _scrollController, + showPRCard: widget.showPRCard, ); } } @@ -435,6 +441,7 @@ class ScoreContent extends StatefulWidget { this.finalScoreBuilder, required this.isAnalysisView, this.scrollController, + this.showPRCard, }); final ScoreData? scoreData; @@ -446,6 +453,7 @@ class ScoreContent extends StatefulWidget { final Widget Function(int index)? finalScoreBuilder; final bool isAnalysisView; final ScrollController? scrollController; + final bool? showPRCard; @override _ScoreContentState createState() => _ScoreContentState(); @@ -461,6 +469,7 @@ class _ScoreContentState extends State { scoreData: widget.scoreData!, onRefresh: widget.onRefresh, controller: widget.scrollController, + showPRCard: widget.showPRCard, ); } else { return _ScoreListTab( @@ -646,8 +655,7 @@ class _ScoreListTab extends StatelessWidget { Color scoreColor, ) { final String raw = _effectiveScoreStr(score) ?? '-'; - final bool isNumeric = - scoreData.scoreType == ScoreType.numeric; + final bool isNumeric = scoreData.scoreType == ScoreType.numeric; if (scoreValue == null) { // Cannot parse at all — show raw string @@ -748,11 +756,13 @@ class _ScoreAnalysisTab extends StatelessWidget { required this.scoreData, this.onRefresh, this.controller, + this.showPRCard, }); final ScoreData scoreData; final VoidCallback? onRefresh; final ScrollController? controller; + final bool? showPRCard; @override Widget build(BuildContext context) { @@ -769,8 +779,10 @@ class _ScoreAnalysisTab extends StatelessWidget { children: [ _buildMainSummaryCard(colorScheme, context.ap, analysis), const SizedBox(height: 16), - ScorePRCard(analysis: analysis), - const SizedBox(height: 16), + if (showPRCard != false) ...[ + ScorePRCard(analysis: analysis), + const SizedBox(height: 16), + ], ScoreGPACard(analysis: analysis), const SizedBox(height: 16), ScoreStatisticsCard(analysis: analysis), @@ -956,14 +968,12 @@ class ScoreAnalysis { late List _gradePoints; late int _totalSubjects; - bool get isGradePoint => - scoreData.scoreType == ScoreType.gradePoint; + bool get isGradePoint => scoreData.scoreType == ScoreType.gradePoint; /// Whether a numeric score value is considered passing. bool isPassing(double scoreValue) { if (isGradePoint) { - return scoreToGradePoint(scoreValue) >= - scoreData.passingGradePoint; + return scoreToGradePoint(scoreValue) >= scoreData.passingGradePoint; } return scoreValue >= scoreData.passingScore; } @@ -1087,9 +1097,7 @@ class ScoreAnalysis { _ScoreListTab._effectiveScoreStr(score), ); final double? unit = double.tryParse(score.units); - if (scoreValue != null && - isPassing(scoreValue) && - unit != null) { + if (scoreValue != null && isPassing(scoreValue) && unit != null) { credits += unit; } } @@ -1101,12 +1109,10 @@ class ScoreAnalysis { /// Returns the effective score string for a [Score], preferring /// [Score.semesterScore] and falling back to [Score.finalScore]. static String? effectiveScoreStr(Score score) { - if (score.semesterScore != null && - score.semesterScore!.isNotEmpty) { + if (score.semesterScore != null && score.semesterScore!.isNotEmpty) { return score.semesterScore; } - if (score.finalScore != null && - score.finalScore!.isNotEmpty) { + if (score.finalScore != null && score.finalScore!.isNotEmpty) { return score.finalScore; } return null; diff --git a/packages/ap_common_flutter_ui/lib/src/widgets/score_analysis_widgets.dart b/packages/ap_common_flutter_ui/lib/src/widgets/score_analysis_widgets.dart index 8379c9b4..dd237d45 100644 --- a/packages/ap_common_flutter_ui/lib/src/widgets/score_analysis_widgets.dart +++ b/packages/ap_common_flutter_ui/lib/src/widgets/score_analysis_widgets.dart @@ -776,16 +776,20 @@ class ScoreGPACard extends StatelessWidget { for (final Score score in analysis.scoreData.scores) { final String? raw = ScoreAnalysis.effectiveScoreStr(score); final double? value = ScoreAnalysis.parseScore(raw); - if (value == null) continue; - entries.add(_GradeEntry( - title: score.title, - score: value, - grade: isGradePoint - ? (raw ?? '') - : ScoreAnalysis.scoreToGradeLetter(value), - gradePoint: ScoreAnalysis.scoreToGradePoint(value), - credits: double.tryParse(score.units) ?? 0, - ),); + final bool isInvalid = value == null; + entries.add( + _GradeEntry( + title: score.title, + score: value ?? 0, + grade: isInvalid + ? '-' + : (isGradePoint + ? (raw ?? '') + : ScoreAnalysis.scoreToGradeLetter(value)), + gradePoint: isInvalid ? 0.0 : ScoreAnalysis.scoreToGradePoint(value), + credits: double.tryParse(score.units) ?? 0, + ), + ); } if (entries.isEmpty) return const SizedBox.shrink(); @@ -825,7 +829,7 @@ class ScoreGPACard extends StatelessWidget { ), ), SizedBox( - width: 40, + width: 50, child: Text( context.ap.gradePoint, style: TextStyle( @@ -871,9 +875,9 @@ class ScoreGPACard extends StatelessWidget { ), ), SizedBox( - width: 40, + width: 50, child: Text( - e.gradePoint.toStringAsFixed(1), + e.grade != '-' ? e.gradePoint.toStringAsFixed(1) : '-', style: TextStyle( fontSize: 13, color: colorScheme.onSurface,