Skip to content

Commit 0d47657

Browse files
committed
fix (attempt): rewrite class attendance info
1 parent 4dc645a commit 0d47657

6 files changed

Lines changed: 101 additions & 83 deletions

File tree

assets/flutter_i18n/zh_CN.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class_attendance:
6969
time_info: "{checkInCount} 已签 / {absenceCount} 缺勤 / {requiredCheckIn} 应签"
7070
not_attend: 复活次数
7171
not_attend_info: "{timeToHaveError} 次 / {totalTimes} 总课程"
72+
not_attend_info_error: 无法对应已有课程
7273
leave: 请假次数
7374
leave_info: 事假 {personalLeave} / 病假 {sickLeave} / 公假 {officialLeave}
7475
study: 学习进度

lib/model/xidian_ids/class_attendance.dart

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,25 @@ part 'class_attendance.g.dart';
99

1010
enum AttendanceStatus { unknown, eligible, warning, ineligible }
1111

12+
extension I18nString on AttendanceStatus {
13+
String get i18nString {
14+
switch (this) {
15+
case AttendanceStatus.unknown:
16+
return "class_attendance.course_state.unknown";
17+
case AttendanceStatus.eligible:
18+
return "class_attendance.course_state.eligible";
19+
case AttendanceStatus.warning:
20+
return "class_attendance.course_state.warning";
21+
case AttendanceStatus.ineligible:
22+
return "class_attendance.course_state.ineligible";
23+
}
24+
}
25+
}
26+
1227
class ClassAttendance {
28+
// 有无预警
29+
final bool isWarning;
30+
1331
// 课程信息
1432
final String courseName; // 课程名称 (课程 名称)
1533
final String className; // 教学班名称 (教学班 名称)
@@ -59,25 +77,23 @@ class ClassAttendance {
5977
this.courseId,
6078
this.clazzId,
6179
this.cpi,
80+
this.isWarning = false,
6281
});
6382

6483
/// Calculate the attendance status based on total class times
6584
/// Returns a translation key for the status
66-
String getAttendanceStatus(int totalTimes) {
67-
final timeToHaveError = (totalTimes / 4).floor();
68-
final absenceNum = int.tryParse(absenceCount) ?? 0;
85+
AttendanceStatus get attendanceStatus {
86+
if (isWarning) return AttendanceStatus.ineligible;
87+
6988
final attendanceRatio = double.tryParse(
7089
attendanceRate.replaceAll(" %", ""),
7190
);
72-
7391
if (attendanceRatio == null) {
74-
return "class_attendance.course_state.unknown";
75-
} else if (timeToHaveError < absenceNum) {
76-
return "class_attendance.course_state.ineligible";
77-
} else if (attendanceRatio >= 90.0 || timeToHaveError >= absenceNum) {
78-
return "class_attendance.course_state.eligible";
92+
return AttendanceStatus.unknown;
93+
} else if (attendanceRatio < 80.0) {
94+
return AttendanceStatus.warning;
7995
} else {
80-
return "class_attendance.course_state.warning";
96+
return AttendanceStatus.eligible;
8197
}
8298
}
8399
}

lib/page/class_attendance/class_attandance_card.dart

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,9 @@ import 'package:watermeter/page/public_widget/re_x_card.dart';
1010

