mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Add voice message rendering to the timeline (#1776)
This commit is contained in:
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user