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,是为西电学生设计的开源信息
[
](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)
[
](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;