Add a SpaceExplorerFlowCoordinator. (#4383)

* Add a SpaceExplorerFlowCoordinator.

* Project tweaks

- iOS 17.5 so that we can use the simulator.
- Make a Spaces group for common Space related views.
- Fix the tab bar badge colour on iPhone landscape and iPadOS 17 (18 is still wrong 😒)
This commit is contained in:
Doug
2025-08-07 12:48:53 +01:00
committed by GitHub
parent e95e8ff5aa
commit c47ec8a7cc
10 changed files with 177 additions and 48 deletions
@@ -236,7 +236,9 @@ private struct NavigationTabCoordinatorView<Tag: Hashable>: View {
private func configureAppearance(_ tabBarController: UITabBarController) {
standardAppearance.configureWithDefaultBackground()
standardAppearance.stackedLayoutAppearance.normal.badgeBackgroundColor = .compound.iconAccentPrimary
standardAppearance.stackedLayoutAppearance.normal.badgeBackgroundColor = .compound.iconAccentPrimary // iPhone Portrait
standardAppearance.compactInlineLayoutAppearance.normal.badgeBackgroundColor = .compound.iconAccentPrimary // iPhone Landscape
standardAppearance.inlineLayoutAppearance.normal.badgeBackgroundColor = .compound.iconAccentPrimary // iPadOS 17 (doesn't work for 18+)
tabBarController.tabBar.standardAppearance = standardAppearance
}
}
@@ -0,0 +1,103 @@
//
// Copyright 2025 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 Combine
import Foundation
import SwiftState
enum SpaceExplorerFlowCoordinatorAction: Equatable {
case showSettings
}
class SpaceExplorerFlowCoordinator: FlowCoordinatorProtocol {
private let userSession: UserSessionProtocol
private let navigationSplitCoordinator: NavigationSplitCoordinator
private let sidebarNavigationStackCoordinator: NavigationStackCoordinator
private let detailNavigationStackCoordinator: NavigationStackCoordinator
private let userIndicatorController: UserIndicatorControllerProtocol
enum State: StateType {
/// The state machine hasn't started.
case initial
/// The root screen for this flow.
case spaceList
}
enum Event: EventType {
/// The flow is being started.
case start
}
private let stateMachine: StateMachine<State, Event>
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<SpaceExplorerFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<SpaceExplorerFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(userSession: UserSessionProtocol,
navigationSplitCoordinator: NavigationSplitCoordinator,
userIndicatorController: UserIndicatorControllerProtocol) {
self.userSession = userSession
self.navigationSplitCoordinator = navigationSplitCoordinator
self.userIndicatorController = userIndicatorController
sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
navigationSplitCoordinator.setSidebarCoordinator(sidebarNavigationStackCoordinator)
stateMachine = .init(state: .initial)
configureStateMachine()
}
func start() {
stateMachine.tryEvent(.start)
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
// There aren't any routes to this screen yet, so clear the stacks.
clearRoute(animated: animated)
}
func clearRoute(animated: Bool) {
switch stateMachine.state {
case .initial, .spaceList:
break
}
}
// MARK: - Private
private func configureStateMachine() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .spaceList]) { [weak self] _ in
self?.presentSpaceList()
}
stateMachine.addErrorHandler { context in
fatalError("Unexpected transition: \(context)")
}
}
private func presentSpaceList() {
let parameters = SpaceListScreenCoordinatorParameters(userSession: userSession)
let coordinator = SpaceListScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .showSettings:
actionsSubject.send(.showSettings)
}
}
.store(in: &cancellables)
sidebarNavigationStackCoordinator.setRootCoordinator(coordinator)
}
}
@@ -30,6 +30,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let onboardingStackCoordinator: NavigationStackCoordinator
private let chatsFlowCoordinator: ChatsFlowCoordinator
private let chatsTabDetails: NavigationTabCoordinator<HomeTab>.TabDetails
private let spaceExplorerFlowCoordinator: SpaceExplorerFlowCoordinator
private let spacesTabDetails: NavigationTabCoordinator<HomeTab>.TabDetails
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
@@ -75,10 +76,10 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
chatsTabDetails.barVisibility = .hidden
// This is just temporary, it needs a flow coordinator to properly handle (amongst other things) navigation/split views.
let spaceListScreenCoordinator = SpaceListScreenCoordinator(parameters: .init(userSession: userSession))
let spacesNavigationCoordinator = NavigationStackCoordinator()
spacesNavigationCoordinator.setRootCoordinator(spaceListScreenCoordinator)
let spacesSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
spaceExplorerFlowCoordinator = SpaceExplorerFlowCoordinator(userSession: userSession,
navigationSplitCoordinator: spacesSplitCoordinator,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
spacesTabDetails = .init(tag: HomeTab.spaces, title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid)
onboardingStackCoordinator = NavigationStackCoordinator()
@@ -94,7 +95,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationTabCoordinator.setTabs([
.init(coordinator: chatsSplitCoordinator, details: chatsTabDetails),
.init(coordinator: spacesNavigationCoordinator, details: spacesTabDetails)
.init(coordinator: spacesSplitCoordinator, details: spacesTabDetails)
])
setupObservers()
@@ -104,6 +105,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
#warning("This flow still needs a state machine.")
chatsFlowCoordinator.start()
spaceExplorerFlowCoordinator.start()
attemptStartingOnboarding()
}
@@ -147,6 +149,16 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
.store(in: &cancellables)
spaceExplorerFlowCoordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .showSettings:
break
}
}
.store(in: &cancellables)
userSession.sessionSecurityStatePublisher
.map(\.verificationState)
.filter { $0 != .unknown }