Skip to content

Commit

Permalink
Key backup returns (#1951)
Browse files Browse the repository at this point in the history
* Converge on UserSessionFlowCoordinator logout confirmation

* Add logout confirmation screen strings to untranslated.

* Fix chat backup learn more URl fragment.

* Implement logout flows that check recovery and key backup for the last session

* Move logout confirmation screen strings to localazy

* Change encrypted timeline item copy to "Waiting for decryption key"

* Use different encrypted history banner based on key backup states

* Introduce a SettingsFlowCoordinator and implement navigation directly to the secure backup screen from the logout flows.

* Fix **mocked** secure backup controller flows

* Simplify encrypted history banner logic

* Address PR comments
  • Loading branch information
stefanceriu authored Oct 24, 2023
1 parent 6e5680f commit 37f1a79
Show file tree
Hide file tree
Showing 34 changed files with 805 additions and 110 deletions.
62 changes: 55 additions & 7 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
guard let self else { return }

switch action {
case .signOut:
case .logout:
stateMachine.processEvent(.signOut(isSoft: false))
case .clearCache:
stateMachine.processEvent(.clearCache)
Expand Down
2 changes: 1 addition & 1 deletion ElementX/Sources/Application/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ final class AppSettings {
/// An email address that should be used for support requests.
let supportEmailAddress = "[email protected]"
// A URL where users can go read more about the chat backup.
let chatBackupDetailsURL: URL = "https://element.io/help#encryption"
let chatBackupDetailsURL: URL = "https://element.io/help#encryption5"

// MARK: - Security

Expand Down
2 changes: 2 additions & 0 deletions ElementX/Sources/Application/Navigation/AppRoutes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ enum AppRoute: Equatable {
case roomMemberDetails(userID: String)
case invites
case genericCallLink(url: URL)
case settings
case chatBackupSettings
}

struct AppRouteURLParser {
Expand Down
7 changes: 3 additions & 4 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
MXLog.error("[RoomFlowCoordinator] Failed to get member: RoomProxy is nil")
}
}
case .invites:
break
case .genericCallLink, .oidcCallback:
case .invites, .genericCallLink, .oidcCallback, .settings, .chatBackupSettings:
break
}
}
Expand Down Expand Up @@ -347,7 +345,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider,
mediaPlayerProvider: mediaPlayerProvider,
voiceMessageMediaManager: userSession.voiceMessageMediaManager)
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
secureBackupController: userSession.clientProxy.secureBackupController)
self.timelineController = timelineController

analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace)
Expand Down
135 changes: 135 additions & 0 deletions ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//
// Copyright 2023 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 Foundation

enum SettingsFlowCoordinatorAction {
case presentedSettings
case dismissedSettings
case runLogoutFlow
case clearCache
}

struct SettingsFlowCoordinatorParameters {
let userSession: UserSessionProtocol
let appLockService: AppLockServiceProtocol
let bugReportService: BugReportServiceProtocol
let notificationSettings: NotificationSettingsProxyProtocol
let secureBackupController: SecureBackupControllerProtocol
let appSettings: AppSettings
let navigationSplitCoordinator: NavigationSplitCoordinator
}

class SettingsFlowCoordinator: FlowCoordinatorProtocol {
private let parameters: SettingsFlowCoordinatorParameters

private var navigationStackCoordinator: NavigationStackCoordinator!
private var userIndicatorController: UserIndicatorControllerProtocol!

private var cancellables = Set<AnyCancellable>()

private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}

init(parameters: SettingsFlowCoordinatorParameters) {
self.parameters = parameters
}

func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
switch appRoute {
case .settings:
presentSettingsScreen(animated: animated)
case .chatBackupSettings:
if navigationStackCoordinator == nil {
presentSettingsScreen(animated: animated)
}

// The navigation stack doesn't like it if the root and the push happen
// on the same loop run
DispatchQueue.main.async {
self.presentSecureBackupScreen(animated: animated)
}
default:
break
}
}

func clearRoute(animated: Bool) { }

// MARK: - Private

private func presentSettingsScreen(animated: Bool) {
navigationStackCoordinator = NavigationStackCoordinator()

userIndicatorController = UserIndicatorController(rootCoordinator: navigationStackCoordinator)

let parameters = SettingsScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: userIndicatorController,
userSession: parameters.userSession,
appLockService: parameters.appLockService,
bugReportService: parameters.bugReportService,
notificationSettings: parameters.userSession.clientProxy.notificationSettings,
secureBackupController: parameters.userSession.clientProxy.secureBackupController,
appSettings: parameters.appSettings)
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters)

settingsScreenCoordinator.actions
.sink { [weak self] action in
guard let self else { return }

switch action {
case .dismiss:
self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
case .logout:
self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil)

// The settings sheet needs to be dismissed before the alert can be shown
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.actionsSubject.send(.runLogoutFlow)
}
case .clearCache:
actionsSubject.send(.clearCache)
case .secureBackup:
presentSecureBackupScreen(animated: true)
}
}
.store(in: &cancellables)

navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)

self.parameters.navigationSplitCoordinator.setSheetCoordinator(userIndicatorController) { [weak self] in
guard let self else { return }

navigationStackCoordinator = nil
userIndicatorController = nil
actionsSubject.send(.dismissedSettings)
}

actionsSubject.send(.presentedSettings)
}

private func presentSecureBackupScreen(animated: Bool) {
let coordinator = SecureBackupScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
secureBackupController: parameters.userSession.clientProxy.secureBackupController,
navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: userIndicatorController))

navigationStackCoordinator.push(coordinator, animated: animated)
}
}
Loading

0 comments on commit 37f1a79

Please sign in to comment.