From f9be39eb4fb958bd55dda74aea2c613c32ef5c82 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 16 Dec 2024 11:12:01 +0200 Subject: [PATCH] Fix the media gallery's empty state showing up at wrong times. --- .../SwiftUI/Layout/TimelineMediaFrame.swift | 5 +++ .../MediaEventsTimelineScreenModels.swift | 5 +++ .../MediaEventsTimelineScreenViewModel.swift | 5 ++- .../View/MediaEventsTimelineScreen.swift | 18 ++++----- .../ImageMediaEventsTimelineView.swift | 14 +++---- .../VideoMediaEventsTimelineView.swift | 37 +++++++++++++------ .../MockRoomTimelineController.swift | 8 ++-- ...geMediaEventsTimelineView-iPad-en-GB.1.png | 4 +- ...eMediaEventsTimelineView-iPad-pseudo.1.png | 4 +- ...iaEventsTimelineView-iPhone-16-en-GB.1.png | 4 +- ...aEventsTimelineView-iPhone-16-pseudo.1.png | 4 +- ...aEventsTimelineScreen-iPad-en-GB.Media.png | 4 +- ...EventsTimelineScreen-iPad-pseudo.Media.png | 4 +- ...tsTimelineScreen-iPhone-16-en-GB.Media.png | 4 +- ...sTimelineScreen-iPhone-16-pseudo.Media.png | 4 +- ...eoMediaEventsTimelineView-iPad-en-GB.1.png | 4 +- ...oMediaEventsTimelineView-iPad-pseudo.1.png | 4 +- ...iaEventsTimelineView-iPhone-16-en-GB.1.png | 4 +- ...aEventsTimelineView-iPhone-16-pseudo.1.png | 4 +- 19 files changed, 80 insertions(+), 60 deletions(-) diff --git a/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift b/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift index c715f5fc9..cd5bb6399 100644 --- a/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift +++ b/ElementX/Sources/Other/SwiftUI/Layout/TimelineMediaFrame.swift @@ -29,4 +29,9 @@ extension View { } } } + + @ViewBuilder + func mediaGalleryTimelineAspectRatio(imageInfo: ImageInfoProxy?) -> some View { + aspectRatio(imageInfo?.aspectRatio, contentMode: .fill) + } } diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift index 38b12d8cb..044caee6f 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift @@ -23,6 +23,7 @@ struct MediaEventsTimelineGroup: Identifiable { } struct MediaEventsTimelineScreenViewState: BindableState { + var hasReachedTimelineStart = false var isBackPaginating = false var groups = [MediaEventsTimelineGroup]() @@ -31,6 +32,10 @@ struct MediaEventsTimelineScreenViewState: BindableState { var bindings: MediaEventsTimelineScreenViewStateBindings var currentPreviewItemID: TimelineItemIdentifier? + + var shouldShowEmptyState: Bool { + groups.isEmpty && hasReachedTimelineStart + } } struct MediaEventsTimelineScreenViewStateBindings { diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift index 09ede04cf..79bdbfc2c 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift @@ -36,13 +36,13 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType init(mediaTimelineViewModel: TimelineViewModelProtocol, filesTimelineViewModel: TimelineViewModelProtocol, mediaProvider: MediaProviderProtocol, - screenMode: MediaEventsTimelineScreenMode = .media, + initialViewState: MediaEventsTimelineScreenViewState = .init(bindings: .init(screenMode: .media)), userIndicatorController: UserIndicatorControllerProtocol) { self.mediaTimelineViewModel = mediaTimelineViewModel self.filesTimelineViewModel = filesTimelineViewModel self.userIndicatorController = userIndicatorController - super.init(initialViewState: .init(bindings: .init(screenMode: screenMode)), mediaProvider: mediaProvider) + super.init(initialViewState: initialViewState, mediaProvider: mediaProvider) state.activeTimelineContextProvider = { [weak self] in guard let self else { fatalError() } @@ -131,6 +131,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType state.groups = newGroups state.isBackPaginating = (timelineViewState.timelineState.paginationState.backward == .paginating) +// state.hasReachedTimelineStart = (timelineViewState.timelineState.paginationState.backward == .timelineEndReached) backPaginateIfNecessary(paginationStatus: timelineViewState.timelineState.paginationState.backward) } diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift index d13523376..111896fe8 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift @@ -34,6 +34,9 @@ struct MediaEventsTimelineScreen: View { } .environmentObject(context.viewState.activeTimelineContextProvider()) .environment(\.timelineContext, context.viewState.activeTimelineContextProvider()) + .onChange(of: context.screenMode) { _, _ in + context.send(viewAction: .changedScreenMode) + } } // The scale effects do the following: @@ -44,7 +47,7 @@ struct MediaEventsTimelineScreen: View { // * flip the items on both axes have them render correctly @ViewBuilder private var mainContent: some View { - if context.viewState.groups.isEmpty, !context.viewState.isBackPaginating { + if context.viewState.shouldShowEmptyState { emptyState } else { ScrollView { @@ -60,9 +63,6 @@ struct MediaEventsTimelineScreen: View { } } .scaleEffect(.init(width: 1, height: -1)) - .onChange(of: context.screenMode) { _, _ in - context.send(viewAction: .changedScreenMode) - } } } @@ -76,12 +76,7 @@ struct MediaEventsTimelineScreen: View { Button { tappedItem(item) } label: { - Color.clear // Let the image aspect fill in place - .aspectRatio(1, contentMode: .fill) - .overlay { - viewForTimelineItem(item) - } - .clipped() + viewForTimelineItem(item) .scaleEffect(scale(for: item, isGridLayout: true)) } .zoomTransitionSource(id: item.identifier, in: zoomTransition) @@ -260,7 +255,8 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: makeTimelineViewModel(timelineKind: timelineKind), filesTimelineViewModel: makeTimelineViewModel(timelineKind: timelineKind), mediaProvider: MediaProviderMock(configuration: .init()), - screenMode: screenMode, + initialViewState: .init(hasReachedTimelineStart: true, + bindings: .init(screenMode: screenMode)), userIndicatorController: UserIndicatorControllerMock()) } diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/ImageMediaEventsTimelineView.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/ImageMediaEventsTimelineView.swift index 0b940429a..97a36827f 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/ImageMediaEventsTimelineView.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/ImageMediaEventsTimelineView.swift @@ -13,7 +13,12 @@ struct ImageMediaEventsTimelineView: View { let timelineItem: ImageRoomTimelineItem var body: some View { - loadableImage + Color.clear // Let the image aspect fill in place + .aspectRatio(1, contentMode: .fill) + .overlay { + loadableImage + } + .clipped() .accessibilityElement(children: .ignore) .accessibilityLabel(L10n.commonImage) } @@ -48,13 +53,6 @@ struct ImageMediaEventsTimelineView: View { } } -private extension View { - @ViewBuilder - func mediaGalleryTimelineAspectRatio(imageInfo: ImageInfoProxy?) -> some View { - aspectRatio(imageInfo?.aspectRatio, contentMode: .fill) - } -} - struct ImageMediaEventsTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/VideoMediaEventsTimelineView.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/VideoMediaEventsTimelineView.swift index 17e1d65ff..2932d710d 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/VideoMediaEventsTimelineView.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/TimelineViews/VideoMediaEventsTimelineView.swift @@ -13,7 +13,13 @@ struct VideoMediaEventsTimelineView: View { let timelineItem: VideoRoomTimelineItem var body: some View { - thumbnail + Color.clear // Let the image aspect fill in place + .aspectRatio(1, contentMode: .fill) + .overlay { + thumbnail + } + .clipped() + .overlay(alignment: .bottom) { overlay } .accessibilityElement(children: .ignore) .accessibilityLabel(L10n.commonVideo) } @@ -25,23 +31,30 @@ struct VideoMediaEventsTimelineView: View { mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID.id), blurhash: timelineItem.content.blurhash, size: timelineItem.content.thumbnailInfo?.size, - mediaProvider: context?.mediaProvider) { imageView in - imageView - .overlay { playIcon } - } placeholder: { + mediaProvider: context?.mediaProvider) { placeholder } + .mediaGalleryTimelineAspectRatio(imageInfo: timelineItem.content.thumbnailInfo) } else { - playIcon + overlay } } - var playIcon: some View { - Image(systemName: "play.circle.fill") - .resizable() - .frame(width: 50, height: 50) - .background(.ultraThinMaterial, in: Circle()) - .foregroundColor(.white) + var overlay: some View { + HStack(spacing: 0) { + CompoundIcon(\.videoCallSolid) + Spacer() + Text(Date(timeIntervalSince1970: timelineItem.content.videoInfo.duration).formattedTime()) + } + .padding(8) + .background { + LinearGradient(stops: [.init(color: .clear, location: 0.0), + .init(color: .compound.bgCanvasDefault, location: 1.0)], + startPoint: .top, + endPoint: .bottom) + } + .font(.compound.bodyXSSemibold) + .foregroundStyle(.compound.textPrimary) } var placeholder: some View { diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift index 922bda39a..b4befe1c9 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift @@ -36,18 +36,20 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false) { self.timelineKind = timelineKind - paginationState = PaginationState(backward: .idle, forward: .timelineEndReached) - callbacks.send(.isLive(true)) switch timelineKind { case .media: + paginationState = PaginationState(backward: .timelineEndReached, forward: .timelineEndReached) timelineItems = (0..<5).reduce([]) { partialResult, _ in partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk } default: - break + paginationState = PaginationState(backward: .idle, forward: .timelineEndReached) } + callbacks.send(.paginationState(paginationState)) + callbacks.send(.isLive(true)) + guard listenForSignals else { return } do { diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-en-GB.1.png index c9143a2f5..84f8e3bef 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e66d65ca577aa31e708a19e92ee3686b73a2fc9179c9dbad65c67c3a41a9ede -size 113146 +oid sha256:cdee3d600b60fdbd1a4ee024a32fcb96aa1acede48816ac44fea31dd6f98b2e0 +size 95367 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-pseudo.1.png index c9143a2f5..84f8e3bef 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e66d65ca577aa31e708a19e92ee3686b73a2fc9179c9dbad65c67c3a41a9ede -size 113146 +oid sha256:cdee3d600b60fdbd1a4ee024a32fcb96aa1acede48816ac44fea31dd6f98b2e0 +size 95367 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-en-GB.1.png index 0159d51e8..5e62e5d43 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64e70e20286ebc6bbf25f0d93e4043b7113284a41ce96ab701c61a83b249ac18 -size 98920 +oid sha256:cc9a10f0defb81ffc49b373264d7a427a4586d00d2953474ed52d4750d0cced2 +size 83265 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-pseudo.1.png index 0159d51e8..5e62e5d43 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_imageMediaEventsTimelineView-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64e70e20286ebc6bbf25f0d93e4043b7113284a41ce96ab701c61a83b249ac18 -size 98920 +oid sha256:cc9a10f0defb81ffc49b373264d7a427a4586d00d2953474ed52d4750d0cced2 +size 83265 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.Media.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.Media.png index 9da05449f..9d80bf98f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.Media.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-en-GB.Media.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22b56f65c9574b55043aa8a7593065a075b253268bfb6e1cf4d5588375efcf98 -size 723194 +oid sha256:812ec06c83ea2c361241ea77163a23156a22ee4535ed50ab86e1f5aec130e342 +size 779101 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.Media.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.Media.png index 80b1ddbfa..212a98933 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.Media.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPad-pseudo.Media.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc265e69d1938da9a7550fa049e4456d06c88aec5bf007613b2c2b896186d993 -size 730597 +oid sha256:47e5e4eb18390da554bb8a6ce643eb7ee9d677d4b98235874de95e1ac484b19b +size 781617 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.Media.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.Media.png index b76e3f248..766d21c5f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.Media.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-en-GB.Media.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86b5bf6112dde6e9de73e5e2b9268346f023bd959f4a07fde51ccaaf4ea6f31e -size 803264 +oid sha256:5f0a4d5ae4dc218dc9a24f0cf7f73c71f99fc9cc2c62e20b7d1bba7d949a273c +size 857546 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.Media.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.Media.png index b3dc42eb1..d2083736d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.Media.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_mediaEventsTimelineScreen-iPhone-16-pseudo.Media.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b26928f0fb0f4e536909ecac7166a0e1cc6606dfeddb0e6b373a0fdd8ba94fac -size 809016 +oid sha256:177b94dea4f1fc4eead6438dfc9dcf94ad5b1f1c209660691aa587e8c6a2dd11 +size 862850 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-en-GB.1.png index becc54bb4..8630686ad 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:463ae69de9a5919702d8d4561ad18f87d5cf32461b5e8fbd015f38710dc9cbfa -size 110192 +oid sha256:ed840d795c1c117436307a02af3668cfd0091d9831572b5d0724acf34290a962 +size 119624 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-pseudo.1.png index becc54bb4..2eabcf25d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPad-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:463ae69de9a5919702d8d4561ad18f87d5cf32461b5e8fbd015f38710dc9cbfa -size 110192 +oid sha256:9a44591168a07d0e590384b128045159dbf9707b89c9e3cac462857770ba215b +size 119534 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-en-GB.1.png index 250479317..bfc444613 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-en-GB.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-en-GB.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8327696e387d06deb4a44651894a5ffee31a024093e1dfbebb99f704601991e -size 100670 +oid sha256:8a1a9ad985d1fdbb5bcbfda1f1a0987cdea236759b5905cdc63abc45a4ec35f3 +size 109469 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-pseudo.1.png index 250479317..55656ea1d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-pseudo.1.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_videoMediaEventsTimelineView-iPhone-16-pseudo.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8327696e387d06deb4a44651894a5ffee31a024093e1dfbebb99f704601991e -size 100670 +oid sha256:1001b44cee3b02729073c1ee91a41872b5eca901dd09a414e2e05ed63b412cf0 +size 109394