Secure backup tweaks (#2277)

* Use a custom SDK build that might fix incomplete recovery state false positives. Enable chat backup by default

* Get rid of the chat backup feature flag

* Fix force unwrap warning

* Fix long line warning

* Prevent the recovery key confirmation banner popping up before the verification state is fetched. Stop showing secure backup user avatar and menu badges while the session is not verified.

* Switch back to the release version of the SDK

* Fix inconsistent session verification states, improve/simplify how to deal with it

* Fix unit tests

* Cleanup and hopefully simplify home screen banner presentations
This commit is contained in:
Stefan Ceriu
2023-12-21 17:08:29 +02:00
committed by GitHub
parent d148827eae
commit 0ae71f740d
18 changed files with 67 additions and 82 deletions
@@ -72,10 +72,29 @@ struct HomeScreenViewState: BindableState {
let userID: String
var userDisplayName: String?
var userAvatarURL: URL?
var needsSessionVerification = false
var isSessionVerified: Bool?
var hasSessionVerificationBannerBeenDismissed = false
var showSessionVerificationBanner: Bool {
guard let isSessionVerified else {
return false
}
return !isSessionVerified && !hasSessionVerificationBannerBeenDismissed
}
var requiresSecureBackupSetup = false
var needsRecoveryKeyConfirmation = false
var showUserMenuBadge = false
var showSettingsMenuOptionBadge = false
var hasRecoveryKeyConfirmationBannerBeenDismissed = false
var showRecoveryKeyConfirmationBanner: Bool {
guard let isSessionVerified else {
return false
}
return isSessionVerified && needsRecoveryKeyConfirmation && !hasRecoveryKeyConfirmationBannerBeenDismissed
}
var rooms: [HomeScreenRoom] = []
var roomListMode: HomeScreenRoomListMode = .skeletons
@@ -31,7 +31,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
private let visibleItemRangePublisher = CurrentValueSubject<(range: Range<Int>, isScrolling: Bool), Never>((0..<0, false))
private var actionsSubject: PassthroughSubject<HomeScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<HomeScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
@@ -50,20 +49,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
super.init(initialViewState: .init(userID: userSession.userID),
imageProvider: userSession.mediaProvider)
userSession.callbacks
.receive(on: DispatchQueue.main)
.sink { [weak self] callback in
switch callback {
case .sessionVerificationNeeded:
self?.state.needsSessionVerification = true
case .didVerifySession:
self?.state.needsSessionVerification = false
default:
break
}
}
.store(in: &cancellables)
userSession.clientProxy.userAvatarURL
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.userAvatarURL, on: self)
@@ -74,15 +59,18 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.weakAssign(to: \.state.userDisplayName, on: self)
.store(in: &cancellables)
userSession.sessionVerificationState
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.isSessionVerified, on: self)
.store(in: &cancellables)
userSession.clientProxy.secureBackupController.recoveryKeyState
.receive(on: DispatchQueue.main)
.sink { [weak self] recoveryKeyState in
guard let self, appSettings.chatBackupEnabled else { return }
guard let self else { return }
let requiresSecureBackupSetup = recoveryKeyState == .disabled || recoveryKeyState == .incomplete
state.showUserMenuBadge = requiresSecureBackupSetup
state.showSettingsMenuOptionBadge = requiresSecureBackupSetup
state.requiresSecureBackupSetup = requiresSecureBackupSetup
state.needsRecoveryKeyConfirmation = recoveryKeyState == .incomplete
}
@@ -138,9 +126,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
case .confirmRecoveryKey:
actionsSubject.send(.presentSecureBackupSettings)
case .skipSessionVerification:
state.needsSessionVerification = false
state.hasSessionVerificationBannerBeenDismissed = true
case .skipRecoveryKeyConfirmation:
state.needsRecoveryKeyConfirmation = false
state.hasRecoveryKeyConfirmationBannerBeenDismissed = true
case .updateVisibleItemRange(let range, let isScrolling):
visibleItemRangePublisher.send((range, isScrolling))
case .startChat:
@@ -191,9 +191,9 @@ struct HomeScreen: View {
@ViewBuilder
/// The session verification banner and invites button if either are needed.
private var topSection: some View {
if context.viewState.needsSessionVerification {
if context.viewState.showSessionVerificationBanner {
HomeScreenSessionVerificationBanner(context: context)
} else if context.viewState.needsRecoveryKeyConfirmation {
} else if context.viewState.showRecoveryKeyConfirmationBanner {
HomeScreenRecoveryKeyConfirmationBanner(context: context)
}
@@ -29,7 +29,7 @@ struct HomeScreenUserMenuButton: View {
Label {
Text(L10n.commonSettings)
} icon: {
if context.viewState.showSettingsMenuOptionBadge {
if context.viewState.requiresSecureBackupSetup, context.viewState.isSessionVerified == true {
CompoundIcon(asset: Asset.Images.settingsIconWithBadge)
} else {
CompoundIcon(\.settings)
@@ -50,7 +50,7 @@ struct HomeScreenUserMenuButton: View {
avatarSize: .user(on: .home),
imageProvider: context.imageProvider)
.accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar)
.overlayBadge(10, isBadged: context.viewState.showUserMenuBadge)
.overlayBadge(10, isBadged: context.viewState.requiresSecureBackupSetup && context.viewState.isSessionVerified == true)
.compositingGroup()
}
.accessibilityLabel(L10n.a11yUserMenu)
@@ -49,7 +49,6 @@ protocol DeveloperOptionsProtocol: AnyObject {
var shouldCollapseRoomStateEvents: Bool { get set }
var userSuggestionsEnabled: Bool { get set }
var swiftUITimelineEnabled: Bool { get set }
var chatBackupEnabled: Bool { get set }
var elementCallBaseURL: URL { get set }
var elementCallUseEncryption: Bool { get set }
@@ -32,13 +32,6 @@ struct DeveloperOptionsScreen: View {
}
}
Section("Security") {
Toggle(isOn: $context.chatBackupEnabled) {
Text("Chat backup")
Text("Requires app reboot")
}
}
Section("Timeline") {
Toggle(isOn: $context.shouldCollapseRoomStateEvents) {
Text("Collapse room state events")
@@ -42,7 +42,6 @@ struct SettingsScreenViewState: BindableState {
var userAvatarURL: URL?
var userDisplayName: String?
var isSessionVerified: Bool?
var chatBackupEnabled = false
var showSecureBackupBadge = false
var showDeveloperOptions: Bool
@@ -44,8 +44,9 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
.weakAssign(to: \.state.userDisplayName, on: self)
.store(in: &cancellables)
appSettings.$chatBackupEnabled
.weakAssign(to: \.state.chatBackupEnabled, on: self)
userSession.sessionVerificationState
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.isSessionVerified, on: self)
.store(in: &cancellables)
userSession.clientProxy.secureBackupController.recoveryKeyState
@@ -53,33 +54,14 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
.sink { [weak self] state in
guard let self else { return }
self.state.showSecureBackupBadge = (state == .incomplete || state == .disabled) && appSettings.chatBackupEnabled
self.state.showSecureBackupBadge = (state == .incomplete || state == .disabled)
}
.store(in: &cancellables)
Task {
await userSession.clientProxy.loadUserAvatarURL()
await userSession.clientProxy.loadUserDisplayName()
if let sessionVerificationController = userSession.sessionVerificationController,
case let .success(isVerified) = await sessionVerificationController.isVerified() {
state.isSessionVerified = isVerified
}
}
userSession.callbacks
.receive(on: DispatchQueue.main)
.sink { [weak self] callback in
switch callback {
case .sessionVerificationNeeded:
self?.state.isSessionVerified = false
case .didVerifySession:
self?.state.isSessionVerified = true
default:
break
}
}
.store(in: &cancellables)
}
override func process(viewAction: SettingsScreenViewAction) {
@@ -88,7 +88,7 @@ struct SettingsScreen: View {
ListRow(label: .default(title: L10n.actionCompleteVerification,
icon: \.checkCircle),
kind: .button { context.send(viewAction: .sessionVerification) })
} else if context.viewState.chatBackupEnabled {
} else {
ListRow(label: .default(title: L10n.commonChatBackup,
icon: Image(asset: Asset.Images.secureBackupIcon)),
details: context.viewState.showSecureBackupBadge ? .icon(secureBackupBadge) : nil,