diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 6389dea82..d55fa9da4 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -1238,6 +1238,7 @@ EDB6915EC953BB2A44AA608E /* EditRoomAddressScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906451FB8CF27C628152BF7A /* EditRoomAddressScreenViewModelTests.swift */; }; EDD45A73B688B87D84B9C2E9 /* AccessibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */; }; EDF8919F15DE0FF00EF99E70 /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */; }; + EE17B7154DCA50677D931A94 /* NavigationTabCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B67DC84B42D86035FCFF6F8 /* NavigationTabCoordinatorTests.swift */; }; EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */; }; EE56238683BC3ECA9BA00684 /* GlobalSearchScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */; }; EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; }; @@ -1664,6 +1665,7 @@ 2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = ""; }; 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = ""; }; 2B1FB56520A847DD2532D5C8 /* VideoMediaEventsTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoMediaEventsTimelineView.swift; sourceTree = ""; }; + 2B67DC84B42D86035FCFF6F8 /* NavigationTabCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTabCoordinatorTests.swift; sourceTree = ""; }; 2B9BCACD0CC4CB8E37F17732 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lt; path = lt.lproj/Localizable.stringsdict; sourceTree = ""; }; 2BA894BC09972DC45E497D37 /* TimelineInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineInteractionHandler.swift; sourceTree = ""; }; 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenModels.swift; sourceTree = ""; }; @@ -4444,6 +4446,7 @@ F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */, 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */, 9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */, + 2B67DC84B42D86035FCFF6F8 /* NavigationTabCoordinatorTests.swift */, 8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */, 514363244AE7D68080D44C6F /* NotificationSettingsScreenViewModelTests.swift */, FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */, @@ -7071,6 +7074,7 @@ 981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */, 69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */, 4BB282209EA82015D0DF8F89 /* NavigationStackCoordinatorTests.swift in Sources */, + EE17B7154DCA50677D931A94 /* NavigationTabCoordinatorTests.swift in Sources */, 1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */, C11939FDC40716C4387275A4 /* NotificationSettingsEditScreenViewModelTests.swift in Sources */, E3AC72E3E58F364EF15C1CC7 /* NotificationSettingsScreenViewModelTests.swift in Sources */, diff --git a/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift b/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift index 2af7d0363..dcc9e09f7 100644 --- a/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift +++ b/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift @@ -15,6 +15,7 @@ import SwiftUI let title: String let icon: KeyPath let selectedIcon: KeyPath + var dismissalCallback: (() -> Void)? } // MARK: Tabs @@ -56,7 +57,10 @@ import SwiftUI transaction.disablesAnimations = !animated withTransaction(transaction) { - tabModules = tabs.map { TabModule(module: .init($0.coordinator), title: $0.title, icon: $0.icon, selectedIcon: $0.selectedIcon) } + tabModules = tabs.map { TabModule(module: .init($0.coordinator, dismissalCallback: $0.dismissalCallback), + title: $0.title, + icon: $0.icon, + selectedIcon: $0.selectedIcon) } } } @@ -199,6 +203,7 @@ private struct NavigationTabCoordinatorView: View { } .tag(module.id) .id(module.id) + .toolbar(.hidden, for: .tabBar) } } .sheet(item: $navigationTabCoordinator.sheetModule) { module in diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index d335d8e0c..a4e9100f7 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -81,8 +81,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { isNewLogin: isNewLogin) navigationTabCoordinator.setTabs([ - .init(coordinator: chatsSplitCoordinator, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid), - .init(coordinator: BlankFormCoordinator(), title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid) + .init(coordinator: chatsSplitCoordinator, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid) + // .init(coordinator: BlankFormCoordinator(), title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid) ]) setupObservers() diff --git a/UnitTests/Sources/NavigationTabCoordinatorTests.swift b/UnitTests/Sources/NavigationTabCoordinatorTests.swift new file mode 100644 index 000000000..099fca35d --- /dev/null +++ b/UnitTests/Sources/NavigationTabCoordinatorTests.swift @@ -0,0 +1,123 @@ +// +// Copyright 2022-2024 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import XCTest + +@testable import ElementX + +@MainActor +class NavigationTabCoordinatorTests: XCTestCase { + private var navigationTabCoordinator: NavigationTabCoordinator! + + override func setUp() { + navigationTabCoordinator = NavigationTabCoordinator() + } + + func testTabs() { + XCTAssertTrue(navigationTabCoordinator.tabCoordinators.isEmpty) + + let someCoordinator = SomeTestCoordinator() + navigationTabCoordinator.setTabs([.init(coordinator: someCoordinator, title: "Whatever", icon: \.help, selectedIcon: \.helpSolid)]) + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [someCoordinator]) + + let chatsCoordinator = SomeTestCoordinator() + let spacesCoordinator = SomeTestCoordinator() + navigationTabCoordinator.setTabs([ + .init(coordinator: chatsCoordinator, title: "Chats", icon: \.chat, selectedIcon: \.chatSolid), + .init(coordinator: spacesCoordinator, title: "Spaces", icon: \.space, selectedIcon: \.spaceSolid) + ]) + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [chatsCoordinator, spacesCoordinator]) + } + + func testSingleSheet() { + let tabCoordinator = SomeTestCoordinator() + navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, title: "Tab", icon: \.help, selectedIcon: \.helpSolid)]) + + let coordinator = SomeTestCoordinator() + navigationTabCoordinator.setSheetCoordinator(coordinator) + + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) + assertCoordinatorsEqual(coordinator, navigationTabCoordinator.sheetCoordinator) + + navigationTabCoordinator.setSheetCoordinator(nil) + + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) + XCTAssertNil(navigationTabCoordinator.sheetCoordinator) + } + + func testMultipleSheets() { + let tabCoordinator = SomeTestCoordinator() + navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, title: "Tab", icon: \.help, selectedIcon: \.helpSolid)]) + + let sheetCoordinator = SomeTestCoordinator() + navigationTabCoordinator.setSheetCoordinator(sheetCoordinator) + + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) + assertCoordinatorsEqual(sheetCoordinator, navigationTabCoordinator.sheetCoordinator) + + let someOtherSheetCoordinator = SomeTestCoordinator() + navigationTabCoordinator.setSheetCoordinator(someOtherSheetCoordinator) + + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator]) + assertCoordinatorsEqual(someOtherSheetCoordinator, navigationTabCoordinator.sheetCoordinator) + } + + func testTabDismissalCallbacks() { + let chatsCoordinator = SomeTestCoordinator() + let spacesCoordinator = SomeTestCoordinator() + + let expectation = expectation(description: "Wait for callback") + expectation.expectedFulfillmentCount = 2 + + navigationTabCoordinator.setTabs([ + .init(coordinator: chatsCoordinator, title: "Chats", icon: \.chat, selectedIcon: \.chatSolid) { expectation.fulfill() }, + .init(coordinator: spacesCoordinator, title: "Spaces", icon: \.space, selectedIcon: \.spaceSolid) { expectation.fulfill() } + ]) + assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [chatsCoordinator, spacesCoordinator]) + + navigationTabCoordinator.setTabs([.init(coordinator: SomeTestCoordinator(), title: "Whatever", icon: \.help, selectedIcon: \.helpSolid)]) + waitForExpectations(timeout: 1.0) + } + + func testSheetDismissalCallback() { + let coordinator = SomeTestCoordinator() + let expectation = expectation(description: "Wait for callback") + navigationTabCoordinator.setSheetCoordinator(coordinator) { + expectation.fulfill() + } + + navigationTabCoordinator.setSheetCoordinator(nil) + waitForExpectations(timeout: 1.0) + } + + // MARK: - Private + + private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) { + guard let lhs = lhs as? SomeTestCoordinator, + let rhs = rhs as? SomeTestCoordinator else { + XCTFail("Coordinators are not the same") + return + } + + XCTAssertEqual(lhs.id, rhs.id) + } + + private func assertCoordinatorsEqual(_ lhs: [CoordinatorProtocol], _ rhs: [CoordinatorProtocol]) { + guard lhs.count == rhs.count else { + XCTFail("Coordinators are not the same") + return + } + + for (index, coordinator) in lhs.enumerated() { + assertCoordinatorsEqual(coordinator, rhs[index]) + } + } +} + +private class SomeTestCoordinator: CoordinatorProtocol { + let id = UUID() +}