fix: 登录同步后首页恢复课程位置与复习卡片
- 拉取合并时还原当前课程与分段位置,位置只前进不后退,跳过其他设备已完成的课 - 由云端掌握项重建复习卡片,并纠正旧客户端写坏的首次复习到期时间 - 同 checkpoint 时以证据更多的一方为准;推送缺卡片项时不再用当前时间作到期时间 - 登录/注册及合并逻辑升级后强制全量拉取一次 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1081,6 +1081,73 @@ class AppState extends ChangeNotifier {
|
||||
SyncCoordinator.instance.triggerBackgroundSync(this);
|
||||
}
|
||||
|
||||
/// Merges lesson progress pulled from the sync server.
|
||||
///
|
||||
/// Completed lessons/segments are unioned, and the learner's position only
|
||||
/// ever moves forward: a device that has not caught up yet must never drag
|
||||
/// another device back to an earlier lesson or segment.
|
||||
bool mergeSyncedLessonProgress({
|
||||
required Iterable<String> completedLessons,
|
||||
required Iterable<String> completedSegments,
|
||||
required String remoteActiveLessonId,
|
||||
}) {
|
||||
var changed = false;
|
||||
final completedBefore = completedLessonIds.toSet();
|
||||
for (final id in completedLessons) {
|
||||
if (completedLessonIds.add(id)) changed = true;
|
||||
}
|
||||
for (final id in completedSegments) {
|
||||
if (completedSegmentIds.add(id)) changed = true;
|
||||
}
|
||||
if (this.completedLessons != completedLessonIds.length) {
|
||||
this.completedLessons = completedLessonIds.length;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
int lessonIndex(String id) =>
|
||||
a0SeedLessons.indexWhere((lesson) => lesson.id == id);
|
||||
|
||||
var targetIndex = lessonIndex(activeLessonId);
|
||||
final remoteIndex = lessonIndex(remoteActiveLessonId);
|
||||
if (remoteIndex > targetIndex && isLessonUnlocked(remoteActiveLessonId)) {
|
||||
targetIndex = remoteIndex;
|
||||
}
|
||||
// The server's active lesson can itself be stale. Skip lessons that only
|
||||
// became complete through this pull, but leave a lesson alone when the
|
||||
// learner deliberately reopened it locally after finishing it.
|
||||
while (targetIndex >= 0 &&
|
||||
targetIndex < a0SeedLessons.length - 1 &&
|
||||
!completedBefore.contains(a0SeedLessons[targetIndex].id) &&
|
||||
completedLessonIds.contains(a0SeedLessons[targetIndex].id)) {
|
||||
targetIndex++;
|
||||
}
|
||||
|
||||
var positionChanged = false;
|
||||
if (targetIndex >= 0 && a0SeedLessons[targetIndex].id != activeLessonId) {
|
||||
activeLessonId = a0SeedLessons[targetIndex].id;
|
||||
positionChanged = true;
|
||||
}
|
||||
|
||||
for (final lesson in a0SeedLessons) {
|
||||
final segments = lesson.segments;
|
||||
var firstOpen = segments.indexWhere(
|
||||
(segment) => !completedSegmentIds.contains(segment.id),
|
||||
);
|
||||
if (firstOpen < 0) firstOpen = segments.length - 1;
|
||||
if (firstOpen > activeSegmentIndexFor(lesson.id)) {
|
||||
activeSegmentIndexes[lesson.id] = firstOpen;
|
||||
if (lesson.id == activeLessonId) positionChanged = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (positionChanged) {
|
||||
_resetLessonFlow();
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
void _resetLessonFlow() {
|
||||
lessonStep = LessonStep.preview;
|
||||
previewIndex = 0;
|
||||
|
||||
Reference in New Issue
Block a user