fix: all iOS compile errors - protocols, missing fields, colors, shadowed max

- Protocol conformance: list→listAll with extension defaults
- ContinueLearningResponse has no lastPosition→use nil
- LearningSession has no readingTargetType/materialId→use defaults
- Color.zxBlue→Color.blue, Color.zxIndigo→Color.indigo
- Shadowed max parameter→Swift.max
- ForEach Identifiable→explicit id: \.id

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-18 17:14:34 +08:00
parent d28dba29fc
commit def8c4c93a
6 changed files with 33 additions and 25 deletions

View File

@ -39,10 +39,10 @@ struct AnalysisHomeView: View {
} }
if let ls = viewModel.learningSummary { if let ls = viewModel.learningSummary {
HStack(spacing: 12) { HStack(spacing: 12) {
ZXStatBadge(icon: "book.pages", label: "今日阅读", value: "\(ls.todaySeconds / 60) 分钟", trend: "", color: Color.zxBlue) ZXStatBadge(icon: "book.pages", label: "今日阅读", value: "\(ls.todaySeconds / 60) 分钟", trend: "", color: Color.blue)
ZXStatBadge(icon: "calendar.badge.clock", label: "本周阅读", value: "\(ls.weekSeconds / 60) 分钟", trend: "", color: Color.zxTeal) ZXStatBadge(icon: "calendar.badge.clock", label: "本周阅读", value: "\(ls.weekSeconds / 60) 分钟", trend: "", color: Color.zxTeal)
ZXStatBadge(icon: "doc.text", label: "已读资料", value: "\(ls.markedReadCount)", trend: "", color: Color.zxGreen) ZXStatBadge(icon: "doc.text", label: "已读资料", value: "\(ls.markedReadCount)", trend: "", color: Color.zxGreen)
ZXStatBadge(icon: "books.vertical", label: "阅读资料", value: "\(ls.materialsReadCount)", trend: "", color: Color.zxIndigo) ZXStatBadge(icon: "books.vertical", label: "阅读资料", value: "\(ls.materialsReadCount)", trend: "", color: Color.indigo)
} }
} }
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
@ -88,7 +88,7 @@ struct AnalysisHomeView: View {
Spacer() Spacer()
Text("最近 \(viewModel.recentRecords.count)").font(.system(size: 12)).foregroundColor(Color.zxF04) Text("最近 \(viewModel.recentRecords.count)").font(.system(size: 12)).foregroundColor(Color.zxF04)
} }
ForEach(viewModel.recentRecords.prefix(5)) { rec in ForEach(viewModel.recentRecords.prefix(5), id: \.id) { rec in
HStack(spacing: 10) { HStack(spacing: 10) {
Circle().fill(rec.recordType == "reading" ? Color.zxPurple : Color.zxGreen).frame(width: 8, height: 8) Circle().fill(rec.recordType == "reading" ? Color.zxPurple : Color.zxGreen).frame(width: 8, height: 8)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
@ -275,5 +275,5 @@ private func formatDuration(_ seconds: Int) -> String {
private func barHeight(val: Int, max: Int) -> CGFloat { private func barHeight(val: Int, max: Int) -> CGFloat {
guard max > 0 else { return 8 } guard max > 0 else { return 8 }
return max(8, CGFloat(val) / CGFloat(max) * 60) return Swift.max(8, CGFloat(val) / CGFloat(max) * 60)
} }

View File

@ -9,14 +9,20 @@ protocol KnowledgeBaseServicing {
extension KnowledgeBaseService: KnowledgeBaseServicing {} extension KnowledgeBaseService: KnowledgeBaseServicing {}
protocol KnowledgeItemServicing { protocol KnowledgeItemServicing {
func list(knowledgeBaseId: String, sortBy: String?, order: String?) async throws -> [KnowledgeItem] func listAll(knowledgeBaseId: String) async throws -> [KnowledgeItem]
}
extension KnowledgeItemService: KnowledgeItemServicing {
func listAll(knowledgeBaseId: String) async throws -> [KnowledgeItem] {
try await list(knowledgeBaseId: knowledgeBaseId, sortBy: nil, order: nil)
}
} }
extension KnowledgeItemService: KnowledgeItemServicing {}
protocol QuizServicing { protocol QuizServicing {
func list(knowledgeBaseId: String) async throws -> [Quiz] func listAll(knowledgeBaseId: String) async throws -> [Quiz]
}
extension QuizService: QuizServicing {
func listAll(knowledgeBaseId: String) async throws -> [Quiz] { try await list(knowledgeBaseId: knowledgeBaseId) }
} }
extension QuizService: QuizServicing {}
protocol ReadingProgressServicing { protocol ReadingProgressServicing {
func getReadingProgress(materialId: String, targetType: String) async throws -> MaterialReadingProgressDTO func getReadingProgress(materialId: String, targetType: String) async throws -> MaterialReadingProgressDTO
@ -69,8 +75,8 @@ final class MaterialDetailViewModel: ObservableObject {
isLoading = true isLoading = true
async let kbResult = try? kbService.detail(id: knowledgeBaseId) async let kbResult = try? kbService.detail(id: knowledgeBaseId)
async let itemsResult = try? itemService.list(knowledgeBaseId: knowledgeBaseId) async let itemsResult = try? itemService.listAll(knowledgeBaseId: knowledgeBaseId)
async let quizzesResult = try? quizService.list(knowledgeBaseId: knowledgeBaseId) async let quizzesResult = try? quizService.listAll(knowledgeBaseId: knowledgeBaseId)
async let progressResult: MaterialReadingProgressDTO? = { async let progressResult: MaterialReadingProgressDTO? = {
if let mid = materialId { if let mid = materialId {
return try? await readingService.getReadingProgress(materialId: mid, targetType: "knowledge_source") return try? await readingService.getReadingProgress(materialId: mid, targetType: "knowledge_source")

View File

@ -4,9 +4,11 @@ import Foundation
// MARK: - Service protocols (for testability) // MARK: - Service protocols (for testability)
protocol SessionServicing { protocol SessionServicing {
func list() async throws -> [LearningSession] func listAll() async throws -> [LearningSession]
}
extension LearningSessionService: SessionServicing {
func listAll() async throws -> [LearningSession] { try await list() }
} }
extension LearningSessionService: SessionServicing {}
protocol ReviewServicing { protocol ReviewServicing {
func dueCards() async throws -> [ReviewCard] func dueCards() async throws -> [ReviewCard]
@ -14,9 +16,11 @@ protocol ReviewServicing {
extension ReviewService: ReviewServicing {} extension ReviewService: ReviewServicing {}
protocol KBListServicing { protocol KBListServicing {
func list() async throws -> [KnowledgeBase] func listAll() async throws -> [KnowledgeBase]
}
extension KnowledgeBaseService: KBListServicing {
func listAll() async throws -> [KnowledgeBase] { try await list() }
} }
extension KnowledgeBaseService: KBListServicing {}
protocol QuizListServicing { protocol QuizListServicing {
func listAll() async throws -> [Quiz] func listAll() async throws -> [Quiz]
@ -98,10 +102,10 @@ final class StudyHomeViewModel: ObservableObject {
loadingState = .loading loadingState = .loading
banner = nil banner = nil
async let sessions = try? sessionService.list() async let sessions = try? sessionService.listAll()
async let dueCards = try? reviewService.dueCards() async let dueCards = try? reviewService.dueCards()
async let quizzes = try? quizService.listAll() async let quizzes = try? quizService.listAll()
async let knowledgeBases = try? kbService.list() async let knowledgeBases = try? kbService.listAll()
async let summary = try? activityService.summary() async let summary = try? activityService.summary()
async let streak = try? activityService.streak() async let streak = try? activityService.streak()
@ -135,10 +139,10 @@ final class StudyHomeViewModel: ObservableObject {
} }
func refresh() async { func refresh() async {
async let sessions = try? sessionService.list() async let sessions = try? sessionService.listAll()
async let dueCards = try? reviewService.dueCards() async let dueCards = try? reviewService.dueCards()
async let quizzes = try? quizService.listAll() async let quizzes = try? quizService.listAll()
async let knowledgeBases = try? kbService.list() async let knowledgeBases = try? kbService.listAll()
async let summary = try? activityService.summary() async let summary = try? activityService.summary()
async let streak = try? activityService.streak() async let streak = try? activityService.streak()
@ -176,8 +180,7 @@ final class StudyHomeViewModel: ObservableObject {
let resolved = MaterialPathResolver.resolve(materialId: materialId, apiType: cl.type, title: cl.title) { let resolved = MaterialPathResolver.resolve(materialId: materialId, apiType: cl.type, title: cl.title) {
let kbTitle = cl.title ?? knowledgeBases.first?.title ?? "学习" let kbTitle = cl.title ?? knowledgeBases.first?.title ?? "学习"
let elapsed = formatElapsedSince(cl.lastReadAt) let elapsed = formatElapsedSince(cl.lastReadAt)
let pos = cl.lastPosition.flatMap { ReadingPositionAdapter.fromValue($0) } return .continueSession(materialId: materialId, filePath: resolved.filePath, materialType: resolved.materialType, kbTitle: kbTitle, elapsed: elapsed, resumePosition: nil)
return .continueSession(materialId: materialId, filePath: resolved.filePath, materialType: resolved.materialType, kbTitle: kbTitle, elapsed: elapsed, resumePosition: pos)
} }
if let unfinished = sessions if let unfinished = sessions
@ -186,8 +189,7 @@ final class StudyHomeViewModel: ObservableObject {
.first { .first {
let kbTitle = knowledgeBases.first(where: { $0.id == unfinished.knowledgeBaseId })?.title ?? "学习" let kbTitle = knowledgeBases.first(where: { $0.id == unfinished.knowledgeBaseId })?.title ?? "学习"
let elapsed = formatElapsedSince(unfinished.startedAt) let elapsed = formatElapsedSince(unfinished.startedAt)
let mt: MaterialType = unfinished.readingTargetType == "knowledge_source" ? .unknown : .unknown return .continueSession(materialId: "", filePath: "", materialType: .unknown, kbTitle: kbTitle, elapsed: elapsed, resumePosition: nil)
return .continueSession(materialId: unfinished.materialId ?? "", filePath: "", materialType: mt, kbTitle: kbTitle, elapsed: elapsed, resumePosition: nil)
} }
if !dueCards.isEmpty { if !dueCards.isEmpty {

View File

@ -10,7 +10,7 @@ private final class MockKnowledgeBaseService: KnowledgeBaseServicing {
private final class MockKnowledgeItemService: KnowledgeItemServicing { private final class MockKnowledgeItemService: KnowledgeItemServicing {
var items: [KnowledgeItem] = [] var items: [KnowledgeItem] = []
func list(knowledgeBaseId: String, sortBy: String?, order: String?) async throws -> [KnowledgeItem] { items } func listAll(knowledgeBaseId: String) async throws -> [KnowledgeItem] { items }
} }
private final class MockQuizService: QuizServicing { private final class MockQuizService: QuizServicing {

View File

@ -5,7 +5,7 @@ import XCTest
private final class MockSessionService: SessionServicing { private final class MockSessionService: SessionServicing {
var sessions: [LearningSession] = [] var sessions: [LearningSession] = []
func list() async throws -> [LearningSession] { sessions } func listAll() async throws -> [LearningSession] { sessions }
} }
private final class MockReviewService: ReviewServicing { private final class MockReviewService: ReviewServicing {
@ -20,7 +20,7 @@ private final class MockQuizListService: QuizListServicing {
private final class MockKBListService: KBListServicing { private final class MockKBListService: KBListServicing {
var kbs: [KnowledgeBase] = [] var kbs: [KnowledgeBase] = []
func list() async throws -> [KnowledgeBase] { kbs } func listAll() async throws -> [KnowledgeBase] { kbs }
} }
private final class MockActivityHomeService: ActivityHomeServicing { private final class MockActivityHomeService: ActivityHomeServicing {