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, showUsername: Binding, isKeyboardVisible: Binding, uploadedFileId: Binding) { self._selectedImage = selectedImage self._showUsername = showUsername self._isKeyboardVisible = isKeyboardVisible self._uploadedFileId = uploadedFileId } private var avatarSize: CGFloat { isKeyboardVisible ? 125 : 225 } private var borderWidth: CGFloat { isKeyboardVisible ? 3 : 4 } public var body: some View { VStack() { VStack(spacing: 20) { // Avatar Image Button Button(action: { withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { showMediaPicker = true } }) { ZStack { if let selectedImage = selectedImage { Image(uiImage: selectedImage) .resizable() .scaledToFill() .frame(width: avatarSize, height: avatarSize) .clipShape(RoundedRectangle(cornerRadius: 20)) .overlay( RoundedRectangle(cornerRadius: 20) .stroke(Color.themePrimary, lineWidth: borderWidth) ) } else { // Default SVG avatar with animated dashed border SVGImage(svgName: "IP") .frame(width: avatarSize, height: avatarSize) .contentShape(Rectangle()) .overlay( RoundedRectangle(cornerRadius: 20) .stroke(style: StrokeStyle( lineWidth: borderWidth, lineCap: .round, dash: [12, 8], dashPhase: isAnimating ? 40 : 0 )) .foregroundColor(Color.themePrimary) ) .onAppear { withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) { isAnimating = true } } } // Upload indicator if isUploading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .themePrimary)) .scaleEffect(1.5) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.black.opacity(0.3)) .clipShape(RoundedRectangle(cornerRadius: 20)) .transition(.opacity) } } .frame(width: avatarSize, height: avatarSize) .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isKeyboardVisible) } .buttonStyle(ScaleButtonStyle()) // Upload Button (only shown when username is not shown) if !showUsername { Button(action: { withAnimation { 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) ) } .frame(maxWidth: .infinity) .transition(.opacity.combined(with: .move(edge: .bottom))) } } .sheet(isPresented: $showMediaPicker) { MediaPicker( selectedMedia: $uploadManager.selectedMedia, imageSelectionLimit: 1, videoSelectionLimit: 0, allowedMediaTypes: .imagesOnly, selectionMode: .single, onDismiss: { showMediaPicker = false if !uploadManager.selectedMedia.isEmpty { withAnimation { isUploading = true } uploadManager.startUpload() } } ) } .onChange(of: uploadManager.uploadStatus) { _ in if let firstMedia = uploadManager.selectedMedia.first, case .image(let image) = firstMedia, uploadManager.isAllUploaded { withAnimation(.spring()) { selectedImage = image isUploading = false if let status = uploadManager.uploadStatus["0"], case .completed(let fileId) = status { uploadedFileId = fileId } uploadManager.clearAllMedia() } } } if !showUsername { Button(action: { withAnimation { 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) ) } .frame(maxWidth: .infinity) .sheet(isPresented: $showImageCapture) { CustomCameraView(isPresented: $showImageCapture) { image in selectedImage = image uploadManager.selectedMedia = [.image(image)] withAnimation { 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) } }