feat: 新增 Fake 仓库实现(含可提交的打卡/计划与会话)

This commit is contained in:
AndyChen1024
2026-09-15 14:35:52 +08:00
parent ec1eed8f04
commit c39a324534
5 changed files with 316 additions and 12 deletions

View File

@@ -0,0 +1,136 @@
import Combine
import CoreDomain
import CoreModel
import Foundation
/// Fake 计划仓库(骨架阶段返回假数据,可提交)。
public final class FakePlanRepository: PlanRepository, @unchecked Sendable {
private let plans: [PlanType: CurrentValueSubject<PlanRecord, Never>]
private var submits: [String: CurrentValueSubject<PlanSubmitData, Never>] = [:]
private let lock = NSLock()
public init() {
var dict: [PlanType: CurrentValueSubject<PlanRecord, Never>] = [:]
for type in PlanType.allCases {
dict[type] = CurrentValueSubject<PlanRecord, Never>(Self.seedPlan(type))
}
plans = dict
}
public func observePlan(type: PlanType) -> AsyncThrowingStream<PlanRecord, Error> {
Self.stream(plans[type]!)
}
public func observePlanSubmit(type: PlanType, periodKey: String) -> AsyncThrowingStream<PlanSubmitData, Error> {
Self.stream(submitSubject(type: type, periodKey: periodKey))
}
public func savePlan(type: PlanType, periodKey: String, content: String, attachments: [String]) async throws {
try await Task.sleep(for: .milliseconds(300))
let submit = submitSubject(type: type, periodKey: periodKey)
let currentSubmit = submit.value
submit.value = PlanSubmitData(
content: content,
attachments: attachments,
checked: true,
canSubmit: currentSubmit.canSubmit,
message: currentSubmit.message,
history: currentSubmit.history
)
let plan = plans[type]!
let currentPlan = plan.value
plan.value = PlanRecord(
type: currentPlan.type,
periodKey: currentPlan.periodKey,
canSubmit: currentPlan.canSubmit,
message: currentPlan.message,
myChecked: true,
myRecord: MyPlanRecord(status: "已完成", submittedTime: "现在", content: content, attachments: attachments),
groups: currentPlan.groups,
unassignedCount: currentPlan.unassignedCount
)
}
private func submitSubject(type: PlanType, periodKey: String) -> CurrentValueSubject<PlanSubmitData, Never> {
let key = "\(type.rawValue):\(periodKey)"
lock.lock()
defer { lock.unlock() }
if let existing = submits[key] {
return existing
}
let subject = CurrentValueSubject<PlanSubmitData, Never>(Self.seedSubmit(type: type))
submits[key] = subject
return subject
}
private static func stream<T>(_ subject: CurrentValueSubject<T, Never>) -> AsyncThrowingStream<T, Error> {
AsyncThrowingStream { continuation in
let cancellable = subject.sink { continuation.yield($0) }
continuation.onTermination = { _ in cancellable.cancel() }
}
}
private static func seedPlan(_ type: PlanType) -> PlanRecord {
PlanRecord(
type: type,
periodKey: periodKey(for: type),
canSubmit: true,
message: type == .weekly ? "本周计划提交截止:周日晚 23:59" : nil,
myChecked: type == .weekly,
myRecord: type == .weekly
? MyPlanRecord(status: "已完成", submittedTime: "今天 18:30", content: "本周完成两章学习,整理笔记并复习错题。", attachments: ["图1", "图2"])
: nil,
groups: seedGroups(),
unassignedCount: 2
)
}
private static func seedSubmit(type: PlanType) -> PlanSubmitData {
if type == .weekly {
return PlanSubmitData(
content: "本周完成两章学习,整理笔记并复习错题。",
attachments: ["图1", "图2"],
checked: true,
canSubmit: true,
message: nil,
history: []
)
}
return PlanSubmitData(
content: "",
attachments: [],
checked: false,
canSubmit: true,
message: nil,
history: [
MyPlanRecord(status: "已完成", submittedTime: "2026-08 完成", content: "上月计划内容……", attachments: []),
]
)
}
private static func periodKey(for type: PlanType) -> String {
switch type {
case .weekly: return "2026 第 37 周"
case .monthly: return "2026 年 9 月"
case .yearly: return "2026 年"
}
}
private static func seedGroups() -> [PlanGroup] {
[
PlanGroup(id: "g1", name: "进阶一组", checkedCount: 4, memberCount: 6, members: seedMembers()),
PlanGroup(id: "g2", name: "进阶二组", checkedCount: 5, memberCount: 5, members: seedMembers()),
PlanGroup(id: "g3", name: "进阶三组", checkedCount: 2, memberCount: 7, members: seedMembers()),
]
}
private static func seedMembers() -> [PlanMember] {
[
PlanMember(id: "p1", name: "张三", filled: true),
PlanMember(id: "p2", name: "李四", filled: true),
PlanMember(id: "p3", name: "王五", filled: false),
]
}
}