wake-ios/wake/View/OnBoarding/UserInfo.swift
2025-09-12 14:21:44 +08:00

333 lines
16 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
struct UserInfo: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var router = Router.shared
// 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 uploadedFileId: String? // Add state for file ID
@State private var errorMessage: String = ""
@State private var showError: Bool = false
//
@State private var isKeyboardVisible = false
@FocusState private var isTextFieldFocused: Bool
// 使
private static let keyboardPreloader: Void = {
let textField = UITextField()
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.spellCheckingType = .no
textField.isHidden = true
if let window = UIApplication.shared.windows.first {
window.addSubview(textField)
textField.becomeFirstResponder()
textField.resignFirstResponder()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
textField.removeFromSuperview()
}
}
}()
private let keyboardPublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
.map { _ in true }
.merge(with: NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in false })
.receive(on: RunLoop.main)
init() {
//
_ = UserInfo.keyboardPreloader
}
var body: some View {
ZStack {
//
Color.themeTextWhiteSecondary
.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
//
HStack {
Button(action: {
dismiss()
}) {
Image(systemName: "chevron.left")
.font(.system(size: 17, weight: .semibold))
.foregroundColor(.themeTextMessageMain)
}
.padding(.leading, 16)
Spacer()
Text("Complete Your Profile")
.font(Typography.font(for: .title2, family: .quicksandBold))
.foregroundColor(.themeTextMessageMain)
Spacer()
//
Color.clear
.frame(width: 24, height: 24)
.padding(.trailing, 16)
}
.padding(.vertical, 12)
.background(Color.themeTextWhiteSecondary)
.zIndex(1) //
// Dynamic text that changes based on keyboard state
HStack(spacing: 6) {
Image(systemName: "lightbulb")
.font(.system(size: 16, weight: .regular))
.foregroundColor(.themeTextMessageMain)
.padding(.leading,6)
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(3)
}
.animation(.easeInOut(duration: 0.3), value: isKeyboardVisible)
.transition(.opacity)
.background(
Color.themeTextWhite
.cornerRadius(12)
)
.padding(10)
//
GeometryReader { geometry in
ZStack(alignment: .bottom) {
ScrollView {
VStack(spacing: 0) {
// Spacer -
if !isKeyboardVisible {
Spacer(minLength: 0)
.frame(height: geometry.size.height * 0.1) // 10% of available height
}
// Content VStack
VStack(spacing: 20) {
// Title
Text(showUsername ? "What's Your Name?" : "Add Your Avatar")
.font(Typography.font(for: .body, family: .quicksandBold))
.frame(maxWidth: .infinity, alignment: .center)
// Avatar
AvatarPicker(
selectedImage: $avatarImage,
showUsername: $showUsername,
isKeyboardVisible: $isKeyboardVisible,
uploadedFileId: $uploadedFileId
)
.padding(.top, isKeyboardVisible ? 0 : 20)
if showUsername {
TextField("Username", text: $userName)
.font(Typography.font(for: .subtitle, family: .inter))
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding()
.foregroundColor(.black)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color.themePrimaryLight)
)
.focused($isTextFieldFocused)
.submitLabel(.done)
.onSubmit {
isTextFieldFocused = false
}
}
}
.padding()
.background(Color(.white))
.cornerRadius(20)
.padding(.horizontal)
//
Spacer(minLength: 40) // +
}
.frame(minHeight: geometry.size.height) //
.padding(.bottom, isKeyboardVisible ? 300 : 0) //
}
// Fixed Button at bottom
VStack {
Spacer()
Button(action: {
if showUsername {
let parameters: [String: Any] = [
"user_name": userName,
"avatar_file_id": uploadedFileId ?? ""
]
NetworkService.shared.postWithToken(
path: "/iam/user/info",
parameters: parameters
) { (result: Result<UserInfoResponse, NetworkError>) in
DispatchQueue.main.async {
switch result {
case .success(let response):
print("✅ 用户信息更新成功")
if let userData = response.data {
self.userName = userData.username
}
//
MaterialUpload.shared.addMaterial(
fileId: uploadedFileId ?? "",
previewFileId: uploadedFileId ?? ""
) { result in
switch result {
case .success(let data):
print("素材添加成功返回ID: \(data ?? [])")
//
BlindBoxApi.shared.generateBlindBox(
boxType: "First",
materialIds: data ?? []
) { result in
switch result {
case .success(let blindBoxData):
print("✅ 盲盒生成成功: \(blindBoxData?.id ?? "0")")
//
Router.shared.navigate(to: .blindBox(mediaType: .image, blindBoxId: blindBoxData?.id ?? "0"))
case .failure(let error):
print("❌ 盲盒生成失败: \(error.localizedDescription)")
}
}
case .failure(let error):
print("素材添加失败: \(error.localizedDescription)")
}
}
case .failure(let error):
print("❌ 用户信息更新失败: \(error.localizedDescription)")
self.errorMessage = "更新失败: \(error.localizedDescription)"
self.showError = true
}
}
}
} else {
withAnimation {
showUsername = true
}
}
}) {
// Text(showUsername ? "Open" : "Continue")
Text("Continue")
.font(Typography.font(for: .body))
.fontWeight(.bold)
.frame(maxWidth: .infinity)
.padding()
.foregroundColor(.black)
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.themePrimary)
)
}
.padding(.horizontal, 32)
.padding(.bottom, isKeyboardVisible ? 20 : 40)
.animation(.easeInOut, value: showUsername)
}
}
}
.background(Color.themeTextWhiteSecondary)
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
//
if isKeyboardVisible {
Color.black.opacity(0.001)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
//
if showError {
VStack {
Text(errorMessage)
.font(Typography.font(for: .body))
.foregroundColor(.red)
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 2)
}
.frame(maxWidth: .infinity, alignment: .center)
.padding()
}
}
.onAppear {
// Set up keyboard notifications
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in
withAnimation(.easeInOut(duration: 0.3)) {
isKeyboardVisible = true
}
}
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in
withAnimation(.easeInOut(duration: 0.3)) {
isKeyboardVisible = false
// TextField
}
}
}
.onDisappear {
// Clean up observers
NotificationCenter.default.removeObserver(self)
}
.onReceive(keyboardPublisher) { isVisible in
withAnimation(.easeInOut(duration: 0.2)) {
isKeyboardVisible = isVisible
}
}
.onChange(of: isTextFieldFocused) { _, newValue in
withAnimation(.easeInOut(duration: 0.2)) {
isKeyboardVisible = newValue
}
}
}
}
// MARK: - Settings Row View
struct SettingsRow: View {
let icon: String
let title: String
let color: Color
var body: some View {
HStack {
Image(systemName: icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.padding(6)
.background(color.opacity(0.1))
.foregroundColor(color)
.cornerRadius(6)
Text(title)
.padding(.leading, 5)
}
.padding(.vertical, 4)
}
}
// MARK: - Preview
struct UserInfo_Previews: PreviewProvider {
static var previews: some View {
UserInfo()
}
}