feat: 键盘唤起动画

This commit is contained in:
jinyaqiu 2025-09-01 14:46:16 +08:00
parent 8bf24a3afe
commit 85bd75b03a

View File

@ -23,20 +23,26 @@ public struct AvatarPicker: View {
self._uploadedFileId = uploadedFileId self._uploadedFileId = uploadedFileId
} }
private var avatarSize: CGFloat { //
isKeyboardVisible ? 125 : 225 private var scaleFactor: CGFloat {
isKeyboardVisible ? 0.55 : 1.0
} }
private var borderWidth: CGFloat { private var borderWidth: CGFloat {
isKeyboardVisible ? 3 : 4 isKeyboardVisible ? 3 : 4
} }
//
private var animation: Animation {
.spring(response: 0.4, dampingFraction: 0.7, blendDuration: 0.3)
}
public var body: some View { public var body: some View {
VStack() { VStack(spacing: 20) {
VStack(spacing: 20) { VStack(spacing: 20) {
// Avatar Image Button // Avatar Image Button
Button(action: { Button(action: {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { withAnimation(animation) {
showMediaPicker = true showMediaPicker = true
} }
}) { }) {
@ -45,18 +51,21 @@ public struct AvatarPicker: View {
Image(uiImage: selectedImage) Image(uiImage: selectedImage)
.resizable() .resizable()
.scaledToFill() .scaledToFill()
.frame(width: avatarSize, height: avatarSize) .frame(width: 225, height: 225)
.clipShape(RoundedRectangle(cornerRadius: 20)) .scaleEffect(scaleFactor)
.clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor))
.overlay( .overlay(
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 20)
.stroke(Color.themePrimary, lineWidth: borderWidth) .stroke(Color.themePrimary, lineWidth: borderWidth)
.scaleEffect(scaleFactor)
) )
} else { } else {
// Default SVG avatar with animated dashed border // Default SVG avatar with animated dashed border
SVGImage(svgName: "IP") SVGImage(svgName: "IP")
.frame(width: avatarSize, height: avatarSize) .frame(width: 225, height: 225)
.scaleEffect(scaleFactor)
.contentShape(Rectangle()) .contentShape(Rectangle())
.clipShape(RoundedRectangle(cornerRadius: 20)) .clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor))
.overlay( .overlay(
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 20)
.stroke(style: StrokeStyle( .stroke(style: StrokeStyle(
@ -66,6 +75,7 @@ public struct AvatarPicker: View {
dashPhase: isAnimating ? 40 : 0 dashPhase: isAnimating ? 40 : 0
)) ))
.foregroundColor(Color.themePrimary) .foregroundColor(Color.themePrimary)
.scaleEffect(scaleFactor)
) )
.onAppear { .onAppear {
withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) { withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) {
@ -78,22 +88,23 @@ public struct AvatarPicker: View {
if isUploading { if isUploading {
ProgressView() ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .themePrimary)) .progressViewStyle(CircularProgressViewStyle(tint: .themePrimary))
.scaleEffect(1.5) .scaleEffect(1.5 * scaleFactor)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(width: 225, height: 225)
.scaleEffect(scaleFactor)
.background(Color.black.opacity(0.3)) .background(Color.black.opacity(0.3))
.clipShape(RoundedRectangle(cornerRadius: 20)) .clipShape(RoundedRectangle(cornerRadius: 20 * scaleFactor))
.transition(.opacity) .transition(.opacity)
} }
} }
.frame(width: avatarSize, height: avatarSize) .frame(width: 225 * scaleFactor, height: 225 * scaleFactor)
.animation(.spring(response: 0.4, dampingFraction: 0.7), value: isKeyboardVisible) .animation(animation, value: isKeyboardVisible)
} }
.buttonStyle(ScaleButtonStyle()) .buttonStyle(ScaleButtonStyle())
// Upload Button (only shown when username is not shown) // Upload Button (only shown when username is not shown)
if !showUsername { if !showUsername {
Button(action: { Button(action: {
withAnimation { withAnimation(animation) {
showMediaPicker = true showMediaPicker = true
} }
}) { }) {
@ -107,11 +118,14 @@ public struct AvatarPicker: View {
RoundedRectangle(cornerRadius: 16) RoundedRectangle(cornerRadius: 16)
.fill(Color.themePrimaryLight) .fill(Color.themePrimaryLight)
) )
.scaleEffect(scaleFactor)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.transition(.opacity.combined(with: .move(edge: .bottom))) .transition(.opacity.combined(with: .move(edge: .bottom)))
.animation(animation, value: isKeyboardVisible)
} }
} }
.animation(animation, value: isKeyboardVisible)
.sheet(isPresented: $showMediaPicker) { .sheet(isPresented: $showMediaPicker) {
MediaPicker( MediaPicker(
selectedMedia: Binding( selectedMedia: Binding(
@ -123,7 +137,7 @@ public struct AvatarPicker: View {
uploadManager.addMedia(newMedia) uploadManager.addMedia(newMedia)
// Start upload process // Start upload process
withAnimation { withAnimation(animation) {
isUploading = true isUploading = true
} }
uploadManager.startUpload() uploadManager.startUpload()
@ -168,7 +182,7 @@ public struct AvatarPicker: View {
print("🖼️ Updating selected image") print("🖼️ Updating selected image")
DispatchQueue.main.async { DispatchQueue.main.async {
withAnimation(.spring()) { withAnimation(animation) {
self.selectedImage = image self.selectedImage = image
self.uploadedFileId = fileId self.uploadedFileId = fileId
self.isUploading = false self.isUploading = false
@ -190,7 +204,7 @@ public struct AvatarPicker: View {
if hasFailures { if hasFailures {
print("❌ Some uploads failed") print("❌ Some uploads failed")
DispatchQueue.main.async { DispatchQueue.main.async {
withAnimation { withAnimation(animation) {
self.isUploading = false self.isUploading = false
} }
} }
@ -198,7 +212,7 @@ public struct AvatarPicker: View {
} }
if !showUsername { if !showUsername {
Button(action: { Button(action: {
withAnimation { withAnimation(animation) {
showImageCapture = true showImageCapture = true
} }
}) { }) {
@ -212,14 +226,16 @@ public struct AvatarPicker: View {
RoundedRectangle(cornerRadius: 16) RoundedRectangle(cornerRadius: 16)
.fill(Color.themePrimaryLight) .fill(Color.themePrimaryLight)
) )
.scaleEffect(scaleFactor)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.animation(animation, value: isKeyboardVisible)
.sheet(isPresented: $showImageCapture) { .sheet(isPresented: $showImageCapture) {
CustomCameraView(isPresented: $showImageCapture) { image in CustomCameraView(isPresented: $showImageCapture) { image in
selectedImage = image selectedImage = image
uploadManager.clearAllMedia() uploadManager.clearAllMedia()
uploadManager.addMedia([.image(image)]) uploadManager.addMedia([.image(image)])
withAnimation { withAnimation(animation) {
isUploading = true isUploading = true
} }
uploadManager.startUpload() uploadManager.startUpload()