diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index e62879dbf..089741f42 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -494,7 +494,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private func presentHomeScreen() { let parameters = HomeScreenCoordinatorParameters(userSession: userSession, bugReportService: bugReportService, - selectedRoomPublisher: selectedRoomSubject.asCurrentValuePublisher()) + selectedRoomPublisher: selectedRoomSubject.asCurrentValuePublisher(), + appSettings: appSettings, + analyticsService: analytics, + notificationManager: notificationManager, + userIndicatorController: ServiceLocator.shared.userIndicatorController) let coordinator = HomeScreenCoordinator(parameters: parameters) coordinator.actions diff --git a/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift b/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift index 05bb0e44f..247d7da56 100644 --- a/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift +++ b/ElementX/Sources/Other/SwiftUI/Animation/ShimmerModifier.swift @@ -8,6 +8,13 @@ import Combine import SwiftUI +extension View { + /// Applies a shimmering effect to the view. + func shimmer() -> some View { + modifier(ShimmerModifier()) + } +} + /// A view modifier that applies a shimmering effect to the view. struct ShimmerModifier: ViewModifier { /// A boolean which is toggled to trigger the animation. @@ -59,18 +66,12 @@ struct ShimmerModifier: ViewModifier { } } -extension View { - /// Applies a shimmering effect to the view. - func shimmer() -> some View { - modifier(ShimmerModifier()) - } -} - struct ShimmerOverlay_Previews: PreviewProvider, TestablePreview { static let viewModel = HomeScreenViewModel(userSession: UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "")))), - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController) static var previews: some View { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index a528626ed..28b052c81 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -12,6 +12,10 @@ struct HomeScreenCoordinatorParameters { let userSession: UserSessionProtocol let bugReportService: BugReportServiceProtocol let selectedRoomPublisher: CurrentValuePublisher + let appSettings: AppSettings + let analyticsService: AnalyticsService + let notificationManager: NotificationManagerProtocol + let userIndicatorController: UserIndicatorControllerProtocol } enum HomeScreenCoordinatorAction { @@ -45,10 +49,11 @@ final class HomeScreenCoordinator: CoordinatorProtocol { init(parameters: HomeScreenCoordinatorParameters) { viewModel = HomeScreenViewModel(userSession: parameters.userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: parameters.selectedRoomPublisher, - userIndicatorController: ServiceLocator.shared.userIndicatorController) + appSettings: parameters.appSettings, + analyticsService: parameters.analyticsService, + notificationManager: parameters.notificationManager, + userIndicatorController: parameters.userIndicatorController) bugReportService = parameters.bugReportService viewModel.actions diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 0251fc95f..4281c87c3 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -16,6 +16,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private let userSession: UserSessionProtocol private let analyticsService: AnalyticsService private let appSettings: AppSettings + private let notificationManager: NotificationManagerProtocol private let userIndicatorController: UserIndicatorControllerProtocol private let roomSummaryProvider: RoomSummaryProviderProtocol? @@ -26,13 +27,15 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } init(userSession: UserSessionProtocol, - analyticsService: AnalyticsService, - appSettings: AppSettings, selectedRoomPublisher: CurrentValuePublisher, + appSettings: AppSettings, + analyticsService: AnalyticsService, + notificationManager: NotificationManagerProtocol, userIndicatorController: UserIndicatorControllerProtocol) { self.userSession = userSession self.analyticsService = analyticsService self.appSettings = appSettings + self.notificationManager = notificationManager self.userIndicatorController = userIndicatorController roomSummaryProvider = userSession.clientProxy.roomSummaryProvider @@ -462,6 +465,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol switch result { case .success: + await notificationManager.removeDeliveredMessageNotifications(for: roomID) // Normally handled by the room flow, but that's never presented in this case. appSettings.seenInvites.remove(roomID) case .failure: displayError() diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 1d991297e..58851f479 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -137,9 +137,10 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview { let userSession = UserSessionMock(.init(clientProxy: clientProxy)) return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift index a4e5e68f8..0a17b5f37 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift @@ -144,9 +144,10 @@ struct HomeScreenEmptyStateView_Previews: PreviewProvider, TestablePreview { roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))))))) return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController) }() } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift index fb4295da3..f3a209a5f 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift @@ -155,38 +155,39 @@ struct HomeScreenInviteCell_Previews: PreviewProvider, TestablePreview { static var previews: some View { VStack(spacing: 0) { HomeScreenInviteCell(room: .dmInvite, - context: viewModel().context, hideInviteAvatars: false) + context: makeViewModel().context, hideInviteAvatars: false) HomeScreenInviteCell(room: .dmInvite, - context: viewModel().context, hideInviteAvatars: false) + context: makeViewModel().context, hideInviteAvatars: false) HomeScreenInviteCell(room: .roomInvite(), - context: viewModel().context, hideInviteAvatars: false) + context: makeViewModel().context, hideInviteAvatars: false) HomeScreenInviteCell(room: .roomInvite(), - context: viewModel().context, hideInviteAvatars: false) + context: makeViewModel().context, hideInviteAvatars: false) HomeScreenInviteCell(room: .roomInvite(alias: "#footest:somewhere.org", avatarURL: .mockMXCAvatar), - context: viewModel().context, hideInviteAvatars: false) + context: makeViewModel().context, hideInviteAvatars: false) HomeScreenInviteCell(room: .roomInvite(alias: "#footest-hidden-avatars:somewhere.org", avatarURL: .mockMXCAvatar), - context: viewModel().context, hideInviteAvatars: true) + context: makeViewModel().context, hideInviteAvatars: true) HomeScreenInviteCell(room: .roomInvite(alias: "#footest:somewhere.org"), - context: viewModel().context, hideInviteAvatars: false) + context: makeViewModel().context, hideInviteAvatars: false) .dynamicTypeSize(.accessibility1) .previewDisplayName("Aliased room (AX1)") } .previewLayout(.sizeThatFits) } - static func viewModel() -> HomeScreenViewModel { + static func makeViewModel() -> HomeScreenViewModel { let clientProxy = ClientProxyMock(.init()) let userSession = UserSessionMock(.init(clientProxy: clientProxy)) return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift index cf7292dc0..24d7bb8a2 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift @@ -101,37 +101,38 @@ struct HomeScreenKnockedCell_Previews: PreviewProvider, TestablePreview { ScrollView { VStack(spacing: 0) { HomeScreenKnockedCell(room: .dmInvite, - context: viewModel().context) + context: makeViewModel().context) HomeScreenKnockedCell(room: .dmInvite, - context: viewModel().context) + context: makeViewModel().context) HomeScreenKnockedCell(room: .roomKnocked(), - context: viewModel().context) + context: makeViewModel().context) HomeScreenKnockedCell(room: .roomKnocked(), - context: viewModel().context) + context: makeViewModel().context) HomeScreenKnockedCell(room: .roomKnocked(alias: "#footest:somewhere.org", avatarURL: .mockMXCAvatar), - context: viewModel().context) + context: makeViewModel().context) HomeScreenKnockedCell(room: .roomKnocked(alias: "#footest:somewhere.org"), - context: viewModel().context) + context: makeViewModel().context) .dynamicTypeSize(.accessibility1) .previewDisplayName("Aliased room (AX1)") } } } - static func viewModel() -> HomeScreenViewModel { + static func makeViewModel() -> HomeScreenViewModel { let clientProxy = ClientProxyMock(.init()) let userSession = UserSessionMock(.init(clientProxy: clientProxy)) return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift index af1106f99..f45a1e956 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift @@ -103,7 +103,7 @@ struct HomeScreenRecoveryKeyConfirmationBanner: View { } struct HomeScreenRecoveryKeyConfirmationBanner_Previews: PreviewProvider, TestablePreview { - static let viewModel = buildViewModel() + static let viewModel = makeViewModel() static var previews: some View { HomeScreenRecoveryKeyConfirmationBanner(state: .setUpRecovery, @@ -114,16 +114,17 @@ struct HomeScreenRecoveryKeyConfirmationBanner_Previews: PreviewProvider, Testab .previewDisplayName("Out of sync") } - static func buildViewModel() -> HomeScreenViewModel { + static func makeViewModel() -> HomeScreenViewModel { let clientProxy = ClientProxyMock(.init(userID: "@alice:example.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loading)))) let userSession = UserSessionMock(.init(clientProxy: clientProxy)) return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), userIndicatorController: ServiceLocator.shared.userIndicatorController) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 4fe98d6b2..581ae75ed 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -164,36 +164,14 @@ private extension View { struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview { static let summaryProviderGeneric = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))) - static let viewModelGeneric = { - let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "John Doe", roomSummaryProvider: summaryProviderGeneric)))) - - return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userIndicatorController: ServiceLocator.shared.userIndicatorController) - }() + static let viewModelGeneric = makeViewModel(roomSummaryProvider: summaryProviderGeneric) + static let genericRooms = summaryProviderGeneric.roomListPublisher.value.compactMap(mockRoom) static let summaryProviderForNotificationsState = RoomSummaryProviderMock(.init(state: .loaded(.mockRoomsWithNotificationsState))) - static let viewModelForNotificationsState = { - let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "John Doe", roomSummaryProvider: summaryProviderForNotificationsState)))) - - return HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: ServiceLocator.shared.settings, - selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userIndicatorController: ServiceLocator.shared.userIndicatorController) - }() - - static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? { - HomeScreenRoom(summary: summary, hideUnreadMessagesBadge: false) - } + static let viewModelForNotificationsState = makeViewModel(roomSummaryProvider: summaryProviderForNotificationsState) + static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom) static var previews: some View { - let genericRooms: [HomeScreenRoom] = summaryProviderGeneric.roomListPublisher.value.compactMap(mockRoom) - - let notificationsStateRooms: [HomeScreenRoom] = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom) - VStack(spacing: 0) { ForEach(genericRooms) { room in HomeScreenRoomCell(room: room, context: viewModelGeneric.context, isSelected: false) @@ -212,4 +190,19 @@ struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview { .previewLayout(.sizeThatFits) .previewDisplayName("Notifications State") } + + static func mockRoom(summary: RoomSummary) -> HomeScreenRoom? { + HomeScreenRoom(summary: summary, hideUnreadMessagesBadge: false) + } + + static func makeViewModel(roomSummaryProvider: RoomSummaryProviderProtocol) -> HomeScreenViewModel { + let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "John Doe", roomSummaryProvider: roomSummaryProvider)))) + + return HomeScreenViewModel(userSession: userSession, + selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: ServiceLocator.shared.settings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: NotificationManagerMock(), + userIndicatorController: ServiceLocator.shared.userIndicatorController) + } } diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index bc32e8d0a..6513c4eb8 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -18,6 +18,7 @@ class HomeScreenViewModelTests: XCTestCase { var clientProxy: ClientProxyMock! var roomSummaryProvider: RoomSummaryProviderMock! var appSettings: AppSettings! + var notificationManager: NotificationManagerMock! var cancellables = Set() @@ -298,6 +299,7 @@ class HomeScreenViewModelTests: XCTestCase { try await deferred.fulfill() XCTAssertEqual(appSettings.seenInvites, [invitedRoomIDs[1]]) + XCTAssertFalse(notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.") } func testDeclineInvite() async throws { @@ -325,6 +327,8 @@ class HomeScreenViewModelTests: XCTestCase { await fulfillment(of: [rejectExpectation], timeout: 1.0) XCTAssertEqual(appSettings.seenInvites, [invitedRoomIDs[1]]) + XCTAssertTrue(notificationManager.removeDeliveredMessageNotificationsForCalled) + XCTAssertEqual(notificationManager.removeDeliveredMessageNotificationsForReceivedInvocations, [invitedRoomIDs[0]]) } // MARK: - Helpers @@ -350,10 +354,13 @@ class HomeScreenViewModelTests: XCTestCase { userSession.sessionSecurityStatePublisher = securityStatePublisher } + notificationManager = NotificationManagerMock() + viewModel = HomeScreenViewModel(userSession: userSession, - analyticsService: ServiceLocator.shared.analytics, - appSettings: appSettings, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), + appSettings: appSettings, + analyticsService: ServiceLocator.shared.analytics, + notificationManager: notificationManager, userIndicatorController: ServiceLocator.shared.userIndicatorController) } }