diff --git a/.gitignore b/.gitignore index 6647cc79..3a4a1dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,5 @@ app.*.map.json /android/app/release # fvm flutter sdk -.fvm/flutter_sdk +.fvm/ *.APK diff --git a/.vscode/settings.json b/.vscode/settings.json index 59e93a9c..c029eee2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "mesonbuild.configureOnOpen": false, "dart.flutterSdkPaths": [ - ".fvm/flutter_sdk", + ".fvm/flutter_sdk" ], "dart.analysisExcludedFolders": [ ".flutter" diff --git a/README.md b/README.md index b3f83074..5d1ddf18 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,12 @@ XDYou,代码名称为 Traintime PDA,是为西电学生设计的开源信息 [Get it on App Store](https://apps.apple.com/us/app/xdyou/id6461723688?l=zh-Hans-CN) + height="60px">](https://apps.apple.com/us/app/xdyou/id6461723688?l=zh-Hans-CN) [Get it on F-Droid](https://f-droid.org/zh_Hans/packages/io.github.benderblog.traintime_pda/) + height="60px">](https://f-droid.org/zh_Hans/packages/io.github.benderblog.traintime_pda/) + + ## 特性概览 diff --git a/assets/flutter_i18n/en_US.yaml b/assets/flutter_i18n/en_US.yaml index 47fb22ab..7e3346a0 100644 --- a/assets/flutter_i18n/en_US.yaml +++ b/assets/flutter_i18n/en_US.yaml @@ -867,6 +867,8 @@ sport: test_score: "Sport test score" # TODO: change sport score to support i18n total_score: "Four-year total score" + total_score_label: "Total Score" + rank_label: "Rank" semester: "Semester {year} {gradeType}" subject: "Subject" data: "Data" diff --git a/assets/flutter_i18n/zh_CN.yaml b/assets/flutter_i18n/zh_CN.yaml index 052ba615..3b368c7f 100644 --- a/assets/flutter_i18n/zh_CN.yaml +++ b/assets/flutter_i18n/zh_CN.yaml @@ -854,6 +854,8 @@ sport: test_score: "体测成绩" # TODO: change sport score to support i18n total_score: "四年总分" + total_score_label: "总分" + rank_label: "等级" semester: "{year} 第{gradeType}" subject: "项目" data: "数据" diff --git a/assets/flutter_i18n/zh_TW.yaml b/assets/flutter_i18n/zh_TW.yaml index a14776bc..456550ed 100644 --- a/assets/flutter_i18n/zh_TW.yaml +++ b/assets/flutter_i18n/zh_TW.yaml @@ -826,6 +826,8 @@ sport: empty_class_info: 未查詢到課程信息 test_score: 體測成績 total_score: 四年總分 + total_score_label: 總分 + rank_label: 等級 semester: '{year} 第{gradeType}' subject: 項目 data: 數據 diff --git a/lib/page/sport/sport_score_window.dart b/lib/page/sport/sport_score_window.dart index fd9d2454..193a0484 100644 --- a/lib/page/sport/sport_score_window.dart +++ b/lib/page/sport/sport_score_window.dart @@ -14,6 +14,15 @@ import 'package:watermeter/model/xidian_sport/score.dart'; import 'package:watermeter/page/public_widget/re_x_card.dart'; import 'package:watermeter/repository/xidian_sport_session.dart'; +// 常量定义 +const double _textBackgroundAlpha = 0.3; +// const double _textTitleBackgroundAlpha = 0.6; +const int _primaryColorShade = 900; +const int _secondaryColorShade = 900; +const double _scoreFontSize = 13.0; +const double _rankFontSize = 13.0; +const double _labelFontSize = 11.0; + class SportScoreWindow extends StatefulWidget { const SportScoreWindow({super.key}); @@ -26,6 +35,32 @@ class _SportScoreWindowState extends State @override bool get wantKeepAlive => true; + /// 根据合格/不合格状态获取颜色方案 + Map _getColorScheme(bool isQualified, bool isUnknown) { + if (isUnknown) { + return { + 'scoreBackgroundColor': Colors.grey.withValues( + alpha: _textBackgroundAlpha, + ), + 'scoreTextColor': Colors.grey[_primaryColorShade], + 'rankBackgroundColor': Colors.grey.withValues( + alpha: _textBackgroundAlpha, + ), + 'rankTextColor': Colors.grey[_secondaryColorShade], + }; + } + + final baseColor = isQualified ? Colors.green : Colors.red; + return { + 'scoreBackgroundColor': baseColor.withValues( + alpha: _textBackgroundAlpha, + ), + 'scoreTextColor': baseColor[_primaryColorShade], + 'rankBackgroundColor': baseColor.withValues(alpha: _textBackgroundAlpha), + 'rankTextColor': baseColor[_secondaryColorShade], + }; + } + @override void initState() { super.initState(); @@ -34,6 +69,102 @@ class _SportScoreWindowState extends State } } + /// 判断四年成绩是否完整 + bool _isFourYearsComplete() { + // 标准的四年应该有4年的成绩记录 + return sportScore.value.list.length >= 4; + } + + /// 获取总分显示值和颜色 + Map _getTotalScoreInfo() { + final score = sportScore.value.total; + final isUnknown = !_isFourYearsComplete(); + final isQualified = !sportScore.value.rank.contains("不"); + + final colorScheme = _getColorScheme(isQualified, isUnknown); + + return { + 'score': score, + 'rank': isUnknown + ? FlutterI18n.translate( + context, + "class_attendance.course_state.unknown", + ) + : sportScore.value.rank, + ...colorScheme, + }; + } + + /// 显示分数与等级的行布局 + Widget _buildScoreRankRow(Map displayInfo) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + FlutterI18n.translate(context, "sport.total_score_label"), + style: const TextStyle(fontSize: _labelFontSize), + ), + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: displayInfo['scoreBackgroundColor'], + borderRadius: BorderRadius.circular(6), + ), + child: Text( + displayInfo['score'], + style: TextStyle( + color: displayInfo['scoreTextColor'], + fontWeight: FontWeight.bold, + fontSize: _scoreFontSize, + ), + ), + ), + ], + ), + ), + const SizedBox(width: 12), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + FlutterI18n.translate(context, "sport.rank_label"), + style: const TextStyle(fontSize: _labelFontSize), + ), + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: displayInfo['rankBackgroundColor'], + borderRadius: BorderRadius.circular(6), + ), + child: Text( + displayInfo['rank'], + style: TextStyle( + color: displayInfo['rankTextColor'], + fontWeight: FontWeight.bold, + fontSize: _rankFontSize, + ), + ), + ), + ], + ), + ), + ], + ); + } + @override Widget build(BuildContext context) { super.build(context); @@ -44,30 +175,33 @@ class _SportScoreWindowState extends State child: Obx(() { if (sportScore.value.situation == null && sportScore.value.detail.isNotEmpty) { + final scoreInfo = _getTotalScoreInfo(); List things = [ ReXCard( title: Text(FlutterI18n.translate(context, "sport.total_score")), - remaining: [ - ReXCardRemaining( - sportScore.value.total, - color: sportScore.value.rank.contains("不") - ? Colors.red - : null, - isBold: true, - ), - ReXCardRemaining( - sportScore.value.rank, - color: sportScore.value.rank.contains("不") - ? Colors.red - : null, - isBold: sportScore.value.rank.contains("不"), - ), - ], - bottomRow: Text( - sportScore.value.detail.substring( - 0, - sportScore.value.detail.indexOf("\\"), - ), + remaining: [], + bottomRow: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: _buildScoreRankRow(scoreInfo), + ), + const Divider(height: 16, thickness: 0.5), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Center( + child: Text( + sportScore.value.detail.substring( + 0, + sportScore.value.detail.indexOf("\\"), + ), + style: Theme.of(context).textTheme.bodySmall, + textAlign: TextAlign.center, + ), + ), + ), + ], ), ), ]; @@ -112,30 +246,92 @@ class ScoreCard extends StatelessWidget { String unitToShow(String eval) => eval.contains(".") ? eval.substring(0, eval.indexOf(".")) : eval; + Map _getScoreDisplayInfo() { + final isQualified = !toUse.rank.contains("不"); + final baseColor = isQualified ? Colors.green : Colors.red; + return { + 'scoreBackgroundColor': baseColor.withValues( + alpha: _textBackgroundAlpha, + ), + 'scoreTextColor': baseColor[_primaryColorShade], + 'rankBackgroundColor': baseColor.withValues(alpha: _textBackgroundAlpha), + 'rankTextColor': baseColor[_secondaryColorShade], + }; + } + + Map _getTitleBadgeColorScheme() { + final isQualified = !toUse.rank.contains("不"); + final baseColor = isQualified ? Colors.green : Colors.red; + return { + 'scoreBackgroundColor': baseColor[_primaryColorShade], + 'scoreTextColor': Colors.white, + 'rankBackgroundColor': baseColor[_primaryColorShade], + 'rankTextColor': Colors.white, + }; + } + @override Widget build(BuildContext context) { + final displayInfo = _getScoreDisplayInfo(); + final titleBadgeInfo = _getTitleBadgeColorScheme(); + return ReXCard( - title: Text( - FlutterI18n.translate( - context, - "sport.semester", - translationParams: {"year": toUse.year, "gradeType": toUse.gradeType}, - ), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + FlutterI18n.translate( + context, + "sport.semester", + translationParams: { + "year": toUse.year, + "gradeType": toUse.gradeType, + }, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 8), + Flexible( + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 4, + children: [ + Text( + "${FlutterI18n.translate(context, "sport.total_score_label")}:", + style: const TextStyle(fontSize: _labelFontSize), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: titleBadgeInfo['scoreBackgroundColor'], + borderRadius: BorderRadius.circular(6), + ), + child: Text( + toUse.totalScore, + style: TextStyle( + color: titleBadgeInfo['scoreTextColor'], + fontWeight: FontWeight.bold, + fontSize: _scoreFontSize, + ), + ), + ), + ], + ), + ), + ], ), - remaining: [ - ReXCardRemaining( - toUse.totalScore, - color: toUse.rank.contains("不") ? Colors.red : null, - isBold: true, - ), - ReXCardRemaining( - toUse.rank, - color: toUse.rank.contains("不") ? Colors.red : null, - isBold: toUse.rank.contains("不"), - ), - ], - bottomRow: toUse.details.isNotEmpty - ? Table( + remaining: [], + bottomRow: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (toUse.details.isNotEmpty) + Table( columnWidths: const { 0: FlexColumnWidth(1.2), 1: FlexColumnWidth(1.4), @@ -212,7 +408,10 @@ class ScoreCard extends StatelessWidget { ), ], ) - : Text(toUse.moreinfo), + else + Text(toUse.moreinfo), + ], + ), ); } } diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index 60608d0f..8f1b6b24 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -187,6 +187,14 @@ Win32Window::MessageHandler(HWND hwnd, } return 0; + case WM_GETMINMAXINFO: { + MINMAXINFO* min_max_info = reinterpret_cast(lparam); + // Set minimum window size to prevent excessive compression + min_max_info->ptMinTrackSize.x = 800; // Minimum width + min_max_info->ptMinTrackSize.y = 600; // Minimum height + return 0; + } + case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left;