mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Custom media gallery views for files and voice messages (#3610)
* Extract the timeline item background from the BubbledStyler. * Use different views for files and voices messages, reuse the timeline content but only keep the bubble background instead of the whole TimelineStyler. * Add back max voice message width and add missing accessibility label
This commit is contained in:
+12
-11
@@ -95,16 +95,17 @@ struct MediaEventsTimelineScreen: View {
|
||||
ForEach(context.viewState.groups) { group in
|
||||
Section {
|
||||
ForEach(group.items) { item in
|
||||
viewForTimelineItem(item)
|
||||
.scaleEffect(.init(width: 1, height: -1))
|
||||
.onTapGesture {
|
||||
VStack(spacing: 20) {
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
context.send(viewAction: .tappedItem(item))
|
||||
} label: {
|
||||
viewForTimelineItem(item)
|
||||
.scaleEffect(.init(width: 1, height: -1))
|
||||
}
|
||||
.accessibilityActions {
|
||||
Button(L10n.actionShow) {
|
||||
context.send(viewAction: .tappedItem(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
} footer: {
|
||||
// Use a footer as the header because the scrollView is flipped
|
||||
@@ -143,13 +144,13 @@ struct MediaEventsTimelineScreen: View {
|
||||
case .video(let timelineItem):
|
||||
VideoMediaEventsTimelineView(timelineItem: timelineItem)
|
||||
case .file(let timelineItem):
|
||||
FileRoomTimelineView(timelineItem: timelineItem)
|
||||
FileMediaEventsTimelineView(timelineItem: timelineItem)
|
||||
case .audio(let timelineItem):
|
||||
AudioRoomTimelineView(timelineItem: timelineItem)
|
||||
AudioMediaEventsTimelineView(timelineItem: timelineItem)
|
||||
case .voice(let timelineItem):
|
||||
let defaultPlayerState = AudioPlayerState(id: .timelineItemIdentifier(timelineItem.id), title: L10n.commonVoiceMessage, duration: 0)
|
||||
let playerState = context.viewState.activeTimelineContextProvider().viewState.audioPlayerStateProvider?(timelineItem.id) ?? defaultPlayerState
|
||||
VoiceMessageRoomTimelineView(timelineItem: timelineItem, playerState: playerState)
|
||||
VoiceMessageMediaEventsTimelineView(timelineItem: timelineItem, playerState: playerState)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct AudioMediaEventsTimelineView: View {
|
||||
let timelineItem: AudioRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
MediaFileRoomTimelineContent(timelineItemID: timelineItem.id,
|
||||
filename: timelineItem.content.filename,
|
||||
fileSize: timelineItem.content.fileSize,
|
||||
caption: timelineItem.content.caption,
|
||||
formattedCaption: timelineItem.content.formattedCaption,
|
||||
additionalWhitespaces: timelineItem.additionalWhitespaces(),
|
||||
isAudioFile: true)
|
||||
.accessibilityLabel(L10n.commonAudio)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.bubbleBackground(timelineItem: timelineItem,
|
||||
insets: .init(top: 8, leading: 12, bottom: 8, trailing: 12),
|
||||
color: .compound.bgSubtleSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioMediaEventsTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = TimelineViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20) {
|
||||
AudioMediaEventsTimelineView(timelineItem: makeItem(filename: "audio.ogg",
|
||||
fileSize: 2 * 1024 * 1024))
|
||||
|
||||
AudioMediaEventsTimelineView(timelineItem: makeItem(filename: "Best Song Ever.mp3",
|
||||
fileSize: 7 * 1024 * 1024,
|
||||
caption: "This song rocks!"))
|
||||
}
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
|
||||
static func makeItem(filename: String, fileSize: UInt, caption: String? = nil) -> AudioRoomTimelineItem {
|
||||
.init(id: .randomEvent,
|
||||
timestamp: .mock,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "Bob"),
|
||||
content: .init(filename: filename,
|
||||
caption: caption,
|
||||
duration: 300,
|
||||
waveform: nil,
|
||||
source: nil,
|
||||
fileSize: fileSize,
|
||||
contentType: nil))
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct FileMediaEventsTimelineView: View {
|
||||
let timelineItem: FileRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
MediaFileRoomTimelineContent(timelineItemID: timelineItem.id,
|
||||
filename: timelineItem.content.filename,
|
||||
fileSize: timelineItem.content.fileSize,
|
||||
caption: timelineItem.content.caption,
|
||||
formattedCaption: timelineItem.content.formattedCaption,
|
||||
additionalWhitespaces: timelineItem.additionalWhitespaces())
|
||||
.accessibilityLabel(L10n.commonFile)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.bubbleBackground(timelineItem: timelineItem,
|
||||
insets: .init(top: 8, leading: 12, bottom: 8, trailing: 12),
|
||||
color: .compound.bgSubtleSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
struct FileMediaEventsTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = TimelineViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20.0) {
|
||||
FileMediaEventsTimelineView(timelineItem: makeItem(filename: "document.pdf"))
|
||||
|
||||
FileMediaEventsTimelineView(timelineItem: makeItem(filename: "document.pdf",
|
||||
fileSize: 3 * 1024 * 1024))
|
||||
|
||||
FileMediaEventsTimelineView(timelineItem: makeItem(filename: "spreadsheet.xlsx",
|
||||
fileSize: 17 * 1024,
|
||||
caption: "The important figures you asked me to send over."))
|
||||
|
||||
FileMediaEventsTimelineView(timelineItem: makeItem(filename: "document.txt",
|
||||
fileSize: 456,
|
||||
caption: "Plain caption",
|
||||
formattedCaption: "Formatted caption"))
|
||||
}
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
|
||||
static func makeItem(filename: String,
|
||||
fileSize: UInt? = nil,
|
||||
caption: String? = nil,
|
||||
formattedCaption: AttributedString? = nil) -> FileRoomTimelineItem {
|
||||
.init(id: .randomEvent,
|
||||
timestamp: .mock,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "Bob"),
|
||||
content: .init(filename: filename,
|
||||
caption: caption,
|
||||
formattedCaption: formattedCaption,
|
||||
source: nil,
|
||||
fileSize: fileSize,
|
||||
thumbnailSource: nil,
|
||||
contentType: nil))
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct VoiceMessageMediaEventsTimelineView: View {
|
||||
let timelineItem: VoiceMessageRoomTimelineItem
|
||||
let playerState: AudioPlayerState
|
||||
|
||||
var body: some View {
|
||||
VoiceMessageRoomTimelineContent(timelineItem: timelineItem,
|
||||
playerState: playerState)
|
||||
.accessibilityLabel(L10n.commonVoiceMessage)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.bubbleBackground(timelineItem: timelineItem,
|
||||
insets: .init(top: 8, leading: 12, bottom: 8, trailing: 12),
|
||||
color: .compound.bgSubtleSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Content
|
||||
|
||||
struct VoiceMessageMediaEventsTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = TimelineViewModel.mock
|
||||
static let timelineItemIdentifier = TimelineItemIdentifier.randomEvent
|
||||
static let voiceRoomTimelineItem = VoiceMessageRoomTimelineItem(id: timelineItemIdentifier,
|
||||
timestamp: .mock,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "Bob"),
|
||||
content: .init(filename: "audio.ogg",
|
||||
duration: 300,
|
||||
waveform: EstimatedWaveform.mockWaveform,
|
||||
source: nil,
|
||||
fileSize: nil,
|
||||
contentType: nil))
|
||||
|
||||
static let playerState = AudioPlayerState(id: .timelineItemIdentifier(timelineItemIdentifier),
|
||||
title: L10n.commonVoiceMessage,
|
||||
duration: 10.0,
|
||||
waveform: EstimatedWaveform.mockWaveform,
|
||||
progress: 0.4)
|
||||
|
||||
static var previews: some View {
|
||||
body
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
|
||||
static var body: some View {
|
||||
VoiceMessageMediaEventsTimelineView(timelineItem: voiceRoomTimelineItem, playerState: playerState)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user