fix: preserve queued settings and recover interrupted or corrupt state
This commit is contained in:
@@ -85,7 +85,10 @@ class AppController extends ChangeNotifier {
|
||||
Future<void> leaveDemo() => _run(() async {
|
||||
isDemo = false;
|
||||
});
|
||||
Future<void> updateSettings(AppSettings value) => _run(() async {
|
||||
Future<void> updateSettings(
|
||||
AppSettings Function(AppSettings current) change,
|
||||
) => _run(() async {
|
||||
final value = change(snapshot.settings);
|
||||
if (![5, 10, 15, 30, 60].contains(value.reminderMinutes)) {
|
||||
throw ArgumentError('提醒时间无效');
|
||||
}
|
||||
@@ -170,43 +173,54 @@ class AppController extends ChangeNotifier {
|
||||
}
|
||||
calendarSyncError = null;
|
||||
});
|
||||
Future<void> enableCalendarFromUserAction(
|
||||
String calendarId,
|
||||
) => _run(() async {
|
||||
if (isDemo) throw StateError('示例课表不会导入日历');
|
||||
if (snapshot.settings.semester == null) throw StateError('请先设置学期');
|
||||
if (!await calendar.requestAccess()) {
|
||||
calendarSyncError = '日历权限未获允许';
|
||||
return;
|
||||
}
|
||||
if (snapshot.calendarCleanupPending) {
|
||||
await _cleanupCalendar();
|
||||
}
|
||||
final calendars = await calendar.listWritableCalendars();
|
||||
if (!calendars.any((c) => c.id == calendarId)) {
|
||||
calendarSyncError = '请选择可写入的日历';
|
||||
return;
|
||||
}
|
||||
final settings = snapshot.settings.copyWith(
|
||||
calendarEnabled: true,
|
||||
calendarId: calendarId,
|
||||
);
|
||||
// Keep the chosen calendar for recovery if the first native sync fails midway.
|
||||
await _save(
|
||||
snapshot.copyWith(settings: settings.copyWith(calendarEnabled: false)),
|
||||
);
|
||||
try {
|
||||
final ids = await calendar.sync(
|
||||
ScheduleEngine.occurrences(snapshot.courses, settings.semester!),
|
||||
calendarId,
|
||||
snapshot.calendarEventIds,
|
||||
);
|
||||
await _save(snapshot.copyWith(settings: settings, calendarEventIds: ids));
|
||||
calendarSyncError = null;
|
||||
} catch (_) {
|
||||
calendarSyncError = '日历同步失败,可重试';
|
||||
}
|
||||
});
|
||||
Future<void> enableCalendarFromUserAction(String calendarId) =>
|
||||
_run(() async {
|
||||
if (isDemo) throw StateError('示例课表不会导入日历');
|
||||
if (snapshot.settings.semester == null) throw StateError('请先设置学期');
|
||||
if (!await calendar.requestAccess()) {
|
||||
calendarSyncError = '日历权限未获允许';
|
||||
return;
|
||||
}
|
||||
if (snapshot.calendarCleanupPending) {
|
||||
await _cleanupCalendar();
|
||||
}
|
||||
final calendars = await calendar.listWritableCalendars();
|
||||
if (!calendars.any((c) => c.id == calendarId)) {
|
||||
calendarSyncError = '请选择可写入的日历';
|
||||
return;
|
||||
}
|
||||
final settings = snapshot.settings.copyWith(
|
||||
calendarEnabled: true,
|
||||
calendarId: calendarId,
|
||||
);
|
||||
// Keep the chosen calendar for recovery if the first native sync fails midway.
|
||||
await _save(
|
||||
snapshot.copyWith(
|
||||
settings: settings.copyWith(calendarEnabled: false),
|
||||
calendarCleanupPending: true,
|
||||
calendarEventIds: snapshot.settings.calendarId == calendarId
|
||||
? snapshot.calendarEventIds
|
||||
: {},
|
||||
),
|
||||
);
|
||||
try {
|
||||
final ids = await calendar.sync(
|
||||
ScheduleEngine.occurrences(snapshot.courses, settings.semester!),
|
||||
calendarId,
|
||||
snapshot.calendarEventIds,
|
||||
);
|
||||
await _save(
|
||||
snapshot.copyWith(
|
||||
settings: settings,
|
||||
calendarEventIds: ids,
|
||||
calendarCleanupPending: false,
|
||||
),
|
||||
);
|
||||
calendarSyncError = null;
|
||||
} catch (_) {
|
||||
calendarSyncError = '日历同步失败,可重试';
|
||||
}
|
||||
});
|
||||
Future<void> enableRemindersFromUserAction() => _run(() async {
|
||||
if (isDemo) throw StateError('示例课表不会生成提醒');
|
||||
await notifications.requestPermissions();
|
||||
@@ -218,6 +232,16 @@ class AppController extends ChangeNotifier {
|
||||
await _reconcile();
|
||||
});
|
||||
Future<void> refreshServices() => _run(_reconcile);
|
||||
Future<void> startFreshData() => _run(() async {
|
||||
if (storageReady) {
|
||||
throw StateError('保存文件正常,无需重新建立');
|
||||
}
|
||||
snapshot = await store.startFresh();
|
||||
storageReady = true;
|
||||
isDemo = false;
|
||||
lastError = null;
|
||||
await _reconcile();
|
||||
});
|
||||
Future<void> retryCalendarCleanup() => _run(() async {
|
||||
if (await calendar.requestAccess()) {
|
||||
await _cleanupCalendar();
|
||||
|
||||
@@ -159,6 +159,31 @@ class _ShellState extends State<_Shell> with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startFresh() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('重新建立课表'),
|
||||
content: const Text(
|
||||
'损坏文件会保留为恢复副本,然后重新建立空课表。已有课程通知会取消;手机日历中原有课程日程需手动处理。',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('保留损坏文件并重新建立'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await widget.controller.startFreshData();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
@@ -198,6 +223,11 @@ class _ShellState extends State<_Shell> with WidgetsBindingObserver {
|
||||
onPressed: widget.controller.restoreBackup,
|
||||
child: const Text('恢复最近备份'),
|
||||
),
|
||||
if (!widget.controller.storageReady)
|
||||
TextButton(
|
||||
onPressed: _startFresh,
|
||||
child: const Text('重新建立课表'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user