313 lines
15 KiB
Swift
313 lines
15 KiB
Swift
import SwiftUI
|
|
|
|
struct UserInfo: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
// 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 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)
|
|
|
|
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: 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)
|
|
.lineLimit(isKeyboardVisible ? 1 : 2)
|
|
.padding(6)
|
|
.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
|
|
)
|
|
)
|
|
}
|
|
.padding(10)
|
|
.animation(.easeInOut(duration: 0.3), value: isKeyboardVisible)
|
|
.transition(.opacity)
|
|
|
|
// 可滚动的内容区域
|
|
GeometryReader { geometry in
|
|
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 ? "Add Your Avatar" : "What's Your Name?")
|
|
.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 {
|
|
Button(action: {
|
|
// Action for second button
|
|
}) {
|
|
Text("Take a Photo")
|
|
.font(Typography.font(for: .subtitle, family: .inter))
|
|
.fontWeight(.regular)
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
.foregroundColor(.black)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 16)
|
|
.fill(Color.themePrimaryLight)
|
|
)
|
|
}
|
|
}
|
|
|
|
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 - 只在非键盘状态下生效
|
|
if !isKeyboardVisible {
|
|
Spacer(minLength: 0)
|
|
.frame(height: geometry.size.height * 0.1) // 10% of available height
|
|
}
|
|
|
|
// Continue Button
|
|
Button(action: {
|
|
if showUsername {
|
|
let parameters: [String: Any] = [
|
|
"username": 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("✅ 用户信息更新成功")
|
|
// Update local state with the new user info
|
|
if let userData = response.data {
|
|
self.userName = userData.username
|
|
// You can update other user data here if needed
|
|
}
|
|
// Show success message or navigate back
|
|
self.dismiss()
|
|
|
|
case .failure(let error):
|
|
print("❌ 用户信息更新失败: \(error.localizedDescription)")
|
|
// Show error message to user
|
|
// You can use an @State variable to show an alert or toast
|
|
self.errorMessage = "更新失败: \(error.localizedDescription)"
|
|
self.showError = true
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
withAnimation {
|
|
showUsername = true
|
|
}
|
|
}
|
|
}) {
|
|
Text(showUsername ? "Open" : "Continue")
|
|
.font(Typography.font(for: .body))
|
|
.fontWeight(.bold)
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
.foregroundColor(.black)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 25)
|
|
.fill(Color.themePrimary)
|
|
)
|
|
}
|
|
.padding(.horizontal)
|
|
.padding(.bottom, isKeyboardVisible ? 20 : 40)
|
|
.disabled(showUsername && userName.trimmingCharacters(in: .whitespaces).isEmpty)
|
|
.opacity((showUsername && userName.trimmingCharacters(in: .whitespaces).isEmpty) ? 0.6 : 1.0)
|
|
.animation(.easeInOut, value: showUsername)
|
|
.frame(maxWidth: .infinity)
|
|
|
|
// 底部安全区域占位
|
|
if isKeyboardVisible {
|
|
Spacer(minLength: 0)
|
|
.frame(height: 20) // 添加一些底部间距当键盘显示时
|
|
}
|
|
}
|
|
.frame(minHeight: geometry.size.height) // 确保内容至少填满可用高度
|
|
}
|
|
}
|
|
.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
|
|
}
|
|
}
|
|
}
|
|
.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()
|
|
}
|
|
} |