From 79fc03b52a6c1dc665d3503b1ccf7046b46cd4d1 Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Fri, 25 Jul 2025 16:22:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=B8=8E=E6=95=B0=E6=8D=AE=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除DataMigrationManager类,简化数据迁移逻辑。 - 在FeedListView和MeView中新增图片预览功能,提升用户体验。 - 更新OptimizedDynamicCardView以支持图片点击回调,增强交互性。 - 新增PreviewItem结构体以管理图片预览状态,提升代码可读性与维护性。 - 清理AppDelegate中的冗余代码,优化启动流程。 --- Package.resolved | 9 + Package.swift | 4 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- yana/AppDelegate.swift | 76 +--- yana/Features/MainFeature.swift | 3 - .../Utils/Security/DataMigrationManager.swift | 357 ------------------ .../Components/OptimizedDynamicCardView.swift | 173 +++++---- yana/Views/Components/PreviewItem.swift | 7 + yana/Views/FeedListView.swift | 70 ++-- yana/Views/MeView.swift | 35 +- 10 files changed, 174 insertions(+), 562 deletions(-) delete mode 100644 yana/Utils/Security/DataMigrationManager.swift create mode 100644 yana/Views/Components/PreviewItem.swift diff --git a/Package.resolved b/Package.resolved index 1e26262..d05bfb1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -9,6 +9,15 @@ "version" : "1.0.3" } }, + { + "identity" : "liquidglass", + "kind" : "remoteSourceControl", + "location" : "https://github.com/BarredEwe/LiquidGlass.git", + "state" : { + "revision" : "d5bf927a08a97c2d94db7ef71f1e15f8532d1005", + "version" : "0.7.0" + } + }, { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 4b93d2d..4715f4c 100644 --- a/Package.swift +++ b/Package.swift @@ -16,13 +16,15 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.20.2"), - .package(url: "https://github.com/pointfreeco/swift-case-paths.git", branch: "main") + .package(url: "https://github.com/pointfreeco/swift-case-paths.git", branch: "main"), + .package(url: "https://github.com/BarredEwe/LiquidGlass.git", from: "0.7.0") ], targets: [ .target( name: "yana", dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + "LiquidGlass" ], path: "yana", ), diff --git a/yana.xcworkspace/xcshareddata/swiftpm/Package.resolved b/yana.xcworkspace/xcshareddata/swiftpm/Package.resolved index ba57ea3..5e53261 100644 --- a/yana.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/yana.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -88,7 +88,7 @@ "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { "revision" : "ae208d1a5cf33aee1d43734ea780a09ada6e2a21", - "version" : "2.3.1" + "version" : "2.3.2" } }, { diff --git a/yana/AppDelegate.swift b/yana/AppDelegate.swift index 911f876..0b6b55a 100644 --- a/yana/AppDelegate.swift +++ b/yana/AppDelegate.swift @@ -3,84 +3,10 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) async -> Bool { - -// isPerceptionCheckingEnabled = false - - // 执行数据迁移(从 UserDefaults 到 Keychain) - DataMigrationManager.performStartupMigration() - + // 预加载用户信息缓存 await UserInfoManager.preloadCache() - // 开启网络监控 -// NetworkManager.shared.networkStatusChanged = { status in -// print("🌍 网络状态更新:\(status)") -// } - -#if DEBUG - // 🔍 DES加密已切换到OC版本 -// print("🔐 使用OC版本的DES加密") -// DESEncryptOCTest.runInAppDelegate() - - // 网络诊断 - 使用完整的登录参数测试 -// let testURL = URL(string: "http://192.168.10.211:8080/oauth/token")! -// var request = URLRequest(url: testURL) -// request.httpMethod = "POST" -// request.setValue("application/json", forHTTPHeaderField: "Content-Type") -// request.setValue("application/json", forHTTPHeaderField: "Accept") -// request.setValue("zh-Hant", forHTTPHeaderField: "Accept-Language") -// -// // 添加完整的测试参数 -// let testParameters: [String: Any] = [ -// "ispType": "65535", -// "phone": "3+TbIQYiwIk=", -// "netType": 2, -// "channel": "molistar_enterprise", -// "version": "20.20.61", -// "pub_sign": "2E7C50AA17A20B32A0023F20B7ECE108", -// "osVersion": "16.4", -// "deviceId": "b715b75715e3417c9c70e72bbe502c6c", -// "grant_type": "password", -// "os": "iOS", -// "app": "youmi", -// "password": "nTW/lEgupIQ=", -// "client_id": "erban-client", -// "lang": "zh-Hant-CN", -// "client_secret": "uyzjdhds", -// "Accept-Language": "zh-Hant", -// "model": "iPhone XR", -// "appVersion": "1.0.0" -// ] -// -// do { -// let jsonData = try JSONSerialization.data(withJSONObject: testParameters, options: .prettyPrinted) -// request.httpBody = jsonData -// -// print("🛠 原生URLSession登录测试开始") -// print("📍 测试端点: \(testURL.absoluteString)") -// print("📦 请求参数: \(String(data: jsonData, encoding: .utf8) ?? "无法解析")") -// -// URLSession.shared.dataTask(with: request) { data, response, error in -// DispatchQueue.main.async { -// let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 -// let responseString = data != nil ? String(data: data!, encoding: .utf8) ?? "无法解析响应" : "无数据" -// -// print(""" -// === 网络诊断结果 === -// 🔗 URL: \(testURL.absoluteString) -// 📊 响应状态码: \(statusCode) -// ❌ 错误信息: \(error?.localizedDescription ?? "无") -// 📦 原始数据: \(data?.count ?? 0) bytes -// 📄 响应内容: \(responseString) -// ================== -// """) -// } -// }.resume() -// } catch { -// print("❌ JSON序列化失败: \(error.localizedDescription)") -// } -#endif - // NIMConfigurationManager.setupNimSDK() return true diff --git a/yana/Features/MainFeature.swift b/yana/Features/MainFeature.swift index 64c17ab..8dd0616 100644 --- a/yana/Features/MainFeature.swift +++ b/yana/Features/MainFeature.swift @@ -50,8 +50,6 @@ struct MainFeature { MeFeature() } Reduce { state, action in - debugInfoSync("MainFeature action: \(action)") - debugInfoSync("MainFeature state: \(state)") switch action { case .onAppear: return .run { send in @@ -84,7 +82,6 @@ struct MainFeature { let nickname = userInfo?.nick ?? "" state.appSettingState = AppSettingFeature.State(nickname: nickname, avatarURL: avatarURL, userInfo: userInfo) state.navigationPath.append(.appSetting) - debugInfoSync("\(state.navigationPath)") return .none case .me: return .none diff --git a/yana/Utils/Security/DataMigrationManager.swift b/yana/Utils/Security/DataMigrationManager.swift deleted file mode 100644 index 56fa5d2..0000000 --- a/yana/Utils/Security/DataMigrationManager.swift +++ /dev/null @@ -1,357 +0,0 @@ -import Foundation - -/// 数据迁移管理器 -/// -/// 负责将旧版本的 UserDefaults 存储数据迁移到新的 Keychain 存储方案。 -/// 确保用户升级应用后无需重新登录。 -/// -/// 迁移策略: -/// 1. 检测旧数据是否存在 -/// 2. 迁移到 Keychain -/// 3. 验证迁移结果 -/// 4. 清理旧数据 -@MainActor -final class DataMigrationManager { - - // MARK: - 单例 - static let shared = DataMigrationManager() - private init() {} - - // MARK: - 迁移状态 - private let migrationCompleteKey = "keychain_migration_completed_v1" - - // MARK: - 旧版本存储键 - private enum LegacyStorageKeys { - static let userId = "user_id" - static let accessToken = "access_token" - static let userInfo = "user_info" - static let accountModel = "account_model" - static let appLanguage = "AppLanguage" - } - - // MARK: - 迁移结果 - enum MigrationResult { - case completed // 迁移完成 - case alreadyMigrated // 已经迁移过 - case noDataToMigrate // 没有需要迁移的数据 - case failed(Error) // 迁移失败 - - var description: String { - switch self { - case .completed: - return "数据迁移完成" - case .alreadyMigrated: - return "数据已经迁移过" - case .noDataToMigrate: - return "没有需要迁移的数据" - case .failed(let error): - return "迁移失败: \(error.localizedDescription)" - } - } - } - - // MARK: - 公共方法 - - /// 执行数据迁移 - /// - Returns: 迁移结果 - func performMigration() -> MigrationResult { - debugInfoSync("🔄 开始检查数据迁移...") - - // 检查是否已经迁移过 - if isMigrationCompleted() { - debugInfoSync("✅ 数据已经迁移过,跳过迁移") - return .alreadyMigrated - } - - // 检查是否有需要迁移的数据 - let legacyData = collectLegacyData() - if legacyData.isEmpty { - debugInfoSync("ℹ️ 没有发现需要迁移的数据") - markMigrationCompleted() - return .noDataToMigrate - } - - debugInfoSync("📦 发现需要迁移的数据: \(legacyData.keys.joined(separator: ", "))") - - do { - // 执行迁移 - try migrateToKeychain(legacyData) - - // 验证迁移结果 - try verifyMigration(legacyData) - - // 清理旧数据 - cleanupLegacyData(legacyData.keys) - - // 标记迁移完成 - markMigrationCompleted() - - debugInfoSync("✅ 数据迁移完成") - return .completed - - } catch { - debugErrorSync("❌ 数据迁移失败: \(error)") - return .failed(error) - } - } - - /// 强制重新迁移(用于测试或修复) - func forceMigration() -> MigrationResult { - resetMigrationStatus() - return performMigration() - } - - // MARK: - 私有方法 - - /// 检查迁移是否已完成 - private func isMigrationCompleted() -> Bool { - return UserDefaults.standard.bool(forKey: migrationCompleteKey) - } - - /// 标记迁移完成 - private func markMigrationCompleted() { - UserDefaults.standard.set(true, forKey: migrationCompleteKey) - UserDefaults.standard.synchronize() - } - - /// 重置迁移状态 - private func resetMigrationStatus() { - UserDefaults.standard.removeObject(forKey: migrationCompleteKey) - UserDefaults.standard.synchronize() - } - - /// 收集旧版本数据 - private func collectLegacyData() -> [String: Any] { - let userDefaults = UserDefaults.standard - var legacyData: [String: Any] = [:] - - // 检查各种旧数据 - if let userId = userDefaults.string(forKey: LegacyStorageKeys.userId) { - legacyData[LegacyStorageKeys.userId] = userId - } - - if let accessToken = userDefaults.string(forKey: LegacyStorageKeys.accessToken) { - legacyData[LegacyStorageKeys.accessToken] = accessToken - } - - if let userInfoData = userDefaults.data(forKey: LegacyStorageKeys.userInfo) { - legacyData[LegacyStorageKeys.userInfo] = userInfoData - } - - if let accountModelData = userDefaults.data(forKey: LegacyStorageKeys.accountModel) { - legacyData[LegacyStorageKeys.accountModel] = accountModelData - } - - if let appLanguage = userDefaults.string(forKey: LegacyStorageKeys.appLanguage) { - legacyData[LegacyStorageKeys.appLanguage] = appLanguage - } - - return legacyData - } - - /// 迁移数据到 Keychain - private func migrateToKeychain(_ legacyData: [String: Any]) throws { - let keychain = KeychainManager.shared - - // 迁移 AccountModel(优先级最高,包含完整认证信息) - if let accountModelData = legacyData[LegacyStorageKeys.accountModel] as? Data { - do { - let accountModel = try JSONDecoder().decode(AccountModel.self, from: accountModelData) - try keychain.store(accountModel, forKey: "account_model") - debugInfoSync("✅ AccountModel 迁移成功") - } catch { - debugErrorSync("❌ AccountModel 迁移失败: \(error)") - // 如果 AccountModel 迁移失败,尝试从独立字段重建 - try migrateAccountModelFromIndependentFields(legacyData) - } - } else { - // 如果没有 AccountModel,从独立字段构建 - try migrateAccountModelFromIndependentFields(legacyData) - } - - // 迁移 UserInfo - if let userInfoData = legacyData[LegacyStorageKeys.userInfo] as? Data { - do { - let userInfo = try JSONDecoder().decode(UserInfo.self, from: userInfoData) - try keychain.store(userInfo, forKey: "user_info") - debugInfoSync("✅ UserInfo 迁移成功") - } catch { - debugErrorSync("❌ UserInfo 迁移失败: \(error)") - throw error - } - } - - // 迁移语言设置 - if let appLanguage = legacyData[LegacyStorageKeys.appLanguage] as? String { - try keychain.storeString(appLanguage, forKey: "AppLanguage") - debugInfoSync("✅ 语言设置迁移成功") - } - } - - /// 从独立字段重建 AccountModel - private func migrateAccountModelFromIndependentFields(_ legacyData: [String: Any]) throws { - guard let userId = legacyData[LegacyStorageKeys.userId] as? String, - let accessToken = legacyData[LegacyStorageKeys.accessToken] as? String else { - debugInfoSync("ℹ️ 没有足够的独立字段来重建 AccountModel") - return - } - - let accountModel = AccountModel( - uid: userId, - jti: nil, - tokenType: "bearer", - refreshToken: nil, - netEaseToken: nil, - accessToken: accessToken, - expiresIn: nil, - scope: nil, - ticket: nil - ) - - try KeychainManager.shared.store(accountModel, forKey: "account_model") - debugInfoSync("✅ 从独立字段重建 AccountModel 成功") - } - - /// 验证迁移结果 - private func verifyMigration(_ legacyData: [String: Any]) throws { - let keychain = KeychainManager.shared - - // 验证 AccountModel - if legacyData[LegacyStorageKeys.accountModel] != nil || - (legacyData[LegacyStorageKeys.userId] != nil && legacyData[LegacyStorageKeys.accessToken] != nil) { - let accountModel: AccountModel? = try keychain.retrieve(AccountModel.self, forKey: "account_model") - guard accountModel != nil else { - throw MigrationError.verificationFailed("AccountModel 验证失败") - } - } - - // 验证 UserInfo - if legacyData[LegacyStorageKeys.userInfo] != nil { - let userInfo: UserInfo? = try keychain.retrieve(UserInfo.self, forKey: "user_info") - guard userInfo != nil else { - throw MigrationError.verificationFailed("UserInfo 验证失败") - } - } - - // 验证语言设置 - if legacyData[LegacyStorageKeys.appLanguage] != nil { - let appLanguage = try keychain.retrieveString(forKey: "AppLanguage") - guard appLanguage != nil else { - throw MigrationError.verificationFailed("语言设置验证失败") - } - } - - debugInfoSync("✅ 迁移数据验证成功") - } - - /// 清理旧数据 - private func cleanupLegacyData(_ keys: Dictionary.Keys) { - let userDefaults = UserDefaults.standard - - for key in keys { - userDefaults.removeObject(forKey: key) - debugInfoSync("🗑️ 清理旧数据: \(key)") - } - - userDefaults.synchronize() - debugInfoSync("✅ 旧数据清理完成") - } -} - -// MARK: - 迁移错误 - -enum MigrationError: Error, LocalizedError { - case verificationFailed(String) - case dataCorrupted(String) - case keychainError(Error) - - var errorDescription: String? { - switch self { - case .verificationFailed(let message): - return "验证失败: \(message)" - case .dataCorrupted(let message): - return "数据损坏: \(message)" - case .keychainError(let error): - return "Keychain 错误: \(error.localizedDescription)" - } - } -} - -// MARK: - 应用启动时的迁移支持 - -extension DataMigrationManager { - - /// 在应用启动时执行迁移 - /// 这个方法应该在 AppDelegate 或 App 的初始化阶段调用 - static func performStartupMigration() { - let migrationResult = DataMigrationManager.shared.performMigration() - - switch migrationResult { - case .completed: - debugInfoSync("🎉 应用启动时数据迁移完成") - case .alreadyMigrated: - break // 静默处理 - case .noDataToMigrate: - break // 静默处理 - case .failed(let error): - debugErrorSync("⚠️ 应用启动时数据迁移失败: \(error)") - // 这里可以添加错误上报或降级策略 - } - } -} - -// MARK: - 调试支持 - -#if DEBUG -extension DataMigrationManager { - - /// 调试:打印旧数据信息 - func debugPrintLegacyData() { - let legacyData = collectLegacyData() - debugInfoSync("🔍 旧版本数据:") - for (key, value) in legacyData { - debugInfoSync(" - \(key): \(type(of: value))") - } - } - - /// 调试:模拟创建旧数据(用于测试) - func debugCreateLegacyData() { - let userDefaults = UserDefaults.standard - - userDefaults.set("test_user_123", forKey: LegacyStorageKeys.userId) - userDefaults.set("test_access_token", forKey: LegacyStorageKeys.accessToken) - userDefaults.set("zh-Hans", forKey: LegacyStorageKeys.appLanguage) - userDefaults.synchronize() - - debugInfoSync("🧪 已创建测试用的旧版本数据") - } - - /// 调试:清除所有迁移相关数据 - func debugClearAllData() { - // 清除 Keychain 数据 - do { - try KeychainManager.shared.clearAll() - } catch { - debugErrorSync("❌ 清除 Keychain 数据失败: \(error)") - } - - // 清除 UserDefaults 数据 - let userDefaults = UserDefaults.standard - let allKeys = [ - LegacyStorageKeys.userId, - LegacyStorageKeys.accessToken, - LegacyStorageKeys.userInfo, - LegacyStorageKeys.accountModel, - LegacyStorageKeys.appLanguage, - migrationCompleteKey - ] - - for key in allKeys { - userDefaults.removeObject(forKey: key) - } - userDefaults.synchronize() - - debugInfoSync("🧪 已清除所有迁移相关数据") - } -} -#endif diff --git a/yana/Views/Components/OptimizedDynamicCardView.swift b/yana/Views/Components/OptimizedDynamicCardView.swift index 2931741..ab7977e 100644 --- a/yana/Views/Components/OptimizedDynamicCardView.swift +++ b/yana/Views/Components/OptimizedDynamicCardView.swift @@ -7,108 +7,105 @@ struct OptimizedDynamicCardView: View { let moment: MomentsInfo let allMoments: [MomentsInfo] let currentIndex: Int + // 新增:图片点击回调 + let onImageTap: (_ images: [String], _ index: Int) -> Void - // 预览相关状态 - @State private var showPreview = false - @State private var previewImageUrls: [String] = [] - @State private var previewIndex: Int = 0 - - init(moment: MomentsInfo, allMoments: [MomentsInfo], currentIndex: Int) { + init(moment: MomentsInfo, allMoments: [MomentsInfo], currentIndex: Int, onImageTap: @escaping (_ images: [String], _ index: Int) -> Void) { self.moment = moment self.allMoments = allMoments self.currentIndex = currentIndex + self.onImageTap = onImageTap } public var body: some View { - VStack(alignment: .leading, spacing: 12) { - // 用户信息 - HStack(alignment: .top) { - // 头像 - CachedAsyncImage(url: moment.avatar) { image in - image - .resizable() - .aspectRatio(contentMode: .fill) - } placeholder: { - Circle() - .fill(Color.gray.opacity(0.3)) - .overlay( - Text(String(moment.nick.prefix(1))) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - ) - } - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading, spacing: 2) { - Text(moment.nick) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - Text("ID: \(moment.uid)") - .font(.system(size: 12)) - .foregroundColor(.white.opacity(0.6)) - } - Spacer() - // 时间(原VIP位置) - Text(formatDisplayTime(moment.publishTime)) - .font(.system(size: 12, weight: .bold)) - .foregroundColor(.white.opacity(0.8)) - .padding(.horizontal, 6) - .padding(.vertical, 2) - .background(Color.white.opacity(0.15)) - .cornerRadius(4) - } - - // 动态内容 - if !moment.content.isEmpty { - Text(moment.content) - .font(.system(size: 14)) - .foregroundColor(.white.opacity(0.9)) - .multilineTextAlignment(.leading) - .padding(.leading, 40 + 8) // 与用户名左边对齐 - } - - // 优化的图片网格 - if let images = moment.dynamicResList, !images.isEmpty { - OptimizedImageGrid(images: images) { tappedIndex in - previewImageUrls = images.map { $0.resUrl ?? "" } - previewIndex = tappedIndex - showPreview = true - } - .padding(.bottom, images.count == 2 ? 16 : 0) // 两张图片时增加底部间距 - } - - // 互动按钮 - HStack(spacing: 20) { - // Like 按钮左对齐 - Button(action: {}) { - HStack(spacing: 4) { - Image(systemName: moment.isLike ? "heart.fill" : "heart") - .font(.system(size: 16)) - Text("\(moment.likeCount)") - .font(.system(size: 14)) + ZStack { + // 背景层 + RoundedRectangle(cornerRadius: 12) + .fill(Color.clear) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.white.opacity(0.1), lineWidth: 1) + ) + .shadow(color: Color(red: 0.43, green: 0.43, blue: 0.43, opacity: 0.34), radius: 10.7, x: 0, y: 1.9) + // 内容层 + VStack(alignment: .leading, spacing: 12) { + // 用户信息 + HStack(alignment: .top) { + // 头像 + CachedAsyncImage(url: moment.avatar) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + Circle() + .fill(Color.gray.opacity(0.3)) + .overlay( + Text(String(moment.nick.prefix(1))) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + ) } - .foregroundColor(moment.isLike ? .red : .white.opacity(0.8)) + .frame(width: 40, height: 40) + .clipShape(Circle()) + + VStack(alignment: .leading, spacing: 2) { + Text(moment.nick) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white) + Text("ID: \(moment.uid)") + .font(.system(size: 12)) + .foregroundColor(.white.opacity(0.6)) + } + Spacer() + // 时间(原VIP位置) + Text(formatDisplayTime(moment.publishTime)) + .font(.system(size: 12, weight: .bold)) + .foregroundColor(.white.opacity(0.8)) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.white.opacity(0.15)) + .cornerRadius(4) } - Spacer() + + // 动态内容 + if !moment.content.isEmpty { + Text(moment.content) + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.9)) + .multilineTextAlignment(.leading) + .padding(.leading, 40 + 8) // 与用户名左边对齐 + } + + // 优化的图片网格 + if let images = moment.dynamicResList, !images.isEmpty { + OptimizedImageGrid(images: images) { tappedIndex in + let urls = images.map { $0.resUrl ?? "" } + onImageTap(urls, tappedIndex) + } + .padding(.bottom, images.count == 2 ? 46 : 0) // 两张图片时增加底部间距 + } + + // 互动按钮 + HStack(spacing: 20) { + // Like 按钮左对齐 + Button(action: {}) { + HStack(spacing: 4) { + Image(systemName: moment.isLike ? "heart.fill" : "heart") + .font(.system(size: 16)) + Text("\(moment.likeCount)") + .font(.system(size: 14)) + } + .foregroundColor(moment.isLike ? .red : .white.opacity(0.8)) + } + Spacer() + } + .padding(.top, 8) } - .padding(.top, 8) + .padding(16) } - .padding(16) - .background( - Color.white.opacity(0.1) - .cornerRadius(12) - ) .onAppear { preloadNearbyImages() } - // 图片预览弹窗 - .fullScreenCover(isPresented: $showPreview) { - ImagePreviewPager(images: previewImageUrls, currentIndex: $previewIndex) { - showPreview = false - previewImageUrls = [] - } - } } private func formatTime(_ timestamp: Int) -> String { diff --git a/yana/Views/Components/PreviewItem.swift b/yana/Views/Components/PreviewItem.swift new file mode 100644 index 0000000..b53f87d --- /dev/null +++ b/yana/Views/Components/PreviewItem.swift @@ -0,0 +1,7 @@ +import Foundation + +struct PreviewItem: Identifiable, Equatable { + let id = UUID() + let images: [String] + let index: Int +} \ No newline at end of file diff --git a/yana/Views/FeedListView.swift b/yana/Views/FeedListView.swift index 4e8b558..7cfce94 100644 --- a/yana/Views/FeedListView.swift +++ b/yana/Views/FeedListView.swift @@ -1,9 +1,10 @@ import SwiftUI import ComposableArchitecture -//import OptimizedDynamicCardView // 导入新组件 struct FeedListView: View { let store: StoreOf + // 新增:图片预览状态 + @State private var previewItem: PreviewItem? = nil var body: some View { WithViewStore(self.store, observe: { $0 }) { viewStore in @@ -19,43 +20,35 @@ struct FeedListView: View { .ignoresSafeArea(.all) VStack(alignment: .center, spacing: 0) { // 顶部栏 - HStack { - Button(action: { - viewStore.send(.testButtonTapped) - }) { - Text("测试") - .font(.system(size: 14)) + ZStack { + HStack { + Spacer(minLength: 0) + Text(NSLocalizedString("feedList.title", comment: "Enjoy your Life Time")) + .font(.system(size: 22, weight: .semibold)) .foregroundColor(.white) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(Color.blue.opacity(0.7)) - .cornerRadius(8) + .frame(maxWidth: .infinity, alignment: .center) + Spacer(minLength: 0) } - Spacer(minLength: 0) - Spacer(minLength: 0) - Text(NSLocalizedString("feedList.title", comment: "Enjoy your Life Time")) - .font(.system(size: 22, weight: .semibold)) - .foregroundColor(.white) - .frame(maxWidth: .infinity, alignment: .center) - Spacer(minLength: 0) - Button(action: { - viewStore.send(.editFeedButtonTapped) - }) { - Image("add icon") - .resizable() - .frame(width: 36, height: 36) + HStack { + Spacer(minLength: 0) + Button(action: { + viewStore.send(.editFeedButtonTapped) + }) { + Image("add icon") + .resizable() + .frame(width: 40, height: 40) + } } } + .padding(.horizontal, 20) - .padding(.top, geometry.safeAreaInsets.top) // 其他内容 - Image(systemName: "heart.fill") - .font(.system(size: 60)) - .foregroundColor(.red) - .padding(.top, 40) + Image("Volume") + .frame(width: 56, height: 41) + .padding(.top, 16) 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) + .multilineTextAlignment(.leading) .foregroundColor(.white.opacity(0.9)) .padding(.horizontal, 30) .padding(.bottom, 30) @@ -81,7 +74,14 @@ struct FeedListView: View { WithPerceptionTracking { LazyVStack(spacing: 16) { ForEach(Array(viewStore.moments.enumerated()), id: \ .element.dynamicId) { index, moment in - OptimizedDynamicCardView(moment: moment, allMoments: viewStore.moments, currentIndex: index) + OptimizedDynamicCardView( + moment: moment, + allMoments: viewStore.moments, + currentIndex: index, + onImageTap: { images, tappedIndex in + previewItem = PreviewItem(images: images, index: tappedIndex) + } + ) // 上拉加载更多触发点 if index == viewStore.moments.count - 1, viewStore.hasMore, !viewStore.isLoadingMore { Color.clear @@ -97,6 +97,8 @@ struct FeedListView: View { .progressViewStyle(CircularProgressViewStyle(tint: .white)) .padding(.vertical, 8) } + // 新增底部间距 + Color.clear.frame(height: 120) } .padding(.horizontal, 16) .padding(.top, 10) @@ -133,6 +135,12 @@ struct FeedListView: View { } ) } + // 新增:图片预览弹窗 + .fullScreenCover(item: $previewItem) { item in + ImagePreviewPager(images: item.images, currentIndex: .constant(item.index)) { + previewItem = nil + } + } } } } diff --git a/yana/Views/MeView.swift b/yana/Views/MeView.swift index 604c9ba..880e09a 100644 --- a/yana/Views/MeView.swift +++ b/yana/Views/MeView.swift @@ -3,6 +3,8 @@ import ComposableArchitecture struct MeView: View { let store: StoreOf + // 新增:图片预览状态 + @State private var previewItem: PreviewItem? = nil var body: some View { WithPerceptionTracking { @@ -14,8 +16,8 @@ struct MeView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .clipped() .ignoresSafeArea(.all) - VStack(spacing: 0) { - // 顶部栏,右上角设置按钮 + // 顶部栏,右上角设置按钮 + VStack { HStack { Spacer() WithViewStore(self.store, observe: { $0 }) { viewStore in @@ -23,13 +25,16 @@ struct MeView: View { viewStore.send(.settingButtonTapped) }) { Image(systemName: "gearshape") - .font(.system(size: 22, weight: .medium)) + .font(.system(size: 33, weight: .medium)) .foregroundColor(.white) } .padding(.trailing, 16) .padding(.top, 8) } } + Spacer() + } + VStack(spacing: 16) { // 用户信息区域 WithViewStore(self.store, observe: { $0 }) { viewStore in if viewStore.isLoadingUserInfo { @@ -61,6 +66,7 @@ struct MeView: View { .font(.system(size: 14)) .foregroundColor(.white.opacity(0.7)) } + .padding(.top, 0) .frame(height: 130) } else { Spacer().frame(height: 130) @@ -96,9 +102,17 @@ struct MeView: View { ScrollView { WithPerceptionTracking { LazyVStack(spacing: 12) { - ForEach(Array(viewStore.moments.enumerated()), id: \.element.dynamicId) { index, moment in - OptimizedDynamicCardView(moment: moment, allMoments: viewStore.moments, currentIndex: index) - .padding(.horizontal, 12) + ForEach(viewStore.moments.indices, id: \ .self) { index in + let moment = viewStore.moments[index] + OptimizedDynamicCardView( + moment: moment, + allMoments: viewStore.moments, + currentIndex: index, + onImageTap: { images, tappedIndex in + previewItem = PreviewItem(images: images, index: tappedIndex) + } + ) + .padding(.horizontal, 12) } if viewStore.hasMore { ProgressView() @@ -106,6 +120,8 @@ struct MeView: View { viewStore.send(.loadMore) } } + // 新增底部间距 + Color.clear.frame(height: 120) } .padding(.top, 8) } @@ -118,11 +134,18 @@ struct MeView: View { Spacer() } .frame(maxWidth: .infinity, alignment: .top) + .padding(.top, 8) } } .onAppear { ViewStore(self.store, observe: { $0 }).send(.onAppear) } + // 新增:图片预览弹窗 + .fullScreenCover(item: $previewItem) { item in + ImagePreviewPager(images: item.images, currentIndex: .constant(item.index)) { + previewItem = nil + } + } } } }