diff --git a/ElementX/Sources/Screens/KnockRequestsListScreen/View/KnockRequestsListScreen.swift b/ElementX/Sources/Screens/KnockRequestsListScreen/View/KnockRequestsListScreen.swift index e60c6ca6f..7adebe703 100644 --- a/ElementX/Sources/Screens/KnockRequestsListScreen/View/KnockRequestsListScreen.swift +++ b/ElementX/Sources/Screens/KnockRequestsListScreen/View/KnockRequestsListScreen.swift @@ -88,13 +88,40 @@ struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview { static let emptyViewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([])) - static let singleRequestViewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "Now", reason: "Hello"))])) + static let singleRequestViewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([KnockRequestProxyMock(.init(eventID: "1", + userID: "@alice:matrix.org", + displayName: "Alice", + avatarURL: nil, + timestamp: "Now", + reason: "Hello"))])) - static let viewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", avatarURL: nil, timestamp: "Now", reason: "Hello")), - // swiftlint:disable:next line_length - KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org", displayName: "Bob", avatarURL: nil, timestamp: "Now", reason: "Hello this one is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason")), - KnockRequestProxyMock(.init(eventID: "3", userID: "@charlie:matrix.org", displayName: "Charlie", avatarURL: nil, timestamp: "Now", reason: nil)), - KnockRequestProxyMock(.init(eventID: "4", userID: "@dan:matrix.org", displayName: "Dan", avatarURL: nil, timestamp: "Now", reason: "Hello! It's a me! Dan!"))])) + static let viewModel = KnockRequestsListScreenViewModel.mockWithRequestsState(.loaded([ + KnockRequestProxyMock(.init(eventID: "1", + userID: "@alice:matrix.org", + displayName: "Alice", + avatarURL: nil, + timestamp: "Now", + reason: "Hello")), + KnockRequestProxyMock(.init(eventID: "2", + userID: "@bob:matrix.org", + displayName: "Bob", + avatarURL: nil, + timestamp: "Now", + // swiftlint:disable:next line_length + reason: "Hello this one is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long reason")), + KnockRequestProxyMock(.init(eventID: "3", + userID: "@charlie:matrix.org", + displayName: "Charlie", + avatarURL: nil, + timestamp: "Now", + reason: nil)), + KnockRequestProxyMock(.init(eventID: "4", + userID: "@dan:matrix.org", + displayName: "Dan", + avatarURL: nil, + timestamp: "Now", + reason: "Hello! It's a me! Dan!")) + ])) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift index 044caee6f..72fa7e231 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenModels.swift @@ -23,8 +23,9 @@ struct MediaEventsTimelineGroup: Identifiable { } struct MediaEventsTimelineScreenViewState: BindableState { - var hasReachedTimelineStart = false var isBackPaginating = false + var shouldShowEmptyState = false + var groups = [MediaEventsTimelineGroup]() var activeTimelineContextProvider: (() -> TimelineViewModel.Context)! @@ -32,10 +33,6 @@ 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 79bdbfc2c..a87dec648 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/MediaEventsTimelineScreenViewModel.swift @@ -35,8 +35,8 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType init(mediaTimelineViewModel: TimelineViewModelProtocol, filesTimelineViewModel: TimelineViewModelProtocol, - mediaProvider: MediaProviderProtocol, initialViewState: MediaEventsTimelineScreenViewState = .init(bindings: .init(screenMode: .media)), + mediaProvider: MediaProviderProtocol, userIndicatorController: UserIndicatorControllerProtocol) { self.mediaTimelineViewModel = mediaTimelineViewModel self.filesTimelineViewModel = filesTimelineViewModel @@ -130,8 +130,8 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType state.groups = newGroups - state.isBackPaginating = (timelineViewState.timelineState.paginationState.backward == .paginating) -// state.hasReachedTimelineStart = (timelineViewState.timelineState.paginationState.backward == .timelineEndReached) + state.isBackPaginating = timelineViewState.timelineState.paginationState.backward == .paginating + state.shouldShowEmptyState = newGroups.isEmpty && timelineViewState.timelineState.paginationState.backward == .timelineEndReached backPaginateIfNecessary(paginationStatus: timelineViewState.timelineState.paginationState.backward) } @@ -157,10 +157,9 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType actionsSubject.send(.viewItem(.init(item: item, viewModel: activeTimelineViewModel, - namespace: namespace, - completion: { [weak self] in - self?.state.currentPreviewItemID = nil - }))) + namespace: namespace) { [weak self] in + self?.state.currentPreviewItemID = nil + })) // Set the current item in the next run loop so that (hopefully) the presentation will be ready before we flip the thumbnail. Task { state.currentPreviewItemID = item.id } diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift index 111896fe8..583c7982e 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift @@ -225,8 +225,8 @@ struct MediaEventsTimelineScreen: View { struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { static let mediaViewModel = makeViewModel(screenMode: .media) static let filesViewModel = makeViewModel(screenMode: .files) - static let emptyMediaViewModel = makeViewModel(timelineKind: .detached, screenMode: .media) - static let emptyFilesViewModel = makeViewModel(timelineKind: .detached, screenMode: .files) + static let emptyMediaViewModel = makeViewModel(empty: true, screenMode: .media) + static let emptyFilesViewModel = makeViewModel(empty: true, screenMode: .files) static var previews: some View { NavigationStack { @@ -250,18 +250,22 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { .previewDisplayName("Empty Files") } - private static func makeViewModel(timelineKind: TimelineKind = .media(.mediaFilesScreen), + private static func makeViewModel(empty: Bool = false, screenMode: MediaEventsTimelineScreenMode) -> MediaEventsTimelineScreenViewModel { - MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: makeTimelineViewModel(timelineKind: timelineKind), - filesTimelineViewModel: makeTimelineViewModel(timelineKind: timelineKind), + MediaEventsTimelineScreenViewModel(mediaTimelineViewModel: makeTimelineViewModel(empty: empty), + filesTimelineViewModel: makeTimelineViewModel(empty: empty), + initialViewState: .init(bindings: .init(screenMode: screenMode)), mediaProvider: MediaProviderMock(configuration: .init()), - initialViewState: .init(hasReachedTimelineStart: true, - bindings: .init(screenMode: screenMode)), userIndicatorController: UserIndicatorControllerMock()) } - private static func makeTimelineViewModel(timelineKind: TimelineKind) -> TimelineViewModel { - let timelineController = MockRoomTimelineController(timelineKind: timelineKind) + private static func makeTimelineViewModel(empty: Bool) -> TimelineViewModel { + let timelineController = if empty { + MockRoomTimelineController.emptyMediaGallery + } else { + MockRoomTimelineController.mediaGallery + } + return TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room")), timelineController: timelineController, mediaProvider: MediaProviderMock(configuration: .init()), diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift index 4b6f74e96..effae2043 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift @@ -37,8 +37,9 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, state.bindings.alertInfo = .init(id: .enableEncryption, title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertTitle, message: L10n.screenSecurityAndPrivacyEnableEncryptionAlertDescription, - primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle, - action: { [weak self] in self?.state.bindings.desiredSettings.isEncryptionEnabled = true }), + primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle) { [weak self] in + self?.state.bindings.desiredSettings.isEncryptionEnabled = true + }, secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) } else { state.bindings.desiredSettings.isEncryptionEnabled = false diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift index 391171418..920c19b3b 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift @@ -36,7 +36,7 @@ struct MediaFileRoomTimelineContent: View { let additionalWhitespaces: Int var isAudioFile = false - var onMediaTap: (() -> Void)? = nil + var onMediaTap: (() -> Void)? private var icon: KeyPath { isAudioFile ? \.audio : \.attachment diff --git a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift index b4befe1c9..eb34a2354 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/MockRoomTimelineController.swift @@ -34,18 +34,21 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { private var client: UITestsSignalling.Client? - init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false) { + static var mediaGallery: MockRoomTimelineController { + MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in + partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk + }) + } + + static var emptyMediaGallery: MockRoomTimelineController { + let mock = MockRoomTimelineController(timelineKind: .media(.mediaFilesScreen)) + mock.paginationState = PaginationState(backward: .timelineEndReached, forward: .timelineEndReached) + return mock + } + + init(timelineKind: TimelineKind = .live, listenForSignals: Bool = false, timelineItems: [RoomTimelineItemProtocol] = RoomTimelineItemFixtures.default) { self.timelineKind = timelineKind - - switch timelineKind { - case .media: - paginationState = PaginationState(backward: .timelineEndReached, forward: .timelineEndReached) - timelineItems = (0..<5).reduce([]) { partialResult, _ in - partialResult + [RoomTimelineItemFixtures.separator] + RoomTimelineItemFixtures.mediaChunk - } - default: - paginationState = PaginationState(backward: .idle, forward: .timelineEndReached) - } + self.timelineItems = timelineItems callbacks.send(.paginationState(paginationState)) callbacks.send(.isLive(true))