Сделать плавную анимацию кнопки send

This commit is contained in:
2026-03-02 19:56:09 +05:00
parent 8238fd1940
commit 003c262378

View File

@@ -75,6 +75,17 @@ struct ChatDetailView: View {
!messageText.isEmpty
}
private var sendButtonProgress: CGFloat {
shouldShowSendButton ? 1 : 0
}
private var micButtonProgress: CGFloat {
shouldShowSendButton ? 0 : 1
}
private var sendButtonWidth: CGFloat { 38 }
private var sendButtonHeight: CGFloat { 36 }
private var composerHorizontalPadding: CGFloat {
isInputFocused ? 16 : 28
}
@@ -337,7 +348,7 @@ private extension ChatDetailView {
.padding(.horizontal, 16)
}
HStack(alignment: .bottom, spacing: 6) {
HStack(alignment: .bottom, spacing: shouldShowSendButton ? 0 : 6) {
Button {
// Placeholder for attachment picker
} label: {
@@ -366,7 +377,7 @@ private extension ChatDetailView {
.padding(.bottom, 8)
.frame(maxWidth: .infinity, minHeight: 36, alignment: .bottomLeading)
HStack(alignment: .center, spacing: 8) {
HStack(alignment: .center, spacing: 0) {
Button {
// Placeholder for quick actions
} label: {
@@ -381,10 +392,9 @@ private extension ChatDetailView {
.accessibilityLabel("Quick actions")
.buttonStyle(ChatDetailGlassPressButtonStyle())
}
.padding(.trailing, 8)
.padding(.trailing, 8 + (sendButtonWidth * sendButtonProgress))
.frame(height: 36, alignment: .center)
if shouldShowSendButton {
.overlay(alignment: .trailing) {
Button(action: sendCurrentMessage) {
ZStack {
// Mirrors the layered blend stack from the original SVG icon.
@@ -431,8 +441,10 @@ private extension ChatDetailView {
.blendMode(.overlay)
}
.compositingGroup()
.opacity(0.42 + (0.58 * sendButtonProgress))
.scaleEffect(0.72 + (0.28 * sendButtonProgress))
.frame(width: 22, height: 19)
.frame(width: 44, height: 36)
.frame(width: sendButtonWidth, height: sendButtonHeight)
.background {
Capsule().fill(Color(hex: 0x008BFF))
}
@@ -440,7 +452,18 @@ private extension ChatDetailView {
.accessibilityLabel("Send")
.disabled(!canSend)
.buttonStyle(ChatDetailGlassPressButtonStyle())
.transition(.move(edge: .trailing).combined(with: .opacity))
.allowsHitTesting(shouldShowSendButton)
.opacity(Double(sendButtonProgress))
.scaleEffect(0.74 + (0.26 * sendButtonProgress), anchor: .trailing)
.blur(radius: (1 - sendButtonProgress) * 2.1)
.mask(
Capsule()
.frame(
width: max(0.001, sendButtonWidth * sendButtonProgress),
height: max(0.001, sendButtonHeight * sendButtonProgress)
)
.frame(width: sendButtonWidth, height: sendButtonHeight, alignment: .trailing)
)
}
}
.padding(3)
@@ -451,21 +474,28 @@ private extension ChatDetailView {
)
}
if !shouldShowSendButton {
Button(action: trailingAction) {
TelegramVectorIcon(
pathData: TelegramIconPath.microphone,
viewBox: CGSize(width: 18, height: 24),
color: Color.white
)
.frame(width: 18, height: 24)
.frame(width: 42, height: 42)
.background { floatingCircleBackground(strokeOpacity: 0.18) }
}
.accessibilityLabel("Voice message")
.buttonStyle(ChatDetailGlassPressButtonStyle())
.transition(.move(edge: .trailing).combined(with: .opacity))
Button(action: trailingAction) {
TelegramVectorIcon(
pathData: TelegramIconPath.microphone,
viewBox: CGSize(width: 18, height: 24),
color: Color.white
)
.frame(width: 18, height: 24)
.frame(width: 42, height: 42)
.background { floatingCircleBackground(strokeOpacity: 0.18) }
}
.accessibilityLabel("Voice message")
.buttonStyle(ChatDetailGlassPressButtonStyle())
.allowsHitTesting(!shouldShowSendButton)
.opacity(Double(micButtonProgress))
.scaleEffect(
x: max(0.001, 0.42 + (0.58 * micButtonProgress)),
y: 0.78 + (0.22 * micButtonProgress),
anchor: .trailing
)
.blur(radius: (1 - micButtonProgress) * 2.4)
.frame(width: 42 * micButtonProgress, height: 42, alignment: .trailing)
.clipped()
}
.padding(.horizontal, composerHorizontalPadding)
.padding(.top, 4)