mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
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:
@@ -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)
|
||||
|
||||
-1
@@ -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 }
|
||||
|
||||
-7
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user