diff --git a/yana.xcodeproj/project.pbxproj b/yana.xcodeproj/project.pbxproj index a7a88a0..30fa9e7 100644 --- a/yana.xcodeproj/project.pbxproj +++ b/yana.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 4C4C8FBE2DE5AF9200384527 /* yanaAPITests */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); path = yanaAPITests; sourceTree = ""; }; @@ -269,14 +271,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-yana/Pods-yana-frameworks.sh\"\n"; diff --git a/yana/Assets.xcassets/AppIcon.appiconset/Contents.json b/yana/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..7ed679b 100644 --- a/yana/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/yana/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "logo.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/yana/Assets.xcassets/AppIcon.appiconset/logo.png b/yana/Assets.xcassets/AppIcon.appiconset/logo.png new file mode 100644 index 0000000..50d70b8 Binary files /dev/null and b/yana/Assets.xcassets/AppIcon.appiconset/logo.png differ diff --git a/yana/Assets.xcassets/Home/Contents.json b/yana/Assets.xcassets/Home/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/yana/Assets.xcassets/Home/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/yana/Assets.xcassets/Home/add icon.imageset/Contents.json b/yana/Assets.xcassets/Home/add icon.imageset/Contents.json new file mode 100644 index 0000000..9995fab --- /dev/null +++ b/yana/Assets.xcassets/Home/add icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "发布@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/yana/Assets.xcassets/Home/add icon.imageset/发布@3x.png b/yana/Assets.xcassets/Home/add icon.imageset/发布@3x.png new file mode 100644 index 0000000..c091d30 Binary files /dev/null and b/yana/Assets.xcassets/Home/add icon.imageset/发布@3x.png differ diff --git a/yana/Assets.xcassets/Home/feed selected.imageset/3@3x.png b/yana/Assets.xcassets/Home/feed selected.imageset/3@3x.png new file mode 100644 index 0000000..1bca5df Binary files /dev/null and b/yana/Assets.xcassets/Home/feed selected.imageset/3@3x.png differ diff --git a/yana/Assets.xcassets/Home/feed selected.imageset/Contents.json b/yana/Assets.xcassets/Home/feed selected.imageset/Contents.json new file mode 100644 index 0000000..fa52775 --- /dev/null +++ b/yana/Assets.xcassets/Home/feed selected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "3@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/yana/Assets.xcassets/Home/feed unselected.imageset/3@3x (1).png b/yana/Assets.xcassets/Home/feed unselected.imageset/3@3x (1).png new file mode 100644 index 0000000..2c19ff7 Binary files /dev/null and b/yana/Assets.xcassets/Home/feed unselected.imageset/3@3x (1).png differ diff --git a/yana/Assets.xcassets/Home/feed unselected.imageset/Contents.json b/yana/Assets.xcassets/Home/feed unselected.imageset/Contents.json new file mode 100644 index 0000000..3aacb35 --- /dev/null +++ b/yana/Assets.xcassets/Home/feed unselected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "3@3x (1).png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/yana/Assets.xcassets/Home/me selected.imageset/5@3x (1).png b/yana/Assets.xcassets/Home/me selected.imageset/5@3x (1).png new file mode 100644 index 0000000..560b7ab Binary files /dev/null and b/yana/Assets.xcassets/Home/me selected.imageset/5@3x (1).png differ diff --git a/yana/Assets.xcassets/Home/me selected.imageset/Contents.json b/yana/Assets.xcassets/Home/me selected.imageset/Contents.json new file mode 100644 index 0000000..b972d33 --- /dev/null +++ b/yana/Assets.xcassets/Home/me selected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "5@3x (1).png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/yana/Assets.xcassets/Home/me unselected.imageset/5@3x.png b/yana/Assets.xcassets/Home/me unselected.imageset/5@3x.png new file mode 100644 index 0000000..3d58163 Binary files /dev/null and b/yana/Assets.xcassets/Home/me unselected.imageset/5@3x.png differ diff --git a/yana/Assets.xcassets/Home/me unselected.imageset/Contents.json b/yana/Assets.xcassets/Home/me unselected.imageset/Contents.json new file mode 100644 index 0000000..21354d0 --- /dev/null +++ b/yana/Assets.xcassets/Home/me unselected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "5@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/yana/Features/HomeFeature.swift b/yana/Features/HomeFeature.swift index 423becd..663d16a 100644 --- a/yana/Features/HomeFeature.swift +++ b/yana/Features/HomeFeature.swift @@ -9,6 +9,10 @@ struct HomeFeature { var userInfo: UserInfo? var accountModel: AccountModel? var error: String? + + // 设置页面相关状态 + var isSettingPresented = false + var settingState = SettingFeature.State() } enum Action: Equatable { @@ -19,9 +23,17 @@ struct HomeFeature { case accountModelLoaded(AccountModel?) case logoutTapped case logout + + // 设置页面相关actions + case settingDismissed + case setting(SettingFeature.Action) } var body: some ReducerOf { + Scope(state: \.settingState, action: \.setting) { + SettingFeature() + } + Reduce { state, action in switch action { case .onAppear: @@ -59,6 +71,14 @@ struct HomeFeature { // 发送通知返回登录页面 NotificationCenter.default.post(name: .homeLogout, object: nil) return .none + + case .settingDismissed: + state.isSettingPresented = false + return .none + + case .setting: + // 由子reducer处理 + return .none } } } diff --git a/yana/Features/SettingFeature.swift b/yana/Features/SettingFeature.swift new file mode 100644 index 0000000..ba70a51 --- /dev/null +++ b/yana/Features/SettingFeature.swift @@ -0,0 +1,75 @@ +import Foundation +import ComposableArchitecture + +@Reducer +struct SettingFeature { + @ObservableState + struct State: Equatable { + var userInfo: UserInfo? + var accountModel: AccountModel? + var isLoading = false + var error: String? + } + + enum Action: Equatable { + case onAppear + case loadUserInfo + case userInfoLoaded(UserInfo?) + case loadAccountModel + case accountModelLoaded(AccountModel?) + case logoutTapped + case logout + case dismissTapped + } + + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .onAppear: + return .concatenate( + .send(.loadUserInfo), + .send(.loadAccountModel) + ) + + case .loadUserInfo: + let userInfo = UserInfoManager.getUserInfo() + return .send(.userInfoLoaded(userInfo)) + + case let .userInfoLoaded(userInfo): + state.userInfo = userInfo + return .none + + case .loadAccountModel: + let accountModel = UserInfoManager.getAccountModel() + return .send(.accountModelLoaded(accountModel)) + + case let .accountModelLoaded(accountModel): + state.accountModel = accountModel + return .none + + case .logoutTapped: + return .send(.logout) + + case .logout: + state.isLoading = true + + // 清除所有认证数据 + UserInfoManager.clearAllAuthenticationData() + + // 发送通知返回登录页面 + NotificationCenter.default.post(name: .homeLogout, object: nil) + return .none + + case .dismissTapped: + // 发送关闭设置页面的通知 + NotificationCenter.default.post(name: .settingsDismiss, object: nil) + return .none + } + } + } +} + +// MARK: - Notification Extension +extension Notification.Name { + static let settingsDismiss = Notification.Name("settingsDismiss") +} \ No newline at end of file diff --git a/yana/Info.plist b/yana/Info.plist index d5b7ef5..efb2927 100644 --- a/yana/Info.plist +++ b/yana/Info.plist @@ -2,6 +2,10 @@ + CFBundleDisplayName + E-PARTi + CFBundleName + E-PARTi NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/yana/Views/Components/BottomTabView.swift b/yana/Views/Components/BottomTabView.swift new file mode 100644 index 0000000..ce9c067 --- /dev/null +++ b/yana/Views/Components/BottomTabView.swift @@ -0,0 +1,73 @@ +import SwiftUI + +// MARK: - Tab 枚举 +enum Tab: Int, CaseIterable { + case feed = 0 + case me = 1 + + var title: String { + switch self { + case .feed: + return "动态" + case .me: + return "我的" + } + } + + var iconName: String { + switch self { + case .feed: + return "sparkles" + case .me: + return "person" + } + } + + var selectedIconName: String { + switch self { + case .feed: + return "sparkles" + case .me: + return "person.fill" + } + } +} + +// MARK: - BottomTabView 组件 +struct BottomTabView: View { + @Binding var selectedTab: Tab + + var body: some View { + HStack(spacing: 0) { + ForEach(Tab.allCases, id: \.rawValue) { tab in + Button(action: { + selectedTab = tab + }) { + VStack(spacing: 4) { + Image(systemName: selectedTab == tab ? tab.selectedIconName : tab.iconName) + .font(.system(size: 20)) + .foregroundColor(selectedTab == tab ? .purple : .gray) + + Text(tab.title) + .font(.caption2) + .foregroundColor(selectedTab == tab ? .purple : .gray) + } + .frame(maxWidth: .infinity) + } + .buttonStyle(PlainButtonStyle()) + } + } + .padding(.vertical, 12) + .padding(.horizontal, 8) + .background( + .ultraThinMaterial, + in: Rectangle() + ) + .overlay( + Rectangle() + .frame(height: 0.5) + .foregroundColor(.black.opacity(0.2)), + alignment: .top + ) + } +} diff --git a/yana/Views/FeedView.swift b/yana/Views/FeedView.swift new file mode 100644 index 0000000..7b04ccb --- /dev/null +++ b/yana/Views/FeedView.swift @@ -0,0 +1,134 @@ +import SwiftUI + +struct FeedView: View { + var body: some View { + ScrollView { + VStack(spacing: 20) { + // 顶部标题 + HStack { + Spacer() + Text("Enjoy your Life Time") + .font(.system(size: 22, weight: .semibold)) + .foregroundColor(.white) + Spacer() + } + .padding(.top, 20) + + // 心脏图标 + Image(systemName: "heart.fill") + .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.") + .font(.system(size: 16)) + .multilineTextAlignment(.center) + .foregroundColor(.white.opacity(0.9)) + .padding(.horizontal, 30) + .padding(.top, 20) + + // 模拟动态卡片 + LazyVStack(spacing: 16) { + ForEach(0..<3) { index in + DynamicCardView(index: index) + } + } + .padding(.horizontal, 16) + .padding(.top, 30) + + // 底部安全区域 + Color.clear.frame(height: 100) + } + } + } +} + +// MARK: - 动态卡片组件 +struct DynamicCardView: View { + let index: Int + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + // 用户信息行 + HStack(spacing: 12) { + // 头像 + Circle() + .fill(Color.blue.opacity(0.6)) + .frame(width: 40, height: 40) + .overlay( + Text("👤") + .font(.system(size: 20)) + ) + + VStack(alignment: .leading, spacing: 2) { + HStack { + Text("NAMENAMENAME....") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.white) + + Spacer() + + Text("ID:7271557") + .font(.caption) + .foregroundColor(.white.opacity(0.7)) + } + + Text("09/12") + .font(.caption) + .foregroundColor(.white.opacity(0.7)) + } + } + + // 内容文字 + Text("这是动态内容 \(index + 1)。今天是美好的一天,分享一些生活中的点点滴滴。") + .font(.system(size: 15)) + .foregroundColor(.white) + .lineLimit(nil) + + // 图片网格(模拟) + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), spacing: 8) { + ForEach(0..<3) { imageIndex in + Rectangle() + .fill(Color.gray.opacity(0.3)) + .aspectRatio(1, contentMode: .fit) + .overlay( + Image(systemName: "photo") + .foregroundColor(.white.opacity(0.6)) + ) + } + } + + // 互动按钮 + HStack(spacing: 20) { + Button(action: {}) { + HStack(spacing: 4) { + Image(systemName: "message") + .font(.system(size: 16)) + Text("354") + .font(.system(size: 14)) + } + .foregroundColor(.white.opacity(0.8)) + } + + Button(action: {}) { + HStack(spacing: 4) { + Image(systemName: "heart") + .font(.system(size: 16)) + Text("354") + .font(.system(size: 14)) + } + .foregroundColor(.white.opacity(0.8)) + } + + Spacer() + } + .padding(.top, 8) + } + .padding(16) + .background( + Color.white.opacity(0.1) + .cornerRadius(12) + ) + } +} \ No newline at end of file diff --git a/yana/Views/HomeView.swift b/yana/Views/HomeView.swift index ccee78b..6208925 100644 --- a/yana/Views/HomeView.swift +++ b/yana/Views/HomeView.swift @@ -4,111 +4,70 @@ import ComposableArchitecture struct HomeView: View { let store: StoreOf @ObservedObject private var localizationManager = LocalizationManager.shared + @State private var selectedTab: Tab = .feed var body: some View { WithPerceptionTracking { GeometryReader { geometry in ZStack { - // 背景图片 - 使用"bg"图片,全屏显示 + // 使用 "bg" 图片作为背景 Image("bg") .resizable() .aspectRatio(contentMode: .fill) + .frame(width: geometry.size.width, height: geometry.size.height) + .clipped() .ignoresSafeArea(.all) VStack(spacing: 0) { - // Navigation Bar 标题区域 - Text("home.title".localized) - .font(.custom("PingFang SC-Semibold", size: 16)) - .foregroundColor(.white) - .frame( - width: 158, - height: 22, - alignment: .center - ) // 参考代码中的尺寸 - .padding(.top, 8) - .padding(.horizontal) - - // 中间内容区域 - VStack(spacing: 32) { + // 顶部导航区域 + HStack { Spacer() - // 用户信息区域 - VStack(spacing: 16) { - // 优先显示 UserInfo 中的用户名,否则显示通用欢迎信息 - if let userInfo = store.userInfo, let userName = userInfo.username { - Text("欢迎, \(userName)") - .font(.title2) - .foregroundColor(.white) - } else { - Text("欢迎") - .font(.title2) - .foregroundColor(.white) - } - - // 显示用户ID信息:优先 UserInfo,其次 AccountModel - 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)) - } - - // 显示账户状态(如果有 AccountModel) - if let accountModel = store.accountModel { - VStack(spacing: 4) { - if accountModel.hasValidSession { - HStack { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - Text("已登录") - .foregroundColor(.white.opacity(0.9)) - } - .font(.caption) - } else if accountModel.hasValidAuthentication { - HStack { - Image(systemName: "clock.circle.fill") - .foregroundColor(.orange) - Text("认证中") - .foregroundColor(.white.opacity(0.9)) - } - .font(.caption) - } - } - } - } - .padding() - .background(Color.black.opacity(0.3)) - .cornerRadius(12) - .padding(.horizontal, 32) - - Spacer() - - // 登出按钮 + // 右上角加号按钮 Button(action: { - store.send(.logoutTapped) + // 加号按钮操作 }) { - HStack { - Image(systemName: "arrow.right.square") - Text("退出登录") - } - .font(.body) - .foregroundColor(.white) - .padding(.horizontal, 24) - .padding(.vertical, 12) - .background(Color.red.opacity(0.7)) - .cornerRadius(8) + Image(systemName: "plus") + .font(.system(size: 20, weight: .medium)) + .foregroundColor(.red) + .frame(width: 36, height: 36) + .background( + Color.white.opacity(0.2) + .cornerRadius(18) + ) } - .padding(.bottom, 50) } + .padding(.horizontal, 20) + .padding(.top, 10) + + // 主要内容区域 + ZStack { + // 根据选中的 tab 显示不同的视图 + switch selectedTab { + case .feed: + FeedView() + .transition(.opacity) + case .me: + MeView() + .transition(.opacity) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + + // 底部导航栏 + BottomTabView(selectedTab: $selectedTab) } } } .onAppear { store.send(.onAppear) } + .sheet(isPresented: Binding( + get: { store.isSettingPresented }, + set: { _ in store.send(.settingDismissed) } + )) { + SettingView(store: store.scope(state: \.settingState, action: \.setting)) + } } } } diff --git a/yana/Views/MeView.swift b/yana/Views/MeView.swift new file mode 100644 index 0000000..415316c --- /dev/null +++ b/yana/Views/MeView.swift @@ -0,0 +1,107 @@ +import SwiftUI + +struct MeView: View { + var body: some View { + ScrollView { + VStack(spacing: 20) { + // 顶部标题 + HStack { + Spacer() + Text("我的") + .font(.system(size: 22, weight: .semibold)) + .foregroundColor(.white) + Spacer() + } + .padding(.top, 20) + + // 用户头像区域 + VStack(spacing: 16) { + Circle() + .fill(Color.white.opacity(0.2)) + .frame(width: 80, height: 80) + .overlay( + Image(systemName: "person.fill") + .font(.system(size: 40)) + .foregroundColor(.white) + ) + + Text("用户昵称") + .font(.system(size: 18, weight: .medium)) + .foregroundColor(.white) + + Text("ID: 123456789") + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.7)) + } + .padding(.top, 30) + + // 功能菜单 + VStack(spacing: 12) { + MenuItemView(icon: "gearshape", title: "设置", action: {}) + MenuItemView(icon: "person.circle", title: "个人信息", action: {}) + MenuItemView(icon: "heart", title: "我的收藏", action: {}) + MenuItemView(icon: "clock", title: "浏览历史", action: {}) + MenuItemView(icon: "questionmark.circle", title: "帮助与反馈", action: {}) + } + .padding(.horizontal, 20) + .padding(.top, 40) + + // 退出登录按钮 + Button(action: {}) { + HStack { + Image(systemName: "rectangle.portrait.and.arrow.right") + .font(.system(size: 16)) + Text("退出登录") + .font(.system(size: 16, weight: .medium)) + } + .foregroundColor(.red) + .frame(maxWidth: .infinity) + .frame(height: 50) + .background( + Color.white.opacity(0.1) + .cornerRadius(12) + ) + } + .padding(.horizontal, 20) + .padding(.top, 30) + + // 底部安全区域 + Color.clear.frame(height: 100) + } + } + } +} + +// MARK: - 菜单项组件 +struct MenuItemView: View { + let icon: String + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 16) { + Image(systemName: icon) + .font(.system(size: 20)) + .foregroundColor(.white) + .frame(width: 24) + + Text(title) + .font(.system(size: 16)) + .foregroundColor(.white) + .frame(maxWidth: .infinity, alignment: .leading) + + Image(systemName: "chevron.right") + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.6)) + } + .padding(.horizontal, 20) + .frame(height: 56) + .background( + Color.white.opacity(0.1) + .cornerRadius(12) + ) + } + .buttonStyle(PlainButtonStyle()) + } +} \ No newline at end of file diff --git a/yana/Views/SettingView.swift b/yana/Views/SettingView.swift new file mode 100644 index 0000000..c332085 --- /dev/null +++ b/yana/Views/SettingView.swift @@ -0,0 +1,199 @@ +import SwiftUI +import ComposableArchitecture + +struct SettingView: View { + let store: StoreOf + @ObservedObject private var localizationManager = LocalizationManager.shared + + var body: some View { + WithPerceptionTracking { + GeometryReader { geometry in + ZStack { + // 背景图片 - 使用"bg"图片,全屏显示 + Image("bg") + .resizable() + .aspectRatio(contentMode: .fill) + .ignoresSafeArea(.all) + + VStack(spacing: 0) { + // Navigation Bar + HStack { + // 返回按钮 + Button(action: { + store.send(.dismissTapped) + }) { + Image(systemName: "chevron.left") + .font(.title2) + .foregroundColor(.white) + } + .padding(.leading, 16) + + Spacer() + + // 标题 + Text("设置") + .font(.custom("PingFang SC-Semibold", size: 16)) + .foregroundColor(.white) + + Spacer() + + // 占位符,保持标题居中 + Color.clear + .frame(width: 44, height: 44) + } + .padding(.top, 8) + .padding(.horizontal) + + // 内容区域 + 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) + + // 设置选项列表 + 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) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .background(Color.black.opacity(0.2)) + .cornerRadius(12) + } + .padding(.horizontal, 24) + + Spacer(minLength: 50) + + // 退出登录按钮 + Button(action: { + 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) + } + } + } + } + } + .onAppear { + store.send(.onAppear) + } + } + } +} + +// MARK: - Setting Row View +struct SettingRowView: View { + let icon: String + let title: String + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack { + Image(systemName: icon) + .foregroundColor(.white.opacity(0.8)) + .frame(width: 24) + + Text(title) + .foregroundColor(.white) + + Spacer() + + Image(systemName: "chevron.right") + .foregroundColor(.white.opacity(0.6)) + .font(.caption) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .background(Color.black.opacity(0.2)) + .cornerRadius(12) + } + } +} + +#Preview { + SettingView( + store: Store( + initialState: SettingFeature.State() + ) { + SettingFeature() + } + ) +} \ No newline at end of file diff --git a/项目问题排查与解决流程.md b/项目问题排查与解决流程.md new file mode 100644 index 0000000..1e14f19 --- /dev/null +++ b/项目问题排查与解决流程.md @@ -0,0 +1,269 @@ +# Yana 项目问题排查与解决流程文档 + +## 目录 +1. [问题概述](#问题概述) +2. [解决流程](#解决流程) +3. [技术细节](#技术细节) +4. [最终解决方案](#最终解决方案) +5. [预防措施](#预防措施) +6. [常见问题FAQ](#常见问题faq) + +--- + +## 问题概述 + +### 初始错误 +**错误信息**: `"Could not compute dependency graph: unable to load transferred PIF: The workspace contains multiple references with the same GUID"` + +**问题表现**: +- 项目无法启动 +- Xcode 无法计算依赖图 +- 出现 GUID 冲突错误 + +### 根本原因分析 +1. **混合包管理系统**: 项目同时使用了 Swift Package Manager (SPM) 和 CocoaPods +2. **缓存冲突**: Xcode DerivedData 与 SPM 状态不同步 +3. **TCA 结构问题**: 代码中 HomeFeature 缺少必要的状态和 Action 定义 + +--- + +## 解决流程 + +### 第一阶段:GUID 冲突解决 + +#### 步骤 1: 清理缓存 +```bash +# 清理 Xcode DerivedData +rm -rf ~/Library/Developer/Xcode/DerivedData/* + +# 重置 Swift Package Manager +swift package reset +swift package resolve +``` + +#### 步骤 2: 重新安装 CocoaPods +```bash +pod install --clean-install +``` + +#### 步骤 3: 验证项目解析 +```bash +xcodebuild -workspace yana.xcworkspace -list +``` + +### 第二阶段:TCA 结构修复 + +#### 问题识别 +- `HomeFeature.State` 缺少 `isSettingPresented` 和 `settingState` 属性 +- `HomeFeature.Action` 缺少 `settingDismissed` 和 `setting` actions +- `HomeView.swift` 中的 `store.scope()` 调用语法错误 + +#### 修复步骤 + +**1. 修复 HomeFeature.swift** +```swift +@ObservableState +struct State: Equatable { + var isInitialized = false + var userInfo: UserInfo? + var accountModel: AccountModel? + var error: String? + + // 添加设置页面相关状态 + var isSettingPresented = false + var settingState = SettingFeature.State() +} + +enum Action: Equatable { + case onAppear + case loadUserInfo + case userInfoLoaded(UserInfo?) + case loadAccountModel + case accountModelLoaded(AccountModel?) + case logoutTapped + case logout + + // 添加设置页面相关actions + case settingDismissed + case setting(SettingFeature.Action) +} +``` + +**2. 添加子 Reducer** +```swift +var body: some ReducerOf { + Scope(state: \.settingState, action: \.setting) { + SettingFeature() + } + + Reduce { state, action in + // ... existing cases ... + + case .settingDismissed: + state.isSettingPresented = false + return .none + + case .setting: + // 由子reducer处理 + return .none + } +} +``` + +**3. 修复 HomeView.swift** +```swift +.sheet(isPresented: Binding( + get: { store.isSettingPresented }, + set: { _ in store.send(.settingDismissed) } +)) { + SettingView(store: store.scope(state: \.settingState, action: \.setting)) +} +``` + +--- + +## 技术细节 + +### 依赖管理配置 + +**Swift Package Manager (Package.swift)**: +- ComposableArchitecture: 1.20.2+ +- 其他依赖根据需要添加 + +**CocoaPods (Podfile)**: +- Alamofire (网络请求) +- SDWebImage (图像加载) +- CocoaLumberjack (日志) +- 其他 UI 相关库 + +### TCA 架构模式 + +``` +Feature +├── State (数据状态) +├── Action (用户操作) +├── Reducer (状态转换逻辑) +└── Dependencies (外部依赖) +``` + +### 文件结构 +``` +yana/ +├── Features/ # TCA Feature 定义 +├── Views/ # SwiftUI 视图 +├── APIs/ # 网络 API 层 +├── Utils/ # 工具类 +└── Managers/ # 管理器类 +``` + +--- + +## 最终解决方案 + +### 命令执行顺序 +```bash +# 1. 清理环境 +rm -rf ~/Library/Developer/Xcode/DerivedData/* +swift package reset + +# 2. 重新解析依赖 +swift package resolve +pod install --clean-install + +# 3. 验证项目 +xcodebuild -workspace yana.xcworkspace -scheme yana -configuration Debug build +``` + +### 关键代码修改 +1. **HomeFeature.swift**: 添加设置相关状态管理 +2. **HomeView.swift**: 修复 TCA store 绑定语法 +3. **SettingFeature.swift**: 确保 Action 完整性 + +### 构建结果 +✅ **编译成功**: Exit code 0 +⚠️ **警告信息**: 仅 Swift 6 兼容性警告,不影响运行 + +--- + +## 预防措施 + +### 开发规范 +1. **统一包管理**: 优先使用一种包管理工具 +2. **定期清理**: 定期清理 DerivedData 避免缓存问题 +3. **代码审查**: 确保 TCA Feature 结构完整 +4. **版本控制**: 及时提交关键配置文件 + +### 监控指标 +- [ ] 项目编译时间 < 30s +- [ ] 无编译错误 +- [ ] 依赖解析正常 +- [ ] TCA 结构完整 + +### 工具使用 +```bash +# 项目健康检查脚本 +check_project() { + echo "🔍 检查项目状态..." + xcodebuild -workspace yana.xcworkspace -list > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "✅ 项目解析正常" + else + echo "❌ 项目解析失败,需要执行清理流程" + return 1 + fi +} +``` + +--- + +## 常见问题FAQ + +### Q1: 再次出现 GUID 冲突怎么办? +**A**: 执行完整清理流程: +```bash +rm -rf ~/Library/Developer/Xcode/DerivedData/* +swift package reset && swift package resolve +pod install --clean-install +``` + +### Q2: TCA Reducer 编译错误如何处理? +**A**: 检查以下项目: +- State 属性完整性 +- Action 枚举完整性 +- Reducer body 中的 case 处理 +- 子 Reducer 的 Scope 配置 + +### Q3: 如何避免混合包管理器问题? +**A**: +- 尽量使用单一包管理工具 +- 如需混合使用,确保依赖版本兼容 +- 定期更新依赖并测试 + +### Q4: Swift 6 兼容性警告如何处理? +**A**: +- 短期:可以忽略,不影响功能 +- 长期:逐步迁移到 Swift 6 Sendable 模式 + +### Q5: 项目构建缓慢怎么办? +**A**: +- 使用 `xcodebuild -quiet` 减少输出 +- 开启 Xcode Build System 并行构建 +- 定期清理 DerivedData + +--- + +## 总结 + +本次问题解决涉及以下关键技术点: +1. **Xcode 项目配置管理** +2. **Swift Package Manager 与 CocoaPods 共存** +3. **TCA (The Composable Architecture) 最佳实践** +4. **iOS 开发环境故障排除** + +通过系统性的排查和修复,项目现已恢复正常运行状态。建议团队建立定期维护机制,避免类似问题再次发生。 + +--- + +**文档更新时间**: 2025-07-10 +**适用版本**: iOS 15.6+, Swift 6, TCA 1.20.2+ +**维护者**: AI Assistant & 开发团队 \ No newline at end of file