1111
class CourseCard extends StatelessWidget {
1212
final ClassAttendance course;
13-
final int totalTimes;
14-
late final int timeToHaveError;
15-
late final int remainAbsenceNum;
16-
late final int absenceNum;
17-
late final String attendanceStatus;
13+
final int? totalTimes;
1814

19-
CourseCard({super.key, required this.course, required this.totalTimes}) {
20-
timeToHaveError = (totalTimes / 4).floor();
21-
absenceNum = int.tryParse(course.absenceCount) ?? 0;
22-
remainAbsenceNum = timeToHaveError - absenceNum;
23-
attendanceStatus = course.getAttendanceStatus(totalTimes);
24-
}
15+
const CourseCard({super.key, required this.course, required this.totalTimes});
2516

2617
Widget _buildInfoRow(String label, String value) {
2718
return Padding(
@@ -42,7 +33,9 @@ class CourseCard extends StatelessWidget {
4233
return ReXCard(
4334
title: Text(course.courseName),
4435
remaining: [
45-
ReXCardRemaining(FlutterI18n.translate(context, attendanceStatus)),
36+
ReXCardRemaining(
37+
FlutterI18n.translate(context, course.attendanceStatus.i18nString),
38+
),
4639
],
4740
bottomRow: Column(
4841
children: [
@@ -60,14 +53,22 @@ class CourseCard extends StatelessWidget {
6053
),
6154
_buildInfoRow(
6255
FlutterI18n.translate(context, "class_attendance.card.not_attend"),
63-
FlutterI18n.translate(
64-
context,
65-
"class_attendance.card.not_attend_info",
66-
translationParams: {
67-
"timeToHaveError": remainAbsenceNum.toString(),
68-
"totalTimes": totalTimes.toString(),
69-
},
70-
),
56+
(totalTimes == null)
57+
? FlutterI18n.translate(
58+
context,
59+
"class_attendance.card.not_attend_info_error",
60+
)
61+
: FlutterI18n.translate(
62+
context,
63+
"class_attendance.card.not_attend_info",
64+
translationParams: {
65+
"timeToHaveError":
66+
((totalTimes! / 4).floor() -
67+
(int.tryParse(course.absenceCount) ?? 0))
68+
.toString(),
69+
"totalTimes": totalTimes.toString(),
70+
},
71+
),
7172
),
7273
_buildInfoRow(
7374
FlutterI18n.translate(context, "class_attendance.card.leave"),
@@ -97,15 +98,13 @@ class CourseCard extends StatelessWidget {
9798
),
9899
).gestures(
99100
onTap: () async {
100-
if (!attendanceStatus.contains("unknown")) {
101+
if (course.attendanceStatus == AttendanceStatus.unknown) {
101102
await BothSideSheet.show(
102103
context: context,
103104
title: FlutterI18n.translate(
104105
context,
105106
"class_attendance.detail_title",
106-
translationParams: {
107-
"courseName": course.courseName,
108-
},
107+
translationParams: {"courseName": course.courseName},
109108
),
110109
child: ClassAttendanceDetailView(
111110
classAttendance: course,

lib/page/class_attendance/class_attendance_table.dart

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,26 @@ class ClassAttendanceTable extends StatefulWidget {
2525
}
2626

2727
class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
28-
String? _selectedFilter;
28+
AttendanceStatus? _selectedFilter;
2929
int? _sortColumnIndex = 1; // 默认按状态列排序
3030
bool _sortAscending = true;
3131

32-
int _getStatusPriority(String status) {
33-
if (status.contains("ineligible")) return 0; // 最高优先级
34-
if (status.contains("warning")) return 1;
35-
if (status.contains("eligible")) return 2;
32+
int _getStatusPriority(AttendanceStatus status) {
33+
if (status == AttendanceStatus.ineligible) return 0; // 最高优先级
34+
if (status == AttendanceStatus.warning) return 1;
35+
if (status == AttendanceStatus.eligible) return 2;
3636
return 3; // unknown 最低优先级
3737
}
3838

3939
List<ClassAttendance> get _filteredCourses {
4040
List<ClassAttendance> filtered;
4141

42-
if (_selectedFilter == null || _selectedFilter == 'all') {
42+
if (_selectedFilter == null) {
4343
filtered = widget.courses.toList();
4444
} else {
4545
filtered = widget.courses.where((course) {
46-
final totalTimes = widget.classTimes[course.courseName] ?? 0;
47-
final status = course.getAttendanceStatus(totalTimes);
46+
// final totalTimes = widget.classTimes[course.courseName] ?? 0;
47+
final status = course.attendanceStatus;
4848
return status == _selectedFilter;
4949
}).toList();
5050
}
@@ -53,16 +53,16 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
5353
if (_sortColumnIndex != null) {
5454
filtered.sort((a, b) {
5555
int comparison = 0;
56-
final totalTimesA = widget.classTimes[a.courseName] ?? 0;
57-
final totalTimesB = widget.classTimes[b.courseName] ?? 0;
56+
// final totalTimesA = widget.classTimes[a.courseName] ?? 0;
57+
// final totalTimesB = widget.classTimes[b.courseName] ?? 0;
5858

5959
switch (_sortColumnIndex) {
6060
case 0: // 课程名称
6161
comparison = a.courseName.compareTo(b.courseName);
6262
break;
6363
case 1: // 状态
64-
final statusA = a.getAttendanceStatus(totalTimesA);
65-
final statusB = b.getAttendanceStatus(totalTimesB);
64+
final statusA = a.attendanceStatus;
65+
final statusB = b.attendanceStatus;
6666
comparison = _getStatusPriority(
6767
statusA,
6868
).compareTo(_getStatusPriority(statusB));
@@ -98,15 +98,16 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
9898
return filtered;
9999
}
100100

101-
Color _getStatusColor(String status) {
102-
if (status.contains("ineligible")) {
103-
return Colors.red;
104-
} else if (status.contains("warning")) {
105-
return Colors.orange;
106-
} else if (status.contains("eligible")) {
107-
return Colors.green;
108-
} else {
109-
return Colors.grey;
101+
Color _getStatusColor(AttendanceStatus status) {
102+
switch (status) {
103+
case AttendanceStatus.unknown:
104+
return Colors.grey;
105+
case AttendanceStatus.eligible:
106+
return Colors.green;
107+
case AttendanceStatus.warning:
108+
return Colors.orange;
109+
case AttendanceStatus.ineligible:
110+
return Colors.red;
110111
}
111112
}
112113

@@ -135,11 +136,11 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
135136
"class_attendance.table.filter_all",
136137
),
137138
),
138-
selected:
139-
_selectedFilter == null || _selectedFilter == 'all',
139+
selected: _selectedFilter == null,
140140
onSelected: (selected) {
141141
setState(() {
142-
_selectedFilter = selected ? 'all' : null;
142+
// TODO: Check
143+
_selectedFilter = null;
143144
});
144145
},
145146
),
@@ -151,14 +152,12 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
151152
"class_attendance.course_state.ineligible",
152153
),
153154
),
154-
selected:
155-
_selectedFilter ==
156-
'class_attendance.course_state.ineligible',
155+
selected: _selectedFilter == AttendanceStatus.ineligible,
157156
selectedColor: Colors.red.withValues(alpha: 0.2),
158157
onSelected: (selected) {
159158
setState(() {
160159
_selectedFilter = selected
161-
? 'class_attendance.course_state.ineligible'
160+
? AttendanceStatus.ineligible
162161
: null;
163162
});
164163
},
@@ -171,14 +170,12 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
171170
"class_attendance.course_state.warning",
172171
),
173172
),
174-
selected:
175-
_selectedFilter ==
176-
'class_attendance.course_state.warning',
173+
selected: _selectedFilter == AttendanceStatus.warning,
177174
selectedColor: Colors.orange.withValues(alpha: 0.2),
178175
onSelected: (selected) {
179176
setState(() {
180177
_selectedFilter = selected
181-
? 'class_attendance.course_state.warning'
178+
? AttendanceStatus.warning
182179
: null;
183180
});
184181
},
@@ -191,14 +188,12 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
191188
"class_attendance.course_state.eligible",
192189
),
193190
),
194-
selected:
195-
_selectedFilter ==
196-
'class_attendance.course_state.eligible',
191+
selected: _selectedFilter == AttendanceStatus.eligible,
197192
selectedColor: Colors.green.withValues(alpha: 0.2),
198193
onSelected: (selected) {
199194
setState(() {
200195
_selectedFilter = selected
201-
? 'class_attendance.course_state.eligible'
196+
? AttendanceStatus.eligible
202197
: null;
203198
});
204199
},
@@ -211,14 +206,12 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
211206
"class_attendance.course_state.unknown",
212207
),
213208
),
214-
selected:
215-
_selectedFilter ==
216-
'class_attendance.course_state.unknown',
209+
selected: _selectedFilter == AttendanceStatus.unknown,
217210
selectedColor: Colors.grey.withValues(alpha: 0.2),
218211
onSelected: (selected) {
219212
setState(() {
220213
_selectedFilter = selected
221-
? 'class_attendance.course_state.unknown'
214+
? AttendanceStatus.unknown
222215
: null;
223216
});
224217
},
@@ -374,9 +367,7 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
374367
),
375368
],
376369
rows: filteredCourses.map((course) {
377-
final totalTimes =
378-
widget.classTimes[course.courseName] ?? 0;
379-
final status = course.getAttendanceStatus(totalTimes);
370+
final status = course.attendanceStatus;
380371
final statusColor = _getStatusColor(status);
381372

382373
return DataRow(
@@ -389,7 +380,7 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
389380
overflow: TextOverflow.ellipsis,
390381
),
391382
onTap: () async {
392-
if (!status.contains("unknown")) {
383+
if (status != AttendanceStatus.unknown) {
393384
await BothSideSheet.show(
394385
context: context,
395386
title: FlutterI18n.translate(
@@ -418,7 +409,7 @@ class _ClassAttendanceTableState extends State<ClassAttendanceTable> {
418409
borderRadius: BorderRadius.circular(4),
419410
),
420411
child: Text(
421-
FlutterI18n.translate(context, status),
412+
FlutterI18n.translate(context, status.i18nString),
422413
style: TextStyle(
423414
color: statusColor,
424415
fontWeight: FontWeight.bold,

lib/page/class_attendance/class_attendance_view.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
66
import 'package:flutter_i18n/flutter_i18n.dart';
77
import 'package:provider/provider.dart';
88
import 'package:styled_widget/styled_widget.dart';
9+
import 'package:watermeter/model/xidian_ids/class_attendance.dart';
910
import 'package:watermeter/page/class_attendance/class_attandance_card.dart';
1011
import 'package:watermeter/page/class_attendance/class_attendance_state.dart';
1112
import 'package:watermeter/page/class_attendance/class_attendance_table.dart';
@@ -97,13 +98,21 @@ class ClassAttendanceView extends StatelessWidget {
9798
}).toList();
9899

99100
final warningCourses = courseCards.toList()
100-
..retainWhere((e) => e.attendanceStatus.contains("warning"));
101+
..retainWhere(
102+
(e) => e.course.attendanceStatus == AttendanceStatus.warning,
103+
);
101104
final ineligibleCourses = courseCards.toList()
102-
..retainWhere((e) => e.attendanceStatus.contains("ineligible"));
105+
..retainWhere(
106+
(e) => e.course.attendanceStatus == AttendanceStatus.ineligible,
107+
);
103108
final eligibleCourses = courseCards.toList()
104-
..retainWhere((e) => e.attendanceStatus.contains("eligible"));
109+
..retainWhere(
110+
(e) => e.course.attendanceStatus == AttendanceStatus.eligible,
111+
);
105112
final unknownCourses = courseCards.toList()
106-
..retainWhere((e) => e.attendanceStatus.contains("unknown"));
113+
..retainWhere(
114+
(e) => e.course.attendanceStatus == AttendanceStatus.unknown,
115+
);
107116

108117
return RefreshIndicator(
109118
onRefresh: state.refreshData,

lib/repository/xidian_ids/learning_session.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ class LearningSession extends IDSSession {
213213

214214
List<Element> rows = table.querySelectorAll('tbody tr');
215215
for (var row in rows) {
216+
bool isWarning = row.classes.contains('redtr');
216217
List<Element> cells = row.querySelectorAll('td');
217218
if (cells.length < 17) {
218219
continue;
@@ -226,6 +227,7 @@ class LearningSession extends IDSSession {
226227
);
227228
results.add(
228229
ClassAttendance(
230+
isWarning: isWarning,
229231
courseName: rowData[0],
230232
className: rowData[1],
231233
checkInCount: rowData[2],

0 commit comments

Comments
 (0)