From fc75f90ef51e03bcede4f99d134b583e0e616302 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:19:44 +0100 Subject: [PATCH] Remove the dynamic member lookup from UserSettings in favour of nested access. (#6294) * Remove the dynamic member lookup from UserSettings in favour of nested access. * Remove forwarding methods from UserSettings. * Update the Enterprise submodule. --- .../Sources/Application/AppCoordinator.swift | 12 ++-- .../Application/Settings/UserSettings.swift | 34 +--------- .../OnboardingFlowCoordinator.swift | 16 ++--- .../PinnedEventsTimelineFlowCoordinator.swift | 2 +- .../RoomFlowCoordinator.swift | 10 +-- .../SettingsFlowCoordinator.swift | 2 +- .../UserSessionFlowCoordinator.swift | 10 +-- .../Other/SwiftUI/Views/VisualListItem.swift | 2 +- .../CallScreen/CallScreenViewModel.swift | 8 +-- .../CreateRoomScreenViewModel.swift | 2 +- .../View/CreateRoomScreen.swift | 2 +- .../HomeScreen/HomeScreenViewModel.swift | 26 ++++---- .../View/Filters/RoomListFilterModels.swift | 4 +- .../JoinRoomScreenViewModel.swift | 6 +- .../LocationSharingScreenViewModel.swift | 8 +-- .../View/AnalyticsPromptScreen.swift | 2 +- .../IdentityConfirmationScreenViewModel.swift | 2 +- .../SessionVerificationScreenViewModel.swift | 2 +- .../PinnedEventsTimelineScreenViewModel.swift | 2 +- .../View/PinnedEventsTimelineScreen.swift | 2 +- .../RoomDetailsScreenViewModel.swift | 2 +- .../ComposerToolbarViewModel.swift | 2 +- .../RoomScreen/RoomScreenCoordinator.swift | 6 +- .../RoomScreen/RoomScreenViewModel.swift | 14 ++--- .../Screens/RoomScreen/View/RoomScreen.swift | 2 +- .../SearchScreen/SearchScreenViewModel.swift | 12 ++-- .../SearchScreen/View/SearchScreen.swift | 2 +- .../SecureBackupScreenCoordinator.swift | 2 +- .../SecurityAndPrivacyScreenViewModel.swift | 8 +-- .../View/SecurityAndPrivacyScreen.swift | 6 +- .../OAuthAccountSettingsPresenter.swift | 2 +- .../AnalyticsSettingsScreenViewModel.swift | 4 +- .../LegalInformationScreenViewModel.swift | 6 +- .../LocationSharingSettingsScreenModels.swift | 4 +- ...diaUploadQualitySettingsScreenModels.swift | 4 +- ...erationAndSafetySettingsScreenModels.swift | 4 +- .../NotificationSettingsScreenViewModel.swift | 16 ++--- .../SettingsScreen/SettingsScreenModels.swift | 4 +- .../SettingsScreenViewModel.swift | 16 ++--- .../SettingsScreen/View/SettingsScreen.swift | 4 +- .../ThreadTimelineScreenCoordinator.swift | 6 +- .../Timeline/TimelineInteractionHandler.swift | 8 +-- .../Screens/Timeline/TimelineViewModel.swift | 30 ++++----- .../Style/TimelineItemBubbledStylerView.swift | 6 +- .../AlertTones/NotificationToneManager.swift | 4 +- .../AuthenticationService.swift | 4 +- .../Sources/Services/Client/ClientProxy.swift | 10 +-- .../Services/Emojis/EmojiProvider.swift | 8 +-- .../Location/LiveLocationManager.swift | 28 ++++----- .../Media/MediaUploadingPreprocessor.swift | 4 +- .../Services/Presence/PresenceService.swift | 4 +- .../Services/Room/JoinedRoomProxy.swift | 4 +- .../RoomSummary/RoomSummaryProvider.swift | 6 +- .../TimelineController.swift | 2 +- .../UITests/UITestsAppCoordinator.swift | 12 ++-- Enterprise | 2 +- UnitTests/Sources/BugReportServiceTests.swift | 14 ++--- .../Sources/CreateRoomViewModelTests.swift | 4 +- .../Sources/HomeScreenViewModelTests.swift | 42 ++++++------- .../JoinRoomScreenViewModelTests.swift | 20 +++--- .../Sources/LiveLocationManagerTests.swift | 38 ++++++------ .../LocationSharingScreenViewModelTests.swift | 10 +-- ...diaUploadPreviewScreenViewModelTests.swift | 2 +- .../MediaUploadingPreprocessorTests.swift | 20 +++--- ...ficationSettingsScreenViewModelTests.swift | 24 +++---- UnitTests/Sources/PresenceServiceTests.swift | 24 +++---- .../Sources/RoomFlowCoordinatorTests.swift | 2 +- .../Sources/RoomListFiltersStateTests.swift | 4 +- .../Sources/RoomScreenViewModelTests.swift | 2 +- .../Sources/RoomSummaryProviderTests.swift | 2 +- .../Sources/SearchScreenViewModelTests.swift | 24 +++---- ...curityAndPrivacyScreenViewModelTests.swift | 4 +- .../SettingsScreenViewModelTests.swift | 4 +- .../Sources/TimelineViewModelTests.swift | 6 +- UnitTests/Sources/UserSettingsTests.swift | 62 +++++++++---------- 75 files changed, 346 insertions(+), 374 deletions(-) diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 1ca5458dd..10a52201c 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -495,8 +495,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg if oldVersion < Version(1, 6, 0) { MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.") - userSession.userSettings.hasRunIdentityConfirmationOnboarding = true - userSession.userSettings.hasRunNotificationPermissionsOnboarding = true + userSession.userSettings.account.hasRunIdentityConfirmationOnboarding = true + userSession.userSettings.app.hasRunNotificationPermissionsOnboarding = true } if oldVersion < Version(25, 6, 0) { @@ -521,8 +521,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg if oldVersion < Version(26, 10, 1) { MXLog.info("Migrating to version 26.10.1, moving initial 'session specific' settings.") - userSession.userSettings.migrateAppSettingsValueToAccountSettings(\.hasRunIdentityConfirmationOnboardingKey) - userSession.userSettings.migrateAppSettingsValueToAccountSettings(\.searchBreadcrumbsKey) + userSession.userSettings.account.migrateAppSettingsValue(\.hasRunIdentityConfirmationOnboardingKey) + userSession.userSettings.account.migrateAppSettingsValue(\.searchBreadcrumbsKey) } userSessionMigrationsOldVersion = nil @@ -872,7 +872,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg userSessionStore.logout(userSession: userSession) tearDownUserSession() - userSession.userSettings.resetSessionSpecificSettings() + userSession.userSettings.account.reset() appHooks.remoteSettingsHook.reset(appSettings) // Reset analytics @@ -1385,7 +1385,7 @@ private extension AppCoordinator { } func scheduleSearchBackfill() { - guard let userSession, userSession.userSettings.globalSearchEnabled, #available(iOS 26.0, *) else { + guard let userSession, userSession.userSettings.app.globalSearchEnabled, #available(iOS 26.0, *) else { return } diff --git a/ElementX/Sources/Application/Settings/UserSettings.swift b/ElementX/Sources/Application/Settings/UserSettings.swift index 9965bfcc1..00e9be2ef 100644 --- a/ElementX/Sources/Application/Settings/UserSettings.swift +++ b/ElementX/Sources/Application/Settings/UserSettings.swift @@ -10,44 +10,16 @@ import Macros /// The settings for a signed in account, composed of the app wide settings and the settings /// specific to that account. -@dynamicMemberLookup final nonisolated class UserSettings: Sendable { - // TODO: @pixlwave Make this private after handling Advanced/Labs/Developer screen models. let app: AppSettings - private let account: AccountSettings + let account: AccountSettings init(appSettings: AppSettings, accountSettings: AccountSettings) { - self.app = appSettings - self.account = accountSettings + app = appSettings + account = accountSettings } static func mock(userID: String = "@me:matrix.org") -> UserSettings { AppSettings.volatile().userSettings(for: userID) } - - subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Value { - get { app[keyPath: keyPath] } - set { app[keyPath: keyPath] = newValue } - } - - subscript(dynamicMember keyPath: KeyPath) -> Value { - app[keyPath: keyPath] - } - - subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Value { - get { account[keyPath: keyPath] } - set { account[keyPath: keyPath] = newValue } - } - - subscript(dynamicMember keyPath: KeyPath) -> Value { - account[keyPath: keyPath] - } - - func migrateAppSettingsValueToAccountSettings(_ keyPath: KeyPath) { - account.migrateAppSettingsValue(keyPath) - } - - func resetSessionSpecificSettings() { - account.reset() - } } diff --git a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift index 61e851c45..2ccdddfbd 100644 --- a/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/OnboardingFlowCoordinator.swift @@ -94,7 +94,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { value == .verified, stateMachine.state == .identityConfirmation else { return } - userSettings.hasRunIdentityConfirmationOnboarding = true + userSettings.account.hasRunIdentityConfirmationOnboarding = true stateMachine.tryEvent(.nextSkippingIdentityConfirmed) } } @@ -129,11 +129,11 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { private var requiresVerification: Bool { // We want to make sure onboarding finishes but also every time the user becomes unverified (e.g. account reset) - !userSettings.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified + !userSettings.account.hasRunIdentityConfirmationOnboarding || userSession.sessionSecurityStatePublisher.value.verificationState == .unverified } private var requiresAppLockSetup: Bool { - userSettings.appLockIsMandatory && !appLockService.isEnabled + userSettings.app.appLockIsMandatory && !appLockService.isEnabled } private var requiresAnalyticsSetup: Bool { @@ -141,7 +141,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { } private var requiresNotificationsSetup: Bool { - !userSettings.hasRunNotificationPermissionsOnboarding + !userSettings.app.hasRunNotificationPermissionsOnboarding } private func configureStateMachine() { @@ -225,7 +225,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { presentNotificationPermissionsScreen() case (_, _, .finished): isNewLogin = false - userSettings.hasSignedInBefore = true + userSettings.app.hasSignedInBefore = true actionsSubject.send(.dismiss) stateMachine.tryState(.initial) case (.finished, _, .initial): @@ -261,7 +261,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { case .recoveryKey: presentRecoveryKeyScreen() case .skip: - userSettings.hasRunIdentityConfirmationOnboarding = true + userSettings.account.hasRunIdentityConfirmationOnboarding = true stateMachine.tryEvent(.nextSkippingIdentityConfirmed) case .reset: startEncryptionResetFlow() @@ -384,7 +384,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { } private func presentAnalyticsPromptScreen() { - let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: userSettings.analyticsTermsURL) + let coordinator = AnalyticsPromptScreenCoordinator(analytics: analyticsService, termsURL: userSettings.app.analyticsTermsURL) coordinator.actions .sink { [weak self] action in @@ -407,7 +407,7 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol { guard let self else { return } switch action { case .done: - userSettings.hasRunNotificationPermissionsOnboarding = true + userSettings.app.hasRunNotificationPermissionsOnboarding = true stateMachine.tryEvent(.next) } } diff --git a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift index d7fa1cf91..32d9d959d 100644 --- a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift @@ -107,7 +107,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol { let stackCoordinator = NavigationStackCoordinator() let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode, - mapURLBuilder: flowParameters.userSettings.mapTilerConfiguration.publisher.value, + mapURLBuilder: flowParameters.userSettings.app.mapTilerConfiguration.publisher.value, roomProxy: roomProxy, timelineController: timelineController, liveLocationManager: flowParameters.userSession.liveLocationManager, diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 80d3d51fb..996dfd061 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -238,7 +238,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { defer { hideLoadingIndicator() } switch await roomProxy.loadOrFetchEventDetails(for: eventID) { case .success(let event): - if flowParameters.userSettings.threadsEnabled, let threadRootEventID = event.threadRootEventId() { + if flowParameters.userSettings.app.threadsEnabled, let threadRootEventID = event.threadRootEventId() { if case .thread(threadRootEventID: threadRootEventID, _) = stateMachine.state, let threadCoordinator = childThreadScreenCoordinators.last { threadCoordinator.focusOnEvent(eventID: eventID) } else { @@ -318,7 +318,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { // Otherwise check if the focussed event exists to handle a possible error or theaded event. switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) { case .success(let event): - if flowParameters.userSettings.threadsEnabled, let threadRootEventID = event.threadRootEventId() { + if flowParameters.userSettings.app.threadsEnabled, let threadRootEventID = event.threadRootEventId() { stateMachine.tryEvent(.presentRoom(presentationAction: .thread(rootEventID: threadRootEventID, focusEvent: .init(eventID: focusEvent.eventID, shouldSetPin: focusEvent.shouldSetPin))), @@ -1119,8 +1119,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let parameters = MediaUploadPreviewScreenCoordinatorParameters(mediaURLs: mediaURLs, caption: caption, title: title, - shouldShowCaptionWarning: flowParameters.userSettings.shouldShowMediaCaptionWarning, - galleryEnabled: flowParameters.userSettings.galleryEnabled, + shouldShowCaptionWarning: flowParameters.userSettings.app.shouldShowMediaCaptionWarning, + galleryEnabled: flowParameters.userSettings.app.galleryEnabled, mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: flowParameters.userSettings), timelineController: timelineController, clientProxy: userSession.clientProxy, @@ -1176,7 +1176,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let stackCoordinator = NavigationStackCoordinator() let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode, - mapURLBuilder: flowParameters.userSettings.mapTilerConfiguration.publisher.value, + mapURLBuilder: flowParameters.userSettings.app.mapTilerConfiguration.publisher.value, roomProxy: roomProxy, timelineController: timelineController, liveLocationManager: flowParameters.userSession.liveLocationManager, diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 2de2b9677..a7ed0cafe 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -264,7 +264,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { } private func presentBlockedUsersScreen() { - let coordinator = BlockedUsersScreenCoordinator(parameters: .init(hideProfiles: flowParameters.userSettings.hideIgnoredUserProfiles, + let coordinator = BlockedUsersScreenCoordinator(parameters: .init(hideProfiles: flowParameters.userSettings.app.hideIgnoredUserProfiles, userSession: flowParameters.userSession, userIndicatorController: flowParameters.userIndicatorController)) navigationStackCoordinator.push(coordinator) diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index a9091c43f..e18b1ad46 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -87,19 +87,19 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { navigationTabCoordinator = NavigationTabCoordinator() navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator) - let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.hideBrandChrome)) + let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.app.hideBrandChrome)) chatsTabFlowCoordinator = ChatsTabFlowCoordinator(navigationSplitCoordinator: chatsSplitCoordinator, flowParameters: flowParameters) chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid) chatsTabDetails.navigationSplitCoordinator = chatsSplitCoordinator - let spacesSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.hideBrandChrome)) + let spacesSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: flowParameters.userSettings.app.hideBrandChrome)) spacesTabFlowCoordinator = SpacesTabFlowCoordinator(navigationSplitCoordinator: spacesSplitCoordinator, flowParameters: flowParameters) spacesTabDetails = .init(tag: HomeTab.spaces, title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid) spacesTabDetails.navigationSplitCoordinator = spacesSplitCoordinator - if flowParameters.userSettings.globalSearchEnabled, #available(iOS 26.0, *) { + if flowParameters.userSettings.app.globalSearchEnabled, #available(iOS 26.0, *) { let searchCoordinator = SearchScreenCoordinator(parameters: .init(roomSummaryProvider: flowParameters.userSession.clientProxy.alternateRoomSummaryProvider, clientProxy: flowParameters.userSession.clientProxy, mediaProvider: flowParameters.userSession.mediaProvider, @@ -463,8 +463,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { presentCallScreen(configuration: .init(roomProxy: roomProxy, clientProxy: userSession.clientProxy, clientID: InfoPlistReader.main.bundleIdentifier, - elementCallBaseURL: flowParameters.userSettings.elementCallBaseURL, - elementCallBaseURLOverride: flowParameters.userSettings.elementCallBaseURLOverride, + elementCallBaseURL: flowParameters.userSettings.app.elementCallBaseURL, + elementCallBaseURLOverride: flowParameters.userSettings.app.elementCallBaseURLOverride, voiceOnly: voiceOnly, colorScheme: colorScheme)) } diff --git a/ElementX/Sources/Other/SwiftUI/Views/VisualListItem.swift b/ElementX/Sources/Other/SwiftUI/Views/VisualListItem.swift index bb54147e2..342a78ab8 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/VisualListItem.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/VisualListItem.swift @@ -56,7 +56,7 @@ private struct VisualListItemLabelStyle: LabelStyle { // MARK: - Previews struct VisualListItem_Previews: PreviewProvider, TestablePreview { - static let strings = AnalyticsPromptScreenStrings(termsURL: UserSettings.mock().analyticsTermsURL) + static let strings = AnalyticsPromptScreenStrings(termsURL: AppSettings.volatile().analyticsTermsURL) static var testImage1: some View { Image(systemName: "circle") diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 68772af4c..45a33a64e 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -165,13 +165,13 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol // We only set the analytics configuration if analytics are enabled let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled { - .init(posthogAPIHost: userSettings.elementCallPosthogAPIHost, - posthogAPIKey: userSettings.elementCallPosthogAPIKey, - sentryDSN: userSettings.elementCallPosthogSentryDSN) + .init(posthogAPIHost: userSettings.app.elementCallPosthogAPIHost, + posthogAPIKey: userSettings.app.elementCallPosthogAPIKey, + sentryDSN: userSettings.app.elementCallPosthogSentryDSN) } else { nil } - let rageshakeURL: String? = if case let .url(baseURL) = userSettings.bugReportRageshakeURL.publisher.value { + let rageshakeURL: String? = if case let .url(baseURL) = userSettings.app.bugReportRageshakeURL.publisher.value { baseURL.absoluteString } else { nil diff --git a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift index 207684def..323b421b5 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift @@ -64,7 +64,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen shouldShowCancelButton: shouldShowCancelButton, roomName: "", serverName: userSession.clientProxy.userIDServerName ?? "", - isKnockingFeatureEnabled: userSettings.knockingEnabled, + isKnockingFeatureEnabled: userSettings.app.knockingEnabled, canSelectSpace: canSelectSpace, aliasLocalPart: roomAliasNameFromRoomDisplayName(roomName: ""), bindings: bindings) diff --git a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift index ebe792968..4fab4ee1b 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift @@ -452,7 +452,7 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview { selectionMode: CreateRoomScreenSpaceSelectionMode = .editableSpacesList(preSelectedSpace: nil), isAliasAvailable: Bool = true) -> CreateRoomScreenViewModel { let userSettings = UserSettings.mock() - userSettings.knockingEnabled = isKnockingEnabled + userSettings.app.knockingEnabled = isKnockingEnabled let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com")) diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 764685b3a..8e2e325d3 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -52,7 +52,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol bindings: .init(filtersState: .init(userSettings: userSettings))), mediaProvider: userSession.mediaProvider) - if userSettings.globalSearchEnabled, #available(iOS 26.0, *) { + if userSettings.app.globalSearchEnabled, #available(iOS 26.0, *) { state.isRoomListSearchEnabled = false } @@ -113,21 +113,21 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .weakAssign(to: \.state.selectedRoomID, on: self) .store(in: &cancellables) - userSettings.showAllRoomListActivityPublisher + userSettings.app.showAllRoomListActivityPublisher .removeDuplicates() .sink { [weak self] _ in self?.updateRooms() } .store(in: &cancellables) - userSettings.seenInvitesPublisher + userSettings.app.seenInvitesPublisher .removeDuplicates() .sink { [weak self] _ in self?.updateRooms() } .store(in: &cancellables) - userSettings.hasSeenNewSoundBannerPublisher + userSettings.app.hasSeenNewSoundBannerPublisher .sink { [weak self] hasSeenNewSoundBanner in self?.state.shouldShowNewSoundBanner = !hasSeenNewSoundBanner } @@ -217,13 +217,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol case .skipRecoveryKeyConfirmation: state.securityBannerMode = .dismissed case .dismissNewSoundBanner: - userSettings.hasSeenNewSoundBanner = true + userSettings.app.hasSeenNewSoundBanner = true case .screenAppeared: // Use a task otherwise the presentation isn't animated. Task { presentMultiAccountAnnouncementIfNeeded() } case .multiAccountAnnouncementAppeared: MXLog.info("The multi-account announcement has been seen.") - userSettings.hasSeenMultiAccountAnnouncement = true + userSettings.app.hasSeenMultiAccountAnnouncement = true case .dismissMultiAccountAnnouncement, .addAccount: // There is no add account flow to start yet. state.bindings.isPresentingMultiAccountAnnouncement = false case .updateVisibleItemRange(let range): @@ -275,7 +275,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol case .success: analyticsService.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle) - if case .failure(let error) = await roomProxy.markAsRead(receiptType: userSettings.sharePresence ? .read : .readPrivate) { + if case .failure(let error) = await roomProxy.markAsRead(receiptType: userSettings.app.sharePresence ? .read : .readPrivate) { MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)") } case .failure(let error): @@ -389,11 +389,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } var rooms = [HomeScreenRoom]() - let seenInvites = userSettings.seenInvites + let seenInvites = userSettings.app.seenInvites for summary in roomSummaryProvider.roomListPublisher.value { let room = HomeScreenRoom(summary: summary, - showAllActivity: userSettings.showAllRoomListActivity, + showAllActivity: userSettings.app.showAllRoomListActivity, seenInvites: seenInvites) rooms.append(room) } @@ -488,8 +488,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private func presentMultiAccountAnnouncementIfNeeded() { // An unverified session gets the identity confirmation cover first, the room list appears again once it's dismissed. - guard userSettings.multiAccountEnabled, - !userSettings.hasSeenMultiAccountAnnouncement, + guard userSettings.app.multiAccountEnabled, + !userSettings.app.hasSeenMultiAccountAnnouncement, userSession.sessionSecurityStatePublisher.value.verificationState == .verified, !state.bindings.isPresentingMultiAccountAnnouncement else { return @@ -545,7 +545,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol analyticsService.trackJoinedRoom(isDM: roomProxy.info.isDirect, isSpace: roomProxy.info.isSpace, activeMemberCount: UInt(roomProxy.info.activeMembersCount)) - userSettings.seenInvites.remove(roomProxy.id) + userSettings.app.seenInvites.remove(roomProxy.id) } private func showDeclineInviteConfirmationAlert(roomID: String) async { @@ -595,7 +595,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol switch result { case .success: await notificationManager.removeDeliveredMessageNotifications(for: roomID) // Normally handled by the room flow, but that's never presented in this case. - userSettings.seenInvites.remove(roomID) + userSettings.app.seenInvites.remove(roomID) case .failure: displayError() } diff --git a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterModels.swift b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterModels.swift index 86083af89..0692cee95 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFilterModels.swift @@ -99,11 +99,11 @@ struct RoomListFiltersState { var availableFilters: [RoomListFilter] { var availableFilters = OrderedSet(RoomListFilter.availableFilters) - if !userSettings.lowPriorityFilterEnabled { + if !userSettings.app.lowPriorityFilterEnabled { availableFilters.remove(.lowPriority) } - if !userSettings.mentionsFilterEnabled { + if !userSettings.app.mentionsFilterEnabled { availableFilters.remove(.mentions) } diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index 34940d65e..3a2d27615 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -52,7 +52,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo .sink { mode in switch mode { case .invited: - userSettings.seenInvites.insert(roomID) + userSettings.app.seenInvites.insert(roomID) default: break } @@ -350,7 +350,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo guard !hasSentJoinAction else { return } let roomID = state.roomID - userSettings.seenInvites.remove(roomID) + userSettings.app.seenInvites.remove(roomID) guard state.roomDetails?.isSpace == true else { hasSentJoinAction = true @@ -464,7 +464,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo return false } - userSettings.seenInvites.remove(roomID) + userSettings.app.seenInvites.remove(roomID) actionsSubject.send(.dismiss) return true diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift index c8d512456..3e97eb9ad 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift @@ -173,7 +173,7 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati } .store(in: &cancellables) - userSettings.liveLocationSharingSessionsByRoomIDPublisher + userSettings.app.liveLocationSharingSessionsByRoomIDPublisher .map { [roomID = roomProxy.id] sessions in sessions[roomID] != nil } .removeDuplicates() .sink { [weak self] isSharingLiveLocationOnThisDevice in @@ -261,14 +261,14 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati } private func showLiveLocationFlow() { - if userSettings.liveLocationDisclaimerDisplayed { + if userSettings.app.liveLocationDisclaimerDisplayed { showLiveLocationDurationPicker() } else { state.bindings.alertInfo = .init(alertID: .liveLocationDisclaimer, primaryButton: .init(title: L10n.actionDecline, role: .cancel, action: nil), secondaryButton: .init(title: L10n.actionAccept) { [weak self] in guard let self else { return } - userSettings.liveLocationDisclaimerDisplayed = true + userSettings.app.liveLocationDisclaimerDisplayed = true // Delay so SwiftUI finishes dismissing the current alert // before presenting the next one. DispatchQueue.main.async { @@ -435,7 +435,7 @@ extension LocationSharingScreenViewModel { let userSettings = UserSettings.mock() return LocationSharingScreenViewModel(interactionMode: interactionMode, - mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, + mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value, roomProxy: roomProxy, timelineController: TimelineControllerMock(.init()), liveLocationManager: LiveLocationManagerMock(), diff --git a/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift index 3a2689611..cb1e18131 100644 --- a/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift @@ -102,6 +102,6 @@ struct AnalyticsPromptScreen_Previews: PreviewProvider, TestablePreview { } static func makeViewModel(showTerms: Bool = true) -> AnalyticsPromptScreenViewModel { - AnalyticsPromptScreenViewModel(termsURL: showTerms ? UserSettings.mock().analyticsTermsURL : nil) + AnalyticsPromptScreenViewModel(termsURL: showTerms ? AppSettings.volatile().analyticsTermsURL : nil) } } diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift index e2035a5cc..54ed6165d 100644 --- a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/IdentityConfirmationScreenViewModel.swift @@ -24,7 +24,7 @@ class IdentityConfirmationScreenViewModel: IdentityConfirmationScreenViewModelTy self.userSession = userSession self.userIndicatorController = userIndicatorController - super.init(initialViewState: IdentityConfirmationScreenViewState(learnMoreURL: userSettings.deviceVerificationURL)) + super.init(initialViewState: IdentityConfirmationScreenViewState(learnMoreURL: userSettings.app.deviceVerificationURL)) Task { [weak self] in for await state in userSession.sessionSecurityStatePublisher.values { diff --git a/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift index 3765e05f5..185e7c8b4 100644 --- a/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/SessionVerificationScreenViewModel.swift @@ -34,7 +34,7 @@ class SessionVerificationScreenViewModel: SessionVerificationViewModelType, Sess stateMachine = SessionVerificationScreenStateMachine(state: verificationState) super.init(initialViewState: .init(flow: flow, - learnMoreURL: userSettings.encryptionURL, + learnMoreURL: userSettings.app.encryptionURL, verificationState: verificationState), mediaProvider: mediaProvider) diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift index fff7e7294..a655ac688 100644 --- a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenViewModel.swift @@ -77,7 +77,7 @@ class PinnedEventsTimelineScreenViewModel: PinnedEventsTimelineScreenViewModelTy private func viewInRoomTimeline(eventID: String) async { switch await roomProxy.loadOrFetchEventDetails(for: eventID) { case .success(let event): - let threadRootEventID: String? = if userSettings.threadsEnabled { + let threadRootEventID: String? = if userSettings.app.threadsEnabled { event.threadRootEventId() } else { nil diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift index f4b980f75..6c01f8c9e 100644 --- a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift @@ -107,7 +107,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { let timelineController = TimelineControllerMock(.init(timelineKind: .pinned, timelineItems: timelineItems)) let userSettings = UserSettings.mock() - userSettings.messageMultiSelectEnabled = isSelecting + userSettings.app.messageMultiSelectEnabled = isSelecting let userSession = UserSessionMock(.init(userSettings: userSettings)) let timelineViewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Preview room", pinnedEventIDs: Set(eventIDs))), diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index 7bcb08e06..e5cc5a961 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -68,7 +68,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr topicSummary: topic?.unattributedStringByReplacingNewlinesWithSpaces(), joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount, notificationSettingsState: .loading, - isNativeCallingEnabled: userSettings.nativeCallEnabled, + isNativeCallingEnabled: userSettings.app.nativeCallEnabled, bindings: .init()) super.init(initialViewState: appHooks.roomDetailsScreenHook.update(viewState), mediaProvider: userSession.mediaProvider) diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift index 0f30aeef2..dbc0959da 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift @@ -80,7 +80,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool super.init(initialViewState: ComposerToolbarViewState(wysiwygViewModel: wysiwygViewModel, isRoomEncrypted: roomProxy.infoPublisher.value.isEncrypted, - isLocationSharingEnabled: userSettings.mapTilerConfiguration.publisher.value.isEnabled, + isLocationSharingEnabled: userSettings.app.mapTilerConfiguration.publisher.value.isEnabled, bindings: .init()), mediaProvider: mediaProvider) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index f6285419e..d9a8ed2fe 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -122,13 +122,13 @@ final class RoomScreenCoordinator: CoordinatorProtocol { case .displayReportContent(let itemID, let senderID): actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID)) case .displayCameraPicker: - actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)), + actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .camera, selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)), caption: composerViewModel.context.plainComposerText)) case .displayMediaPicker: - actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)), + actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .photoLibrary, selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)), caption: composerViewModel.context.plainComposerText)) case .displayDocumentPicker: - actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple(galleryEnabled: userSettings.galleryEnabled)), + actionsSubject.send(.presentMediaUploadPicker(mode: .init(source: .documents(), selectionType: .multiple(galleryEnabled: userSettings.app.galleryEnabled)), caption: composerViewModel.context.plainComposerText)) case .displayMediaPreview(let mediaPreviewViewModel): roomViewModel.displayMediaPreview(mediaPreviewViewModel) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 134cff3ac..36e7ade67 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -68,7 +68,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol let viewState = RoomScreenViewState(roomTitle: roomProxy.infoPublisher.value.displayNameOrID, roomAvatar: roomProxy.infoPublisher.value.avatar, - isNativeCallingEnabled: userSettings.nativeCallEnabled, + isNativeCallingEnabled: userSettings.app.nativeCallEnabled, hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall, isDM: roomProxy.infoPublisher.value.isDM, hasSuccessor: roomProxy.infoPublisher.value.successor != nil, @@ -127,7 +127,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol Task { // When navigating away from the room, we need to mark the room as both read // and fully read for Synapse to clear this room from the app's badge count. - _ = await roomProxy.markAsRead(receiptType: userSettings.sharePresence ? .read : .readPrivate) + _ = await roomProxy.markAsRead(receiptType: userSettings.app.sharePresence ? .read : .readPrivate) _ = await roomProxy.markAsRead(receiptType: .fullyRead) } // Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier. @@ -166,11 +166,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol // MARK: - Private private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher) { - userSettings.threadsEnabledPublisher + userSettings.app.threadsEnabledPublisher .weakAssign(to: \.state.roomThreadListEnabled, on: self) .store(in: &cancellables) - userSettings.liveLocationSharingSessionsByRoomIDPublisher + userSettings.app.liveLocationSharingSessionsByRoomIDPublisher .receive(on: DispatchQueue.main) .sink { [weak self] sessionsByRoomID in guard let self else { return } @@ -264,10 +264,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol if let member = identityVerificationViolations.values.first { state.footerDetails = .verificationViolation(member: member, - learnMoreURL: userSettings.identityPinningViolationDetailsURL) + learnMoreURL: userSettings.app.identityPinningViolationDetailsURL) } else if let member = identityPinningViolations.values.first { state.footerDetails = .pinViolation(member: member, - learnMoreURL: userSettings.identityPinningViolationDetailsURL) + learnMoreURL: userSettings.app.identityPinningViolationDetailsURL) } else { state.footerDetails = nil } @@ -428,7 +428,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol Task { switch await roomProxy.loadOrFetchEventDetails(for: eventID) { case .success(let event): - if userSettings.threadsEnabled, + if userSettings.app.threadsEnabled, let threadRootEventID = event.threadRootEventId() { actionsSubject.send(.focusEvent(eventID: threadRootEventID)) actionsSubject.send(.displayThread(threadRootEventID: threadRootEventID, focussedEventID: eventID)) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 4bc67e182..a98357e6c 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -396,7 +396,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview { let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock) let userSettings = UserSettings.mock() - userSettings.messageMultiSelectEnabled = isSelecting + userSettings.app.messageMultiSelectEnabled = isSelecting let userSession = UserSessionMock(.init(userSettings: userSettings)) let timelineController = TimelineControllerMock(.init()) diff --git a/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift b/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift index 0d03b14f0..a361176a8 100644 --- a/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift +++ b/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift @@ -73,7 +73,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro .store(in: &cancellables) // The room list is empty on a cold start, so the room breadcrumbs are re-resolved as it loads. - userSettings.searchBreadcrumbsPublisher + userSettings.account.searchBreadcrumbsPublisher .combineLatest(clientProxy.staticRoomSummaryProvider.roomListPublisher) .receive(on: DispatchQueue.main) .sink { [weak self] storedBreadcrumbs, _ in @@ -122,7 +122,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro } updateRooms(with: roomSummaryProvider.roomListPublisher.value) - state.breadcrumbs = makeBreadcrumbs(from: userSettings.searchBreadcrumbs) + state.breadcrumbs = makeBreadcrumbs(from: userSettings.account.searchBreadcrumbs) } isolated deinit { @@ -154,7 +154,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro case .removeBreadcrumb(let breadcrumb): removeBreadcrumb(breadcrumb) case .clearBreadcrumbs: - userSettings.searchBreadcrumbs = [] + userSettings.account.searchBreadcrumbs = [] case .reachedTop: if state.bindings.searchMode == .rooms { updateVisibleRange(edge: .top) @@ -223,11 +223,11 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro /// Moves the breadcrumb to the top of the history, adding it if needed. private func recordBreadcrumb(_ breadcrumb: SearchBreadcrumb) { - var breadcrumbs = userSettings.searchBreadcrumbs + var breadcrumbs = userSettings.account.searchBreadcrumbs breadcrumbs.removeAll { $0 == breadcrumb } breadcrumbs.insert(breadcrumb, at: 0) - userSettings.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount)) + userSettings.account.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount)) } private func removeBreadcrumb(_ breadcrumb: SearchScreenBreadcrumb) { @@ -235,7 +235,7 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro case .query(let query): .query(query) case .room(let room): .room(roomID: room.id) } - userSettings.searchBreadcrumbs.removeAll { $0 == storedBreadcrumb } + userSettings.account.searchBreadcrumbs.removeAll { $0 == storedBreadcrumb } } private func setActiveTabLoading(_ isLoading: Bool) { diff --git a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift index 610a77c07..414b089d1 100644 --- a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift +++ b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift @@ -709,7 +709,7 @@ struct SearchScreen_Previews: PreviewProvider, TestablePreview { private static func makeUserSettings(breadcrumbs: [SearchBreadcrumb]) -> UserSettings { let userSettings = UserSettings.mock() - userSettings.searchBreadcrumbs = breadcrumbs + userSettings.account.searchBreadcrumbs = breadcrumbs return userSettings } diff --git a/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift b/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift index 9723d5cf5..1ab154219 100644 --- a/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SecureBackup/SecureBackupScreen/SecureBackupScreenCoordinator.swift @@ -33,7 +33,7 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol { init(parameters: SecureBackupScreenCoordinatorParameters) { viewModel = SecureBackupScreenViewModel(secureBackupController: parameters.clientProxy.secureBackupController, userIndicatorController: parameters.userIndicatorController, - chatBackupDetailsURL: parameters.userSettings.chatBackupDetailsURL) + chatBackupDetailsURL: parameters.userSettings.app.chatBackupDetailsURL) } func start() { diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift index 6af6cf906..72eabd104 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift @@ -37,8 +37,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted, historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility, isSpace: roomProxy.infoPublisher.value.isSpace, - isKnockingEnabled: userSettings.knockingEnabled, - historySharingDetailsURL: userSettings.historySharingDetailsURL) + isKnockingEnabled: userSettings.app.knockingEnabled, + historySharingDetailsURL: userSettings.app.historySharingDetailsURL) super.init(initialViewState: appHooks.securityAndPrivacyScreenHook.update(viewState, homeserver: clientProxy.homeserver)) @@ -154,7 +154,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, .weakAssign(to: \.state.isSpace, on: self) .store(in: &cancellables) - userSettings.knockingEnabledPublisher + userSettings.app.knockingEnabledPublisher .weakAssign(to: \.state.isKnockingEnabled, on: self) .store(in: &cancellables) } @@ -163,7 +163,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, state.canEditAddress = powerLevels.canOwnUser(sendStateEvent: .roomCanonicalAlias) state.canEditJoinRule = powerLevels.canOwnUser(sendStateEvent: .roomJoinRules) state.canEditHistoryVisibility = powerLevels.canOwnUser(sendStateEvent: .roomHistoryVisibility) - state.canEnableEncryption = powerLevels.canOwnUser(sendStateEvent: .roomEncryption) && !userSettings.forceDisableE2EE.publisher.value + state.canEnableEncryption = powerLevels.canOwnUser(sendStateEvent: .roomEncryption) && !userSettings.app.forceDisableE2EE.publisher.value } private func setupRoomDirectoryVisibility() { diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift index 8d665c1c9..5bc46139c 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift @@ -292,7 +292,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { static let askToJoinViewModel = { let userSettings = UserSettings.mock() - userSettings.knockingEnabled = true + userSettings.app.knockingEnabled = true return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, canonicalAlias: "#room:matrix.org", @@ -307,7 +307,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { static let singleAskToJoinSpaceMembersViewModel = { let userSettings = UserSettings.mock() - userSettings.knockingEnabled = true + userSettings.app.knockingEnabled = true let space = [SpaceServiceRoom].mockSingleRoom[0] @@ -325,7 +325,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { static let multipleAskToJoinSpacesMembersViewModel = { let userSettings = UserSettings.mock() - userSettings.knockingEnabled = true + userSettings.app.knockingEnabled = true let spaces = [SpaceServiceRoom].mockJoinedSpaces diff --git a/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift b/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift index d872835ae..925aecd52 100644 --- a/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift +++ b/ElementX/Sources/Screens/Settings/AccountSettings/OAuthAccountSettingsPresenter.swift @@ -31,7 +31,7 @@ class OAuthAccountSettingsPresenter: NSObject { appHooks: AppHooks, continuation: Continuation? = nil) { self.accountURL = accountURL - redirectURL = userSettings.oAuthRedirectURL + redirectURL = userSettings.app.oAuthRedirectURL self.presentationAnchor = presentationAnchor self.appMediator = appMediator self.appHooks = appHooks diff --git a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift index bf25f6d94..f5afb5210 100644 --- a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/AnalyticsSettingsScreenViewModel.swift @@ -17,13 +17,13 @@ class AnalyticsSettingsScreenViewModel: AnalyticsSettingsScreenViewModelType, An init(userSettings: UserSettings, analytics: AnalyticsServiceProtocol) { self.analytics = analytics - let strings = AnalyticsSettingsScreenStrings(termsURL: userSettings.analyticsTermsURL) + let strings = AnalyticsSettingsScreenStrings(termsURL: userSettings.app.analyticsTermsURL) let bindings = AnalyticsSettingsScreenViewStateBindings(enableAnalytics: analytics.isEnabled) let state = AnalyticsSettingsScreenViewState(strings: strings, bindings: bindings) super.init(initialViewState: state) - userSettings.analyticsConsentStatePublisher + userSettings.app.analyticsConsentStatePublisher .map { $0 == .optedIn } .weakAssign(to: \.state.bindings.enableAnalytics, on: self) .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/Settings/LegalInformationScreen/LegalInformationScreenViewModel.swift b/ElementX/Sources/Screens/Settings/LegalInformationScreen/LegalInformationScreenViewModel.swift index c959e65f8..2d384c5b7 100644 --- a/ElementX/Sources/Screens/Settings/LegalInformationScreen/LegalInformationScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/LegalInformationScreen/LegalInformationScreenViewModel.swift @@ -13,8 +13,8 @@ typealias LegalInformationScreenViewModelType = StateStoreViewModelV2 $1.count } + userSettings.app.frequentlyUsedSystemEmojis = frequentlyUsed.sorted { $0.count > $1.count } } // MARK: - Private diff --git a/ElementX/Sources/Services/Location/LiveLocationManager.swift b/ElementX/Sources/Services/Location/LiveLocationManager.swift index cd9c8eae0..51afa415f 100644 --- a/ElementX/Sources/Services/Location/LiveLocationManager.swift +++ b/ElementX/Sources/Services/Location/LiveLocationManager.swift @@ -64,7 +64,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana // https://developer.apple.com/documentation/corelocation/cllocationmanager/pauseslocationupdatesautomatically self.locationManager.pausesLocationUpdatesAutomatically = false - setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.liveLocationMinimumDistanceUpdate) + setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.app.liveLocationMinimumDistanceUpdate) setupSubscriptions() } @@ -72,8 +72,8 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana @discardableResult func requestAlwaysAuthorizationIfPossible() -> Bool { - guard !userSettings.hasRequestedLocationAlwaysLocationAuthorization else { return false } - userSettings.hasRequestedLocationAlwaysLocationAuthorization = true + guard !userSettings.app.hasRequestedLocationAlwaysLocationAuthorization else { return false } + userSettings.app.hasRequestedLocationAlwaysLocationAuthorization = true locationManager.requestAlwaysAuthorization() return true } @@ -81,7 +81,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana func startLiveLocation(roomID: String, duration: Duration) async -> Result { // Stop any existing session for this room first var didAlreadyStopLocalSession = false - if userSettings.liveLocationSharingSessionsByRoomID[roomID] != nil + if userSettings.app.liveLocationSharingSessionsByRoomID[roomID] != nil || startingLiveLocationSharingSessionsByRoomID[roomID] != nil { await stopLiveLocation(roomID: roomID) didAlreadyStopLocalSession = true @@ -114,7 +114,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana var roomProxy: JoinedRoomProxyProtocol? let cachedRoomProxy = activeRoomProxies[roomID] startingLiveLocationSharingSessionsByRoomID.removeValue(forKey: roomID) - userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: roomID) + userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: roomID) if let cachedRoomProxy { roomProxy = cachedRoomProxy @@ -137,7 +137,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana // If the system resets authorization to notDetermined (e.g. after app reinstall or // settings reset), clear the flag so we can request again. if manager.authorizationStatus == .notDetermined { - userSettings.hasRequestedLocationAlwaysLocationAuthorization = false + userSettings.app.hasRequestedLocationAlwaysLocationAuthorization = false } // If authorization was revoked, stop all active sessions. @@ -146,7 +146,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } // Accuracy authorization may have changed, reapply new accuracy settings. - setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.liveLocationMinimumDistanceUpdate) + setupMinimumDistanceUpdatesAndAccuracy(minimumDistance: userSettings.app.liveLocationMinimumDistanceUpdate) authorizationStatusSubject.send(manager.authorizationStatus) } @@ -183,7 +183,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } .store(in: &cancellables) - userSettings.liveLocationSharingSessionsByRoomIDPublisher + userSettings.app.liveLocationSharingSessionsByRoomIDPublisher .removeDuplicates() .sink { [weak self] sessions in guard let self else { return } @@ -197,7 +197,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } .store(in: &cancellables) - userSettings.liveLocationMinimumDistanceUpdatePublisher + userSettings.app.liveLocationMinimumDistanceUpdatePublisher .removeDuplicates() .debounce(for: .seconds(1), scheduler: DispatchQueue.main) .sink { [weak self] newValue in @@ -210,7 +210,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana // A new beaconInfo has been received in a room with existing active session. // This is either a new start or a new stop from a different device, so we // should remove the session from the current local one. - userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID) + userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID) // Instead if we receive a new isLiveUpdate guard update.isLive else { return } @@ -224,7 +224,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana // This means the server has echoed the start of the session and we can safely promote it // to a started session and start sending live locations. startingLiveLocationSharingSessionsByRoomID.removeValue(forKey: update.roomID) - userSettings.liveLocationSharingSessionsByRoomID[update.roomID] = session + userSettings.app.liveLocationSharingSessionsByRoomID[update.roomID] = session if isUpdatingLocation, let lastLocation { locationUpdateSubject.send(lastLocation) @@ -281,7 +281,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana /// discarding any intermediate updates while always keeping the last one. private func processLocationUpdateIfNeeded() { guard !isProcessingLocationUpdate, let location = latestPendingLocation else { return } - guard !userSettings.liveLocationSharingSessionsByRoomID.isEmpty else { return } + guard !userSettings.app.liveLocationSharingSessionsByRoomID.isEmpty else { return } latestPendingLocation = nil isProcessingLocationUpdate = true @@ -306,7 +306,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } private func sendLocationToActiveRooms(_ coordinate: CLLocationCoordinate2D) async { - let sessions = userSettings.liveLocationSharingSessionsByRoomID + let sessions = userSettings.app.liveLocationSharingSessionsByRoomID let geoURI = GeoURI(coordinate: coordinate, uncertainty: nil) for (roomID, session) in sessions { @@ -351,7 +351,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana } private func stopAllSessions() { - let roomIDs = Array(Set(userSettings.liveLocationSharingSessionsByRoomID.keys) + let roomIDs = Array(Set(userSettings.app.liveLocationSharingSessionsByRoomID.keys) .union(startingLiveLocationSharingSessionsByRoomID.keys)) Task { [weak self] in guard let self else { return } diff --git a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift index 2eb4a5c3f..682690855 100644 --- a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift +++ b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift @@ -171,7 +171,7 @@ struct MediaUploadingPreprocessor { try stripLocationFromImage(at: url, type: type) var mimeType = mimeType - if userSettings.optimizeMediaUploads, !type.conforms(to: .gif) { + if userSettings.app.optimizeMediaUploads, !type.conforms(to: .gif) { let outputType = type.conforms(to: .png) ? UTType.png : .jpeg mimeType = outputType.preferredMIMEType ?? "application/octet-stream" try resizeImage(at: url, maxPixelSize: Constants.optimizedMaxPixelSize, destination: url, type: outputType) @@ -439,7 +439,7 @@ struct MediaUploadingPreprocessor { /// - Returns: the URL for the resulting video and its media info as a `VideoProcessingResult` private func convertVideoToMP4(_ url: URL, targetFileSize: UInt) async throws(MediaUploadingPreprocessorError) -> VideoProcessingInfo { let asset = AVURLAsset(url: url) - let presetName = userSettings.optimizeMediaUploads ? AVAssetExportPreset1280x720 : AVAssetExportPreset1920x1080 + let presetName = userSettings.app.optimizeMediaUploads ? AVAssetExportPreset1280x720 : AVAssetExportPreset1920x1080 guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else { throw .failedConvertingVideo diff --git a/ElementX/Sources/Services/Presence/PresenceService.swift b/ElementX/Sources/Services/Presence/PresenceService.swift index d06ecd0c3..5edf39731 100644 --- a/ElementX/Sources/Services/Presence/PresenceService.swift +++ b/ElementX/Sources/Services/Presence/PresenceService.swift @@ -41,7 +41,7 @@ final class PresenceService { } private var desiredPresence: ClientProxyPresence { - switch (isForegroundActive, userSettings.sharePresence) { + switch (isForegroundActive, userSettings.app.sharePresence) { case (true, true): .online case (true, false): @@ -68,7 +68,7 @@ final class PresenceService { } private func observeSharePresence() { - userSettings.sharePresencePublisher + userSettings.app.sharePresencePublisher .removeDuplicates() .sink { [weak self] _ in self?.reportCurrentState() diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index 13c18ffdf..ccbd72b22 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -83,7 +83,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo())) let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .openRoom) - timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live(hideThreadedEvents: userSettings.threadsEnabled), + timeline = try await TimelineProxy(timeline: room.timelineWithConfiguration(configuration: .init(focus: .live(hideThreadedEvents: userSettings.app.threadsEnabled), filter: .eventFilter(filter: Self.excludedEventsFilter), internalIdPrefix: nil, dateDividerMode: .daily, @@ -150,7 +150,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { let openRoomSpan = analyticsService.signpost.addSpan(.timelineLoad, toTransaction: .notificationToMessage) let sdkTimeline = try await room.timelineWithConfiguration(configuration: .init(focus: .event(eventId: eventID, numContextEvents: numberOfEvents, - threadMode: .automatic(hideThreadedEvents: userSettings.threadsEnabled)), + threadMode: .automatic(hideThreadedEvents: userSettings.app.threadsEnabled)), filter: .all, internalIdPrefix: UUID().uuidString, dateDividerMode: .daily, diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 4240bdadf..dc93ddc3f 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -165,7 +165,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { rustFilters.append(.identifiers(identifiers: Array(roomIDs))) - if !filters.contains(.lowPriority), userSettings.lowPriorityFilterEnabled { + if !filters.contains(.lowPriority), userSettings.app.lowPriorityFilterEnabled { rustFilters.append(.nonLowPriority) } @@ -173,7 +173,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { case let .all(filters): var rustFilters = filters.map(\.rustFilter) + baseFilter - if !filters.contains(.lowPriority), userSettings.lowPriorityFilterEnabled { + if !filters.contains(.lowPriority), userSettings.app.lowPriorityFilterEnabled { rustFilters.append(.nonLowPriority) } @@ -184,7 +184,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { // MARK: - Private private func nameFilter(for query: String) -> [RoomListEntriesDynamicFilterKind] { - userSettings.fuzzyRoomListSearchEnabled ? [.fuzzyMatchRoomName(pattern: query)] : [.normalizedMatchRoomName(pattern: query)] + userSettings.app.fuzzyRoomListSearchEnabled ? [.fuzzyMatchRoomName(pattern: query)] : [.normalizedMatchRoomName(pattern: query)] } private func setupVisibleRangeObservers() { diff --git a/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift index 5c6c95272..7197a39b4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/TimelineController.swift @@ -128,7 +128,7 @@ class TimelineController: TimelineControllerProtocol { } func sendReadReceipt(for itemID: TimelineItemIdentifier) async { - let receiptType: MatrixRustSDK.ReceiptType = userSettings.sharePresence ? .read : .readPrivate + let receiptType: MatrixRustSDK.ReceiptType = userSettings.app.sharePresence ? .read : .readPrivate guard let eventID = itemID.eventID else { return diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index cc0e956c7..081fca962 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -603,9 +603,9 @@ class MockScreen: Identifiable { case .userSessionScreen, .userSessionScreenReply, .userSessionSpacesFlow: let userID = "@mock:client.com" let userSettings = appSettings.userSettings(for: userID) - userSettings.hasRunIdentityConfirmationOnboarding = true - userSettings.hasRunNotificationPermissionsOnboarding = true - userSettings.analyticsConsentState = .optedOut + userSettings.account.hasRunIdentityConfirmationOnboarding = true + userSettings.app.hasRunNotificationPermissionsOnboarding = true + userSettings.app.analyticsConsentState = .optedOut let roomSummaries: [RoomSummary] = if id == .userSessionSpacesFlow { [[RoomSummary].mockSpaceInvites[0]] + .mockRooms @@ -828,9 +828,9 @@ class MockScreen: Identifiable { case .autoUpdatingTimeline: let userID = "@mock:client.com" let userSettings = appSettings.userSettings(for: userID) - userSettings.hasRunIdentityConfirmationOnboarding = true - userSettings.hasRunNotificationPermissionsOnboarding = true - userSettings.analyticsConsentState = .optedOut + userSettings.account.hasRunIdentityConfirmationOnboarding = true + userSettings.app.hasRunNotificationPermissionsOnboarding = true + userSettings.app.analyticsConsentState = .optedOut let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: false)) navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator) diff --git a/Enterprise b/Enterprise index 5628ab611..5792d2c63 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 5628ab611fe8119e0fe22f72900e408e5ef396f3 +Subproject commit 5792d2c63df5f3f9d2d465148db4360d8591c638 diff --git a/UnitTests/Sources/BugReportServiceTests.swift b/UnitTests/Sources/BugReportServiceTests.swift index 5aa06e695..2d9c30820 100644 --- a/UnitTests/Sources/BugReportServiceTests.swift +++ b/UnitTests/Sources/BugReportServiceTests.swift @@ -18,7 +18,7 @@ final class BugReportServiceTests { init() throws { userSettings = UserSettings.mock() - userSettings.bugReportRageshakeURL.reset() + userSettings.app.bugReportRageshakeURL.reset() let bugReportServiceMock = BugReportServiceMock() bugReportServiceMock.lastCrashEventIDSubject = .init(nil) @@ -27,7 +27,7 @@ final class BugReportServiceTests { } deinit { - userSettings.bugReportRageshakeURL.reset() + userSettings.app.bugReportRageshakeURL.reset() } @Test @@ -104,22 +104,22 @@ final class BugReportServiceTests { @Test @MainActor func configurations() async throws { - guard case let .url(initialURL) = userSettings.bugReportRageshakeURL.publisher.value else { + guard case let .url(initialURL) = userSettings.app.bugReportRageshakeURL.publisher.value else { Issue.record("Unexpected initial configuration.") return } - let service = BugReportService(rageshakeURLPublisher: userSettings.bugReportRageshakeURL.publisher, + let service = BugReportService(rageshakeURLPublisher: userSettings.app.bugReportRageshakeURL.publisher, applicationID: "mock_app_id", sdkGitSHA: "1234", session: .mock, appHooks: AppHooks()) #expect(service.isEnabled) - userSettings.bugReportRageshakeURL.applyRemoteValue(.disabled) + userSettings.app.bugReportRageshakeURL.applyRemoteValue(.disabled) #expect(!service.isEnabled) - userSettings.bugReportRageshakeURL.applyRemoteValue(.url("https://bugs.server.net/submit")) + userSettings.app.bugReportRageshakeURL.applyRemoteValue(.url("https://bugs.server.net/submit")) #expect(service.isEnabled) let bugReport = BugReport(userID: "@mock:client.com", @@ -136,7 +136,7 @@ final class BugReportServiceTests { #expect(customConfigurationResponse.reportURL == "https://bugs.server.net/123") - userSettings.bugReportRageshakeURL.reset() + userSettings.app.bugReportRageshakeURL.reset() #expect(service.isEnabled) let defaultConfigurationResponse = try await service.submitBugReport(bugReport, progressListener: progressSubject).get() diff --git a/UnitTests/Sources/CreateRoomViewModelTests.swift b/UnitTests/Sources/CreateRoomViewModelTests.swift index 7021d5439..99faf6aec 100644 --- a/UnitTests/Sources/CreateRoomViewModelTests.swift +++ b/UnitTests/Sources/CreateRoomViewModelTests.swift @@ -85,7 +85,7 @@ final class CreateRoomScreenViewModelTests { spaceServiceConfiguration: .init(spaceRoomLists: ["1": .init()]))) clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) } userSession = UserSessionMock(.init(clientProxy: clientProxy)) - userSettings.knockingEnabled = true + userSettings.app.knockingEnabled = true let viewModel = CreateRoomScreenViewModel(isSpace: true, spaceSelectionMode: .none, shouldShowCancelButton: false, @@ -336,7 +336,7 @@ final class CreateRoomScreenViewModelTests { clientProxy.spaceService = spaceService clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID))) } userSession = UserSessionMock(.init(clientProxy: clientProxy)) - userSettings.knockingEnabled = true + userSettings.app.knockingEnabled = true let viewModel = CreateRoomScreenViewModel(isSpace: isSpace, spaceSelectionMode: spacesSelectionMode, shouldShowCancelButton: false, diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 260c0809c..63b65d162 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -275,7 +275,7 @@ final class HomeScreenViewModelTests { room.roomID == invites[0].roomID && room.badges.isDotShown == false } } - userSettings.seenInvites = Set(invites.compactMap(\.roomID)) + userSettings.app.seenInvites = Set(invites.compactMap(\.roomID)) try await deferred.fulfill() invites = context.viewState.rooms.invites @@ -289,14 +289,14 @@ final class HomeScreenViewModelTests { setupViewModel(invites: .rooms) let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID) - userSettings.seenInvites = Set(invitedRoomIDs) + userSettings.app.seenInvites = Set(invitedRoomIDs) #expect(invitedRoomIDs.count == 2) let deferred = deferFulfillment(viewModel.actions) { $0 == .presentRoom(roomIdentifier: invitedRoomIDs[0]) } context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0])) try await deferred.fulfill() - #expect(userSettings.seenInvites == [invitedRoomIDs[1]]) + #expect(userSettings.app.seenInvites == [invitedRoomIDs[1]]) #expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.") } @@ -305,7 +305,7 @@ final class HomeScreenViewModelTests { setupViewModel(invites: .spaces) let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID) - userSettings.seenInvites = Set(invitedRoomIDs) + userSettings.app.seenInvites = Set(invitedRoomIDs) #expect(invitedRoomIDs.count == 2) let deferred = deferFulfillment(viewModel.actions) { @@ -314,7 +314,7 @@ final class HomeScreenViewModelTests { context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0])) try await deferred.fulfill() - #expect(userSettings.seenInvites == [invitedRoomIDs[1]]) + #expect(userSettings.app.seenInvites == [invitedRoomIDs[1]]) #expect(!notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.") } @@ -322,7 +322,7 @@ final class HomeScreenViewModelTests { func declineInvite() async throws { setupViewModel(invites: .rooms) let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID) - userSettings.seenInvites = Set(invitedRoomIDs) + userSettings.app.seenInvites = Set(invitedRoomIDs) #expect(invitedRoomIDs.count == 2) let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } @@ -345,7 +345,7 @@ final class HomeScreenViewModelTests { try await Task.sleep(for: .milliseconds(100)) #expect(rejectCalled) - #expect(userSettings.seenInvites == [invitedRoomIDs[1]]) + #expect(userSettings.app.seenInvites == [invitedRoomIDs[1]]) #expect(notificationManager.removeDeliveredMessageNotificationsForCalled) #expect(notificationManager.removeDeliveredMessageNotificationsForReceivedInvocations == [invitedRoomIDs[0]]) } @@ -354,7 +354,7 @@ final class HomeScreenViewModelTests { func declineAndBlockInvite() async throws { setupViewModel(invites: .rooms) let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID) - userSettings.seenInvites = Set(invitedRoomIDs) + userSettings.app.seenInvites = Set(invitedRoomIDs) #expect(invitedRoomIDs.count == 2) let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } @@ -368,7 +368,7 @@ final class HomeScreenViewModelTests { @Test func newSoundBanner() { - userSettings.hasSeenNewSoundBanner = false + userSettings.app.hasSeenNewSoundBanner = false setupViewModel() #expect(context.viewState.shouldShowBanner) @@ -377,26 +377,26 @@ final class HomeScreenViewModelTests { context.send(viewAction: .dismissNewSoundBanner) #expect(!context.viewState.shouldShowBanner) #expect(!context.viewState.shouldShowNewSoundBanner) - #expect(userSettings.hasSeenNewSoundBanner) + #expect(userSettings.app.hasSeenNewSoundBanner) } @Test func multiAccountAnnouncement() async throws { - userSettings.multiAccountEnabled = true + userSettings.app.multiAccountEnabled = true setupViewModel() // Arming it after the view model exists (Developer options) doesn't present it on its own. - userSettings.hasSeenMultiAccountAnnouncement = false + userSettings.app.hasSeenMultiAccountAnnouncement = false let deferredFailure = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement } try await deferredFailure.fulfill() let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement } context.send(viewAction: .screenAppeared) try await deferred.fulfill() - #expect(!userSettings.hasSeenMultiAccountAnnouncement) + #expect(!userSettings.app.hasSeenMultiAccountAnnouncement) context.send(viewAction: .multiAccountAnnouncementAppeared) - #expect(userSettings.hasSeenMultiAccountAnnouncement) + #expect(userSettings.app.hasSeenMultiAccountAnnouncement) #expect(context.viewState.bindings.isPresentingMultiAccountAnnouncement) // A swipe down dismisses it without any of the announcement's actions. @@ -408,8 +408,8 @@ final class HomeScreenViewModelTests { @Test(arguments: [HomeScreenViewAction.dismissMultiAccountAnnouncement, .addAccount]) func multiAccountAnnouncementActionsDismissIt(_ action: HomeScreenViewAction) async throws { - userSettings.multiAccountEnabled = true - userSettings.hasSeenMultiAccountAnnouncement = false + userSettings.app.multiAccountEnabled = true + userSettings.app.hasSeenMultiAccountAnnouncement = false setupViewModel() let deferred = deferFulfillment(context.$viewState) { $0.bindings.isPresentingMultiAccountAnnouncement } @@ -422,8 +422,8 @@ final class HomeScreenViewModelTests { @Test(arguments: MultiAccountAnnouncementHiddenCase.allCases) func multiAccountAnnouncementHidden(_ hiddenCase: MultiAccountAnnouncementHiddenCase) async throws { - userSettings.multiAccountEnabled = hiddenCase != .flagOff - userSettings.hasSeenMultiAccountAnnouncement = hiddenCase == .alreadySeen + userSettings.app.multiAccountEnabled = hiddenCase != .flagOff + userSettings.app.hasSeenMultiAccountAnnouncement = hiddenCase == .alreadySeen let verificationState: SessionVerificationState = switch hiddenCase { case .unverified: .unverified case .unknownVerificationState: .unknown @@ -435,13 +435,13 @@ final class HomeScreenViewModelTests { let deferred = deferFailure(context.$viewState, timeout: .seconds(1)) { $0.bindings.isPresentingMultiAccountAnnouncement } context.send(viewAction: .screenAppeared) try await deferred.fulfill() - #expect(userSettings.hasSeenMultiAccountAnnouncement == (hiddenCase == .alreadySeen)) + #expect(userSettings.app.hasSeenMultiAccountAnnouncement == (hiddenCase == .alreadySeen)) } @Test func multiAccountAnnouncementPresentsOnTheNextAppearanceOnceVerified() async throws { - userSettings.multiAccountEnabled = true - userSettings.hasSeenMultiAccountAnnouncement = false + userSettings.app.multiAccountEnabled = true + userSettings.app.hasSeenMultiAccountAnnouncement = false let securityStateSubject = CurrentValueSubject(.init(verificationState: .unverified, recoveryState: .enabled)) setupViewModel(securityStatePublisher: securityStateSubject.asCurrentValuePublisher()) diff --git a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift index b402bb92d..9375f5185 100644 --- a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift +++ b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift @@ -45,12 +45,12 @@ final class JoinRoomScreenViewModelTests { @Test func interaction() async throws { - #expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") + #expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") setupViewModel() try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .joinable }.fulfill() - #expect(userSettings.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.") + #expect(userSettings.app.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.") let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) } context.send(viewAction: .join) @@ -59,28 +59,28 @@ final class JoinRoomScreenViewModelTests { @Test func acceptInviteInteraction() async throws { - #expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") + #expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") setupViewModel(mode: .invited) try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill() - #expect(userSettings.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.") + #expect(userSettings.app.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.") let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .joined(.roomID("1")) } context.send(viewAction: .acceptInvite) try await deferred.fulfill() - #expect(userSettings.seenInvites.isEmpty, "The after accepting an invite the invite should be forgotten in case the user leaves.") + #expect(userSettings.app.seenInvites.isEmpty, "The after accepting an invite the invite should be forgotten in case the user leaves.") } @Test func declineInviteInteraction() async throws { - #expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") + #expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") setupViewModel(mode: .invited) try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .invited(isDM: false) }.fulfill() - #expect(userSettings.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.") + #expect(userSettings.app.seenInvites == ["1"], "The invited room's ID should be registered as a seen invite.") context.send(viewAction: .declineInvite) @@ -89,17 +89,17 @@ final class JoinRoomScreenViewModelTests { context.alertInfo?.secondaryButton?.action?() try await deferred.fulfill() - #expect(userSettings.seenInvites.isEmpty, "The after declining an invite the invite should be forgotten in case another invite is received.") + #expect(userSettings.app.seenInvites.isEmpty, "The after declining an invite the invite should be forgotten in case another invite is received.") } @Test func knockedState() async throws { - #expect(userSettings.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") + #expect(userSettings.app.seenInvites.isEmpty, "There shouldn't be any seen invites before running the tests.") setupViewModel(mode: .knocked) try await deferFulfillment(viewModel.context.$viewState) { $0.mode == .knocked }.fulfill() - #expect(userSettings.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.") + #expect(userSettings.app.seenInvites.isEmpty, "Only an invited room should register the room ID as a seen invite.") } @Test diff --git a/UnitTests/Sources/LiveLocationManagerTests.swift b/UnitTests/Sources/LiveLocationManagerTests.swift index ec623263b..c930c521a 100644 --- a/UnitTests/Sources/LiveLocationManagerTests.swift +++ b/UnitTests/Sources/LiveLocationManagerTests.swift @@ -41,11 +41,11 @@ final class LiveLocationManagerTests { try result.get() #expect(callOrder == ["stop", "start"]) - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org") - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil) #expect(locationManagerMock.startUpdatingLocationCalled) } @@ -54,7 +54,7 @@ final class LiveLocationManagerTests { setUp() let roomProxy = makeRoomProxy(roomID: "!room:matrix.org") clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) } - userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$old_event:matrix.org", expirationDate: Date().addingTimeInterval(300)) + userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$old_event:matrix.org", expirationDate: Date().addingTimeInterval(300)) var callOrder: [String] = [] roomProxy.stopLiveLocationShareClosure = { @@ -73,7 +73,7 @@ final class LiveLocationManagerTests { try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org") - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil) } @Test @@ -82,12 +82,12 @@ final class LiveLocationManagerTests { let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org") clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) } - userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) + userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) _ = await manager.startLiveLocation(roomID: "!room1:matrix.org", duration: .seconds(300)) #expect(roomProxy.stopLiveLocationShareCalled) - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil) } @Test @@ -98,7 +98,7 @@ final class LiveLocationManagerTests { let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)) #expect(throws: LiveLocationManagerError.roomNotJoined) { try result.get() } - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) } @Test @@ -111,7 +111,7 @@ final class LiveLocationManagerTests { let result = await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)) #expect(throws: LiveLocationManagerError.startFailed) { try result.get() } - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) } @Test @@ -127,7 +127,7 @@ final class LiveLocationManagerTests { try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org") - let storedSession = try #require(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"]) + let storedSession = try #require(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"]) let expectedMinTimeout = beforeStart.addingTimeInterval(TimeInterval(duration.seconds)) let expectedMaxTimeout = afterStart.addingTimeInterval(TimeInterval(duration.seconds)) @@ -142,12 +142,12 @@ final class LiveLocationManagerTests { setUp() let roomProxy = makeRoomProxy(roomID: "!room:matrix.org") clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) } - userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) + userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) await manager.stopLiveLocation(roomID: "!room:matrix.org") #expect(roomProxy.stopLiveLocationShareCalled) - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) // Setting the timeout date above starts tracking; removing it stops tracking. #expect(locationManagerMock.startUpdatingLocationCalled) #expect(locationManagerMock.stopUpdatingLocationCalled) @@ -169,13 +169,13 @@ final class LiveLocationManagerTests { setUp() let roomProxy = makeRoomProxy(roomID: "!room1:matrix.org") clientProxy.roomForIdentifierClosure = { _ in .joined(roomProxy) } - userSettings.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) - userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) + userSettings.app.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) + userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] = LiveLocationSession(eventID: "$event:matrix.org", expirationDate: Date().addingTimeInterval(300)) await manager.stopLiveLocation(roomID: "!room1:matrix.org") - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] == nil) - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room1:matrix.org"] == nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room2:matrix.org"] != nil) } // MARK: - Beacon info updates @@ -188,13 +188,13 @@ final class LiveLocationManagerTests { try await manager.startLiveLocation(roomID: "!room:matrix.org", duration: .seconds(300)).get() try await simulateBeaconEcho(roomID: "!room:matrix.org", eventID: "$event:matrix.org") - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] != nil) - let deferred = deferFulfillment(userSettings.liveLocationSharingSessionsByRoomIDPublisher) { $0["!room:matrix.org"] == nil } + let deferred = deferFulfillment(userSettings.app.liveLocationSharingSessionsByRoomIDPublisher) { $0["!room:matrix.org"] == nil } beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: "!room:matrix.org", eventID: "$external_event:matrix.org", isLive: true)) try await deferred.fulfill() - #expect(userSettings.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) + #expect(userSettings.app.liveLocationSharingSessionsByRoomID["!room:matrix.org"] == nil) } // MARK: - Reduced accuracy @@ -237,7 +237,7 @@ final class LiveLocationManagerTests { } private func simulateBeaconEcho(roomID: String, eventID: String) async throws { - let deferred = deferFulfillment(userSettings.liveLocationSharingSessionsByRoomIDPublisher) { $0[roomID] != nil } + let deferred = deferFulfillment(userSettings.app.liveLocationSharingSessionsByRoomIDPublisher) { $0[roomID] != nil } beaconInfoSubject.send(LiveLocationOwnInfoUpdate(roomID: roomID, eventID: eventID, isLive: true)) try await deferred.fulfill() } diff --git a/UnitTests/Sources/LocationSharingScreenViewModelTests.swift b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift index c041de97f..43241129c 100644 --- a/UnitTests/Sources/LocationSharingScreenViewModelTests.swift +++ b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift @@ -420,7 +420,7 @@ struct LocationSharingScreenViewModelTests { let userSettings = UserSettings.mock() viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil), - mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, + mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value, roomProxy: roomProxyMock, timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))), liveLocationManager: LiveLocationManagerMock(.init()), @@ -546,7 +546,7 @@ struct LocationSharingScreenViewModelTests { #expect(context.showsUserLocationMode == .hide) // Another device takes over: this device's session is removed and a new own share arrives. - userSettings.liveLocationSharingSessionsByRoomID.removeValue(forKey: Self.roomID) + userSettings.app.liveLocationSharingSessionsByRoomID.removeValue(forKey: Self.roomID) let newOwnShare = makeLiveLocationShare(userID: RoomMemberProxyMock.mockMe.userID, latitude: 48.8, longitude: 2.3) let deferred = deferFulfillment(context.observe(\.showsUserLocationMode)) { $0 == .show } @@ -633,7 +633,7 @@ struct LocationSharingScreenViewModelTests { userSettings = UserSettings.mock() timelineProxy = TimelineProxyMock(.init()) viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true), - mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, + mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value, roomProxy: JoinedRoomProxyMock(.init(members: members)), timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)), liveLocationManager: liveLocationManagerMock, @@ -651,7 +651,7 @@ struct LocationSharingScreenViewModelTests { isSharingLiveLocationFromThisDevice: Bool = false) { userSettings = UserSettings.mock() if isSharingLiveLocationFromThisDevice { - userSettings.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture) + userSettings.app.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture) } let liveLocationServiceMock = RoomLiveLocationServiceMock() @@ -661,7 +661,7 @@ struct LocationSharingScreenViewModelTests { roomProxyMock.makeLiveLocationServiceReturnValue = liveLocationServiceMock viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: sender, initialLiveLocationShare: initialShare), - mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, + mapURLBuilder: userSettings.app.mapTilerConfiguration.publisher.value, roomProxy: roomProxyMock, timelineController: TimelineControllerMock(.init(timelineProxy: TimelineProxyMock(.init()))), liveLocationManager: LiveLocationManagerMock(.init()), diff --git a/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift b/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift index a8635e0e5..bf273cb3a 100644 --- a/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift +++ b/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift @@ -29,7 +29,7 @@ final class MediaUploadPreviewScreenViewModelTests { init() { userSettings = UserSettings.mock() - userSettings.optimizeMediaUploads = false + userSettings.app.optimizeMediaUploads = false userIndicatorController = UserIndicatorControllerMock() } diff --git a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift index 063feb76a..c4ee5dde2 100644 --- a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift +++ b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift @@ -18,7 +18,7 @@ final class MediaUploadingPreprocessorTests { init() { userSettings = UserSettings.mock() - userSettings.optimizeMediaUploads = false + userSettings.app.optimizeMediaUploads = false mediaUploadingPreprocessor = MediaUploadingPreprocessor(userSettings: userSettings) } @@ -80,7 +80,7 @@ final class MediaUploadingPreprocessorTests { func landscapeMovVideoOptimizedProcessing() async throws { let url = try #require(Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil), "Failed retrieving test asset") - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else { @@ -139,7 +139,7 @@ final class MediaUploadingPreprocessorTests { func portraitMp4VideoOptimizedProcessing() async throws { let url = try #require(Bundle(for: Self.self).url(forResource: "portrait_test_video.mp4", withExtension: nil), "Failed retrieving test asset") - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else { @@ -184,7 +184,7 @@ final class MediaUploadingPreprocessorTests { #expect(imageInfo.thumbnailInfo?.height == 344) // Repeat with optimised media setting - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { @@ -228,7 +228,7 @@ final class MediaUploadingPreprocessorTests { #expect(imageInfo.thumbnailInfo?.height == 800) // Repeat with optimised media setting - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { @@ -274,7 +274,7 @@ final class MediaUploadingPreprocessorTests { #expect(imageInfo.thumbnailInfo?.height == 240) // Repeat with optimised media setting - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else { @@ -325,7 +325,7 @@ final class MediaUploadingPreprocessorTests { #expect(imageInfo.thumbnailInfo?.height == 800) // Repeat with optimised media setting - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { @@ -377,7 +377,7 @@ final class MediaUploadingPreprocessorTests { #expect(imageInfo.thumbnailInfo?.height == 472) // Repeat with optimised media setting - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else { @@ -419,7 +419,7 @@ final class MediaUploadingPreprocessorTests { #expect(imageInfo.thumbnailInfo?.height == 800) // Repeat with optimised media setting - userSettings.optimizeMediaUploads = true + userSettings.app.optimizeMediaUploads = true guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize), case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else { @@ -453,7 +453,7 @@ final class MediaUploadingPreprocessorTests { fatalError() } - if userSettings.optimizeMediaUploads { + if userSettings.app.optimizeMediaUploads { // Check that new image has been scaled within the requirements for an optimised image #expect(convertedImage.size.width <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize) #expect(convertedImage.size.height <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize) diff --git a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift index 14cf1f311..6780df938 100644 --- a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift @@ -42,16 +42,16 @@ struct NotificationSettingsScreenViewModelTests { @Test func enableNotifications() { - userSettings.enableNotifications = false + userSettings.app.enableNotifications = false context.send(viewAction: .changedEnableNotifications) - #expect(userSettings.enableNotifications) + #expect(userSettings.app.enableNotifications) } @Test func disableNotifications() { - userSettings.enableNotifications = true + userSettings.app.enableNotifications = true context.send(viewAction: .changedEnableNotifications) - #expect(!userSettings.enableNotifications) + #expect(!userSettings.app.enableNotifications) } @Test @@ -411,7 +411,7 @@ struct NotificationSettingsScreenViewModelTests { // Given a tone and a manager that persists the selection to app settings let tone = NotificationTone.createBundledSound(label: "Test", filename: "test.caf") toneManager.setSelectedToneClosure = { [userSettings] selectedTone in - userSettings.selectedNotificationTone = selectedTone + userSettings.app.selectedNotificationTone = selectedTone return NotificationToneManager.libraryLocation } @@ -419,21 +419,21 @@ struct NotificationSettingsScreenViewModelTests { context.send(viewAction: .selectAlertTone(tone)) // Then it is reflected in app settings - #expect(userSettings.selectedNotificationTone == tone) + #expect(userSettings.app.selectedNotificationTone == tone) } @Test func deletingActiveToneResetsSelection() { // Given the active tone is a custom tone let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf") - userSettings.selectedNotificationTone = customTone + userSettings.app.selectedNotificationTone = customTone toneManager.customTonesReturnValue = [customTone] // When that tone is deleted context.send(viewAction: .deleteCustomAlertTones([customTone])) // Then the selection is cleared, falling back to the default - #expect(userSettings.selectedNotificationTone == nil) + #expect(userSettings.app.selectedNotificationTone == nil) } @Test @@ -441,14 +441,14 @@ struct NotificationSettingsScreenViewModelTests { // Given a custom tone is active and a different custom tone also exists let activeTone = NotificationTone.createCustomUserSound(filename: "active.caf") let otherTone = NotificationTone.createCustomUserSound(filename: "other.caf") - userSettings.selectedNotificationTone = activeTone + userSettings.app.selectedNotificationTone = activeTone toneManager.customTonesReturnValue = [activeTone, otherTone] // When the non-active tone is deleted context.send(viewAction: .deleteCustomAlertTones([otherTone])) // Then the active selection is unchanged - #expect(userSettings.selectedNotificationTone == activeTone) + #expect(userSettings.app.selectedNotificationTone == activeTone) } @Test @@ -476,7 +476,7 @@ struct NotificationSettingsScreenViewModelTests { func deleteCustomToneFailurePreservesSelection() { // Given the active tone is a custom tone and deletion will fail let customTone = NotificationTone.createCustomUserSound(filename: "custom.caf") - userSettings.selectedNotificationTone = customTone + userSettings.app.selectedNotificationTone = customTone toneManager.deleteCustomToneThrowableError = NSError(domain: "test", code: 1) toneManager.customTonesReturnValue = [customTone] @@ -484,7 +484,7 @@ struct NotificationSettingsScreenViewModelTests { context.send(viewAction: .deleteCustomAlertTones([customTone])) // Then the selection is unchanged - #expect(userSettings.selectedNotificationTone == customTone) + #expect(userSettings.app.selectedNotificationTone == customTone) } @Test diff --git a/UnitTests/Sources/PresenceServiceTests.swift b/UnitTests/Sources/PresenceServiceTests.swift index b4996a36a..de1d1a1ee 100644 --- a/UnitTests/Sources/PresenceServiceTests.swift +++ b/UnitTests/Sources/PresenceServiceTests.swift @@ -39,7 +39,7 @@ struct PresenceServiceTests { @Test func serviceSeededActiveWhileSharingReportsOnline() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let deferred = deferFulfillment(sendsSubject) { $0.presence == .online } let service = makeService(initialApplicationState: .active) @@ -53,7 +53,7 @@ struct PresenceServiceTests { @Test func serviceSeededBackgroundedWhileSharingReportsUnavailable() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let deferred = deferFulfillment(sendsSubject) { $0.presence == .unavailable } let service = makeService(initialApplicationState: .background) @@ -66,7 +66,7 @@ struct PresenceServiceTests { @Test func serviceSeededActiveWhileNotSharingReportsOffline() async throws { - userSettings.sharePresence = false + userSettings.app.sharePresence = false let deferred = deferFulfillment(sendsSubject) { $0.presence == .offline } let service = makeService(initialApplicationState: .active) @@ -79,7 +79,7 @@ struct PresenceServiceTests { @Test func becomingActiveNotificationWhileSharingReportsOnlineImmediately() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let service = makeService(initialApplicationState: .inactive) let deferred = deferFulfillment(sendsSubject) { $0.presence == .online } @@ -93,7 +93,7 @@ struct PresenceServiceTests { @Test func resigningActiveNotificationWhileSharingReportsUnavailableImmediately() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online } let service = makeService(initialApplicationState: .active) _ = try await initialDeferred.fulfill() @@ -109,19 +109,19 @@ struct PresenceServiceTests { @Test func togglingSharingWhileActiveDrivesOnlineAndOffline() async throws { - userSettings.sharePresence = false + userSettings.app.sharePresence = false let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline } let service = makeService(initialApplicationState: .active) _ = try await initialDeferred.fulfill() let onlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .online } - userSettings.sharePresence = true + userSettings.app.sharePresence = true let online = try await onlineDeferred.fulfill() #expect(online.presence == .online) #expect(online.sendImmediately) let offlineDeferred = deferFulfillment(sendsSubject) { $0.presence == .offline } - userSettings.sharePresence = false + userSettings.app.sharePresence = false let offline = try await offlineDeferred.fulfill() #expect(offline.presence == .offline) #expect(offline.sendImmediately) @@ -131,7 +131,7 @@ struct PresenceServiceTests { @Test func repeatingTheSameStateSendsOnlyOnce() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let initialDeferred = deferFulfillment(sendsSubject) { $0.presence == .online } let service = makeService(initialApplicationState: .active) @@ -148,7 +148,7 @@ struct PresenceServiceTests { @Test func failedImmediateSendIsNotRetriedUntilTheDesiredPresenceChanges() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true clientProxy.configurePresenceSendImmediatelyClosure = { [sendsSubject] presence, sendImmediately in sendsSubject.send((presence, sendImmediately)) return presence == .online ? .failure(.forbiddenAccess) : .success(()) @@ -173,7 +173,7 @@ struct PresenceServiceTests { @Test func newerTransitionWinsWhenAnEarlierSendIsInFlight() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let (onlineStartedStream, onlineStartedContinuation) = AsyncStream.makeStream() let (resumeOnlineSendStream, resumeOnlineSendContinuation) = AsyncStream.makeStream() @@ -206,7 +206,7 @@ struct PresenceServiceTests { @Test func deinitCancelsPendingSend() async throws { - userSettings.sharePresence = true + userSettings.app.sharePresence = true let (onlineStartedStream, onlineStartedContinuation) = AsyncStream.makeStream(of: Void.self) let (onlineFinishedStream, onlineFinishedContinuation) = AsyncStream.makeStream(of: Void.self) diff --git a/UnitTests/Sources/RoomFlowCoordinatorTests.swift b/UnitTests/Sources/RoomFlowCoordinatorTests.swift index 8111dd1f5..9b530216b 100644 --- a/UnitTests/Sources/RoomFlowCoordinatorTests.swift +++ b/UnitTests/Sources/RoomFlowCoordinatorTests.swift @@ -230,7 +230,7 @@ final class RoomFlowCoordinatorTests { @Test func threadedEventRoutes() async throws { - userSettings.threadsEnabled = true + userSettings.app.threadsEnabled = true setupRoomFlowCoordinator() // Navigate directly to the threaded event diff --git a/UnitTests/Sources/RoomListFiltersStateTests.swift b/UnitTests/Sources/RoomListFiltersStateTests.swift index 28f6a5570..4de1ce539 100644 --- a/UnitTests/Sources/RoomListFiltersStateTests.swift +++ b/UnitTests/Sources/RoomListFiltersStateTests.swift @@ -148,12 +148,12 @@ final class RoomListFiltersStateTests { // MARK: - Helpers private func enableLowPriorityFeature() { - userSettings.lowPriorityFilterEnabled = true + userSettings.app.lowPriorityFilterEnabled = true state = RoomListFiltersState(userSettings: userSettings) } private func enableMentionsFeature() { - userSettings.mentionsFilterEnabled = true + userSettings.app.mentionsFilterEnabled = true state = RoomListFiltersState(userSettings: userSettings) } } diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index 78d00084b..3620b8c07 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -154,7 +154,7 @@ final class RoomScreenViewModelTests { @Test func pinnedEventsBannerThreadedSelection() async throws { let userSettings = UserSettings.mock() - userSettings.threadsEnabled = true + userSettings.app.threadsEnabled = true let userSession = UserSessionMock(.init(userSettings: userSettings)) let roomProxyMock = JoinedRoomProxyMock(.init()) diff --git a/UnitTests/Sources/RoomSummaryProviderTests.swift b/UnitTests/Sources/RoomSummaryProviderTests.swift index 5cb5b3dc2..c76e37852 100644 --- a/UnitTests/Sources/RoomSummaryProviderTests.swift +++ b/UnitTests/Sources/RoomSummaryProviderTests.swift @@ -123,7 +123,7 @@ final class RoomSummaryProviderTests { private func setup(isLowPriorityFilterEnabled: Bool = false, includesOnlyJoinedRooms: Bool = false, setsRoomList: Bool = true) { userSettings = UserSettings.mock() - userSettings.lowPriorityFilterEnabled = isLowPriorityFilterEnabled + userSettings.app.lowPriorityFilterEnabled = isLowPriorityFilterEnabled let stateEventStringBuilder = RoomStateEventStringBuilder(userID: "@me:matrix.org") let attributedStringBuilder = AttributedStringBuilder(mentionBuilder: MentionBuilder()) diff --git a/UnitTests/Sources/SearchScreenViewModelTests.swift b/UnitTests/Sources/SearchScreenViewModelTests.swift index 6623c6765..3364acc97 100644 --- a/UnitTests/Sources/SearchScreenViewModelTests.swift +++ b/UnitTests/Sources/SearchScreenViewModelTests.swift @@ -124,44 +124,44 @@ struct SearchScreenViewModelTests { context.searchQuery = "Second" context.send(viewAction: .selectRoom(roomID: "2")) - #expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")]) + #expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2")]) context.send(viewAction: .submitQuery) - #expect(userSettings.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")]) + #expect(userSettings.account.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")]) // Selecting the same room again from the empty state only bumps it back to the top. context.searchQuery = "" context.send(viewAction: .selectRoom(roomID: "2")) - #expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")]) + #expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")]) // Submitting an empty query records nothing. context.send(viewAction: .submitQuery) - #expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")]) + #expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")]) } @Test func removingBreadcrumbs() { - userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] + userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] context.send(viewAction: .removeBreadcrumb(.query("Second"))) - #expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")]) + #expect(userSettings.account.searchBreadcrumbs == [.room(roomID: "2")]) context.send(viewAction: .removeBreadcrumb(.room(SearchScreenRoom(.mock(id: "2", name: "Second"))))) - #expect(userSettings.searchBreadcrumbs.isEmpty) + #expect(userSettings.account.searchBreadcrumbs.isEmpty) } @Test func clearingBreadcrumbs() { - userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] + userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] context.send(viewAction: .clearBreadcrumbs) - #expect(userSettings.searchBreadcrumbs.isEmpty) + #expect(userSettings.account.searchBreadcrumbs.isEmpty) } @Test @@ -169,7 +169,7 @@ struct SearchScreenViewModelTests { staticRoomListSubject.send([.mock(id: "2", name: "Second")]) let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 2 } - userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] + userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] try await deferred.fulfill() #expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second", "room-2"]) @@ -180,7 +180,7 @@ struct SearchScreenViewModelTests { staticRoomListSubject.send([.mock(id: "2", name: "Second"), .mock(id: "left", name: "Left", isJoined: false)]) let deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { !$0.isEmpty } - userSettings.searchBreadcrumbs = [.room(roomID: "left"), .room(roomID: "2")] + userSettings.account.searchBreadcrumbs = [.room(roomID: "left"), .room(roomID: "2")] try await deferred.fulfill() #expect(context.viewState.breadcrumbs.map(\.id) == ["room-2"]) @@ -190,7 +190,7 @@ struct SearchScreenViewModelTests { @Test func roomBreadcrumbsAreDisplayedAfterTheRoomListLoads() async throws { var deferred = deferFulfillment(context.observe(\.viewState.breadcrumbs)) { $0.count == 1 } - userSettings.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] + userSettings.account.searchBreadcrumbs = [.query("Second"), .room(roomID: "2")] try await deferred.fulfill() #expect(context.viewState.breadcrumbs.map(\.id) == ["query-Second"]) diff --git a/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift b/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift index a1e149223..2a3a16509 100644 --- a/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift +++ b/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift @@ -458,8 +458,8 @@ final class SecurityAndPrivacyScreenViewModelTests { joinRule: ElementX.JoinRule, forceDisableE2EE: Bool = false) { let userSettings = UserSettings.mock() - userSettings.knockingEnabled = true - userSettings.forceDisableE2EE.applyRemoteValue(forceDisableE2EE) + userSettings.app.knockingEnabled = true + userSettings.app.forceDisableE2EE.applyRemoteValue(forceDisableE2EE) roomProxy = JoinedRoomProxyMock(.init(isEncrypted: false, canonicalAlias: "#room:matrix.org", members: .allMembersAsCreator, diff --git a/UnitTests/Sources/SettingsScreenViewModelTests.swift b/UnitTests/Sources/SettingsScreenViewModelTests.swift index 48ac0d011..5e6dbcde1 100644 --- a/UnitTests/Sources/SettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/SettingsScreenViewModelTests.swift @@ -181,11 +181,11 @@ struct SettingsScreenViewModelTests { #expect(!context.viewState.showAddAccountButton) var deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { $0 } - userSettings.multiAccountEnabled = true + userSettings.app.multiAccountEnabled = true try await deferred.fulfill() deferred = deferFulfillment(context.observe(\.viewState.showAddAccountButton)) { !$0 } - userSettings.multiAccountEnabled = false + userSettings.app.multiAccountEnabled = false try await deferred.fulfill() } diff --git a/UnitTests/Sources/TimelineViewModelTests.swift b/UnitTests/Sources/TimelineViewModelTests.swift index e3529ffb8..9756a00c3 100644 --- a/UnitTests/Sources/TimelineViewModelTests.swift +++ b/UnitTests/Sources/TimelineViewModelTests.swift @@ -900,14 +900,14 @@ final class TimelineViewModelTests { func disablingTheFlagClearsTheSelection() async throws { let items = [TextRoomTimelineItem(eventID: "$1")] let userSettings = UserSettings.mock() - userSettings.messageMultiSelectEnabled = true + userSettings.app.messageMultiSelectEnabled = true let viewModel = makeViewModel(timelineController: TimelineControllerMock(.init(timelineItems: items)), userSettings: userSettings) startSelection(items[0].id, in: viewModel) #expect(viewModel.state.messageSelection.isActive) let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.messageSelection.isEnabled && !$0.messageSelection.isActive } - userSettings.messageMultiSelectEnabled = false + userSettings.app.messageMultiSelectEnabled = false try await deferred.fulfill() } @@ -945,7 +945,7 @@ final class TimelineViewModelTests { private func makeSelectionViewModel(timelineController: TimelineControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol = UserIndicatorControllerMock()) -> TimelineViewModel { let userSettings = UserSettings.mock() - userSettings.messageMultiSelectEnabled = true + userSettings.app.messageMultiSelectEnabled = true return makeViewModel(timelineController: timelineController, userIndicatorController: userIndicatorController, userSettings: userSettings) diff --git a/UnitTests/Sources/UserSettingsTests.swift b/UnitTests/Sources/UserSettingsTests.swift index 3b2564ad6..7834ede00 100644 --- a/UnitTests/Sources/UserSettingsTests.swift +++ b/UnitTests/Sources/UserSettingsTests.swift @@ -24,31 +24,31 @@ struct UserSettingsTests { @Test func accountSettingsAreScopedToAccount() { // Given settings for two different accounts. - #expect(aliceSettings.searchBreadcrumbs.isEmpty) - #expect(bobSettings.searchBreadcrumbs.isEmpty) + #expect(aliceSettings.account.searchBreadcrumbs.isEmpty) + #expect(bobSettings.account.searchBreadcrumbs.isEmpty) // When updating an account setting for Alice. - aliceSettings.searchBreadcrumbs = [.query("Alice")] + aliceSettings.account.searchBreadcrumbs = [.query("Alice")] // Then only Alice should see the change. - #expect(aliceSettings.searchBreadcrumbs == [.query("Alice")]) - #expect(bobSettings.searchBreadcrumbs.isEmpty) + #expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")]) + #expect(bobSettings.account.searchBreadcrumbs.isEmpty) } @Test func appSettingsAreShared() { // Given settings for two different accounts. #expect(!appSettings.hasSignedInBefore) - #expect(!aliceSettings.hasSignedInBefore) - #expect(!bobSettings.hasSignedInBefore) + #expect(!aliceSettings.app.hasSignedInBefore) + #expect(!bobSettings.app.hasSignedInBefore) // When updating an app setting through Alice's settings. - aliceSettings.hasSignedInBefore = true + aliceSettings.app.hasSignedInBefore = true // Then the change should be seen by the app and every account. #expect(appSettings.hasSignedInBefore) - #expect(aliceSettings.hasSignedInBefore) - #expect(bobSettings.hasSignedInBefore) + #expect(aliceSettings.app.hasSignedInBefore) + #expect(bobSettings.app.hasSignedInBefore) } @Test @@ -59,18 +59,18 @@ struct UserSettingsTests { try store.set(JSONEncoder().encode([SearchBreadcrumb.query("Alice")]), forKey: "searchBreadcrumbs") #expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") != nil) #expect(store.object(forKey: "searchBreadcrumbs") != nil) - #expect(!aliceSettings.hasRunIdentityConfirmationOnboarding) - #expect(aliceSettings.searchBreadcrumbs.isEmpty) + #expect(!aliceSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(aliceSettings.account.searchBreadcrumbs.isEmpty) // When migrating those values into Alice's account settings. - aliceSettings.migrateAppSettingsValueToAccountSettings(\.hasRunIdentityConfirmationOnboardingKey) - aliceSettings.migrateAppSettingsValueToAccountSettings(\.searchBreadcrumbsKey) + aliceSettings.account.migrateAppSettingsValue(\.hasRunIdentityConfirmationOnboardingKey) + aliceSettings.account.migrateAppSettingsValue(\.searchBreadcrumbsKey) // Then the values should belong to Alice only. - #expect(aliceSettings.hasRunIdentityConfirmationOnboarding) - #expect(aliceSettings.searchBreadcrumbs == [.query("Alice")]) - #expect(!bobSettings.hasRunIdentityConfirmationOnboarding) - #expect(bobSettings.searchBreadcrumbs.isEmpty) + #expect(aliceSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")]) + #expect(!bobSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(bobSettings.account.searchBreadcrumbs.isEmpty) #expect(store.object(forKey: "hasRunIdentityConfirmationOnboarding") == nil) #expect(store.object(forKey: "searchBreadcrumbs") == nil) } @@ -79,24 +79,24 @@ struct UserSettingsTests { func resetSessionSpecificSettings() { // Given app settings and account settings for two different accounts. appSettings.hasSignedInBefore = true - aliceSettings.hasRunIdentityConfirmationOnboarding = true - aliceSettings.searchBreadcrumbs = [.query("Alice")] - bobSettings.hasRunIdentityConfirmationOnboarding = true - bobSettings.searchBreadcrumbs = [.query("Bob")] + aliceSettings.account.hasRunIdentityConfirmationOnboarding = true + aliceSettings.account.searchBreadcrumbs = [.query("Alice")] + bobSettings.account.hasRunIdentityConfirmationOnboarding = true + bobSettings.account.searchBreadcrumbs = [.query("Bob")] #expect(appSettings.hasSignedInBefore) - #expect(aliceSettings.hasRunIdentityConfirmationOnboarding) - #expect(aliceSettings.searchBreadcrumbs == [.query("Alice")]) - #expect(bobSettings.hasRunIdentityConfirmationOnboarding) - #expect(bobSettings.searchBreadcrumbs == [.query("Bob")]) + #expect(aliceSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(aliceSettings.account.searchBreadcrumbs == [.query("Alice")]) + #expect(bobSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(bobSettings.account.searchBreadcrumbs == [.query("Bob")]) // When resetting Alice's session specific settings. - aliceSettings.resetSessionSpecificSettings() + aliceSettings.account.reset() // Then only Alice's account settings should be reset. - #expect(!aliceSettings.hasRunIdentityConfirmationOnboarding) - #expect(aliceSettings.searchBreadcrumbs.isEmpty) - #expect(bobSettings.hasRunIdentityConfirmationOnboarding) - #expect(bobSettings.searchBreadcrumbs == [.query("Bob")]) + #expect(!aliceSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(aliceSettings.account.searchBreadcrumbs.isEmpty) + #expect(bobSettings.account.hasRunIdentityConfirmationOnboarding) + #expect(bobSettings.account.searchBreadcrumbs == [.query("Bob")]) #expect(appSettings.hasSignedInBefore) } }