mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Add a banner that offers the user to transition to native sliding sync (#3252)
* Fixes #2508 - Offer user to transition to SSS * Rename `HomeScreenBannerMode.shown` to `HomeScreenBannerMode.show` * Generate and use preview tests for the migration banner * Bump the RustSDK to v1.0.46 * Address PR review comment
This commit is contained in:
@@ -407,12 +407,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
||||
case .presentStartChatScreen:
|
||||
stateMachine.processEvent(.showStartChatScreen)
|
||||
case .logout:
|
||||
Task { await self.runLogoutFlow() }
|
||||
case .presentGlobalSearch:
|
||||
presentGlobalSearch()
|
||||
case .presentRoomDirectorySearch:
|
||||
stateMachine.processEvent(.showRoomDirectorySearchScreen)
|
||||
case .logoutWithoutConfirmation:
|
||||
self.actionsSubject.send(.logout)
|
||||
case .logout:
|
||||
Task { await self.runLogoutFlow() }
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -63,6 +63,11 @@ extension ClientProxyMock {
|
||||
ignoreUserReturnValue = .success(())
|
||||
unignoreUserReturnValue = .success(())
|
||||
|
||||
slidingSyncVersion = .native
|
||||
availableSlidingSyncVersionsClosure = {
|
||||
[]
|
||||
}
|
||||
|
||||
trackRecentlyVisitedRoomReturnValue = .success(())
|
||||
recentlyVisitedRoomsReturnValue = .success([])
|
||||
recentConversationCounterpartsReturnValue = []
|
||||
|
||||
@@ -24,6 +24,7 @@ enum HomeScreenCoordinatorAction {
|
||||
case presentStartChatScreen
|
||||
case presentGlobalSearch
|
||||
case presentRoomDirectorySearch
|
||||
case logoutWithoutConfirmation
|
||||
case logout
|
||||
}
|
||||
|
||||
@@ -64,14 +65,16 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentSettingsScreen)
|
||||
case .presentSecureBackupSettings:
|
||||
actionsSubject.send(.presentSecureBackupSettings)
|
||||
case .logout:
|
||||
actionsSubject.send(.logout)
|
||||
case .presentStartChatScreen:
|
||||
actionsSubject.send(.presentStartChatScreen)
|
||||
case .presentGlobalSearch:
|
||||
actionsSubject.send(.presentGlobalSearch)
|
||||
case .presentRoomDirectorySearch:
|
||||
actionsSubject.send(.presentRoomDirectorySearch)
|
||||
case .logoutWithoutConfirmation:
|
||||
actionsSubject.send(.logoutWithoutConfirmation)
|
||||
case .logout:
|
||||
actionsSubject.send(.logout)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -19,6 +19,7 @@ enum HomeScreenViewModelAction {
|
||||
case presentStartChatScreen
|
||||
case presentGlobalSearch
|
||||
case presentRoomDirectorySearch
|
||||
case logoutWithoutConfirmation
|
||||
case logout
|
||||
}
|
||||
|
||||
@@ -31,6 +32,8 @@ enum HomeScreenViewAction {
|
||||
case startChat
|
||||
case confirmRecoveryKey
|
||||
case skipRecoveryKeyConfirmation
|
||||
case confirmSlidingSyncUpgrade
|
||||
case skipSlidingSyncUpgrade
|
||||
case updateVisibleItemRange(Range<Int>)
|
||||
case globalSearch
|
||||
case markRoomAsUnread(roomIdentifier: String)
|
||||
@@ -59,10 +62,10 @@ enum HomeScreenRoomListMode: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
enum SecurityBannerMode {
|
||||
enum HomeScreenBannerMode {
|
||||
case none
|
||||
case dismissed
|
||||
case recoveryKeyConfirmation
|
||||
case show
|
||||
}
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
@@ -70,7 +73,9 @@ struct HomeScreenViewState: BindableState {
|
||||
var userDisplayName: String?
|
||||
var userAvatarURL: URL?
|
||||
|
||||
var securityBannerMode = SecurityBannerMode.none
|
||||
var securityBannerMode = HomeScreenBannerMode.none
|
||||
var slidingSyncMigrationBannerMode = HomeScreenBannerMode.none
|
||||
|
||||
var requiresExtraAccountSetup = false
|
||||
|
||||
var rooms: [HomeScreenRoom] = []
|
||||
@@ -110,10 +115,6 @@ struct HomeScreenViewState: BindableState {
|
||||
var shouldShowFilters: Bool {
|
||||
!bindings.isSearchFieldFocused && roomListMode == .rooms
|
||||
}
|
||||
|
||||
var shouldShowRecoveryKeyConfirmationBanner: Bool {
|
||||
securityBannerMode == .recoveryKeyConfirmation
|
||||
}
|
||||
}
|
||||
|
||||
struct HomeScreenViewStateBindings {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import AnalyticsEvents
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, HomeScreenViewAction>
|
||||
@@ -62,7 +63,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
state.requiresExtraAccountSetup = true
|
||||
|
||||
if state.securityBannerMode != .dismissed {
|
||||
state.securityBannerMode = .recoveryKeyConfirmation
|
||||
state.securityBannerMode = .show
|
||||
}
|
||||
default:
|
||||
state.securityBannerMode = .none
|
||||
@@ -117,6 +118,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
setupRoomListSubscriptions()
|
||||
|
||||
updateRooms()
|
||||
|
||||
Task {
|
||||
await checkSlidingSyncMigration()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -137,6 +142,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
actionsSubject.send(.presentSecureBackupSettings)
|
||||
case .skipRecoveryKeyConfirmation:
|
||||
state.securityBannerMode = .dismissed
|
||||
case .confirmSlidingSyncUpgrade:
|
||||
actionsSubject.send(.logout)
|
||||
case .skipSlidingSyncUpgrade:
|
||||
state.slidingSyncMigrationBannerMode = .dismissed
|
||||
case .updateVisibleItemRange(let range):
|
||||
roomSummaryProvider?.updateVisibleRange(range)
|
||||
case .startChat:
|
||||
@@ -192,12 +201,15 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
// perphery: ignore - used in release mode
|
||||
func presentCrashedLastRunAlert() {
|
||||
state.bindings.alertInfo = AlertInfo(id: UUID(),
|
||||
title: L10n.crashDetectionDialogContent(InfoPlistReader.main.bundleDisplayName),
|
||||
primaryButton: .init(title: L10n.actionNo, action: nil),
|
||||
secondaryButton: .init(title: L10n.actionYes) { [weak self] in
|
||||
self?.actionsSubject.send(.presentFeedbackScreen)
|
||||
})
|
||||
// Delay setting the alert otherwise it automatically gets dismissed. Same as the force logout one.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.state.bindings.alertInfo = AlertInfo(id: UUID(),
|
||||
title: L10n.crashDetectionDialogContent(InfoPlistReader.main.bundleDisplayName),
|
||||
primaryButton: .init(title: L10n.actionNo, action: nil),
|
||||
secondaryButton: .init(title: L10n.actionYes) { [weak self] in
|
||||
self?.actionsSubject.send(.presentFeedbackScreen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -287,7 +299,40 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
state.rooms = rooms
|
||||
}
|
||||
|
||||
/// Check whether we can inform the user about potential migrations
|
||||
/// or have him logout as his proxy is no longer available
|
||||
private func checkSlidingSyncMigration() async {
|
||||
// Not logged in with a proxy, don't need to do anything
|
||||
guard userSession.clientProxy.slidingSyncVersion.isProxy else {
|
||||
return
|
||||
}
|
||||
|
||||
let versions = await userSession.clientProxy.availableSlidingSyncVersions
|
||||
|
||||
// Native not available, nothing we can do
|
||||
guard versions.contains(.native) else {
|
||||
return
|
||||
}
|
||||
|
||||
if versions.contains(.native) {
|
||||
// Both available, prompt for migration
|
||||
if versions.contains(where: \.isProxy) {
|
||||
state.slidingSyncMigrationBannerMode = .show
|
||||
} else { // The proxy has been removed and logout is needed
|
||||
// Delay setting the alert otherwise it automatically gets dismissed. Same as the crashed last run one
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.state.bindings.alertInfo = AlertInfo(id: UUID(),
|
||||
title: L10n.bannerMigrateToNativeSlidingSyncForceLogoutTitle,
|
||||
primaryButton: .init(title: L10n.bannerMigrateToNativeSlidingSyncAction,
|
||||
action: { [weak self] in
|
||||
self?.actionsSubject.send(.logoutWithoutConfirmation)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func markRoomAsFavourite(_ roomID: String, isFavourite: Bool) async {
|
||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
MXLog.error("Failed retrieving room for identifier: \(roomID)")
|
||||
@@ -412,3 +457,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
message: L10n.errorUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
extension SlidingSyncVersion {
|
||||
var isProxy: Bool {
|
||||
switch self {
|
||||
case .proxy:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,13 +120,16 @@ struct HomeScreenContent: View {
|
||||
private var topSection: some View {
|
||||
// An empty VStack causes glitches within the room list
|
||||
if context.viewState.shouldShowFilters ||
|
||||
context.viewState.shouldShowRecoveryKeyConfirmationBanner {
|
||||
context.viewState.securityBannerMode == .show ||
|
||||
context.viewState.slidingSyncMigrationBannerMode == .show {
|
||||
VStack(spacing: 0) {
|
||||
if context.viewState.shouldShowFilters {
|
||||
RoomListFiltersView(state: $context.filtersState)
|
||||
}
|
||||
|
||||
if context.viewState.shouldShowRecoveryKeyConfirmationBanner {
|
||||
|
||||
if context.viewState.slidingSyncMigrationBannerMode == .show {
|
||||
HomeScreenSlidingSyncMigrationBanner(context: context)
|
||||
} else if context.viewState.securityBannerMode == .show {
|
||||
HomeScreenRecoveryKeyConfirmationBanner(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct HomeScreenRecoveryKeyConfirmationBanner: View {
|
||||
@ObservedObject var context: HomeScreenViewModel.Context
|
||||
var context: HomeScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct HomeScreenSlidingSyncMigrationBanner: View {
|
||||
var context: HomeScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 16) {
|
||||
Text(L10n.bannerMigrateToNativeSlidingSyncTitle)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
context.send(viewAction: .skipSlidingSyncUpgrade)
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
.foregroundColor(.compound.iconSecondary)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
}
|
||||
Text(L10n.bannerMigrateToNativeSlidingSyncDescription)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
}
|
||||
|
||||
Button(L10n.bannerMigrateToNativeSlidingSyncAction) {
|
||||
context.send(viewAction: .confirmSlidingSyncUpgrade)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.buttonStyle(.compound(.primary, size: .medium))
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color.compound.bgSubtleSecondary)
|
||||
.cornerRadius(14)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
|
||||
struct HomeScreenSlidingSyncMigrationBanner_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = buildViewModel()
|
||||
|
||||
static var previews: some View {
|
||||
HomeScreenSlidingSyncMigrationBanner(context: viewModel.context)
|
||||
}
|
||||
|
||||
static func buildViewModel() -> HomeScreenViewModel {
|
||||
let clientProxy = ClientProxyMock(.init())
|
||||
|
||||
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||
|
||||
return HomeScreenViewModel(userSession: userSession,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user