Fixes #734 - Add a "View logs" option to the report a problem screen

Move bug report and log viewer responsibility to a separate flow coordinator. Reuse it as a child coordinator in the authentication, userSessionFlow and settings flow coordinators.
This commit is contained in:
Stefan Ceriu
2024-01-23 10:13:48 +02:00
committed by GitHub
parent dd28261e1f
commit 31286c75fc
38 changed files with 541 additions and 143 deletions
@@ -342,6 +342,7 @@
"screen_bug_report_include_logs" = "Allow logs";
"screen_bug_report_include_screenshot" = "Send screenshot";
"screen_bug_report_logs_description" = "Logs will be included with your message to make sure that everything is working properly. To send your message without logs, turn off this setting.";
"screen_bug_report_view_logs" = "View logs";
"screen_change_account_provider_matrix_org_subtitle" = "Matrix.org is a large, free server on the public Matrix network for secure, decentralised communication, run by the Matrix.org Foundation.";
"screen_change_account_provider_other" = "Other";
"screen_change_account_provider_subtitle" = "Use a different account provider, such as your own private server or a work account.";
@@ -19,6 +19,7 @@ import Foundation
// periphery:ignore - markdown protocol
@MainActor
protocol FlowCoordinatorProtocol {
func start()
func handleAppRoute(_ appRoute: AppRoute, animated: Bool)
func clearRoute(animated: Bool)
}
@@ -0,0 +1,127 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
struct BugReportFlowCoordinatorParameters {
enum PresentationMode {
case sheet(NavigationStackCoordinator)
case push(NavigationStackCoordinator)
}
let presentationMode: PresentationMode
let userIndicatorController: UserIndicatorControllerProtocol
let bugReportService: BugReportServiceProtocol
let userID: String?
let deviceID: String?
}
class BugReportFlowCoordinator: FlowCoordinatorProtocol {
private let parameters: BugReportFlowCoordinatorParameters
private var cancellables = Set<AnyCancellable>()
private var internalNavigationStackCoordinator: NavigationStackCoordinator?
private var isModallyPresented: Bool {
switch parameters.presentationMode {
case .sheet:
return true
case .push:
return false
}
}
init(parameters: BugReportFlowCoordinatorParameters) {
self.parameters = parameters
}
func start() {
presentBugReportScreen()
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
fatalError()
}
func clearRoute(animated: Bool) {
fatalError()
}
// MARK: - Private
private func presentBugReportScreen() {
let params = BugReportScreenCoordinatorParameters(bugReportService: parameters.bugReportService,
userID: parameters.userID,
deviceID: parameters.deviceID,
userIndicatorController: parameters.userIndicatorController,
screenshot: nil,
isModallyPresented: isModallyPresented)
let coordinator = BugReportScreenCoordinator(parameters: params)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .cancel:
dismiss()
case .viewLogs:
presentLogViewerScreen()
case .finish:
showSuccess(label: L10n.actionDone)
dismiss()
}
}
.store(in: &cancellables)
switch parameters.presentationMode {
case .sheet(let navigationStackCoordinator):
let internalNavigationStackCoordinator = NavigationStackCoordinator()
internalNavigationStackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(internalNavigationStackCoordinator)
self.internalNavigationStackCoordinator = internalNavigationStackCoordinator
case .push(let navigationStackCoordinator):
internalNavigationStackCoordinator = navigationStackCoordinator
navigationStackCoordinator.push(coordinator)
}
}
private func presentLogViewerScreen() {
let coordinator = LogViewerScreenCoordinator(parameters: .init())
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
internalNavigationStackCoordinator?.pop()
}
}
.store(in: &cancellables)
internalNavigationStackCoordinator?.push(coordinator)
}
private func dismiss() {
switch parameters.presentationMode {
case .push(let navigationStackCoordinator):
navigationStackCoordinator.pop()
case .sheet(let navigationStackCoordinator):
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
private func showSuccess(label: String) {
parameters.userIndicatorController.submitIndicator(UserIndicator(title: label))
}
}
@@ -91,6 +91,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - FlowCoordinatorProtocol
func start() {
fatalError("This flow coordinator expect a route")
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
switch appRoute {
case .room(let roomID):
@@ -48,6 +48,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
// periphery:ignore - retaining purpose
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
@@ -57,6 +60,10 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
self.parameters = parameters
}
func start() {
fatalError("Unavailable")
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
switch appRoute {
case .settings:
@@ -76,17 +83,17 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
}
}
func clearRoute(animated: Bool) { }
func clearRoute(animated: Bool) {
fatalError("Unavailable")
}
// MARK: - Private
private func presentSettingsScreen(animated: Bool) {
navigationStackCoordinator = NavigationStackCoordinator()
let parameters = SettingsScreenCoordinatorParameters(userSession: parameters.userSession,
appSettings: parameters.appSettings)
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters)
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession,
appSettings: parameters.appSettings))
settingsScreenCoordinator.actions
.sink { [weak self] action in
@@ -94,9 +101,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .dismiss:
self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
case .logout:
self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
// The settings sheet needs to be dismissed before the alert can be shown
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
@@ -113,7 +120,12 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
case .appLock:
presentAppLockSetupFlow()
case .bugReport:
presentBugReportScreen()
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .push(navigationStackCoordinator),
userIndicatorController: parameters.userIndicatorController,
bugReportService: parameters.bugReportService,
userID: parameters.userSession.userID,
deviceID: parameters.userSession.deviceID))
bugReportFlowCoordinator?.start()
case .about:
presentLegalInformationScreen()
case .sessionVerification:
@@ -132,7 +144,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)
self.parameters.navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
parameters.navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
guard let self else { return }
navigationStackCoordinator = nil
@@ -187,28 +199,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
coordinator.start()
}
private func presentBugReportScreen() {
let params = BugReportScreenCoordinatorParameters(bugReportService: parameters.bugReportService,
userID: parameters.userSession.userID,
deviceID: parameters.userSession.deviceID,
userIndicatorController: parameters.userIndicatorController,
screenshot: nil,
isModallyPresented: false)
let coordinator = BugReportScreenCoordinator(parameters: params)
coordinator.completion = { [weak self] result in
switch result {
case .finish:
self?.showSuccess(label: L10n.actionDone)
default:
break
}
self?.navigationStackCoordinator.pop()
}
navigationStackCoordinator.push(coordinator)
}
private func presentLegalInformationScreen() {
navigationStackCoordinator.push(LegalInformationScreenCoordinator(appSettings: parameters.appSettings))
}
@@ -269,10 +259,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.push(coordinator)
}
private func showSuccess(label: String) {
parameters.userIndicatorController.submitIndicator(UserIndicator(title: label))
}
// MARK: OIDC Account Management
private func presentAccountProfileURL() {
@@ -33,9 +33,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
private let stateMachine: UserSessionFlowCoordinatorStateMachine
private let roomFlowCoordinator: RoomFlowCoordinator
private let settingsFlowCoordinator: SettingsFlowCoordinator
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
private var cancellables = Set<AnyCancellable>()
private var migrationCancellable: AnyCancellable?
@@ -250,7 +255,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
break
case (.roomList, .feedbackScreen, .feedbackScreen):
presentFeedbackScreen(animated: animated)
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(sidebarNavigationStackCoordinator),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
bugReportService: bugReportService,
userID: userSession.userID,
deviceID: userSession.deviceID))
bugReportFlowCoordinator?.start()
case (.feedbackScreen, .dismissedFeedbackScreen, .roomList):
break
@@ -483,30 +493,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
self?.stateMachine.processEvent(.dismissedStartChatScreen)
}
}
// MARK: Bug reporting
private func presentFeedbackScreen(animated: Bool, for image: UIImage? = nil) {
let feedbackNavigationStackCoordinator = NavigationStackCoordinator()
let parameters = BugReportScreenCoordinatorParameters(bugReportService: bugReportService,
userID: userSession.userID,
deviceID: userSession.deviceID,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
screenshot: image,
isModallyPresented: true)
let coordinator = BugReportScreenCoordinator(parameters: parameters)
coordinator.completion = { [weak self] _ in
self?.navigationSplitCoordinator.setSheetCoordinator(nil)
}
feedbackNavigationStackCoordinator.setRootCoordinator(coordinator)
navigationSplitCoordinator.setSheetCoordinator(feedbackNavigationStackCoordinator, animated: animated) { [weak self] in
self?.stateMachine.processEvent(.dismissedFeedbackScreen)
}
}
// MARK: Invites list
private func presentInvitesList(animated: Bool) {
+2
View File
@@ -838,6 +838,8 @@ public enum L10n {
public static func screenBugReportRashLogsAlertTitle(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_bug_report_rash_logs_alert_title", String(describing: p1))
}
/// View logs
public static var screenBugReportViewLogs: String { return L10n.tr("Localizable", "screen_bug_report_view_logs") }
/// Matrix.org is a large, free server on the public Matrix network for secure, decentralised communication, run by the Matrix.org Foundation.
public static var screenChangeAccountProviderMatrixOrgSubtitle: String { return L10n.tr("Localizable", "screen_change_account_provider_matrix_org_subtitle") }
/// Other
@@ -34,9 +34,13 @@ class AuthenticationCoordinator: CoordinatorProtocol {
private var cancellables = Set<AnyCancellable>()
private var oidcPresenter: OIDCAuthenticationPresenter?
// periphery: ignore - used to store the coordinator to avoid deallocation
private var appLockFlowCoordinator: AppLockSetupFlowCoordinator?
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
weak var delegate: AuthenticationCoordinatorDelegate?
init(authenticationService: AuthenticationServiceProxyProtocol,
@@ -94,24 +98,12 @@ class AuthenticationCoordinator: CoordinatorProtocol {
}
private func showReportProblemScreen() {
let feedbackNavigationStackCoordinator = NavigationStackCoordinator()
let parameters = BugReportScreenCoordinatorParameters(bugReportService: bugReportService,
userID: nil,
deviceID: nil,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
screenshot: nil,
isModallyPresented: true)
let coordinator = BugReportScreenCoordinator(parameters: parameters)
coordinator.completion = { [weak self] _ in
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
feedbackNavigationStackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(feedbackNavigationStackCoordinator, animated: true)
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(navigationStackCoordinator),
userIndicatorController: ServiceLocator.shared.userIndicatorController,
bugReportService: bugReportService,
userID: nil,
deviceID: nil))
bugReportFlowCoordinator?.start()
}
private func startAuthentication() async {
@@ -17,8 +17,9 @@
import Combine
import SwiftUI
enum BugReportScreenCoordinatorResult {
enum BugReportScreenCoordinatorAction {
case cancel
case viewLogs
case finish
}
@@ -37,7 +38,10 @@ final class BugReportScreenCoordinator: CoordinatorProtocol {
private var viewModel: BugReportScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
var completion: ((BugReportScreenCoordinatorResult) -> Void)?
private let actionsSubject: PassthroughSubject<BugReportScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<BugReportScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: BugReportScreenCoordinatorParameters) {
self.parameters = parameters
@@ -56,18 +60,21 @@ final class BugReportScreenCoordinator: CoordinatorProtocol {
.actions
.sink { [weak self] action in
guard let self else { return }
MXLog.info("BugReportViewModel did complete with result: \(action).")
switch action {
case .cancel:
self.completion?(.cancel)
actionsSubject.send(.cancel)
case .viewLogs:
actionsSubject.send(.viewLogs)
case let .submitStarted(progressPublisher):
self.startLoading(label: L10n.commonSending, progressPublisher: progressPublisher)
startLoading(label: L10n.commonSending, progressPublisher: progressPublisher)
case .submitFinished:
self.stopLoading()
self.completion?(.finish)
stopLoading()
actionsSubject.send(.finish)
case .submitFailed(let error):
self.stopLoading()
self.showError(label: error.localizedDescription)
stopLoading()
showError(label: error.localizedDescription)
}
}
.store(in: &cancellables)
@@ -19,6 +19,7 @@ import UIKit
enum BugReportScreenViewModelAction {
case cancel
case viewLogs
case submitStarted(progressPublisher: CurrentValuePublisher<Double, Never>)
case submitFinished
case submitFailed(error: Error)
@@ -42,4 +43,5 @@ enum BugReportScreenViewAction {
case submit
case removeScreenshot
case attachScreenshot(UIImage)
case viewLogs
}
@@ -53,6 +53,8 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie
case .cancel:
uploadTask = nil
actionsSubject.send(.cancel)
case .viewLogs:
actionsSubject.send(.viewLogs)
case .submit:
state.shouldDisableInteraction = true
uploadTask = Task { await submitBugReport() }
@@ -68,6 +68,9 @@ struct BugReportScreen: View {
ListRow(label: .plain(title: L10n.screenBugReportIncludeLogs),
kind: .toggle($context.sendingLogsEnabled))
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs)
ListRow(label: .plain(title: L10n.screenBugReportViewLogs),
kind: .navigationLink { context.send(viewAction: .viewLogs) })
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs)
} footer: {
Text(L10n.screenBugReportLogsDescription)
.compoundListSectionFooter()
@@ -0,0 +1,60 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// periphery:ignore:all - this is just a logViewer remove this comment once generating the final file
import Combine
import SwiftUI
struct LogViewerScreenCoordinatorParameters { }
enum LogViewerScreenCoordinatorAction {
case done
}
final class LogViewerScreenCoordinator: CoordinatorProtocol {
private let parameters: LogViewerScreenCoordinatorParameters
private var viewModel: LogViewerScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<LogViewerScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<LogViewerScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: LogViewerScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = LogViewerScreenViewModel()
}
func start() {
viewModel.actions.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return }
switch action {
case .done:
self.actionsSubject.send(.done)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(LogViewerScreen(context: viewModel.context))
}
}
@@ -0,0 +1,29 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum LogViewerScreenViewModelAction {
case done
}
struct LogViewerScreenViewState: BindableState {
let urls: [URL]
}
enum LogViewerScreenViewAction {
case done
}
@@ -0,0 +1,43 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias LogViewerScreenViewModelType = StateStoreViewModel<LogViewerScreenViewState, LogViewerScreenViewAction>
class LogViewerScreenViewModel: LogViewerScreenViewModelType, LogViewerScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<LogViewerScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<LogViewerScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init() {
super.init(initialViewState: LogViewerScreenViewState(urls: MXLogger.logFiles))
}
// MARK: - Public
override func process(viewAction: LogViewerScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .done:
actionsSubject.send(.done)
}
}
}
@@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol LogViewerScreenViewModelProtocol {
var actions: AnyPublisher<LogViewerScreenViewModelAction, Never> { get }
var context: LogViewerScreenViewModelType.Context { get }
}
@@ -0,0 +1,85 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import QuickLook
import SwiftUI
struct LogViewerScreen: View {
@ObservedObject var context: LogViewerScreenViewModel.Context
var body: some View {
PreviewView(urls: context.viewState.urls)
}
}
private struct PreviewView: UIViewControllerRepresentable {
let urls: [URL]
func makeUIViewController(context: Context) -> UIViewController {
let previewController = QLPreviewController()
previewController.dataSource = context.coordinator
previewController.delegate = context.coordinator
if ProcessInfo.processInfo.isiOSAppOnMac {
return previewController
} else {
return UINavigationController(rootViewController: previewController)
}
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(view: self)
}
class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate {
let view: PreviewView
init(view: PreviewView) {
self.view = view
}
// MARK: - QLPreviewControllerDataSource
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
view.urls.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let url = view.urls[index]
return PreviewItem(previewItemURL: url, previewItemTitle: url.lastPathComponent)
}
// MARK: - QLPreviewControllerDelegate
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
.disabled
}
}
}
private class PreviewItem: NSObject, QLPreviewItem {
var previewItemURL: URL?
var previewItemTitle: String?
init(previewItemURL: URL?, previewItemTitle: String?) {
self.previewItemURL = previewItemURL
self.previewItemTitle = previewItemTitle
}
}
@@ -206,12 +206,12 @@ class MockScreen: Identifiable {
fatalError("Failed to start listening for notifications.")
}
let coordinator = AppLockFlowCoordinator(initialState: .unlocked,
appLockService: appLockService,
navigationCoordinator: navigationCoordinator,
notificationCenter: notificationCenter)
let flowCoordinator = AppLockFlowCoordinator(initialState: .unlocked,
appLockService: appLockService,
navigationCoordinator: navigationCoordinator,
notificationCenter: notificationCenter)
coordinator.actions
flowCoordinator.actions
.sink { [weak self] action in
guard let self else { return }
@@ -226,7 +226,9 @@ class MockScreen: Identifiable {
}
.store(in: &cancellables)
return coordinator
retainedState.append(flowCoordinator)
return navigationCoordinator
case .appLockSetupFlow, .appLockSetupFlowUnlock, .appLockSetupFlowMandatory:
let navigationStackCoordinator = NavigationStackCoordinator()
// The flow expects an existing root coordinator, use a blank form as a placeholder.
@@ -248,11 +250,12 @@ class MockScreen: Identifiable {
}
let flow: AppLockSetupFlowCoordinator.PresentationFlow = id == .appLockSetupFlowMandatory ? .onboarding : .settings
let coordinator = AppLockSetupFlowCoordinator(presentingFlow: flow,
appLockService: appLockService,
navigationStackCoordinator: navigationStackCoordinator)
coordinator.start()
retainedState.append(coordinator)
let flowCoordinator = AppLockSetupFlowCoordinator(presentingFlow: flow,
appLockService: appLockService,
navigationStackCoordinator: navigationStackCoordinator)
flowCoordinator.start()
retainedState.append(flowCoordinator)
return navigationStackCoordinator
case .home:
@@ -559,19 +562,19 @@ class MockScreen: Identifiable {
ServiceLocator.shared.settings.migratedAccounts[clientProxy.userID] = true
ServiceLocator.shared.settings.hasShownWelcomeScreen = true
let coordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock()),
navigationSplitCoordinator: navigationSplitCoordinator,
windowManager: windowManager,
appLockService: AppLockService(keychainController: KeychainControllerMock(),
appSettings: ServiceLocator.shared.settings),
bugReportService: BugReportServiceMock(),
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
appSettings: appSettings,
analytics: ServiceLocator.shared.analytics)
let flowCoordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock()),
navigationSplitCoordinator: navigationSplitCoordinator,
windowManager: windowManager,
appLockService: AppLockService(keychainController: KeychainControllerMock(),
appSettings: ServiceLocator.shared.settings),
bugReportService: BugReportServiceMock(),
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
appSettings: appSettings,
analytics: ServiceLocator.shared.analytics)
coordinator.start()
flowCoordinator.start()
retainedState.append(coordinator)
retainedState.append(flowCoordinator)
return navigationSplitCoordinator
case .roomDetailsScreen: