Skip to content

Commit

Permalink
LoadableImage snapshots.
Browse files Browse the repository at this point in the history
  • Loading branch information
pixlwave committed Oct 3, 2024
1 parent 1d7ddd0 commit a143d31
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 7 deletions.
133 changes: 129 additions & 4 deletions ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,12 @@ private struct LoadableImageContent<TransformerView: View, PlaceholderView: View
case (.gifData, true):
transformer(AnyView(KFAnimatedImage(source: .provider(self))))
case (.none, _), (_, false):
if let blurhash,
// Build a small blurhash image so that it's fast
let image = UIImage(blurHash: blurhash, size: .init(width: 10.0, height: 10.0)) {
transformer(AnyView(Image(uiImage: image).resizable().overlay { blurHashOverlay }))
if let blurHashView {
if shouldRender {
transformer(blurHashView)
} else {
blurHashView
}
} else {
placeholder().overlay { placeholderOverlay }
}
Expand All @@ -153,6 +155,17 @@ private struct LoadableImageContent<TransformerView: View, PlaceholderView: View
}
}

// Note: Returns `AnyView` as this is what `transformer` expects.
var blurHashView: AnyView? {
if let blurhash,
// Build a small blurhash image so that it's fast
let image = UIImage(blurHash: blurhash, size: .init(width: 10.0, height: 10.0)) {
return AnyView(Image(uiImage: image).resizable().overlay { blurHashOverlay })
} else {
return nil
}
}

// MARK: - Overlays

@ViewBuilder
Expand Down Expand Up @@ -286,3 +299,115 @@ extension EnvironmentValues {
/// Whether or not images should be loaded inside `LoadableImage` without a user interaction.
@Entry var shouldAutomaticallyLoadImages = true
}

// MARK: - Previews

struct LoadableImage_Previews: PreviewProvider, TestablePreview {
static let mediaProvider = makeMediaProvider()
static let loadingMediaProvider = makeMediaProvider(isLoading: true)

static var previews: some View {
LazyVGrid(columns: [.init(.adaptive(minimum: 110, maximum: 110))], spacing: 24) {
LoadableImage(url: "mxc://wherever/1234",
mediaType: .timelineItem,
mediaProvider: mediaProvider,
placeholder: placeholder)
.layout(title: "Loaded")

LoadableImage(url: "mxc://wherever/2345",
mediaType: .timelineItem,
blurhash: "KpE4oyayR5|GbHb];3j@of",
mediaProvider: mediaProvider,
placeholder: placeholder)
.layout(title: "Hidden (blurhash)", hideTimelineMedia: true)

LoadableImage(url: "mxc://wherever/3456",
mediaType: .timelineItem,
mediaProvider: mediaProvider,
placeholder: placeholder)
.layout(title: "Hidden (placeholder)", hideTimelineMedia: true)

LoadableImage(url: "mxc://wherever/4567",
mediaType: .timelineItem,
blurhash: "KbLM^j]q$jT|EfR-3rtjXk",
mediaProvider: loadingMediaProvider,
placeholder: placeholder)
.layout(title: "Loading (blurhash)")

LoadableImage(url: "mxc://wherever/5678",
mediaType: .timelineItem,
mediaProvider: loadingMediaProvider,
placeholder: placeholder)
.layout(title: "Loading (placeholder)")

LoadableImage(url: "mxc://wherever/6789",
mediaType: .avatar,
mediaProvider: loadingMediaProvider,
placeholder: placeholder)
.layout(title: "Loading (avatar)")

LoadableImage(url: "mxc://wherever/345",
mediaType: .timelineItem,
blurhash: "KbLM^j]q$jT|EfR-3rtjXk",
mediaProvider: mediaProvider,
transformer: transformer,
placeholder: placeholder)
.layout(title: "Loaded (transformer)")

LoadableImage(url: "mxc://wherever/345",
mediaType: .timelineItem,
blurhash: "KbLM^j]q$jT|EfR-3rtjXk",
mediaProvider: loadingMediaProvider,
transformer: transformer,
placeholder: placeholder)
.layout(title: "Loading (transformer)")

LoadableImage(url: "mxc://wherever/234",
mediaType: .timelineItem,
blurhash: "KbLM^j]q$jT|EfR-3rtjXk",
mediaProvider: mediaProvider,
transformer: transformer,
placeholder: placeholder)
.layout(title: "Hidden (transformer)", hideTimelineMedia: true)
}
}

static func placeholder() -> some View { Color.compound._bgBubbleIncoming }
static func transformer(_ view: AnyView) -> some View {
view.overlay {
Image(systemSymbol: .playCircleFill)
.font(.largeTitle)
.foregroundStyle(.compound.iconAccentPrimary)
}
}

static func makeMediaProvider(isLoading: Bool = false) -> MediaProviderProtocol {
let mediaProvider = MediaProviderMock(configuration: .init())

if isLoading {
mediaProvider.imageFromSourceSizeClosure = { _, _ in nil }
mediaProvider.loadFileFromSourceBodyClosure = { _, _ in .failure(.failedRetrievingFile) }
mediaProvider.loadImageDataFromSourceClosure = { _ in .failure(.failedRetrievingImage) }
mediaProvider.loadImageFromSourceSizeClosure = { _, _ in .failure(.failedRetrievingImage) }
mediaProvider.loadThumbnailForSourceSourceSizeClosure = { _, _ in .failure(.failedRetrievingThumbnail) }
mediaProvider.loadImageRetryingOnReconnectionSizeClosure = { _, _ in
Task { throw MediaProviderError.failedRetrievingImage }
}
}
return mediaProvider
}
}

