Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/flutter_i18n/en_US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ classtable:
output_to_system: "Export to system calendar"
refresh_classtable: "Refresh schedule"
switch_semester: "Switch classtable semester"
enable_current_time_indicator: "Show current time indicator"
disable_current_time_indicator: "Hide current time indicator"
class_change_page:
title: "Schedule Changes"
empty_message: "Currently there's no class schedule changes"
Expand Down
2 changes: 2 additions & 0 deletions assets/flutter_i18n/zh_CN.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ classtable:
output_to_system: "导出到系统日历"
refresh_classtable: "刷新日程表"
switch_semester: "切换课程表学期"
enable_current_time_indicator: "显示当前时间指示条"
disable_current_time_indicator: "隐藏当前时间指示条"
class_change_page:
title: "课程调整"
empty_message: "目前没有调课信息"
Expand Down
2 changes: 2 additions & 0 deletions assets/flutter_i18n/zh_TW.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ classtable:
output_to_system: 導出到系統日曆
refresh_classtable: 刷新日程表
switch_semester: 切換課程表學期
enable_current_time_indicator: 顯示當前時間指示條
disable_current_time_indicator: 隱藏當前時間指示條
class_change_page:
title: 課程調整
empty_message: 目前沒有調課信息
Expand Down
25 changes: 25 additions & 0 deletions lib/page/classtable/class_page/content_classtable_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,19 @@ class _ContentClassTablePageState extends State<ContentClassTablePage> {
),
),
),
PopupMenuItem<String>(
value: 'J',
child: Text(
FlutterI18n.translate(
context,
preference.getBool(
preference.Preference.enableCurrentTimeIndicator,
)
? "classtable.popup_menu.disable_current_time_indicator"
: "classtable.popup_menu.enable_current_time_indicator",
),
),
),
],
onSelected: (String action) async {
final box = context.findRenderObject() as RenderBox?;
Expand Down Expand Up @@ -485,6 +498,18 @@ class _ContentClassTablePageState extends State<ContentClassTablePage> {
}
});
}
break;
case 'J':
bool currentValue = preference.getBool(
preference.Preference.enableCurrentTimeIndicator,
Comment thread
BenderBlog marked this conversation as resolved.
);
await preference.setBool(
preference.Preference.enableCurrentTimeIndicator,
!currentValue,
);
if (context.mounted) {
setState(() {});
}
}
},
),
Expand Down
47 changes: 44 additions & 3 deletions lib/page/classtable/class_table_view/class_organized_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Removed left/right, only use stack.

import 'package:flutter/material.dart';
import 'package:watermeter/model/time_list.dart';
import 'package:watermeter/model/xidian_ids/exam.dart';
import 'package:watermeter/model/xidian_ids/classtable.dart';
import 'package:watermeter/model/xidian_ids/experiment.dart';
Expand Down Expand Up @@ -121,7 +122,7 @@ class ClassOrgainzedData {
this.place,
});

static double _transferIndex(DateTime time) {
static double transferIndex(DateTime time) {
int timeInMin = time.hour * 60 + time.minute;
int previous = 0;
// Start from the second element.
Expand Down Expand Up @@ -162,6 +163,46 @@ class ClassOrgainzedData {
return 61;
}

static double transferIndexForIndicator(DateTime time) {
int minutesOfDay(String value) {
final timeParts = value.split(":");
return int.parse(timeParts[0]) * 60 + int.parse(timeParts[1]);
}

double indexOfMinute(int value) {
return transferIndex(
DateTime(time.year, time.month, time.day, value ~/ 60, value % 60),
);
}

final timeInMin = time.hour * 60 + time.minute;

for (int i = 0; i < timeList.length; i += 2) {
final start = minutesOfDay(timeList[i]);
final stop = minutesOfDay(timeList[i + 1]);
final nextStart = i + 2 < timeList.length
? minutesOfDay(timeList[i + 2])
: null;
final isSmallBreak = nextStart != null && nextStart - stop <= 20;
final visualStop = isSmallBreak ? nextStart : stop;

// During class, map the elapsed class time across the visual block including the following small break.
if (timeInMin >= start && timeInMin < stop) {
final startIndex = indexOfMinute(start);
final stopIndex = indexOfMinute(visualStop);
return startIndex +
(stopIndex - startIndex) * (timeInMin - start) / (stop - start);
}

// During a small break, show the indicator at the end of the class.
if (isSmallBreak && timeInMin >= stop && timeInMin < visualStop) {
return indexOfMinute(visualStop);
}
}

return transferIndex(time);
}

ClassOrgainzedData._({
required this.data,
required DateTime start,
Expand All @@ -170,7 +211,7 @@ class ClassOrgainzedData {
required this.name,
this.place,
}) {
this.start = _transferIndex(start);
this.stop = _transferIndex(stop);
this.start = transferIndex(start);
this.stop = transferIndex(stop);
}
}
35 changes: 35 additions & 0 deletions lib/page/classtable/class_table_view/class_table_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,28 @@ class _ClassTableViewState extends State<ClassTableView> {
}
}

/// This function will be triggered every minute to refresh the time indicator.
void _reloadTimeIndicator() {
if (mounted && classTableState.currentWeek == widget.index) {
setState(() {});
}
}

void updateSize() => size = ClassTableState.of(context)!.constraints;

@override
void didChangeDependencies() {
super.didChangeDependencies();
classTableState = ClassTableState.of(context)!.controllers;
classTableState.addListener(_reload);
classTableState.currentTimeNotifier.addListener(_reloadTimeIndicator);
updateSize();
}

@override
void dispose() {
classTableState.removeListener(_reload);
classTableState.currentTimeNotifier.removeListener(_reloadTimeIndicator);
super.dispose();
}

Expand All @@ -186,6 +195,31 @@ class _ClassTableViewState extends State<ClassTableView> {
updateSize();
}

Widget _buildCurrentTimeIndicator() {
if (classTableState.currentWeek != widget.index) {
return const SizedBox.shrink();
}
if (!preference.getBool(preference.Preference.enableCurrentTimeIndicator)) {
return const SizedBox.shrink();
}
// Keep the indicator correct during short breaks between classes.
final timeIndex = ClassOrgainzedData.transferIndexForIndicator(
classTableState.currentTimeNotifier.value,
);
if (timeIndex <= 0 || timeIndex >= 61) {
return const SizedBox.shrink();
}
return Positioned(
top: blockheight(timeIndex) - 1,
left: leftRow,
width: size.maxWidth - leftRow,
height: 2,
child: Container(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5),
),
);
}

@override
Widget build(BuildContext context) {
return [
Expand All @@ -208,6 +242,7 @@ class _ClassTableViewState extends State<ClassTableView> {
.constrained(width: leftRow)
.positioned(left: 0),
...classSubRow(true),
_buildCurrentTimeIndicator(),
]
.toStack()
.constrained(height: blockheight(61), width: size.maxWidth)
Expand Down
11 changes: 11 additions & 0 deletions lib/page/classtable/classtable_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2025 Traintime PDA authors.
// SPDX-License-Identifier: MPL-2.0 OR Apache-2.0

import 'dart:async';
import 'dart:math' as math;

import 'package:device_calendar/device_calendar.dart';
Expand Down Expand Up @@ -54,8 +55,15 @@ class ClassTableWidgetState with ChangeNotifier {
///*******************************************************************///
bool _disposed = false;

/// A notifier that fires every minute with the current time.
final ValueNotifier<DateTime> currentTimeNotifier =
ValueNotifier(DateTime.now());
Timer? _currentTimeTimer;

@override
void dispose() {
_currentTimeTimer?.cancel();
currentTimeNotifier.dispose();
_disposed = true;
super.dispose();
}
Expand Down Expand Up @@ -461,6 +469,9 @@ END:VTIMEZONE
} else {
_chosenWeek = currentWeek;
}
_currentTimeTimer = Timer.periodic(const Duration(minutes: 1), (_) {
currentTimeNotifier.value = DateTime.now();
});
}

bool _checkIsOverlapping(
Expand Down
3 changes: 2 additions & 1 deletion lib/repository/preference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ enum Preference {
), // 上次通知使用的语言
dormWaterToken(key: "dorm_water_token", type: "String"), // 宿舍水机登录 token
dormWaterUid(key: "dorm_water_uid", type: "String"), // 宿舍水机用户 uid
dormWaterEid(key: "dorm_water_eid", type: "String"); // 宿舍水机用户 eid
dormWaterEid(key: "dorm_water_eid", type: "String"), // 宿舍水机用户 eid
enableCurrentTimeIndicator(key: "enableCurrentTimeIndicator", type: "bool"); // 是否启用当前时间指示条

const Preference({required this.key, this.type = "String"});

Expand Down