added more anomations
This commit is contained in:
1 parent
0d6c562abd
commit
3e990e7af9
4 files changed
+435
-1
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Messages-style entrance for chat rows.
|
||||
///
|
||||
/// Opening a conversation reveals its first page as a bottom-up cascade
|
||||
/// (`.cascade`), and a row appended while the timeline is already rendered
|
||||
/// springs out of the composer's send control when outgoing (`.launch`).
|
||||
/// Everything else stays `.settled`, which keeps history scrolling, reactions
|
||||
/// and selection updates free of animation.
|
||||
struct MessageBubbleEntrance: ViewModifier {
|
||||
enum Mode: Equatable {
|
||||
case settled
|
||||
case cascade(delay: TimeInterval)
|
||||
case launch(outgoing: Bool)
|
||||
|
||||
var isSettled: Bool {
|
||||
if case .settled = self { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let mode: Mode
|
||||
|
||||
// A settled row must never pass through an invisible frame, so only the
|
||||
// rows that actually animate start hidden.
|
||||
@State private var isRevealed: Bool
|
||||
@State private var hasStarted = false
|
||||
|
||||
init(mode: Mode) {
|
||||
self.mode = mode
|
||||
_isRevealed = State(initialValue: mode.isSettled)
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.opacity(isRevealed ? 1 : 0)
|
||||
.scaleEffect(isRevealed ? 1 : displacedScale, anchor: scaleAnchor)
|
||||
.offset(y: isRevealed ? 0 : displacedRise)
|
||||
.onAppear(perform: reveal)
|
||||
}
|
||||
|
||||
/// Outgoing bubbles shrink toward the trailing bottom corner, where the
|
||||
/// composer's send control sits; incoming ones grow from their own edge.
|
||||
private var displacedScale: CGFloat {
|
||||
switch mode {
|
||||
case .settled, .cascade:
|
||||
return ChatMessageEntrancePolicy.entranceScale
|
||||
case .launch(let outgoing):
|
||||
return outgoing
|
||||
? ChatMessageEntrancePolicy.sendScale
|
||||
: ChatMessageEntrancePolicy.receiveScale
|
||||
}
|
||||
}
|
||||
|
||||
private var scaleAnchor: UnitPoint {
|
||||
if case .launch(let outgoing) = mode, outgoing { return .bottomTrailing }
|
||||
return .bottom
|
||||
}
|
||||
|
||||
private var displacedRise: CGFloat {
|
||||
switch mode {
|
||||
case .settled, .cascade:
|
||||
return ChatMessageEntrancePolicy.entranceRise
|
||||
case .launch(let outgoing):
|
||||
return outgoing
|
||||
? ChatMessageEntrancePolicy.sendRise
|
||||
: ChatMessageEntrancePolicy.receiveRise
|
||||
}
|
||||
}
|
||||
|
||||
private func reveal() {
|
||||
guard !hasStarted else { return }
|
||||
hasStarted = true
|
||||
guard !isRevealed else { return }
|
||||
let delay: TimeInterval
|
||||
if case .cascade(let cascadeDelay) = mode {
|
||||
delay = cascadeDelay
|
||||
} else {
|
||||
delay = 0
|
||||
}
|
||||
withAnimation(
|
||||
ChatMessageEntrancePolicy.appendAnimation.delay(delay)
|
||||
) {
|
||||
isRevealed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user