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
@@ -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) {