271 lines
11 KiB
Swift
271 lines
11 KiB
Swift
import SwiftUI
|
|
|
|
public struct AvatarPicker: View {
|
|
@StateObject private var uploadManager = MediaUploadManager()
|
|
@State private var showMediaPicker = false
|
|
@State private var showImageCapture = false
|
|
@State private var isUploading = false
|
|
@Binding var selectedImage: UIImage?
|
|
@Binding var showUsername: Bool
|
|
@Binding var isKeyboardVisible: Bool
|
|
@Binding var uploadedFileId: String?
|
|
|
|
// Animation state
|
|
@State private var isAnimating = false
|
|
|
|
public init(selectedImage: Binding<UIImage?>,
|
|
showUsername: Binding<Bool>,
|
|
isKeyboardVisible: Binding<Bool>,
|
|
uploadedFileId: Binding<String?>) {
|
|
self._selectedImage = selectedImage
|
|
self._showUsername = showUsername
|
|
self._isKeyboardVisible = isKeyboardVisible
|
|
self._uploadedFileId = uploadedFileId
|
|
}
|
|
|
|
// 添加缩放比例
|
|
private var scaleFactor: CGFloat {
|
|
isKeyboardVisible ? 0.55 : 1.0
|
|
}
|
|
|
|
private var borderWidth: CGFloat {
|
|
isKeyboardVisible ? 3 : 4
|
|
}
|
|
|
|
// 添加动画配置
|
|
private var animation: Animation {
|
|
.spring(response: 0.4, dampingFraction: 0.7, blendDuration: 0.3)
|
|
}
|
|
|
|
public var body: some View {
|
|
VStack(spacing: 20) {
|
|
VStack(spacing: 20) {
|
|
// Avatar Image Button
|
|
Button(action: {
|
|
withAnimation(animation) {
|
|
showMediaPicker = true
|
|
}
|
|
}) {
|
|
ZStack {
|
|
if let selectedImage = selectedImage {
|
|
Image(uiImage: selectedImage)
|
|
.resizable()
|
|
.scaledToFill()
|
|
.frame(width: 225, height: 225)
|
|
.scaleEffect(scaleFactor)
|
|
.clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 20)
|
|
.stroke(Color.themePrimary, lineWidth: borderWidth)
|
|
.scaleEffect(scaleFactor)
|
|
)
|
|
} else {
|
|
// Default SVG avatar with animated dashed border
|
|
SVGImage(svgName: "IP")
|
|
.frame(width: 225, height: 225)
|
|
.scaleEffect(scaleFactor)
|
|
.contentShape(Rectangle())
|
|
.clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 20)
|
|
.stroke(style: StrokeStyle(
|
|
lineWidth: borderWidth,
|
|
lineCap: .round,
|
|
dash: [12, 8],
|
|
dashPhase: isAnimating ? 40 : 0
|
|
))
|
|
.foregroundColor(Color.themePrimary)
|
|
.scaleEffect(scaleFactor)
|
|
)
|
|
.onAppear {
|
|
withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) {
|
|
isAnimating = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Upload indicator
|
|
if isUploading {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .themePrimary))
|
|
.scaleEffect(1.5 * scaleFactor)
|
|
.frame(width: 225, height: 225)
|
|
.scaleEffect(scaleFactor)
|
|
.background(Color.black.opacity(0.3))
|
|
.clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor))
|
|
.transition(.opacity)
|
|
}
|
|
}
|
|
.frame(width: 225 * scaleFactor, height: 225 * scaleFactor)
|
|
.animation(animation, value: isKeyboardVisible)
|
|
}
|
|
.buttonStyle(ScaleButtonStyle())
|
|
|
|
// Upload Button (only shown when username is not shown)
|
|
if !showUsername {
|
|
Button(action: {
|
|
withAnimation(animation) {
|
|
showMediaPicker = true
|
|
}
|
|
}) {
|
|
Text("Upload from Gallery")
|
|
.font(Typography.font(for: .subtitle, family: .inter))
|
|
.fontWeight(.regular)
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
.foregroundColor(.black)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 16)
|
|
.fill(Color.themePrimaryLight)
|
|
)
|
|
.scaleEffect(scaleFactor)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
|
.animation(animation, value: isKeyboardVisible)
|
|
}
|
|
}
|
|
.animation(animation, value: isKeyboardVisible)
|
|
.sheet(isPresented: $showMediaPicker) {
|
|
MediaPicker(
|
|
selectedMedia: Binding(
|
|
get: { uploadManager.selectedMedia },
|
|
set: { newMedia in
|
|
// Only process if we have new media
|
|
if !newMedia.isEmpty {
|
|
uploadManager.clearAllMedia()
|
|
uploadManager.addMedia(newMedia)
|
|
|
|
// Start upload process
|
|
withAnimation(animation) {
|
|
isUploading = true
|
|
}
|
|
uploadManager.startUpload()
|
|
print("🔄 Upload started")
|
|
}
|
|
|
|
// Dismiss the picker after processing
|
|
showMediaPicker = false
|
|
}
|
|
),
|
|
imageSelectionLimit: 1,
|
|
videoSelectionLimit: 0,
|
|
allowedMediaTypes: .imagesOnly,
|
|
selectionMode: .single,
|
|
onDismiss: {
|
|
showMediaPicker = false
|
|
}
|
|
)
|
|
}
|
|
.onChange(of: uploadManager.uploadStatus) { status in
|
|
print("🔄 Upload status changed: ", status)
|
|
|
|
// 检查是否有待处理的上传
|
|
let pendingUploads = uploadManager.selectedMedia.filter { media in
|
|
guard let status = uploadManager.uploadStatus[media.id] else { return true }
|
|
return !status.isCompleted && !status.isUploading
|
|
}
|
|
|
|
if !pendingUploads.isEmpty {
|
|
print("🔄 Found \(pendingUploads.count) pending uploads, starting upload...")
|
|
uploadManager.startUpload()
|
|
}
|
|
|
|
// 检查是否有已完成的上传
|
|
for (mediaId, status) in status {
|
|
if case .completed(let fileId) = status {
|
|
print("✅ Found completed upload with fileId: ", fileId)
|
|
|
|
// 查找对应的媒体项
|
|
if let media = uploadManager.selectedMedia.first(where: { $0.id == mediaId }),
|
|
case .image(let image) = media {
|
|
|
|
print("🖼️ Updating selected image")
|
|
DispatchQueue.main.async {
|
|
withAnimation(animation) {
|
|
self.selectedImage = image
|
|
self.uploadedFileId = fileId
|
|
self.isUploading = false
|
|
}
|
|
// 成功更新后清除上传状态
|
|
self.uploadManager.clearAllMedia()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查是否有失败的上传
|
|
let hasFailures = status.values.contains {
|
|
if case .failed = $0 { return true }
|
|
return false
|
|
}
|
|
|
|
if hasFailures {
|
|
print("❌ Some uploads failed")
|
|
DispatchQueue.main.async {
|
|
withAnimation(animation) {
|
|
self.isUploading = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !showUsername {
|
|
Button(action: {
|
|
withAnimation(animation) {
|
|
showImageCapture = true
|
|
}
|
|
}) {
|
|
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)
|
|
)
|
|
.scaleEffect(scaleFactor)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.animation(animation, value: isKeyboardVisible)
|
|
.sheet(isPresented: $showImageCapture) {
|
|
CustomCameraView(isPresented: $showImageCapture) { image in
|
|
selectedImage = image
|
|
uploadManager.clearAllMedia()
|
|
uploadManager.addMedia([.image(image)])
|
|
withAnimation(animation) {
|
|
isUploading = true
|
|
}
|
|
uploadManager.startUpload()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Button style for scale effect
|
|
private struct ScaleButtonStyle: ButtonStyle {
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
configuration.label
|
|
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
|
|
.animation(.easeInOut(duration: 0.2), value: configuration.isPressed)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
struct AvatarPicker_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
AvatarPicker(
|
|
selectedImage: .constant(nil),
|
|
showUsername: .constant(false),
|
|
isKeyboardVisible: .constant(false),
|
|
uploadedFileId: .constant(nil)
|
|
)
|
|
.padding()
|
|
.background(Color.gray.opacity(0.1))
|
|
.previewLayout(.sizeThatFits)
|
|
}
|
|
} |