// // AppSettingView.swift // yana // // Created by Edwin on 2024/11/20. // import SwiftUI import ComposableArchitecture import PhotosUI struct AppSettingView: View { let store: StoreOf @State private var showPhotoPicker = false @State private var showNicknameAlert = false @State private var nicknameInput = "" @State private var selectedPhotoItem: PhotosPickerItem? var body: some View { // WithPerceptionTracking { WithViewStore(store, observe: { $0 }) { viewStore in WithPerceptionTracking{ mainContent(viewStore: viewStore) .navigationBarHidden(true) .photosPicker( isPresented: $showPhotoPicker, selection: $selectedPhotoItem, matching: .images, photoLibrary: .shared() ) .onChange(of: selectedPhotoItem) { item in handlePhotoSelection(item: item, viewStore: viewStore) selectedPhotoItem = nil } .alert("修改昵称", isPresented: $showNicknameAlert) { nicknameAlertContent(viewStore: viewStore) } message: { Text("昵称最长15个字符") } .sheet(isPresented: userAgreementBinding(viewStore: viewStore)) { WebView(url: URL(string: "https://www.yana.com/user-agreement")!) } .sheet(isPresented: privacyPolicyBinding(viewStore: viewStore)) { WebView(url: URL(string: "https://www.yana.com/privacy-policy")!) } } } // } } // MARK: - 主要内容 private func mainContent(viewStore: ViewStoreOf) -> some View { ZStack { Color(red: 22/255, green: 17/255, blue: 44/255).ignoresSafeArea() VStack(spacing: 0) { topBar ScrollView { VStack(spacing: 32) { // 头像区域 avatarSection(viewStore: viewStore) // 昵称设置项 nicknameSection(viewStore: viewStore) // 设置项区域 settingsSection(viewStore: viewStore) // 退出登录按钮 logoutButton(viewStore: viewStore) } } } } } // MARK: - 照片选择处理 private func handlePhotoSelection(item: PhotosPickerItem?, viewStore: ViewStoreOf) { if let item = item { loadAndProcessImage(item: item) { data in if let data = data { viewStore.send(.avatarSelected(data)) } } } } // MARK: - 昵称Alert内容 @ViewBuilder private func nicknameAlertContent(viewStore: ViewStoreOf) -> some View { TextField("请输入昵称", text: $nicknameInput) .onChange(of: nicknameInput) { newValue in if newValue.count > 15 { nicknameInput = String(newValue.prefix(15)) } } Button("确定") { let trimmed = nicknameInput.trimmingCharacters(in: .whitespacesAndNewlines) if !trimmed.isEmpty && trimmed != viewStore.nickname { viewStore.send(.nicknameEditConfirmed(trimmed)) } } Button("取消", role: .cancel) {} } // MARK: - 顶部栏 private var topBar: some View { HStack { WithViewStore(store, observe: { $0 }) { viewStore in Button(action: { viewStore.send(.dismissTapped) }) { Image(systemName: "chevron.left") .foregroundColor(.white) .font(.system(size: 20, weight: .medium)) } } Spacer() Text(NSLocalizedString("appSetting.title", comment: "Settings")) .font(.system(size: 18, weight: .semibold)) .foregroundColor(.white) Spacer() // 占位符,保持标题居中 Color.clear .frame(width: 20, height: 20) } .padding(.horizontal, 20) .padding(.top, 8) .padding(.bottom, 16) } // MARK: - 头像区域 private func avatarSection(viewStore: ViewStoreOf) -> some View { ZStack(alignment: .bottomTrailing) { avatarImageView(viewStore: viewStore) .onTapGesture { showPhotoPicker = true } cameraButton .onTapGesture { showPhotoPicker = true } } .padding(.top, 24) } // MARK: - 头像图片视图 @ViewBuilder private func avatarImageView(viewStore: ViewStoreOf) -> some View { if viewStore.isLoadingUserInfo { loadingAvatarView } else if let avatarURLString = viewStore.avatarURL, !avatarURLString.isEmpty, let avatarURL = URL(string: avatarURLString) { networkAvatarView(url: avatarURL) } else { defaultAvatarView } } // MARK: - 加载状态头像 private var loadingAvatarView: some View { Circle() .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 120) .overlay( ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(1.2) ) } // MARK: - 网络头像 private func networkAvatarView(url: URL) -> some View { CachedAsyncImage(url: url.absoluteString) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { defaultAvatarView } .frame(width: 120, height: 120) .clipShape(Circle()) } // MARK: - 默认头像 private var defaultAvatarView: some View { Circle() .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 120) .overlay( Image(systemName: "person.fill") .font(.system(size: 40)) .foregroundColor(.white) ) } // MARK: - 相机按钮 private var cameraButton: some View { Button(action: {}) { ZStack { Circle().fill(Color.purple).frame(width: 36, height: 36) Image(systemName: "camera.fill") .foregroundColor(.white) } } .offset(x: 8, y: 8) } // MARK: - 昵称设置项 private func nicknameSection(viewStore: ViewStoreOf) -> some View { VStack(spacing: 0) { HStack { Text(NSLocalizedString("appSetting.nickname", comment: "Nickname")) .foregroundColor(.white) Spacer() Text(viewStore.nickname) .foregroundColor(.gray) Image(systemName: "chevron.right") .foregroundColor(.gray) } .padding(.horizontal, 32) .padding(.vertical, 18) .onTapGesture { nicknameInput = viewStore.nickname showNicknameAlert = true } Divider().background(Color.gray.opacity(0.3)) .padding(.horizontal, 32) } } // MARK: - 设置项区域 private func settingsSection(viewStore: ViewStoreOf) -> some View { VStack(spacing: 0) { personalInfoPermissionsRow(viewStore: viewStore) helpRow(viewStore: viewStore) clearCacheRow(viewStore: viewStore) checkUpdatesRow(viewStore: viewStore) aboutUsRow(viewStore: viewStore) } .background(Color.clear) .padding(.horizontal, 0) } // MARK: - 个人信息权限行 private func personalInfoPermissionsRow(viewStore: ViewStoreOf) -> some View { settingRow( title: NSLocalizedString("appSetting.personalInfoPermissions", comment: "Personal Information and Permissions"), action: { viewStore.send(.personalInfoPermissionsTapped) } ) } // MARK: - 帮助行 private func helpRow(viewStore: ViewStoreOf) -> some View { settingRow( title: NSLocalizedString("appSetting.help", comment: "Help"), action: { viewStore.send(.helpTapped) } ) } // MARK: - 清除缓存行 private func clearCacheRow(viewStore: ViewStoreOf) -> some View { settingRow( title: NSLocalizedString("appSetting.clearCache", comment: "Clear Cache"), action: { viewStore.send(.clearCacheTapped) } ) } // MARK: - 检查更新行 private func checkUpdatesRow(viewStore: ViewStoreOf) -> some View { settingRow( title: NSLocalizedString("appSetting.checkUpdates", comment: "Check for Updates"), action: { viewStore.send(.checkUpdatesTapped) } ) } // MARK: - 关于我们行 private func aboutUsRow(viewStore: ViewStoreOf) -> some View { settingRow( title: NSLocalizedString("appSetting.aboutUs", comment: "About Us"), action: { viewStore.send(.aboutUsTapped) } ) } // MARK: - 设置项行 private func settingRow(title: String, action: @escaping () -> Void) -> some View { VStack(spacing: 0) { HStack { Text(title) .foregroundColor(.white) Spacer() Image(systemName: "chevron.right") .foregroundColor(.gray) } .padding(.horizontal, 32) .padding(.vertical, 18) .onTapGesture { action() } Divider().background(Color.gray.opacity(0.3)) .padding(.horizontal, 32) } } // MARK: - 退出登录按钮 private func logoutButton(viewStore: ViewStoreOf) -> some View { Button(action: { viewStore.send(.logoutTapped) }) { Text(NSLocalizedString("appSetting.logoutAccount", comment: "Log out of account")) .font(.system(size: 18, weight: .semibold)) .foregroundColor(.white) .frame(maxWidth: .infinity) .padding(.vertical, 18) .background(Color.white.opacity(0.08)) .cornerRadius(28) .padding(.horizontal, 32) } .padding(.bottom, 32) } // MARK: - 用户协议绑定 private func userAgreementBinding(viewStore: ViewStoreOf) -> Binding { viewStore.binding( get: \.showUserAgreement, send: AppSettingFeature.Action.userAgreementDismissed ) } // MARK: - 隐私政策绑定 private func privacyPolicyBinding(viewStore: ViewStoreOf) -> Binding { viewStore.binding( get: \.showPrivacyPolicy, send: AppSettingFeature.Action.privacyPolicyDismissed ) } // MARK: - 图片处理 private func loadAndProcessImage(item: PhotosPickerItem, completion: @escaping (Data?) -> Void) { item.loadTransferable(type: Data.self) { result in guard let data = try? result.get(), let uiImage = UIImage(data: data) else { completion(nil) return } let square = cropToSquare(image: uiImage) let resized = resizeImage(image: square, targetSize: CGSize(width: 180, height: 180)) let jpegData = resized.jpegData(compressionQuality: 0.8) completion(jpegData) } } } // MARK: - 图片处理全局函数 private func cropToSquare(image: UIImage) -> UIImage { let size = min(image.size.width, image.size.height) let x = (image.size.width - size) / 2 let y = (image.size.height - size) / 2 let cropRect = CGRect(x: x, y: y, width: size, height: size) guard let cgImage = image.cgImage?.cropping(to: cropRect) else { return image } return UIImage(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation) } private func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage { let renderer = UIGraphicsImageRenderer(size: targetSize) return renderer.image { _ in image.draw(in: CGRect(origin: .zero, size: targetSize)) } }