Add voice message rendering to the timeline (#1776)

This commit is contained in:
Nicolas Mauri
2023-09-21 16:36:21 +02:00
committed by GitHub
parent eba3e25373
commit b8e5611c00
31 changed files with 660 additions and 20 deletions
@@ -30,7 +30,7 @@ struct TimelineReplyView: View {
switch timelineItemReplyDetails {
case .loaded(let sender, let content):
switch content {
case .audio(let content):
case .audio(let content), .voice(let content):
ReplyView(sender: sender,
plainBody: content.body,
formattedBody: nil,
@@ -204,6 +204,7 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview {
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .audio(.init(body: "Some audio",
duration: 0,
waveform: nil,
source: nil,
contentType: nil))))
@@ -18,6 +18,7 @@ import SwiftUI
struct LongPressWithFeedback: ViewModifier {
let action: () -> Void
let disabled: () -> Bool
@State private var triggerTask: Task<Void, Never>?
@State private var isLongPressing = false
@@ -48,7 +49,9 @@ struct LongPressWithFeedback: ViewModifier {
try? await Task.sleep(for: .seconds(0.35))
if Task.isCancelled { return }
guard !disabled() else { return }
action()
feedbackGenerator.impactOccurred()
}
@@ -57,8 +60,8 @@ struct LongPressWithFeedback: ViewModifier {
}
extension View {
func longPressWithFeedback(action: @escaping () -> Void) -> some View {
modifier(LongPressWithFeedback(action: action))
func longPressWithFeedback(disabled: @escaping @autoclosure () -> Bool = false, action: @escaping () -> Void) -> some View {
modifier(LongPressWithFeedback(action: action, disabled: disabled))
}
}
@@ -128,7 +128,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
}
// We need a tap gesture before this long one so that it doesn't
// steal away the gestures from the scroll view
.longPressWithFeedback {
.longPressWithFeedback(disabled: context.viewState.longPressDisabledItemID == timelineItem.id) {
context.send(viewAction: .timelineItemMenu(itemID: timelineItem.id))
}
.swipeRightAction {
@@ -435,10 +435,12 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
sender: .init(id: ""),
content: .init(body: "audio.ogg",
duration: 100,
waveform: Waveform.mockWaveform,
source: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .text(.init(body: "Short")))))
FileRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: false,
@@ -482,6 +484,21 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .text(.init(body: "Short")))))
VoiceRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
isThreaded: true,
sender: .init(id: ""),
content: .init(body: "audio.ogg",
duration: 100,
waveform: Waveform.mockWaveform,
source: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .text(.init(body: "Short")))),
playbackViewState: VoiceRoomPlaybackViewState(duration: 10, waveform: Waveform.mockWaveform))
}
.environmentObject(viewModel.context)
}
@@ -73,7 +73,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
}
// We need a tap gesture before this long one so that it doesn't
// steal away the gestures from the scroll view
.longPressWithFeedback {
.longPressWithFeedback(disabled: context.viewState.longPressDisabledItemID == timelineItem.id) {
context.send(viewAction: .timelineItemMenu(itemID: timelineItem.id))
}
.swipeRightAction {
@@ -164,6 +164,7 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
sender: .init(id: ""),
content: .init(body: "audio.ogg",
duration: 100,
waveform: nil,
source: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
@@ -211,6 +212,20 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .text(.init(body: "Short")))))
VoiceRoomTimelineView(timelineItem: .init(id: .init(timelineID: ""),
timestamp: "10:42",
isOutgoing: true,
isEditable: false,
isThreaded: true,
sender: .init(id: ""),
content: .init(body: "audio.ogg",
duration: 100,
waveform: Waveform.mockWaveform,
source: nil,
contentType: nil),
replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .text(.init(body: "Short")))),
playbackViewState: VoiceRoomPlaybackViewState(duration: 10, waveform: Waveform.mockWaveform))
}
.environmentObject(viewModel.context)
}
@@ -24,6 +24,7 @@ struct SwipeRightAction<Label: View>: ViewModifier {
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
@State private var canStartAction = false
@State private var animate = false
@GestureState private var dragGestureActive = false
@State private var hasReachedActionThreshold = false
@@ -39,7 +40,7 @@ struct SwipeRightAction<Label: View>: ViewModifier {
func body(content: Content) -> some View {
content
.offset(x: xOffset, y: 0.0)
.animation(.interactiveSpring().speed(0.5), value: xOffset)
.animation(.interactiveSpring().speed(0.5), value: animate)
.gesture(DragGesture()
.updating($dragGestureActive) { _, state, _ in
// Available actions should be computed on the fly so we use a gesture state change
@@ -68,6 +69,7 @@ struct SwipeRightAction<Label: View>: ViewModifier {
} else {
hasReachedActionThreshold = false
}
animate = true
}
.onEnded { _ in
if xOffset > actionThreshold {
@@ -75,6 +77,7 @@ struct SwipeRightAction<Label: View>: ViewModifier {
}
xOffset = 0.0
animate = false
}
)
.onChange(of: dragGestureActive, perform: { value in
@@ -52,6 +52,6 @@ struct AudioRoomTimelineView_Previews: PreviewProvider, TestablePreview {
isEditable: false,
isThreaded: false,
sender: .init(id: "Bob"),
content: .init(body: "audio.ogg", duration: 300, source: nil, contentType: nil)))
content: .init(body: "audio.ogg", duration: 300, waveform: nil, source: nil, contentType: nil)))
}
}