200 lines
7.8 KiB
Swift
200 lines
7.8 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 avatarSize: CGFloat {
|
|
isKeyboardVisible ? 125 : 225
|
|
}
|
|
|
|
private var borderWidth: CGFloat {
|
|
isKeyboardVisible ? 3 : 4
|
|
}
|
|
|
|
public var body: some View {
|
|
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) {
|
|
CameraView(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)
|
|
}
|
|
} |