private extension View {
func layout(title: String, hideTimelineMedia: Bool = false) -> some View {
aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(alignment: .bottom) {
Text(title)
.font(.caption2)
.offset(y: 16)
.padding(.horizontal, -5)
}
.environment(\.shouldAutomaticallyLoadImages, !hideTimelineMedia)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,40 @@ enum RoomTimelineItemFixtures {
senderDisplayName: index > 10 ? "Alice" : "Bob")
}
}

static var mediaChunk: [RoomTimelineItemProtocol] {
[
VideoRoomTimelineItem(id: .random,
timestamp: "10:47 am",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: ""),
content: .init(body: "video",
duration: 100,
source: .init(url: .picturesDirectory, mimeType: nil),
thumbnailSource: .init(url: .picturesDirectory, mimeType: nil),
width: 1920,
height: 1080,
aspectRatio: 1.78,
blurhash: "KtI~70X5V?yss9oyrYs:t6")),
ImageRoomTimelineItem(id: .random,
timestamp: "10:47 am",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: ""),
content: .init(body: "image",
source: .init(url: .picturesDirectory, mimeType: nil),
thumbnailSource: nil,
width: 5120,
height: 3412,
aspectRatio: 1.5,
blurhash: "KpE4oyayR5|GbHb];3j@of"))
]
}
}

private extension TextRoomTimelineItem {
Expand All @@ -260,9 +294,7 @@ private extension TextRoomTimelineItem {
sender: .init(id: "", displayName: senderDisplayName),
content: .init(body: text))
}
}

private extension TextRoomTimelineItem {

func withReadReceipts(_ receipts: [ReadReceipt]) -> TextRoomTimelineItem {
var newSelf = self
newSelf.properties.orderedReadReceipts = receipts
Expand Down
6 changes: 6 additions & 0 deletions PreviewTests/Sources/GeneratedPreviewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ extension PreviewTests {
}
}

func test_loadableImage() {
for preview in LoadableImage_Previews._allPreviews {
assertSnapshots(matching: preview)
}
}

func test_locationMarkerView() {
for preview in LocationMarkerView_Previews._allPreviews {
assertSnapshots(matching: preview)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a143d31

Please sign in to comment.