diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate index bd31ee9..b15312a 100644 Binary files a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate and b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/wake/View/Owner/UserInfo/AvatarPicker.swift b/wake/View/Owner/UserInfo/AvatarPicker.swift index 67e9361..6222eb3 100644 --- a/wake/View/Owner/UserInfo/AvatarPicker.swift +++ b/wake/View/Owner/UserInfo/AvatarPicker.swift @@ -1,17 +1,26 @@ import SwiftUI +import Combine public struct AvatarPicker: View { @StateObject private var uploadManager = MediaUploadManager() @State private var showMediaPicker = false @State private var isUploading = false + @State private var uploadedFileId: String? = nil + @State private var uploadTask: AnyCancellable? @Binding var selectedImage: UIImage? @Binding var showUsername: Bool + @Binding var isKeyboardVisible: Bool - public init(selectedImage: Binding, showUsername: Binding) { + // 添加完成回调 + var onFileUploaded: ((String) -> Void)? = nil + + public init(selectedImage: Binding, showUsername: Binding, isKeyboardVisible: Binding, onFileUploaded: ((String) -> Void)? = nil) { self._selectedImage = selectedImage self._showUsername = showUsername + self._isKeyboardVisible = isKeyboardVisible + self.onFileUploaded = onFileUploaded } - + public var body: some View { VStack(spacing: 20) { // Avatar Image @@ -23,12 +32,12 @@ public struct AvatarPicker: View { Image(uiImage: selectedImage) .resizable() .scaledToFill() - .frame(width: 225, height: 225) + .frame(width: isKeyboardVisible ? 125 : 225, height: isKeyboardVisible ? 125 : 225) .clipShape(RoundedRectangle(cornerRadius: 20)) } else { // Default SVG avatar SVGImage(svgName: "Avatar") - .frame(width: 225, height: 225) + .frame(width: isKeyboardVisible ? 125 : 225, height: isKeyboardVisible ? 125 : 225) .contentShape(Rectangle()) } @@ -71,17 +80,31 @@ public struct AvatarPicker: View { if !uploadManager.selectedMedia.isEmpty { isUploading = true uploadManager.startUpload() + + // 使用 Combine 监听上传状态变化 + uploadTask = uploadManager.$uploadStatus + .receive(on: DispatchQueue.main) + .sink { _ in + if uploadManager.isAllUploaded { + isUploading = false + if let firstResult = uploadManager.getUploadResults().values.first { + self.uploadedFileId = firstResult + self.onFileUploaded?(firstResult) + } + uploadTask?.cancel() + } + } } } ) } - .onChange(of: uploadManager.uploadStatus) { _ in - if let firstMedia = uploadManager.selectedMedia.first, - case .image(let image) = firstMedia, - uploadManager.isAllUploaded { + .onDisappear { + uploadTask?.cancel() + } + .onChange(of: uploadManager.selectedMedia) { newMedia in + if let firstMedia = newMedia.first, + case .image(let image) = firstMedia { selectedImage = image - isUploading = false - uploadManager.clearAllMedia() } } } diff --git a/wake/View/Owner/UserInfo/UserInfo.swift b/wake/View/Owner/UserInfo/UserInfo.swift index 888e4bc..76722cb 100644 --- a/wake/View/Owner/UserInfo/UserInfo.swift +++ b/wake/View/Owner/UserInfo/UserInfo.swift @@ -1,17 +1,82 @@ import SwiftUI +// MARK: - UIApplication Extension for Hiding Keyboard +#if canImport(UIKit) +extension View { + func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} +#endif + struct UserInfo: View { @Environment(\.dismiss) private var dismiss + @State private var keyboardHeight: CGFloat = 0 + @State private var isKeyboardVisible = false // Sample user data - replace with your actual data model @State private var userName = "" - @State private var userEmail = "memo@example.com" @State private var notificationsEnabled = true @State private var darkModeEnabled = false @State private var showLogoutAlert = false - @State private var avatarImage: UIImage? - @State private var showUsername: Bool = false + @State private var avatarImage: UIImage? = nil + @State private var uploadedFileId: String? = nil + @State private var showUsername = false + private struct MessageView: View { + let isKeyboardVisible: Bool + + private var singleLineText: some View { + Text("Choose a photo as your avatar, and we'll generate a video mystery box for you.") + .font(Typography.font(for: .caption)) + .foregroundColor(.black) + .lineLimit(1) + .truncationMode(.tail) + .padding(3) + } + + private var multiLineText: some View { + Text("Choose a photo as your avatar, and we'll generate a video mystery box for you.") + .font(Typography.font(for: .caption)) + .foregroundColor(.black) + .fixedSize(horizontal: false, vertical: true) + .padding(3) + } + + private var background: some View { + LinearGradient( + gradient: Gradient(colors: [ + Color(red: 1.0, green: 0.97, blue: 0.87), + .white, + Color(red: 1.0, green: 0.97, blue: 0.84) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + } + + var body: some View { + HStack { + if isKeyboardVisible { + singleLineText + .transition(.asymmetric( + insertion: .opacity.combined(with: .scale(scale: 0.95)), + removal: .opacity.combined(with: .scale(scale: 1.05)) + )) + } else { + multiLineText + .transition(.asymmetric( + insertion: .opacity.combined(with: .scale(scale: 1.05)), + removal: .opacity.combined(with: .scale(scale: 0.95)) + )) + } + } + .background(background) + .animation(.spring(response: 0.3, dampingFraction: 1), value: isKeyboardVisible) + } + +} + var body: some View { VStack(spacing: 0) { HStack { @@ -25,26 +90,14 @@ struct UserInfo: View { Spacer() } .padding() - HStack(spacing: 20) { - Text("Choose a photo as your avatar, and we'll generate a video mystery box for you.") - .font(Typography.font(for: .caption)) - .foregroundColor(.black) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.vertical, 10) - .background( - LinearGradient( - gradient: Gradient(colors: [ - Color(red: 1.0, green: 0.97, blue: 0.87), - .white, - Color(red: 1.0, green: 0.97, blue: 0.84) - ]), - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) + + // 文描述 + MessageView(isKeyboardVisible: isKeyboardVisible).padding(.bottom, 6) + + if !isKeyboardVisible { + Spacer() } - .padding(10) - Spacer() + VStack(spacing: 20) { // Title Text(showUsername ? "Add Your Avatar" : "What‘s Your Name?") @@ -55,7 +108,13 @@ struct UserInfo: View { ZStack { AvatarPicker( selectedImage: $avatarImage, - showUsername: $showUsername + showUsername: $showUsername, + isKeyboardVisible: $isKeyboardVisible, + onFileUploaded: { fileId in + // 保存上传后的文件ID + print("Uploaded file ID: \(fileId)") + uploadedFileId = fileId + } ) } .padding(.top, 20) @@ -87,17 +146,34 @@ struct UserInfo: View { .background( RoundedRectangle(cornerRadius: 16) .fill(Color.themePrimaryLight) - ) + ) } } .padding() .background(Color(.white)) .cornerRadius(20) - Spacer() + + if !isKeyboardVisible { + Spacer() + } + Button(action: { - showUsername = true + if showUsername { + print("调接口将头像和username传到后端", uploadedFileId ?? "") + // 调接口将头像和username传到后端 + UserService.shared.updateUsername(userName, userId: uploadedFileId ?? "") { result in + switch result { + case .success(let response): + print("Username updated: \(response.message ?? "")") + case .failure(let error): + print("Error updating username: \(error.localizedDescription)") + } + } + } else { + showUsername = true + } }) { - Text("Continue") + Text(showUsername ? "Open" : "Continue") .font(Typography.font(for: .body)) .fontWeight(.bold) .frame(maxWidth: .infinity) @@ -109,10 +185,23 @@ struct UserInfo: View { ) } .padding() - } + } .padding() .navigationBarTitleDisplayMode(.inline) .background(Color(red: 0.98, green: 0.98, blue: 0.98)) // #FAFAFA + .onTapGesture { + hideKeyboard() + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in + withAnimation { + isKeyboardVisible = true + } + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in + withAnimation { + isKeyboardVisible = false + } + } } } diff --git a/wake/View/Owner/UserInfo/UserService.swift b/wake/View/Owner/UserInfo/UserService.swift new file mode 100644 index 0000000..10497f1 --- /dev/null +++ b/wake/View/Owner/UserInfo/UserService.swift @@ -0,0 +1,44 @@ +import Foundation +import os.log + +// MARK: - Request/Response Models +struct UpdateUsernameRequest: Codable { + let username: String + let userId: String +} + +struct UpdateUsernameResponse: Codable { + let success: Bool + let message: String? +} + +// MARK: - UserService +class UserService { + static let shared = UserService() + private let networkService = NetworkService.shared + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "UserService") + + private init() {} + + func updateUsername(_ username: String, userId: String, completion: @escaping (Result) -> Void) { + let parameters: [String: Any] = [ + "username": username, + "avatar_file_id": userId + ] + + logger.info("🔄 开始更新用户信息: 用户名=\(username), 头像ID=\(userId)") + + networkService.postWithToken( + path: "/iam/user/info", + parameters: parameters + ) { [weak self] (result: Result) in + switch result { + case .success(let response): + self?.logger.info("✅ 用户信息更新成功: \(response.message ?? "")") + case .failure(let error): + self?.logger.error("❌ 用户信息更新失败: \(error.localizedDescription)") + } + completion(result) + } + } +} \ No newline at end of file