feat: 键盘动画
This commit is contained in:
parent
edef20028d
commit
58f560c714
Binary file not shown.
@ -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<UIImage?>, showUsername: Binding<Bool>) {
|
||||
// 添加完成回调
|
||||
var onFileUploaded: ((String) -> Void)? = nil
|
||||
|
||||
public init(selectedImage: Binding<UIImage?>, showUsername: Binding<Bool>, isKeyboardVisible: Binding<Bool>, 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
44
wake/View/Owner/UserInfo/UserService.swift
Normal file
44
wake/View/Owner/UserInfo/UserService.swift
Normal file
@ -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<UpdateUsernameResponse, NetworkError>) -> 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<UpdateUsernameResponse, NetworkError>) in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
self?.logger.info("✅ 用户信息更新成功: \(response.message ?? "")")
|
||||
case .failure(let error):
|
||||
self?.logger.error("❌ 用户信息更新失败: \(error.localizedDescription)")
|
||||
}
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user