From be7fca5305ac6798f3e3f97ba15e115ba134ae60 Mon Sep 17 00:00:00 2001 From: WangDL Date: Mon, 18 May 2026 11:35:23 +0800 Subject: [PATCH] fix: resolve 3 compilation errors - NotificationListView.swift: unwrap optional String? for Text() - LocalCache.swift: add @escaping to fetch closure parameter - ContentView.swift: reformat ZXTabBar body for item scope fix Co-Authored-By: Claude Opus 4.7 --- AIStudyApp/AIStudyApp/ContentView.swift | 122 +++++++++++++++--- .../AIStudyApp/Core/Security/LocalCache.swift | 4 +- .../Profile/NotificationListView.swift | 2 +- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/AIStudyApp/AIStudyApp/ContentView.swift b/AIStudyApp/AIStudyApp/ContentView.swift index 202b77e..1732bd1 100644 --- a/AIStudyApp/AIStudyApp/ContentView.swift +++ b/AIStudyApp/AIStudyApp/ContentView.swift @@ -21,42 +21,124 @@ struct ContentView: View { struct ZXTabBar: View { @Binding var active: String - private let items = [("ai","AI","brain.head.profile"),("library","知识库","books.vertical.fill"),("study","学习","bolt.fill"),("analysis","分析","chart.bar.fill"),("profile","我的","person.fill")] - var body: some View{HStack(spacing:0){ForEach(items,id:\.0){item in let on=item.0==active;Button{active=item.0}label:{VStack(spacing:4){ZStack{if on{Circle().fill(Color.zxPurple.opacity(0.2)).frame(width:28,height:28).scaleEffect(1.4)};Image(systemName:item.2).font(.system(size:22,weight:on ? .semibold:.regular)).foregroundColor(on ? Color.zxPurple:Color.zxF03)};Text(item.1).font(.system(size:10,weight:on ? .semibold:.regular)).foregroundColor(on ? Color.zxPurple:Color.zxF03)}}.frame(maxWidth:.infinity)}.accessibilityLabel("\(item.1)标签")}.padding(.top,6).padding(.bottom,34).frame(height:83).background(.ultraThinMaterial).background(Color.zxBg0.opacity(0.95)).overlay(alignment:.top){Rectangle().fill(Color.zxBorder008).frame(height:1)}} + private let items = [ + ("ai", "AI", "brain.head.profile"), + ("library", "知识库", "books.vertical.fill"), + ("study", "学习", "bolt.fill"), + ("analysis", "分析", "chart.bar.fill"), + ("profile", "我的", "person.fill"), + ] + + var body: some View { + HStack(spacing: 0) { + ForEach(items, id: \.0) { item in + let on = item.0 == active + Button { + active = item.0 + } label: { + VStack(spacing: 4) { + ZStack { + if on { + Circle() + .fill(Color.zxPurple.opacity(0.2)) + .frame(width: 28, height: 28) + .scaleEffect(1.4) + } + Image(systemName: item.2) + .font(.system(size: 22, weight: on ? .semibold : .regular)) + .foregroundColor(on ? Color.zxPurple : Color.zxF03) + } + Text(item.1) + .font(.system(size: 10, weight: on ? .semibold : .regular)) + .foregroundColor(on ? Color.zxPurple : Color.zxF03) + } + .frame(maxWidth: .infinity) + } + .accessibilityLabel("\(item.1)标签") + } + } + .padding(.top, 6) + .padding(.bottom, 34) + .frame(height: 83) + .background(.ultraThinMaterial) + .background(Color.zxBg0.opacity(0.95)) + .overlay(alignment: .top) { + Rectangle().fill(Color.zxBorder008).frame(height: 1) + } + } } struct ZXIconBtn: View { let icon: String; let size: CGFloat; var branded = false; let action: () -> Void - var body: some View {Button(action:action){Image(systemName:icon).font(.system(size:size*0.44)).frame(width:size,height:size)}.foregroundColor(branded ? .white:Color.zxF05).background(branded ? AnyView(ZXGradient.brand):AnyView(Color(hex:"#FFFFFF",opacity:0.05))).clipShape(RoundedRectangle(cornerRadius:10)).overlay{if !branded{RoundedRectangle(cornerRadius:10).stroke(Color.zxBorder008,lineWidth:1)}}} + var body: some View { + Button(action: action) { + Image(systemName: icon).font(.system(size: size * 0.44)).frame(width: size, height: size) + } + .foregroundColor(branded ? .white : Color.zxF05) + .background(branded ? AnyView(ZXGradient.brand) : AnyView(Color(hex: "#FFFFFF", opacity: 0.05))) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .overlay { + if !branded { + RoundedRectangle(cornerRadius: 10).stroke(Color.zxBorder008, lineWidth: 1) + } + } + } } -struct ZXScoreBox: View {let score:Int;let bg:Color;let fg:Color - var body: some View {Text("\(score)").font(.system(size:12,weight:.heavy)).foregroundColor(fg).frame(width:36,height:36).background(bg).clipShape(RoundedRectangle(cornerRadius:10))} +struct ZXScoreBox: View { + let score: Int; let bg: Color; let fg: Color + var body: some View { + Text("\(score)") + .font(.system(size: 12, weight: .heavy)) + .foregroundColor(fg) + .frame(width: 36, height: 36) + .background(bg) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } } struct ZXWeakRow: View { let score: Int; let topic: String; let lib: String; let priority: String var body: some View { HStack(spacing: 12) { - Text("\(score)").font(.system(size:13,weight:.heavy)).foregroundColor(Color.zxYellow) - .frame(width:40,height:40).background(Color.zxYellowBG(0.15)).clipShape(RoundedRectangle(cornerRadius:12)) - VStack(alignment:.leading,spacing:2){ - Text(topic).font(.system(size:13,weight:.semibold)).foregroundColor(Color.zxF0) - Text(lib).font(.system(size:11)).foregroundColor(Color.zxF04) - }.frame(maxWidth:.infinity,alignment:.leading) - Text("\(priority)优先").font(.system(size:11,weight:.bold)) - .foregroundColor(priority=="高" ? Color.zxRed:Color.zxYellow) - .padding(.horizontal,8).padding(.vertical,3) - .background((priority=="高" ? Color.zxRedBG(0.15):Color.zxYellowBG(0.15))).clipShape(Capsule()) + Text("\(score)").font(.system(size: 13, weight: .heavy)).foregroundColor(Color.zxYellow) + .frame(width: 40, height: 40).background(Color.zxYellowBG(0.15)).clipShape(RoundedRectangle(cornerRadius: 12)) + VStack(alignment: .leading, spacing: 2) { + Text(topic).font(.system(size: 13, weight: .semibold)).foregroundColor(Color.zxF0) + Text(lib).font(.system(size: 11)).foregroundColor(Color.zxF04) + }.frame(maxWidth: .infinity, alignment: .leading) + Text("\(priority)优先").font(.system(size: 11, weight: .bold)) + .foregroundColor(priority == "高" ? Color.zxRed : Color.zxYellow) + .padding(.horizontal, 8).padding(.vertical, 3) + .background((priority == "高" ? Color.zxRedBG(0.15) : Color.zxYellowBG(0.15))).clipShape(Capsule()) } - .padding(.horizontal,16).padding(.vertical,12) + .padding(.horizontal, 16).padding(.vertical, 12) .background(Color.zxYellowBG(0.06)) - .overlay(RoundedRectangle(cornerRadius:14).stroke(Color(hex:"#F59E0B",opacity:0.15),lineWidth:1)) - .clipShape(RoundedRectangle(cornerRadius:14)) + .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color(hex: "#F59E0B", opacity: 0.15), lineWidth: 1)) + .clipShape(RoundedRectangle(cornerRadius: 14)) } } struct ZXAIInputBar: View { - @Binding var text:String;let onSend:()->Void - var body: some View {HStack(spacing:10){Image(systemName:"sparkles").font(.system(size:16)).foregroundColor(Color.zxPurple);TextField("问 AI 任何学习问题…",text:$text).font(.system(size:14)).tint(Color.zxPurple).accessibilityLabel("AI 学习问题输入框");Spacer();Image(systemName:"mic.fill").font(.system(size:18)).foregroundColor(Color.zxF03).accessibilityLabel("语音输入");Button(action:onSend){Image(systemName:"arrow.up").font(.system(size:14,weight:.bold)).foregroundColor(.white).frame(width:30,height:30).background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius:9))}.zxPressable().disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty).accessibilityLabel("发送消息")}.padding(.horizontal,14).padding(.vertical,10).background(.ultraThinMaterial).background(Color.zxFill004).overlay(RoundedRectangle(cornerRadius:20).stroke(Color.zxBorder008,lineWidth:1)).clipShape(RoundedRectangle(cornerRadius:20)).padding(.horizontal,20).padding(.bottom,34)} + @Binding var text: String; let onSend: () -> Void + var body: some View { + HStack(spacing: 10) { + Image(systemName: "sparkles").font(.system(size: 16)).foregroundColor(Color.zxPurple) + TextField("问 AI 任何学习问题…", text: $text).font(.system(size: 14)).tint(Color.zxPurple).accessibilityLabel("AI 学习问题输入框") + Spacer() + Image(systemName: "mic.fill").font(.system(size: 18)).foregroundColor(Color.zxF03).accessibilityLabel("语音输入") + Button(action: onSend) { + Image(systemName: "arrow.up").font(.system(size: 14, weight: .bold)).foregroundColor(.white) + .frame(width: 30, height: 30).background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius: 9)) + } + .zxPressable() + .disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + .accessibilityLabel("发送消息") + } + .padding(.horizontal, 14).padding(.vertical, 10) + .background(.ultraThinMaterial).background(Color.zxFill004) + .overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder008, lineWidth: 1)) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .padding(.horizontal, 20).padding(.bottom, 34) + } } diff --git a/AIStudyApp/AIStudyApp/Core/Security/LocalCache.swift b/AIStudyApp/AIStudyApp/Core/Security/LocalCache.swift index 674f05e..4641db7 100644 --- a/AIStudyApp/AIStudyApp/Core/Security/LocalCache.swift +++ b/AIStudyApp/AIStudyApp/Core/Security/LocalCache.swift @@ -70,7 +70,7 @@ extension LocalCache { func cacheFirst( key: String, ttl: TimeInterval = 300, - fetch: @Sendable () async throws -> T + fetch: @Sendable @escaping () async throws -> T ) async throws -> T { if let cached: T = getWithExpiry(key, ttl: ttl) { Task { try? await refreshCache(key: key, ttl: ttl, fetch: fetch) } @@ -84,7 +84,7 @@ extension LocalCache { private func refreshCache( key: String, ttl: TimeInterval, - fetch: @Sendable () async throws -> T + fetch: @Sendable @escaping () async throws -> T ) async throws { let fresh = try await fetch() setWithExpiry(fresh, forKey: key, ttl: ttl) diff --git a/AIStudyApp/AIStudyApp/Features/Profile/NotificationListView.swift b/AIStudyApp/AIStudyApp/Features/Profile/NotificationListView.swift index 5d3de80..85f24be 100644 --- a/AIStudyApp/AIStudyApp/Features/Profile/NotificationListView.swift +++ b/AIStudyApp/AIStudyApp/Features/Profile/NotificationListView.swift @@ -94,7 +94,7 @@ struct ZXNotificationItemRow: View { Circle().fill(Color.zxPurple).frame(width: 6, height: 6) } } - Text(item.content).font(.system(size: 12)).foregroundColor(Color.zxF04).lineLimit(2) + Text(item.content ?? "").font(.system(size: 12)).foregroundColor(Color.zxF04).lineLimit(2) if let createdAt = item.createdAt { Text(createdAt.prefix(10).description).font(.system(size: 10)).foregroundColor(Color.zxF03) }