From 6c363ea88483c8c468e265f8f7683205cfd95d6f Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Tue, 22 Jul 2025 15:13:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E5=8A=9F=E8=83=BD=E5=8F=8A=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在APIEndpoints.swift中新增publishFeed端点以支持发布动态。 - 新增PublishFeedRequest和PublishFeedResponse模型,处理发布请求和响应。 - 在EditFeedFeature中实现动态编辑功能,支持用户输入和发布内容。 - 更新CreateFeedView和EditFeedView以集成新的发布功能,提升用户体验。 - 在Localizable.strings中添加相关文本的本地化支持,确保多语言兼容性。 - 优化FeedListView和FeedView以展示最新动态,增强用户交互体验。 --- yana/APIs/APIEndpoints.swift | 3 + yana/APIs/DynamicsModels.swift | 68 +++++- yana/AppDelegate.swift | 4 +- yana/Features/EditFeedFeature.swift | 89 ++++++++ .../{ => en.lproj}/Localizable.strings | 67 +++++- .../zh-Hans.lproj/Localizable.strings | 39 ++++ yana/Utils/Extensions/Color+Hex.swift | 26 ++- yana/Views/CreateFeedView.swift | 10 +- yana/Views/EditFeedView.swift | 198 +++++++++++++++- yana/Views/FeedListView.swift | 15 +- yana/Views/FeedView.swift | 25 ++- yana/Views/SettingView.swift | 211 ++++++++++-------- yana/Views/SplashView.swift | 2 +- 13 files changed, 624 insertions(+), 133 deletions(-) create mode 100644 yana/Features/EditFeedFeature.swift rename yana/Resources/{ => en.lproj}/Localizable.strings (60%) diff --git a/yana/APIs/APIEndpoints.swift b/yana/APIs/APIEndpoints.swift index efd0073..ea5c0ae 100644 --- a/yana/APIs/APIEndpoints.swift +++ b/yana/APIs/APIEndpoints.swift @@ -21,10 +21,13 @@ enum APIEndpoint: String, CaseIterable { case emailGetCode = "/email/getCode" // 新增:邮箱验证码获取端点 case latestDynamics = "/dynamic/square/latestDynamics" // 新增:获取最新动态列表端点 case tcToken = "/tencent/cos/getToken" // 新增:腾讯云 COS Token 获取端点 + case publishFeed = "/dynamic/square/publish" // 发布动态 + // Web 页面路径 case userAgreement = "/modules/rule/protocol.html" case privacyPolicy = "/modules/rule/privacy-wap.html" + var path: String { return self.rawValue } diff --git a/yana/APIs/DynamicsModels.swift b/yana/APIs/DynamicsModels.swift index f8cecef..c11cf2d 100644 --- a/yana/APIs/DynamicsModels.swift +++ b/yana/APIs/DynamicsModels.swift @@ -157,4 +157,70 @@ struct LatestDynamicsRequest: APIRequestProtocol { // Loading 配置 var shouldShowLoading: Bool { true } var shouldShowError: Bool { true } -} \ No newline at end of file +} + +// MARK: - 发布动态 API 请求与响应 + +/// 发布动态请求 +struct PublishFeedRequest: APIRequestProtocol { + typealias Response = PublishFeedResponse + + let endpoint: String = APIEndpoint.publishFeed.path + let method: HTTPMethod = .POST + + let content: String + let uid: String + let type: String + var pub_sign: String + + var queryParameters: [String: String]? { nil } + var bodyParameters: [String: Any]? { + [ + "content": content, + "uid": uid, + "type": type, + "pub_sign": pub_sign + ] + } + var includeBaseParameters: Bool { true } + var shouldShowLoading: Bool { true } + var shouldShowError: Bool { true } + + /// async 工厂方法,主线程生成 pub_sign + static func make(content: String, uid: String, type: String = "0") async -> PublishFeedRequest { + let base = await MainActor.run { BaseRequest() } + var mutableBase = base + mutableBase.generateSignature(with: [ + "content": content, + "uid": uid, + "type": type + ]) + return PublishFeedRequest( + content: content, + uid: uid, + type: type, + pub_sign: mutableBase.pubSign + ) + } + + /// 禁止外部直接调用 + private init(content: String, uid: String, type: String, pub_sign: String) { + self.content = content + self.uid = uid + self.type = type + self.pub_sign = pub_sign + } +} + +/// 发布动态响应 +struct PublishFeedResponse: Codable, Equatable { + let code: Int + let message: String + let data: PublishFeedData? + let timestamp: Int? +} + +/// 发布动态返回数据 +struct PublishFeedData: Codable, Equatable { + let dynamicId: Int? +} diff --git a/yana/AppDelegate.swift b/yana/AppDelegate.swift index 071d4f5..911f876 100644 --- a/yana/AppDelegate.swift +++ b/yana/AppDelegate.swift @@ -2,7 +2,9 @@ import UIKit //import NIMSDK class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) async -> Bool { + private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) async -> Bool { + +// isPerceptionCheckingEnabled = false // 执行数据迁移(从 UserDefaults 到 Keychain) DataMigrationManager.performStartupMigration() diff --git a/yana/Features/EditFeedFeature.swift b/yana/Features/EditFeedFeature.swift new file mode 100644 index 0000000..21c68a7 --- /dev/null +++ b/yana/Features/EditFeedFeature.swift @@ -0,0 +1,89 @@ +import Foundation +import ComposableArchitecture +import SwiftUI + +@Reducer +struct EditFeedFeature { + @ObservableState + struct State: Equatable { + var content: String = "" + var isLoading: Bool = false + var errorMessage: String? = nil + var shouldDismiss: Bool = false + + var canPublish: Bool { + !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isLoading + } + } + + enum Action { + case contentChanged(String) + case publishButtonTapped + case publishResponse(Result) + case clearError + case dismissView + case clearDismissFlag + } + + @Dependency(\.apiService) var apiService + @Dependency(\.dismiss) var dismiss + @Dependency(\.isPresented) var isPresented + + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .contentChanged(let newContent): + state.content = newContent + return .none + + case .publishButtonTapped: + guard state.canPublish else { + state.errorMessage = "请输入内容" + return .none + } + state.isLoading = true + state.errorMessage = nil + + return .run { [content = state.content] send in + let userId = await UserInfoManager.getCurrentUserId() ?? "" + let request = await PublishFeedRequest.make( + content: content.trimmingCharacters(in: .whitespacesAndNewlines), + uid: userId, + type: "0" + ) + do { + let response = try await apiService.request(request) + await send(.publishResponse(.success(response))) + } catch { + await send(.publishResponse(.failure(error))) + } + } + + case .publishResponse(.success(let response)): + state.isLoading = false + if response.code == 200 { + return .send(.dismissView) + } else { + state.errorMessage = response.message.isEmpty ? "发布失败" : response.message + return .none + } + + case .publishResponse(.failure(let error)): + state.isLoading = false + state.errorMessage = error.localizedDescription + return .none + + case .clearError: + state.errorMessage = nil + return .none + + case .dismissView: + state.shouldDismiss = true + return .none + case .clearDismissFlag: + state.shouldDismiss = false + return .none + } + } + } +} \ No newline at end of file diff --git a/yana/Resources/Localizable.strings b/yana/Resources/en.lproj/Localizable.strings similarity index 60% rename from yana/Resources/Localizable.strings rename to yana/Resources/en.lproj/Localizable.strings index 8b48b84..75ce070 100644 --- a/yana/Resources/Localizable.strings +++ b/yana/Resources/en.lproj/Localizable.strings @@ -2,11 +2,10 @@ Localizable.strings yana - Created on 2024. - 英文本地化文件 + English localization file (auto-aligned) */ -// MARK: - 登录界面 +// MARK: - Login Screen "login.id_login" = "ID Login"; "login.email_login" = "Email Login"; "login.app_title" = "E-PARTI"; @@ -14,32 +13,32 @@ "login.agreement" = "User Service Agreement"; "login.policy" = "Privacy Policy"; -// MARK: - 通用按钮 +// MARK: - Common Buttons "common.login" = "Login"; "common.register" = "Register"; "common.cancel" = "Cancel"; "common.confirm" = "Confirm"; "common.ok" = "OK"; -// MARK: - 错误信息 +// MARK: - Error Messages "error.network" = "Network Error"; "error.invalid_input" = "Invalid Input"; "error.login_failed" = "Login Failed"; -// MARK: - 占位符文本 +// MARK: - Placeholders "placeholder.email" = "Enter your email"; "placeholder.password" = "Enter your password"; "placeholder.username" = "Enter your username"; "placeholder.enter_id" = "Please enter ID"; "placeholder.enter_password" = "Please enter password"; -// MARK: - ID登录页面 +// MARK: - ID Login Page "id_login.title" = "ID Login"; "id_login.forgot_password" = "Forgot Password?"; "id_login.login_button" = "Login"; "id_login.logging_in" = "Logging in..."; -// MARK: - 邮箱登录页面 +// MARK: - Email Login Page "email_login.title" = "Email Login"; "email_login.email_required" = "Please enter email"; "email_login.invalid_email" = "Please enter a valid email address"; @@ -52,13 +51,13 @@ "placeholder.enter_email" = "Please enter email"; "placeholder.enter_verification_code" = "Please enter verification code"; -// MARK: - 验证和错误信息 +// MARK: - Validation and Error Messages "validation.id_required" = "Please enter your ID"; "validation.password_required" = "Please enter your password"; "error.encryption_failed" = "Encryption failed, please try again"; "error.login_failed" = "Login failed, please check your credentials"; -// MARK: - 密码恢复页面 +// MARK: - Password Recovery Page "recover_password.title" = "Recover Password"; "recover_password.placeholder_email" = "Please enter email"; "recover_password.placeholder_verification_code" = "Please enter verification code"; @@ -74,5 +73,49 @@ "recover_password.reset_success" = "Password reset successfully"; "recover_password.resetting" = "Resetting..."; -// MARK: - 主页 -"home.title" = "Enjoy your Life Time"; +// MARK: - Home +"home.title" = "Enjoy your Life Time"; + +// MARK: - Create Feed +"createFeed.enterContent" = "Enter Content"; +"createFeed.processingImages" = "Processing images..."; +"createFeed.publishing" = "Publishing..."; +"createFeed.publish" = "Publish"; +"createFeed.title" = "Image & Text Publish"; + +// MARK: - Edit Feed +"editFeed.title" = "Image & Text Edit"; +"editFeed.publish" = "Publish"; +"editFeed.enterContent" = "Enter Content"; + +// MARK: - Feed List +"feedList.title" = "Enjoy your Life Time"; +"feedList.slogan" = "The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable."; + +// MARK: - Feed +"feed.title" = "Enjoy your Life Time"; +"feed.empty" = "No moments yet"; +"feed.error" = "Error: %@"; +"feed.retry" = "Retry"; +"feed.loadingMore" = "Loading more..."; +"me.title" = "Me"; +"me.nickname" = "Nickname"; +"me.id" = "ID: %@"; +"language.select" = "Select Language"; +"language.current" = "Current Language"; +"language.info" = "Language Info"; +"feed.user" = "User %d"; +"feed.2hoursago" = "2 hours ago"; +"feed.demoContent" = "Today is a beautiful day, sharing some little happiness in life. Hope everyone cherishes every moment."; +"feed.vip" = "VIP%d"; + +// MARK: - Splash +"splash.title" = "E-Parti"; + +// MARK: - Setting +"setting.title" = "Settings"; +"setting.user" = "User"; +"setting.language" = "Language Settings"; +"setting.about" = "About Us"; +"setting.version" = "Version Info"; +"setting.logout" = "Logout"; \ No newline at end of file diff --git a/yana/Resources/zh-Hans.lproj/Localizable.strings b/yana/Resources/zh-Hans.lproj/Localizable.strings index 9eb453e..05ed160 100644 --- a/yana/Resources/zh-Hans.lproj/Localizable.strings +++ b/yana/Resources/zh-Hans.lproj/Localizable.strings @@ -76,3 +76,42 @@ // MARK: - 主页 "home.title" = "享受您的生活时光"; + +"createFeed.enterContent" = "输入内容"; +"createFeed.processingImages" = "处理图片中..."; +"createFeed.publishing" = "发布中..."; +"createFeed.publish" = "发布"; +"createFeed.title" = "图文发布"; + +"editFeed.title" = "图文发布"; +"editFeed.publish" = "发布"; +"editFeed.enterContent" = "输入内容"; + +"feedList.title" = "享受您的生活时光"; +"feedList.slogan" = "疾病如同残酷的统治者,\n而时间是我们最宝贵的财富。\n我们活着的每一刻,都是对不可避免命运的胜利。"; + +"feed.title" = "享受您的生活时光"; +"feed.empty" = "暂无动态内容"; +"feed.error" = "错误: %@"; +"feed.retry" = "重试"; +"feed.loadingMore" = "加载更多..."; + +"splash.title" = "E-Parti"; + +"setting.title" = "设置"; +"setting.user" = "用户"; +"setting.language" = "语言设置"; +"setting.about" = "关于我们"; +"setting.version" = "版本信息"; +"setting.logout" = "退出登录"; + +"me.title" = "我的"; +"me.nickname" = "用户昵称"; +"me.id" = "ID: %@"; +"language.select" = "选择语言"; +"language.current" = "当前语言"; +"language.info" = "语言信息"; +"feed.user" = "用户%d"; +"feed.2hoursago" = "2小时前"; +"feed.demoContent" = "今天是美好的一天,分享一些生活中的小确幸。希望大家都能珍惜每一个当下的时刻。"; +"feed.vip" = "VIP%d"; diff --git a/yana/Utils/Extensions/Color+Hex.swift b/yana/Utils/Extensions/Color+Hex.swift index 3548eb2..71c2345 100644 --- a/yana/Utils/Extensions/Color+Hex.swift +++ b/yana/Utils/Extensions/Color+Hex.swift @@ -23,4 +23,28 @@ extension Color { let blue = Double(hex & 0xFF) / 255.0 self.init(red: red, green: green, blue: blue, opacity: alpha) } -} \ No newline at end of file + + init(hexString: String) { + let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (255, 0, 0, 0) + } + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} diff --git a/yana/Views/CreateFeedView.swift b/yana/Views/CreateFeedView.swift index 2210eed..2cd98b5 100644 --- a/yana/Views/CreateFeedView.swift +++ b/yana/Views/CreateFeedView.swift @@ -25,7 +25,7 @@ struct CreateFeedView: View { .frame(height: 200) // 高度固定为200 if store.content.isEmpty { - Text("Enter Content") + Text(NSLocalizedString("createFeed.enterContent", comment: "Enter Content")) .foregroundColor(.white.opacity(0.5)) .padding(.horizontal, 16) .padding(.vertical, 12) @@ -79,7 +79,7 @@ struct CreateFeedView: View { HStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) - Text("处理图片中...") + Text(NSLocalizedString("createFeed.processingImages", comment: "Processing images...")) .font(.system(size: 14)) .foregroundColor(.white.opacity(0.8)) } @@ -111,11 +111,11 @@ struct CreateFeedView: View { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(0.8) - Text("发布中...") + Text(NSLocalizedString("createFeed.publishing", comment: "Publishing...")) .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) } else { - Text("发布") + Text(NSLocalizedString("createFeed.publish", comment: "Publish")) .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) } @@ -137,7 +137,7 @@ struct CreateFeedView: View { ) } } - .navigationTitle("图文发布") + .navigationTitle(NSLocalizedString("createFeed.title", comment: "Image & Text Publish")) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.hidden, for: .navigationBar) .navigationBarBackButtonHidden(true) diff --git a/yana/Views/EditFeedView.swift b/yana/Views/EditFeedView.swift index f2637da..f878dff 100644 --- a/yana/Views/EditFeedView.swift +++ b/yana/Views/EditFeedView.swift @@ -1,19 +1,195 @@ import SwiftUI +import ComposableArchitecture struct EditFeedView: View { + let onDismiss: () -> Void + let store: StoreOf + @State private var isKeyboardVisible = false + private let maxCount = 500 + + private func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + var body: some View { - VStack(spacing: 20) { - Text("编辑动态") - .font(.title) - .bold() - Text("这里是 EditFeedView 占位内容") - .foregroundColor(.gray) - Spacer() + WithPerceptionTracking { + GeometryReader { geometry in + WithViewStore(store, observe: { $0 }) { viewStore in + WithPerceptionTracking { + ZStack { + backgroundView + mainContent(geometry: geometry, viewStore: viewStore) +// if viewStore.isLoading { +// loadingOverlay +// } + } + .contentShape(Rectangle()) + .onTapGesture { + if isKeyboardVisible { + hideKeyboard() + } + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in + withAnimation(.easeInOut(duration: 0.3)) { + isKeyboardVisible = true + } + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in + withAnimation(.easeInOut(duration: 0.3)) { + isKeyboardVisible = false + } + } + .onChange(of: viewStore.errorMessage) { error in + if error != nil { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + viewStore.send(.clearError) + } + } + } + .onChange(of: viewStore.shouldDismiss) { shouldDismiss in + if shouldDismiss { + onDismiss() + viewStore.send(.clearDismissFlag) + } + } + } + } + } + } + } + + private var backgroundView: some View { + Color(hexString: "0C0527") + .ignoresSafeArea() + } + + private func mainContent(geometry: GeometryProxy, viewStore: ViewStoreOf) -> some View { + VStack(spacing: 0) { + headerView(geometry: geometry, viewStore: viewStore) + textInputArea(viewStore: viewStore) + Spacer() + if !isKeyboardVisible { + publishButtonBottom(viewStore: viewStore, geometry: geometry) + } + } + } + + private func headerView(geometry: GeometryProxy, viewStore: ViewStoreOf) -> some View { + HStack { + Text(NSLocalizedString("editFeed.title", comment: "Image & Text Edit")) + .font(.system(size: 20, weight: .semibold)) + .foregroundColor(.white) + Spacer() + if isKeyboardVisible { + WithPerceptionTracking { + Button(action: { + hideKeyboard() + viewStore.send(.publishButtonTapped) + }) { + Text(NSLocalizedString("editFeed.publish", comment: "Publish")) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background( + LinearGradient( + colors: [ + Color(hexString: "A14AC6"), + Color(hexString: "3B1EEB") + ], + startPoint: .leading, + endPoint: .trailing + ) + .cornerRadius(16) + ) + } + .disabled(!viewStore.canPublish) + } + } + } + .padding(.horizontal, 24) + .padding(.top, geometry.safeAreaInsets.top + 16) + .padding(.bottom, 24) + } + + private func textInputArea(viewStore: ViewStoreOf) -> some View { + ZStack(alignment: .topLeading) { + RoundedRectangle(cornerRadius: 20) + .fill(Color(hexString: "1C143A")) + TextEditor(text: Binding( + get: { viewStore.content }, + set: { viewStore.send(.contentChanged($0)) } + )) + .scrollContentBackground(.hidden) + .padding(16) + .frame(height: 160) + .foregroundColor(.white) + .background(.clear) + .cornerRadius(20) + .font(.system(size: 16)) + if viewStore.content.isEmpty { + Text(NSLocalizedString("editFeed.enterContent", comment: "Enter Content")) + .foregroundColor(Color.white.opacity(0.4)) + .padding(20) + .font(.system(size: 16)) + } + WithPerceptionTracking { + VStack { + Spacer() + HStack { + Spacer() + Text("\(viewStore.content.count)/\(maxCount)") + .foregroundColor(Color.white.opacity(0.4)) + .font(.system(size: 14)) + .padding(.trailing, 16) + .padding(.bottom, 10) + } + } + } + } + .frame(height: 160) + .padding(.horizontal, 24) + .padding(.bottom, 32) + } + + private func publishButtonBottom(viewStore: ViewStoreOf, geometry: GeometryProxy) -> some View { + VStack { + Spacer() + Button(action: { + hideKeyboard() + viewStore.send(.publishButtonTapped) + }) { + Text(NSLocalizedString("editFeed.publish", comment: "Publish")) + .font(.system(size: 18, weight: .semibold)) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .frame(height: 56) + .background( + LinearGradient( + colors: [Color(hexString: "A14AC6"), Color(hexString: "3B1EEB")], + startPoint: .leading, + endPoint: .trailing + ) + .cornerRadius(28) + ) + } + .padding(.horizontal, 24) + .padding(.bottom, geometry.safeAreaInsets.bottom + 24) + .disabled(!viewStore.canPublish) + } + } + + private var loadingOverlay: some View { + Group { + Color.black.opacity(0.3) + .ignoresSafeArea() + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .white)) + .scaleEffect(1.5) } - .padding() } } -#Preview { - EditFeedView() -} \ No newline at end of file +//#Preview { +// EditFeedView() +//} diff --git a/yana/Views/FeedListView.swift b/yana/Views/FeedListView.swift index 44ae472..711d609 100644 --- a/yana/Views/FeedListView.swift +++ b/yana/Views/FeedListView.swift @@ -20,7 +20,7 @@ struct FeedListView: View { HStack { Spacer(minLength: 0) Spacer(minLength: 0) - Text("Enjoy your Life Time") + Text(NSLocalizedString("feedList.title", comment: "Enjoy your Life Time")) .font(.system(size: 22, weight: .semibold)) .foregroundColor(.white) .frame(maxWidth: .infinity, alignment: .center) @@ -40,7 +40,7 @@ struct FeedListView: View { .font(.system(size: 60)) .foregroundColor(.red) .padding(.top, 40) - Text("The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable.") + Text(NSLocalizedString("feedList.slogan", comment: "The disease is like a cruel ruler,\nand time is our most precious treasure.\nEvery moment we live is a victory\nagainst the inevitable.")) .font(.system(size: 16)) .multilineTextAlignment(.center) .foregroundColor(.white.opacity(0.9)) @@ -58,7 +58,16 @@ struct FeedListView: View { get: \.isEditFeedPresented, send: { $0 ? .editFeedButtonTapped : .editFeedDismissed } )) { - EditFeedView() + EditFeedView( + onDismiss: { + viewStore.send(.editFeedDismissed) + }, + store: Store( + initialState: EditFeedFeature.State() + ) { + EditFeedFeature() + } + ) } } } diff --git a/yana/Views/FeedView.swift b/yana/Views/FeedView.swift index 1ff9611..0431ce8 100644 --- a/yana/Views/FeedView.swift +++ b/yana/Views/FeedView.swift @@ -8,12 +8,12 @@ struct FeedTopBarView: View { WithPerceptionTracking { HStack { Spacer() - Text("Enjoy your Life Time") + Text(NSLocalizedString("feed.title", comment: "Enjoy your Life Time")) .font(.system(size: 22, weight: .semibold)) .foregroundColor(.white) Spacer() Button(action: { - onShowCreateFeed() // 只调用回调 +// showEditFeed = true // 显示编辑界面 }) { Image("add icon") .frame(width: 36, height: 36) @@ -34,11 +34,11 @@ struct FeedMomentsListView: View { Image(systemName: "heart.text.square") .font(.system(size: 40)) .foregroundColor(.white.opacity(0.6)) - Text("暂无动态内容") + Text(NSLocalizedString("feed.empty", comment: "No moments yet")) .font(.system(size: 16)) .foregroundColor(.white.opacity(0.8)) if let error = store.error { - Text("错误: \(error)") + Text(String(format: NSLocalizedString("feed.error", comment: "Error: %@"), error)) .font(.system(size: 12)) .foregroundColor(.red.opacity(0.8)) .multilineTextAlignment(.center) @@ -50,7 +50,7 @@ struct FeedMomentsListView: View { Button(action: { store.send(.retryLoad) }) { - Text("重试") + Text(NSLocalizedString("feed.retry", comment: "Retry")) .font(.system(size: 14, weight: .medium)) .foregroundColor(.white) .padding(.horizontal, 20) @@ -85,7 +85,7 @@ struct FeedMomentsListView: View { HStack { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) - Text("加载更多...") + Text(NSLocalizedString("feed.loadingMore", comment: "Loading more...")) .font(.system(size: 14)) .foregroundColor(.white.opacity(0.8)) } @@ -102,6 +102,7 @@ struct FeedMomentsListView: View { struct FeedView: View { let store: StoreOf let onShowCreateFeed: () -> Void + @State private var showEditFeed = false var body: some View { WithPerceptionTracking { @@ -154,6 +155,18 @@ struct FeedView: View { .onAppear { store.send(.onAppear) } + .sheet(isPresented: $showEditFeed) { + EditFeedView( + onDismiss: { + showEditFeed = false + }, + store: Store( + initialState: EditFeedFeature.State() + ) { + EditFeedFeature() + } + ) + } } } } diff --git a/yana/Views/SettingView.swift b/yana/Views/SettingView.swift index ac2d47f..4ab447f 100644 --- a/yana/Views/SettingView.swift +++ b/yana/Views/SettingView.swift @@ -31,7 +31,7 @@ struct SettingView: View { Spacer() // 标题 - Text("设置") + Text(NSLocalizedString("setting.title", comment: "Settings")) .font(.custom("PingFang SC-Semibold", size: 16)) .foregroundColor(.white) @@ -47,104 +47,24 @@ struct SettingView: View { // 内容区域 ScrollView { VStack(spacing: 24) { - // 用户信息卡片 - VStack(spacing: 16) { - // 头像区域 - Image(systemName: "person.circle.fill") - .font(.system(size: 60)) - .foregroundColor(.white.opacity(0.8)) - - // 用户信息 - VStack(spacing: 8) { - if let userInfo = store.userInfo, let userName = userInfo.username { - Text(userName) - .font(.title2.weight(.semibold)) - .foregroundColor(.white) - } else { - Text("用户") - .font(.title2.weight(.semibold)) - .foregroundColor(.white) - } - - // 显示用户ID - if let userInfo = store.userInfo, let userId = userInfo.userId { - Text("ID: \(userId)") - .font(.caption) - .foregroundColor(.white.opacity(0.8)) - } else if let accountModel = store.accountModel, let uid = accountModel.uid { - Text("UID: \(uid)") - .font(.caption) - .foregroundColor(.white.opacity(0.8)) - } - } - } - .padding(.vertical, 24) - .padding(.horizontal, 20) - .background(Color.black.opacity(0.3)) - .cornerRadius(16) - .padding(.horizontal, 24) - .padding(.top, 32) + UserInfoCardView(userInfo: store.userInfo, accountModel: store.accountModel) +// .padding() + .padding(.top, 32) - // 设置选项列表 - VStack(spacing: 12) { - // 语言设置 - SettingRowView( - icon: "globe", - title: "语言设置", - action: { - // TODO: 实现语言设置 - } - ) - - // 关于我们 - SettingRowView( - icon: "info.circle", - title: "关于我们", - action: { - // TODO: 实现关于页面 - } - ) - - // 版本信息 - HStack { - Image(systemName: "app.badge") - .foregroundColor(.white.opacity(0.8)) - .frame(width: 24) - - Text("版本信息") - .foregroundColor(.white) - - Spacer() - - Text("1.0.0") - .foregroundColor(.white.opacity(0.6)) - .font(.caption) + SettingOptionsView( + onLanguageTapped: { + // TODO: 实现语言设置 + }, + onAboutTapped: { + // TODO: 实现关于页面 } - .padding(.horizontal, 20) - .padding(.vertical, 16) - .background(Color.black.opacity(0.2)) - .cornerRadius(12) - } - .padding(.horizontal, 24) + ) Spacer(minLength: 50) - // 退出登录按钮 - Button(action: { + LogoutButtonView { store.send(.logoutTapped) - }) { - HStack { - Image(systemName: "arrow.right.square") - Text("退出登录") - } - .font(.body.weight(.medium)) - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding(.vertical, 16) - .background(Color.red.opacity(0.7)) - .cornerRadius(12) } - .padding(.horizontal, 24) .padding(.bottom, 50) } } @@ -158,6 +78,113 @@ struct SettingView: View { } } +// MARK: - User Info Card View +struct UserInfoCardView: View { + let userInfo: UserInfo? + let accountModel: AccountModel? + + var body: some View { + VStack(spacing: 16) { + // 头像区域 + Image(systemName: "person.circle.fill") + .font(.system(size: 60)) + .foregroundColor(.white.opacity(0.8)) + + // 用户信息 + VStack(spacing: 8) { + if let userInfo = userInfo, let userName = userInfo.username { + Text(userName) + .font(.title2.weight(.semibold)) + .foregroundColor(.white) + } else { + Text(NSLocalizedString("setting.user", comment: "User")) + .font(.title2.weight(.semibold)) + .foregroundColor(.white) + } + + // 显示用户ID + if let userInfo = userInfo, let userId = userInfo.userId { + Text("ID: \(userId)") + .font(.caption) + .foregroundColor(.white.opacity(0.8)) + } else if let accountModel = accountModel, let uid = accountModel.uid { + Text("UID: \(uid)") + .font(.caption) + .foregroundColor(.white.opacity(0.8)) + } + } + } + .padding(.vertical, 24) + .padding(.horizontal, 20) + .background(Color.black.opacity(0.3)) + .cornerRadius(16) + .padding(.horizontal, 24) + } +} + +// MARK: - Setting Options View +struct SettingOptionsView: View { + let onLanguageTapped: () -> Void + let onAboutTapped: () -> Void + + var body: some View { + VStack(spacing: 12) { + SettingRowView( + icon: "globe", + title: NSLocalizedString("setting.language", comment: "Language Settings"), + action: onLanguageTapped + ) + + SettingRowView( + icon: "info.circle", + title: NSLocalizedString("setting.about", comment: "About Us"), + action: onAboutTapped + ) + + HStack { + Image(systemName: "app.badge") + .foregroundColor(.white.opacity(0.8)) + .frame(width: 24) + + Text(NSLocalizedString("setting.version", comment: "Version Info")) + .foregroundColor(.white) + + Spacer() + + Text("1.0.0") + .foregroundColor(.white.opacity(0.6)) + .font(.caption) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .background(Color.black.opacity(0.2)) + .cornerRadius(12) + } + .padding(.horizontal, 24) + } +} + +// MARK: - Logout Button View +struct LogoutButtonView: View { + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack { + Image(systemName: "arrow.right.square") + Text(NSLocalizedString("setting.logout", comment: "Logout")) + } + .font(.body.weight(.medium)) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.red.opacity(0.7)) + .cornerRadius(12) + } + .padding(.horizontal, 24) + } +} + // MARK: - Setting Row View struct SettingRowView: View { let icon: String diff --git a/yana/Views/SplashView.swift b/yana/Views/SplashView.swift index 9ef4769..0579fb5 100644 --- a/yana/Views/SplashView.swift +++ b/yana/Views/SplashView.swift @@ -64,7 +64,7 @@ struct SplashView: View { .frame(width: 100, height: 100) // 应用标题 - 白色,40pt字体 - Text("E-Parti") + Text(NSLocalizedString("splash.title", comment: "E-Parti")) .font(.system(size: 40, weight: .regular)) .foregroundColor(.white)