Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a banner that offers the user to transition to native sliding sync #3252

Merged
merged 5 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */; };
0A0625A271EE5B06D2AAA069 /* HomeScreenSlidingSyncMigrationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4691B8DE1D51DE152680098A /* HomeScreenSlidingSyncMigrationBanner.swift */; };
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; };
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
Expand Down Expand Up @@ -1492,6 +1493,7 @@
45D8149FDDA0315CDC553B4B /* UserNotificationCenterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationCenterProtocol.swift; sourceTree = "<group>"; };
4629710C0337ADD9C8909542 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Localizable.strings; sourceTree = "<group>"; };
466C71A0FED9BFF287613C82 /* RoomDetailsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenModels.swift; sourceTree = "<group>"; };
4691B8DE1D51DE152680098A /* HomeScreenSlidingSyncMigrationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSlidingSyncMigrationBanner.swift; sourceTree = "<group>"; };
46A2AD86F7E618F468F6FAF5 /* VoiceMessageRecordingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingButton.swift; sourceTree = "<group>"; };
46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = "<group>"; };
46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreen.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3354,6 +3356,7 @@
05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */,
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */,
C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */,
4691B8DE1D51DE152680098A /* HomeScreenSlidingSyncMigrationBanner.swift */,
84AF32E4136FD6F159D86C2C /* RoomDirectorySearchView.swift */,
037A5661B26EC6BE068188D7 /* Filters */,
);
Expand Down Expand Up @@ -6372,6 +6375,7 @@
B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */,
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */,
A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */,
0A0625A271EE5B06D2AAA069 /* HomeScreenSlidingSyncMigrationBanner.swift in Sources */,
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */,
56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */,
2BBE320EE426A347AAE5C7DA /* IdentityConfirmationScreen.swift in Sources */,
Expand Down Expand Up @@ -7687,7 +7691,7 @@
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = 1.0.45;
version = 1.0.46;
};
};
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
"state" : {
"revision" : "103b7000e5191485873a81386d0134d71bd9fc36",
"version" : "1.0.45"
"revision" : "50f8730000a1cc9ebe75ecf879c2b85b36adfd34",
"version" : "1.0.46"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions ElementX/Sources/Mocks/ClientProxyMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ extension ClientProxyMock {
ignoreUserReturnValue = .success(())
unignoreUserReturnValue = .success(())

slidingSyncVersion = .native
availableSlidingSyncVersionsClosure = {
[]
}

trackRecentlyVisitedRoomReturnValue = .success(())
recentlyVisitedRoomsReturnValue = .success([])
recentConversationCounterpartsReturnValue = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum HomeScreenCoordinatorAction {
case presentStartChatScreen
case presentGlobalSearch
case presentRoomDirectorySearch
case logoutWithoutConfirmation
case logout
}

Expand Down Expand Up @@ -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)
Expand Down
15 changes: 8 additions & 7 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum HomeScreenViewModelAction {
case presentStartChatScreen
case presentGlobalSearch
case presentRoomDirectorySearch
case logoutWithoutConfirmation
case logout
}

Expand All @@ -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)
Expand Down Expand Up @@ -59,18 +62,20 @@ enum HomeScreenRoomListMode: CustomStringConvertible {
}
}

enum SecurityBannerMode {
enum HomeScreenBannerMode {
case none
case dismissed
case recoveryKeyConfirmation
case show
}

struct HomeScreenViewState: BindableState {
let userID: String
var userDisplayName: String?
var userAvatarURL: URL?

var securityBannerMode = SecurityBannerMode.none
var securityBannerMode = HomeScreenBannerMode.none
var slidingSyncMigrationBannerMode = HomeScreenBannerMode.none

var requiresExtraAccountSetup = false

var rooms: [HomeScreenRoom] = []
Expand Down Expand Up @@ -110,10 +115,6 @@ struct HomeScreenViewState: BindableState {
var shouldShowFilters: Bool {
!bindings.isSearchFieldFocused && roomListMode == .rooms
}

var shouldShowRecoveryKeyConfirmationBanner: Bool {
securityBannerMode == .recoveryKeyConfirmation
}
}

struct HomeScreenViewStateBindings {
Expand Down
70 changes: 63 additions & 7 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import AnalyticsEvents
import Combine
import MatrixRustSDK
import SwiftUI

typealias HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState, HomeScreenViewAction>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -117,6 +118,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
setupRoomListSubscriptions()

updateRooms()

Task {
await checkSlidingSyncMigration()
}
}

// MARK: - Public
Expand All @@ -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:
Expand Down Expand Up @@ -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.
stefanceriu marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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)")
Expand Down Expand Up @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
@ObservedObject var context: HomeScreenViewModel.Context
stefanceriu marked this conversation as resolved.
Show resolved Hide resolved

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)
}
}
6 changes: 6 additions & 0 deletions PreviewTests/Sources/PreviewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ class PreviewTests: XCTestCase {
}
}

func test_homeScreenSlidingSyncMigrationBanner() {
for preview in HomeScreenSlidingSyncMigrationBanner_Previews._allPreviews {
assertSnapshots(matching: preview)
}
}

func test_homeScreen() {
for preview in HomeScreen_Previews._allPreviews {
assertSnapshots(matching: preview)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading