mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Render galleries with non-visual attachments as a list (#5932)
* Render galleries with non-visual attachments as a list Adds GalleryListView (with GalleryDivider) for galleries containing a file or audio item, extracting the shared FileTypeIconView from FileRoomTimelineView. GalleryRoomTimelineView picks the list vs the grid via usesListLayout. Tapping to open the preview lands in a follow-up. Co-authored-by: svetoslav.mitov <svetoslav.mitov@sportinno.com> * pr suggestions --------- Co-authored-by: svetoslav.mitov <svetoslav.mitov@sportinno.com>
This commit is contained in:
@@ -1494,6 +1494,7 @@
|
|||||||
F382E2FE3AC3719BC1F22D9C /* MapURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB08D1F7C27A8C24EF81073C /* MapURLs.swift */; };
|
F382E2FE3AC3719BC1F22D9C /* MapURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB08D1F7C27A8C24EF81073C /* MapURLs.swift */; };
|
||||||
F38D32C1B0232AAFE6A0822C /* ExtensionLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */; };
|
F38D32C1B0232AAFE6A0822C /* ExtensionLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */; };
|
||||||
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */; };
|
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */; };
|
||||||
|
F3D5C73C2A0B7AA00BF3781C /* GalleryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19FF46006358DF7E6F47A61A /* GalleryListView.swift */; };
|
||||||
F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */; };
|
F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */; };
|
||||||
F3ECA377FF77E81A4F1FA062 /* TimelineItemSendInfoLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753B4C6C0EDDCBF0708DC384 /* TimelineItemSendInfoLabel.swift */; };
|
F3ECA377FF77E81A4F1FA062 /* TimelineItemSendInfoLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753B4C6C0EDDCBF0708DC384 /* TimelineItemSendInfoLabel.swift */; };
|
||||||
F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; };
|
F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; };
|
||||||
@@ -1814,6 +1815,7 @@
|
|||||||
1912062B53CE95E6F700DA60 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = "<group>"; };
|
1912062B53CE95E6F700DA60 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = "<group>"; };
|
||||||
196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTrackerViewModifier.swift; sourceTree = "<group>"; };
|
196004E7695FBA292A7944AF /* ScreenTrackerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTrackerViewModifier.swift; sourceTree = "<group>"; };
|
||||||
19DD166C3625EE426203FA29 /* AppLockSetupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupTests.swift; sourceTree = "<group>"; };
|
19DD166C3625EE426203FA29 /* AppLockSetupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupTests.swift; sourceTree = "<group>"; };
|
||||||
|
19FF46006358DF7E6F47A61A /* GalleryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryListView.swift; sourceTree = "<group>"; };
|
||||||
1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderProtocol.swift; sourceTree = "<group>"; };
|
1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderProtocol.swift; sourceTree = "<group>"; };
|
||||||
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationRequestDetailsView.swift; sourceTree = "<group>"; };
|
1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationRequestDetailsView.swift; sourceTree = "<group>"; };
|
||||||
1A13364350970987B93F6018 /* JoinRoomByAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomByAddressView.swift; sourceTree = "<group>"; };
|
1A13364350970987B93F6018 /* JoinRoomByAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomByAddressView.swift; sourceTree = "<group>"; };
|
||||||
@@ -6586,6 +6588,7 @@
|
|||||||
C258C9C815272911A5B132C3 /* FormattedBodyText.swift */,
|
C258C9C815272911A5B132C3 /* FormattedBodyText.swift */,
|
||||||
EEE0273EC14A781BBF715E37 /* GalleryGridView.swift */,
|
EEE0273EC14A781BBF715E37 /* GalleryGridView.swift */,
|
||||||
7927284C6F70D60BE5949F8C /* GalleryItemTileView.swift */,
|
7927284C6F70D60BE5949F8C /* GalleryItemTileView.swift */,
|
||||||
|
19FF46006358DF7E6F47A61A /* GalleryListView.swift */,
|
||||||
A04EFA0E3139C01E1CFDC93B /* GalleryRoomTimelineView.swift */,
|
A04EFA0E3139C01E1CFDC93B /* GalleryRoomTimelineView.swift */,
|
||||||
59B7CC77B82C6C67DE3AD869 /* HighlightedTimelineItemModifier.swift */,
|
59B7CC77B82C6C67DE3AD869 /* HighlightedTimelineItemModifier.swift */,
|
||||||
C5599255A6C98EBDA77B76E6 /* ImageRoomTimelineView.swift */,
|
C5599255A6C98EBDA77B76E6 /* ImageRoomTimelineView.swift */,
|
||||||
@@ -8561,6 +8564,7 @@
|
|||||||
46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */,
|
46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */,
|
||||||
82D1368B43020B55347E1743 /* GalleryGridView.swift in Sources */,
|
82D1368B43020B55347E1743 /* GalleryGridView.swift in Sources */,
|
||||||
52BE121FC21CE88803D2D8B5 /* GalleryItemTileView.swift in Sources */,
|
52BE121FC21CE88803D2D8B5 /* GalleryItemTileView.swift in Sources */,
|
||||||
|
F3D5C73C2A0B7AA00BF3781C /* GalleryListView.swift in Sources */,
|
||||||
E08DFC18E97623B5C91C5025 /* GalleryRoomTimelineItem.swift in Sources */,
|
E08DFC18E97623B5C91C5025 /* GalleryRoomTimelineItem.swift in Sources */,
|
||||||
1CA6E1843E1B3680544EB584 /* GalleryRoomTimelineItemContent.swift in Sources */,
|
1CA6E1843E1B3680544EB584 /* GalleryRoomTimelineItemContent.swift in Sources */,
|
||||||
30DA1A668482E5B95E8F1F2B /* GalleryRoomTimelineView.swift in Sources */,
|
30DA1A668482E5B95E8F1F2B /* GalleryRoomTimelineView.swift in Sources */,
|
||||||
|
|||||||
+17
-6
@@ -102,21 +102,32 @@ struct MediaFileRoomTimelineContent: View {
|
|||||||
.foregroundStyle(.compound.textPrimary)
|
.foregroundStyle(.compound.textPrimary)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
} icon: {
|
} icon: {
|
||||||
|
FileTypeIconView(icon: icon, isScanning: isScanning)
|
||||||
|
}
|
||||||
|
.labelStyle(.custom(spacing: 8, alignment: .center))
|
||||||
|
.padding(.horizontal, 4) // Add to the styler's padding of 8, as we use the default insets for the caption.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rounded icon badge used as the leading accessory of a file/audio row, either showing the
|
||||||
|
/// file-type icon or a scanning spinner.
|
||||||
|
struct FileTypeIconView: View {
|
||||||
|
let icon: KeyPath<CompoundIcons, Image>
|
||||||
|
var isScanning = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if isScanning {
|
if isScanning {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.scaledFrame(size: CompoundIcon.Size.medium.value, relativeTo: .body)
|
.scaledFrame(size: CompoundIcon.Size.medium.value, relativeTo: .compound.bodyLG)
|
||||||
} else {
|
} else {
|
||||||
CompoundIcon(icon, size: .medium, relativeTo: .body)
|
CompoundIcon(icon)
|
||||||
.foregroundColor(.compound.iconPrimary)
|
.foregroundColor(.compound.iconPrimary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.scaledPadding(6)
|
.scaledPadding(6)
|
||||||
.background(.compound.iconOnSolidPrimary,
|
.background(.compound.iconOnSolidPrimary,
|
||||||
in: RoundedRectangle(cornerRadius: 4, style: .continuous))
|
in: RoundedRectangle(cornerRadius: 4))
|
||||||
}
|
|
||||||
.labelStyle(.custom(spacing: 8, alignment: .center))
|
|
||||||
.padding(.horizontal, 4) // Add to the styler's padding of 8, as we use the default insets for the caption.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2026 Element Creations Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// A vertical list of file rows for a gallery message. Used when a gallery has a non-visual
|
||||||
|
/// attachment (file/audio), where the grid layout doesn't read well.
|
||||||
|
struct GalleryListView: View {
|
||||||
|
let items: [GalleryItem]
|
||||||
|
let mediaProvider: MediaProviderProtocol?
|
||||||
|
let contentScannerService: ContentScannerServiceProtocol?
|
||||||
|
let onItemTap: (Int) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
|
||||||
|
if index > 0 {
|
||||||
|
GalleryDivider()
|
||||||
|
}
|
||||||
|
|
||||||
|
GalleryListRow(item: item,
|
||||||
|
mediaProvider: mediaProvider,
|
||||||
|
contentScannerService: contentScannerService) {
|
||||||
|
onItemTap(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hairline divider used between gallery file-list rows (and above the caption).
|
||||||
|
struct GalleryDivider: View {
|
||||||
|
@Environment(\.pixelLength) private var pixelLength
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.compound.borderInteractiveSecondary)
|
||||||
|
.frame(height: pixelLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct GalleryListRow: View {
|
||||||
|
let item: GalleryItem
|
||||||
|
let mediaProvider: MediaProviderProtocol?
|
||||||
|
let contentScannerService: ContentScannerServiceProtocol?
|
||||||
|
let onTap: () -> Void
|
||||||
|
|
||||||
|
private var fileDescription: String {
|
||||||
|
item.filename.validatedFileExtension.uppercased()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var iconKeyPath: KeyPath<CompoundIcons, Image> {
|
||||||
|
item.isAudio ? \.audio : \.attachment
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ContentScanningView(contentScannerService: contentScannerService,
|
||||||
|
mediaSource: item.mediaSource,
|
||||||
|
thumbnailSource: item.thumbnailSource,
|
||||||
|
containerShowsFailure: false) {
|
||||||
|
row(icon: icon)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture { onTap() }
|
||||||
|
} scanningContent: {
|
||||||
|
row(icon: scanningIcon)
|
||||||
|
} unsafeContent: { failure in
|
||||||
|
failureRow(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func row(icon: some View) -> some View {
|
||||||
|
Label {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text(item.filename)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
Text(fileDescription)
|
||||||
|
.font(.compound.bodySM)
|
||||||
|
.foregroundStyle(.compound.textSecondary)
|
||||||
|
}
|
||||||
|
.lineLimit(2)
|
||||||
|
} icon: {
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
.labelStyle(.custom(spacing: 8, alignment: .center))
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The critical row shown when an item fails content scanning. Reuses ``ContentScanningFailureView``
|
||||||
|
/// (icon + title + message) inside its own critical container as the surrounding bubble stays regular.
|
||||||
|
private func failureRow(_ failure: ContentScanningFailure) -> some View {
|
||||||
|
ContentScanningFailureView(failure: failure)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.background(Color.compound.bgCriticalSubtle, in: RoundedRectangle(cornerRadius: 6))
|
||||||
|
.overlay {
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.stroke(Color.compound.borderCriticalSubtle)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var icon: some View {
|
||||||
|
if item.hasThumbnail, let source = item.thumbnailSource ?? item.mediaSource {
|
||||||
|
LoadableImage(mediaSource: source,
|
||||||
|
mediaType: .timelineItem(uniqueID: item.id.timelineItemUniqueID),
|
||||||
|
blurhash: item.blurhash,
|
||||||
|
size: item.size,
|
||||||
|
mediaProvider: mediaProvider) { imageView in
|
||||||
|
imageView
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
} placeholder: {
|
||||||
|
Color.compound.bgSubtleSecondary
|
||||||
|
}
|
||||||
|
.frame(width: 36, height: 36)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 4, style: .continuous))
|
||||||
|
} else {
|
||||||
|
FileTypeIconView(icon: iconKeyPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var scanningIcon: some View {
|
||||||
|
FileTypeIconView(icon: iconKeyPath, isScanning: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
+61
-6
@@ -16,16 +16,32 @@ struct GalleryRoomTimelineView: View {
|
|||||||
timelineItem.content.caption?.isBlank == false
|
timelineItem.content.caption?.isBlank == false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List layout when any item is non-visual (file/audio); a grid reads badly for documents.
|
||||||
|
private var usesListLayout: Bool {
|
||||||
|
timelineItem.content.items.contains { !$0.isImage && !$0.isVideo }
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
if usesListLayout {
|
||||||
|
GalleryListView(items: timelineItem.content.items,
|
||||||
|
mediaProvider: context?.mediaProvider,
|
||||||
|
contentScannerService: context?.contentScannerService) { _ in
|
||||||
|
// Tapping to open the full-screen preview lands in a follow-up.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
GalleryGridView(items: timelineItem.content.items,
|
GalleryGridView(items: timelineItem.content.items,
|
||||||
mediaProvider: context?.mediaProvider,
|
mediaProvider: context?.mediaProvider,
|
||||||
contentScannerService: context?.contentScannerService) { _ in
|
contentScannerService: context?.contentScannerService) { _ in
|
||||||
// Tapping to open the full-screen preview lands in a follow-up.
|
// Tapping to open the full-screen preview lands in a follow-up.
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if hasMediaCaption {
|
if hasMediaCaption {
|
||||||
|
if usesListLayout {
|
||||||
|
GalleryDivider()
|
||||||
|
}
|
||||||
caption
|
caption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,12 +76,10 @@ struct GalleryRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
// swiftlint:enable force_try
|
// swiftlint:enable force_try
|
||||||
|
|
||||||
static let mixedScanViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init { source in
|
static let mixedScanViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init { source in
|
||||||
if source.url == unsafeSource.url {
|
switch source.url {
|
||||||
false
|
case .mockMXCUnsafe: false
|
||||||
} else if source.url == scanningSource.url {
|
case .mockMXCScanning: nil
|
||||||
nil
|
default: true
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -77,6 +91,8 @@ struct GalleryRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
makeView(makeItem(itemCount: 5), viewModel).previewDisplayName("5 images")
|
makeView(makeItem(itemCount: 5), viewModel).previewDisplayName("5 images")
|
||||||
makeView(makeItem(itemCount: 9, caption: "A trip to remember 🌅"), viewModel).previewDisplayName("6+ images with caption")
|
makeView(makeItem(itemCount: 9, caption: "A trip to remember 🌅"), viewModel).previewDisplayName("6+ images with caption")
|
||||||
makeView(makeMixedScanItem(), mixedScanViewModel).previewDisplayName("Mixed content scan")
|
makeView(makeMixedScanItem(), mixedScanViewModel).previewDisplayName("Mixed content scan")
|
||||||
|
makeView(makeFileListItem(caption: "Quarterly reports"), viewModel).previewDisplayName("File list")
|
||||||
|
makeView(makeMixedScanFileListItem(), mixedScanViewModel).previewDisplayName("Mixed content scan (files)")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeView(_ item: GalleryRoomTimelineItem, _ viewModel: TimelineViewModel) -> some View {
|
static func makeView(_ item: GalleryRoomTimelineItem, _ viewModel: TimelineViewModel) -> some View {
|
||||||
@@ -123,4 +139,43 @@ struct GalleryRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
items: items),
|
items: items),
|
||||||
properties: .init())
|
properties: .init())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func makeFileListItem(caption: String?) -> GalleryRoomTimelineItem {
|
||||||
|
let items: [GalleryItem] = [
|
||||||
|
.mockImage(index: 0, filename: "photo.jpg"),
|
||||||
|
.mockFile(index: 1, filename: "report.pdf"),
|
||||||
|
.mockAudio(index: 2, filename: "meeting.m4a")
|
||||||
|
]
|
||||||
|
|
||||||
|
return GalleryRoomTimelineItem(id: .randomEvent,
|
||||||
|
timestamp: .mock,
|
||||||
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
canBeRepliedTo: true,
|
||||||
|
sender: .init(id: "Bob"),
|
||||||
|
content: .init(body: "Mixed gallery",
|
||||||
|
caption: caption,
|
||||||
|
items: items),
|
||||||
|
properties: .init())
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func makeMixedScanFileListItem() -> GalleryRoomTimelineItem {
|
||||||
|
// Safe/unsafe/scanning rows via the mixed-scan sources.
|
||||||
|
let items: [GalleryItem] = [
|
||||||
|
.mockFile(index: 0, filename: "report.zip", source: safeSource, contentType: .zip),
|
||||||
|
.mockFile(index: 1, filename: "contract.pdf", source: unsafeSource),
|
||||||
|
.mockAudio(index: 2, filename: "meeting.m4a", source: scanningSource)
|
||||||
|
]
|
||||||
|
|
||||||
|
return GalleryRoomTimelineItem(id: .randomEvent,
|
||||||
|
timestamp: .mock,
|
||||||
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
canBeRepliedTo: true,
|
||||||
|
sender: .init(id: "Bob"),
|
||||||
|
content: .init(body: "Files",
|
||||||
|
caption: "Mixed scan states",
|
||||||
|
items: items),
|
||||||
|
properties: .init())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -175,4 +175,22 @@ nonisolated extension GalleryItem {
|
|||||||
return .video(id: .mock(index),
|
return .video(id: .mock(index),
|
||||||
.init(filename: filename, videoInfo: .mockVideo(duration: duration), thumbnailInfo: thumbnailInfo, blurhash: blurhash, contentType: contentType))
|
.init(filename: filename, videoInfo: .mockVideo(duration: duration), thumbnailInfo: thumbnailInfo, blurhash: blurhash, contentType: contentType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func mockFile(index: Int = 0,
|
||||||
|
filename: String = "document.pdf",
|
||||||
|
source: MediaSourceProxy? = nil,
|
||||||
|
thumbnailSource: MediaSourceProxy? = nil,
|
||||||
|
contentType: UTType? = .pdf) -> GalleryItem {
|
||||||
|
.file(id: .mock(index),
|
||||||
|
.init(filename: filename, source: source, fileSize: nil, thumbnailSource: thumbnailSource, contentType: contentType))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func mockAudio(index: Int = 0,
|
||||||
|
filename: String = "audio.m4a",
|
||||||
|
source: MediaSourceProxy? = nil,
|
||||||
|
duration: TimeInterval = 65,
|
||||||
|
contentType: UTType? = .mpeg4Audio) -> GalleryItem {
|
||||||
|
.audio(id: .mock(index),
|
||||||
|
.init(filename: filename, duration: duration, waveform: nil, source: source, fileSize: nil, contentType: contentType))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2a04cfde76dda32e6b03de15287fba371469a9a976a276de1170be101e6a4b47
|
||||||
|
size 114825
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5af56b0ee13ff01980b521308e355dd2a97e3e544292c00d1d06e66ea7053a2f
|
||||||
|
size 115619
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:096ad88c39dac4de5ec8a07365abef703b0ec981081d5b99c58625cc7fb117d5
|
||||||
|
size 70447
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8ab463160327c4900a4687c2373617f3ce8e1b56da316e50b5ae142a905046c8
|
||||||
|
size 71049
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0aef46856afe160305ec163ff9edd2abdc34f7bcbc0f5e3b9cd20227a501a79a
|
||||||
|
size 108840
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:dd673d411dbf04a146fc5889e3e98f11f2837ab8e59fe1238140d493621dbae2
|
||||||
|
size 121207
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:927bb7fd0f12f6817247b1f15c3c4fb27573120d280f2a0fd6a17170a7927bc2
|
||||||
|
size 62747
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a6ca4d4e763aacdfc31d978c641072fd506c44d271b42a750659199bf2738962
|
||||||
|
size 71840
|
||||||
Reference in New Issue
Block a user