From 93825c01ad7e1ab0fee6006453c36c577af846ea Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 6 Oct 2026 17:52:24 +0100 Subject: [PATCH] Initial implementation of AccountSettings. (#6275) * Make the UserPreference macro also generate an accessor for the key name. * Initial implementation of AccountSettings. - Add an AccountUserDefaults storage with a userID prefix on the key. - Migrate the 2 values reset by AppSettings.resetSessionSpecificSettings to AccountSettings. * Rename UserSettings.volatile() to UserSettings.mock() Given it now has a userID parameter with a default value.. --- .../Sources/Macros/UserPreference.swift | 12 ++- .../UserPreferenceMacro.swift | 12 +++ .../UserPreferenceMacroTests.swift | 18 +++- ElementX.xcodeproj/project.pbxproj | 84 ++++++++++----- .../Sources/Application/AppCoordinator.swift | 22 ++-- .../Settings/AccountSettings.swift | 42 ++++++++ .../Application/Settings/AppSettings.swift | 19 +--- .../Storage/AccountUserDefaults.swift | 66 ++++++++++++ .../{ => Storage}/TrackedUserDefaults.swift | 0 .../{ => Storage}/UserDefaultsProtocol.swift | 1 + .../{ => Storage}/VolatileUserDefaults.swift | 4 + .../Application/Settings/UserSettings.swift | 22 ++-- ElementX/Sources/Mocks/UserSessionMock.swift | 2 +- .../Other/SwiftUI/Views/VisualListItem.swift | 2 +- .../Screens/CallScreen/View/CallScreen.swift | 2 +- .../View/CreateRoomScreen.swift | 2 +- .../View/CreateRoomSpaceSelectionSheet.swift | 2 +- .../View/EmojiPickerScreen.swift | 4 +- .../RoomListFiltersEmptyStateView.swift | 4 +- .../View/Filters/RoomListFiltersView.swift | 6 +- .../Screens/HomeScreen/View/HomeScreen.swift | 2 +- .../View/HomeScreenEmptyStateView.swift | 2 +- .../View/HomeScreenInviteCell.swift | 2 +- .../View/HomeScreenKnockedCell.swift | 2 +- ...eScreenRecoveryKeyConfirmationBanner.swift | 2 +- .../JoinRoomScreen/View/JoinRoomScreen.swift | 2 +- .../LocationSharingScreenViewModel.swift | 2 +- .../View/MediaEventsTimelineScreen.swift | 2 +- .../View/MediaUploadPreviewScreen.swift | 2 +- .../View/AnalyticsPromptScreen.swift | 2 +- .../View/IdentityConfirmationScreen.swift | 2 +- .../View/SessionVerificationScreen.swift | 2 +- .../View/PinnedEventsTimelineScreen.swift | 6 +- .../View/RoomDetailsEditScreen.swift | 2 +- .../View/RoomDetailsScreen.swift | 6 +- .../ComposerToolbarViewModel.swift | 2 +- .../View/RoomAttachmentPicker.swift | 2 +- .../Screens/RoomScreen/View/RoomScreen.swift | 2 +- .../SearchScreen/View/SearchScreen.swift | 12 +-- .../View/SecurityAndPrivacyScreen.swift | 18 ++-- .../View/AnalyticsSettingsScreen.swift | 2 +- .../View/LegalInformationScreen.swift | 2 +- .../View/NotificationSettingsScreen.swift | 4 +- .../SettingsScreen/View/SettingsScreen.swift | 2 +- .../View/UserDetailsEditScreen.swift | 2 +- .../View/SpaceSettingsScreen.swift | 2 +- .../View/StartChatScreen.swift | 2 +- .../Screens/Timeline/TimelineViewModel.swift | 2 +- .../View/ItemMenu/TimelineItemMenu.swift | 2 +- .../ReadReceiptsSummaryView.swift | 2 +- .../Style/TimelineItemBubbledStylerView.swift | 6 +- .../TimelineReadReceiptsView.swift | 2 +- .../HighlightedTimelineItemModifier.swift | 2 +- .../Screens/Timeline/View/TimelineView.swift | 2 +- .../AuthenticationService.swift | 7 +- .../UserSession/UserSessionStore.swift | 4 +- .../UITests/UITestsAppCoordinator.swift | 55 +++++----- Enterprise | 2 +- .../Sources/AccountUserDefaultsTests.swift | 93 ++++++++++++++++ ...nalyticsSettingsScreenViewModelTests.swift | 2 +- UnitTests/Sources/BugReportServiceTests.swift | 2 +- .../ChatsTabFlowCoordinatorTests.swift | 2 +- .../ComposerToolbarViewModelTests.swift | 10 +- .../Sources/CreateRoomViewModelTests.swift | 2 +- .../EmojiPickerScreenViewModelTests.swift | 2 +- UnitTests/Sources/EmojiProviderTests.swift | 8 +- .../Sources/HomeScreenViewModelTests.swift | 2 +- ...tityConfirmationScreenViewModelTests.swift | 2 +- .../JoinRoomScreenViewModelTests.swift | 2 +- .../Sources/LiveLocationManagerTests.swift | 2 +- .../LocationSharingScreenViewModelTests.swift | 6 +- ...diaUploadPreviewScreenViewModelTests.swift | 2 +- .../MediaUploadingPreprocessorTests.swift | 2 +- ...ficationSettingsScreenViewModelTests.swift | 2 +- .../NotificationToneManagerTests.swift | 2 +- UnitTests/Sources/PillContextTests.swift | 22 ++-- UnitTests/Sources/PresenceServiceTests.swift | 2 +- .../RoomDetailsEditScreenViewModelTests.swift | 2 +- .../RoomDetailsScreenViewModelTests.swift | 44 ++++---- .../Sources/RoomFlowCoordinatorTests.swift | 2 +- .../Sources/RoomListFiltersStateTests.swift | 2 +- .../RoomMembersFlowCoordinatorTests.swift | 2 +- .../Sources/RoomScreenViewModelTests.swift | 2 +- .../Sources/RoomSummaryProviderTests.swift | 2 +- .../Sources/SearchScreenViewModelTests.swift | 2 +- ...curityAndPrivacyScreenViewModelTests.swift | 2 +- .../SessionVerificationViewModelTests.swift | 2 +- .../SettingsScreenViewModelTests.swift | 4 +- .../Sources/TimelineControllerTests.swift | 2 +- .../TimelineInteractionHandlerTests.swift | 2 +- .../TimelineItemMenuActionProviderTests.swift | 2 +- .../Sources/TimelineViewModelTests.swift | 22 ++-- .../UserDetailsEditScreenViewModelTests.swift | 2 +- .../UserSessionFlowCoordinatorTests.swift | 2 +- UnitTests/Sources/UserSettingsTests.swift | 102 ++++++++++++++++++ project.yml | 2 +- 96 files changed, 623 insertions(+), 248 deletions(-) create mode 100644 ElementX/Sources/Application/Settings/AccountSettings.swift create mode 100644 ElementX/Sources/Application/Settings/Storage/AccountUserDefaults.swift rename ElementX/Sources/Application/Settings/{ => Storage}/TrackedUserDefaults.swift (100%) rename ElementX/Sources/Application/Settings/{ => Storage}/UserDefaultsProtocol.swift (98%) rename ElementX/Sources/Application/Settings/{ => Storage}/VolatileUserDefaults.swift (93%) create mode 100644 UnitTests/Sources/AccountUserDefaultsTests.swift create mode 100644 UnitTests/Sources/UserSettingsTests.swift diff --git a/Components/BuildExtensions/Sources/Macros/UserPreference.swift b/Components/BuildExtensions/Sources/Macros/UserPreference.swift index 3e507f32a..53eada53e 100644 --- a/Components/BuildExtensions/Sources/Macros/UserPreference.swift +++ b/Components/BuildExtensions/Sources/Macros/UserPreference.swift @@ -16,7 +16,7 @@ /// persisted under a legacy key). /// /// A `reset()` method is also generated, which clears the stored value so it reverts to the -/// default. +/// default. Non-volatile preferences also get a `Key` property exposing the storage key. @attached(accessor) @attached(peer, names: arbitrary) public macro UserPreference(key: String? = nil, defaultValue: Value, volatile: Bool = false) = @@ -27,3 +27,13 @@ public macro UserPreference(key: String? = nil, defaultValue: Value, vola @attached(peer, names: arbitrary) public macro UserPreference(key: String? = nil, volatile: Bool = false) = #externalMacro(module: "MacrosImplementation", type: "UserPreferenceMacro") + +/// The storage key of a `@UserPreference`, distinct from `String` so that only a generated +/// `Key` property can be passed where a preference key is expected. +public struct UserPreferenceKey: Sendable { + public let rawValue: String + + public init(_ rawValue: String) { + self.rawValue = rawValue + } +} diff --git a/Components/BuildExtensions/Sources/MacrosImplementation/UserPreferenceMacro.swift b/Components/BuildExtensions/Sources/MacrosImplementation/UserPreferenceMacro.swift index d97cb8e96..881320439 100644 --- a/Components/BuildExtensions/Sources/MacrosImplementation/UserPreferenceMacro.swift +++ b/Components/BuildExtensions/Sources/MacrosImplementation/UserPreferenceMacro.swift @@ -45,6 +45,10 @@ enum UserPreferenceMacro { "_\(raw: name)VolatileValue" } + var keyPropertyName: TokenSyntax { + "\(raw: name)Key" + } + var resetMethodName: TokenSyntax { "reset\(raw: name.prefix(1).uppercased() + name.dropFirst())" } @@ -157,6 +161,14 @@ extension UserPreferenceMacro: PeerMacro { } """) + if !preference.isVolatile { + peers.append(""" + var \(preference.keyPropertyName): UserPreferenceKey { + .init(\(preference.key)) + } + """) + } + peers.append(resetMethod(for: preference)) return peers diff --git a/Components/BuildExtensions/Tests/MacrosTests/UserPreferenceMacroTests.swift b/Components/BuildExtensions/Tests/MacrosTests/UserPreferenceMacroTests.swift index cc81057e1..8707c8770 100644 --- a/Components/BuildExtensions/Tests/MacrosTests/UserPreferenceMacroTests.swift +++ b/Components/BuildExtensions/Tests/MacrosTests/UserPreferenceMacroTests.swift @@ -5,7 +5,7 @@ // Please see LICENSE files in the repository root for full details. // -import MacrosImplementation +@testable import MacrosImplementation import SwiftSyntaxMacrosTestSupport import XCTest @@ -35,6 +35,10 @@ final class UserPreferenceMacroTests: XCTestCase { _hasSeenNewSoundBannerSubject.prepend(hasSeenNewSoundBanner).eraseToAnyPublisher() } + var hasSeenNewSoundBannerKey: UserPreferenceKey { + .init("hasSeenNewSoundBanner") + } + func resetHasSeenNewSoundBanner() { store.removeObject(forKey: "hasSeenNewSoundBanner") } @@ -65,6 +69,10 @@ final class UserPreferenceMacroTests: XCTestCase { _liveLocationSharingSessionsByRoomIDSubject.prepend(liveLocationSharingSessionsByRoomID).eraseToAnyPublisher() } + var liveLocationSharingSessionsByRoomIDKey: UserPreferenceKey { + .init("liveLocationSharingTimeoutDatesByRoomID") + } + func resetLiveLocationSharingSessionsByRoomID() { store.removeObject(forKey: "liveLocationSharingTimeoutDatesByRoomID") } @@ -95,6 +103,10 @@ final class UserPreferenceMacroTests: XCTestCase { _pusherProfileTagSubject.prepend(pusherProfileTag).eraseToAnyPublisher() } + var pusherProfileTagKey: UserPreferenceKey { + .init("pusherProfileTag") + } + func resetPusherProfileTag() { store.removeObject(forKey: "pusherProfileTag") } @@ -125,6 +137,10 @@ final class UserPreferenceMacroTests: XCTestCase { _viewSourceEnabledSubject.prepend(viewSourceEnabled).eraseToAnyPublisher() } + var viewSourceEnabledKey: UserPreferenceKey { + .init("viewSourceEnabled") + } + func resetViewSourceEnabled() { store.removeObject(forKey: "viewSourceEnabled") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index f6a4261ae..cc356666f 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -87,6 +87,7 @@ 0AD8EF040A60D62F488C18B5 /* KnockRequestProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F957320D0EB7D7B4E30C79D /* KnockRequestProxyMock.swift */; }; 0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; }; 0B05A35FF5D1ED9E8A0B41A7 /* AuthenticationStartScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */; }; + 0B30DC7D9D445C405ADCB5FA /* VolatileUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3994D186AC9893BEE458ECF /* VolatileUserDefaults.swift */; }; 0B9436A91CFA658202400465 /* WaitingConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E21DC54FCC2A7F78532923F /* WaitingConfirmation.swift */; }; 0BDA19079FD6E17C5AC62E22 /* RoomDetailsEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */; }; 0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; }; @@ -113,6 +114,7 @@ 0F81DEE3E02A6549B20DF09A /* DeactivateAccountScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633924B26ACCD29C18BEF4E8 /* DeactivateAccountScreenViewModelProtocol.swift */; }; 0FA03F5A33C0857231B32B44 /* ReportRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1FDB0B87D925AE830E32621 /* ReportRoomScreenViewModel.swift */; }; 101C8BFCAE35F43F37A6D8ED /* NotificationToneManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18678D0A4177D8F0A255BC36 /* NotificationToneManagerMock.swift */; }; + 106E3126B0C49231381B5B80 /* AccountUserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96792E7EE6742172DA3A35E /* AccountUserDefaultsTests.swift */; }; 108D3C0707A90B0F848CDBB9 /* ResolveVerifiedUserSendFailureScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60011EF0086E49DBD78E16E5 /* ResolveVerifiedUserSendFailureScreenModels.swift */; }; 109AEB7D33C4497727AFB87F /* TimelineInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BA894BC09972DC45E497D37 /* TimelineInteractionHandler.swift */; }; 10D60D287025B71F4743A425 /* RoomDirectorySearchProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */; }; @@ -183,7 +185,6 @@ 1AE4532D92E6ED42BFE87064 /* JoinRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A566F8F2C27B99E1FB80C69B /* JoinRule.swift */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; 1AF4A82B4332CAD2DB3EB5DA /* TopBannerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78483F0143E185EDC6ECD741 /* TopBannerModifier.swift */; }; - 1B1404DD4BB2C5079B074210 /* UserDefaultsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCA4547BE58E17899E95CDE /* UserDefaultsProtocol.swift */; }; 1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */; }; 1B2F9F368619FFF8C63C87CC /* BugReportScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EECE8B331CD169790EF284F /* BugReportScreenViewModelTests.swift */; }; 1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */; }; @@ -261,13 +262,11 @@ 2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7310D8DFE01AF45F0689C3AA /* Publisher.swift */; }; 28426806399E3659BEBC5918 /* NotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6ED50FE104992419310EEB /* NotificationHandler.swift */; }; 288408E6151D7BD3EBAA073A /* RoomScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B343C5255FB408DDE853CFDF /* RoomScreenHook.swift */; }; - 28A494A66D0F21EC1EC4EB11 /* VolatileUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F56C9FFE8D55375CED9741 /* VolatileUserDefaults.swift */; }; 28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CABD320DE5566D133890B24 /* AccessibilityTestsAppCoordinator.swift */; }; 291A3E0EECD496D0FCC2ADAA /* TimelineScrollButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4E14E3688FF3532456A656 /* TimelineScrollButton.swift */; }; 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */; }; 2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; }; 298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */; }; - 29E411E37D3FBF3899B3ED69 /* UserDefaultsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCA4547BE58E17899E95CDE /* UserDefaultsProtocol.swift */; }; 29EE1791E0AFA1ABB7F23D2F /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; 2A4B7856611589DD7B1B36CA /* SearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79A78330B9329E3A99E4105 /* SearchScreen.swift */; }; 2A4F8B76E1F99DE95D5867E6 /* ClassicAppManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633BAD3D9BB44B2AED7CBB93 /* ClassicAppManagerMock.swift */; }; @@ -466,6 +465,7 @@ 4A361E8BAFFA20B9719B47F5 /* InviteUsersConfirmationSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E97CF050B0168F3D605F0E9 /* InviteUsersConfirmationSheetView.swift */; }; 4A4110369DBB79E4A314F415 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */; }; 4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; }; + 4A7957D034F5D96909127881 /* AccountSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C071F3013E8C6DAC7FB768 /* AccountSettings.swift */; }; 4A8287E5281B44A8754BE509 /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */; }; 4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */; }; 4A9786829B79BD13EF1626C1 /* NativeCallSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2316BADD6C1119C1FA229 /* NativeCallSystem.swift */; }; @@ -522,6 +522,7 @@ 513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */; }; 51A075328D68FB4AE65C43B5 /* SearchServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD6FFE581238BD30B53BA0A /* SearchServiceProxy.swift */; }; 51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; }; + 51DBB7D725080FDAA4AB1412 /* UserSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE997F400545F3265935B53 /* UserSettingsTests.swift */; }; 522269133E6F65F68482F4F4 /* RemotePreferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181CF280BC8E3F335AFCB4B8 /* RemotePreferenceTests.swift */; }; 52473A4D7B1FBD4CD1E770C8 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; }; 52511848C094F3097F858F00 /* AvatarSettingsButtonLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3DA6F684A8FD5A70644CDA /* AvatarSettingsButtonLabel.swift */; }; @@ -687,7 +688,6 @@ 6B8F03A85E83755D98DF0443 /* SearchScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4724D5CB16CFA5853EF8F9 /* SearchScreenViewModel.swift */; }; 6BA9214F31A24C7A84287A56 /* SentryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 337AD37403015231CAE3E80B /* SentryEvent.swift */; }; 6BAE34CFA9821709CFE61E50 /* DeveloperOptionsScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F60FDB3AEFC60830BD289FE /* DeveloperOptionsScreenHook.swift */; }; - 6BEB10499149444909EBE42F /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */; }; 6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; }; 6C8BAF1E91618D69E7D652B2 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; }; 6C98153D60FF9B648C166C27 /* TimelineItemMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FFE1F410969ECB23FE9BB2 /* TimelineItemMenu.swift */; }; @@ -714,6 +714,7 @@ 706289B086B0A6B0C211763F /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; }; 7070E5CA0095CBEFAA7DE466 /* MediaEventsTimelineScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F505E3D79018B6EFF953F647 /* MediaEventsTimelineScreenViewModelTests.swift */; }; 707E49BE07E8EB8A13C0EB1E /* SessionVerificationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FACD034DB52525A3CEF2BDF /* SessionVerificationScreen.swift */; }; + 708B77D5E65BB48D44732236 /* AccountUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F0A9118FF25E18D5CD6526 /* AccountUserDefaults.swift */; }; 708FC3184CCED825F0A36273 /* RoomThreadListServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCE06F4A71FD46C9D8CD432E /* RoomThreadListServiceProxy.swift */; }; 709A9B52FC26B4CB86B8B020 /* EncryptedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F06F70B9C433BAD4BC6B9F5 /* EncryptedRoomTimelineView.swift */; }; 70B83D44043293B4B77440B9 /* PollFormScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */; }; @@ -768,6 +769,7 @@ 77E33FF0E4A50B555BF3A8AA /* AudioFileEventsTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9044BE0E4A66F5B963E834 /* AudioFileEventsTimelineView.swift */; }; 77FB08C303F4C74C0E8577E2 /* TimelineMediaPreviewModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2BB38DF61F5100B8723112 /* TimelineMediaPreviewModels.swift */; }; 7807B1DEE32617896886A8E5 /* FormattingToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE1E6FAA3719E9B7A2D5510B /* FormattingToolbar.swift */; }; + 780C0394F8A10C4BCAC85DDC /* UserDefaultsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07204157037AB27CE47AA66C /* UserDefaultsProtocol.swift */; }; 783FFA8F0DB76A1CB23B0ECE /* CLLocationCoordinate2D.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC06B3414D3BBD709E72E4 /* CLLocationCoordinate2D.swift */; }; 784592335560C2E91D32D177 /* DeveloperOptionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */; }; 785613C0C092B532198EB3BB /* TimelineStartRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ECC9D66400727DFFEE12E8 /* TimelineStartRoomTimelineView.swift */; }; @@ -866,6 +868,7 @@ 874704A177B0A6D78E037908 /* MapLibreInterface.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 842F63AA68E8EB410B1BA29F /* MapLibreInterface.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 877D3CE8680536DB430DE6A2 /* TimelineItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48C91C8BE55CAE1A3DBC3BC /* TimelineItemIdentifier.swift */; }; 878070573C7BF19E735707B4 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */; }; + 879013B1A7DA779C7E0C56A4 /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004397AC54CAE108B6E2385D /* TrackedUserDefaults.swift */; }; 87E8C31FCF9276F15CB0B408 /* ThreadTimelineScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1C19A4BE46EDE1411ECCE /* ThreadTimelineScreenViewModelProtocol.swift */; }; 8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; }; 88272A714D81BC507F76A1EE /* RageshakeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC598338E7CF41107293AB5 /* RageshakeConfiguration.swift */; }; @@ -907,7 +910,6 @@ 8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; }; 8C91D242BEEC657FABCC0B95 /* BlockedUsersScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8642512079EEFD622E3AA66B /* BlockedUsersScreenModels.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; - 8CFB8BB99F25FA34B6C6BE86 /* UserDefaultsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCA4547BE58E17899E95CDE /* UserDefaultsProtocol.swift */; }; 8CFDA5F1562479CB3A34D277 /* RedactedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91C8BD78F7B9247AC57FA1A3 /* RedactedRoomTimelineView.swift */; }; 8CFDC1309E8E5192EC2B04FE /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = D48D415C7A4284DEBEB34924 /* portrait_test_video.mp4 */; }; 8D24671992A1C1753B211221 /* EncryptionResetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */; }; @@ -929,6 +931,7 @@ 90C683C87BF6D39419402E5B /* AuthenticationClassicAppBackupInstructionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B0E0A2F16603E7C827C295 /* AuthenticationClassicAppBackupInstructionsView.swift */; }; 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; }; 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; }; + 90F7C2C3DF76B3B00B58CB57 /* UserDefaultsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07204157037AB27CE47AA66C /* UserDefaultsProtocol.swift */; }; 914BDF61447C723F104BCE33 /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; }; 915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; }; 91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; }; @@ -947,7 +950,6 @@ 92FE657CDFAFE3031576EB43 /* LinkNewDeviceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */; }; 9312F5A17AE59A9E910C51D6 /* NotificationItemProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840182D7A61402D5947DE094 /* NotificationItemProxyMock.swift */; }; 9322949BFCA6278921085862 /* DeactivateAccountScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664D3C710B4A12CE4E623645 /* DeactivateAccountScreenModels.swift */; }; - 9331608392D42EF465A8E710 /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */; }; 934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */; }; 937985546F708339711ECDFC /* ComposerToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85666E40F7E817809B4FD787 /* ComposerToolbar.swift */; }; 93A549135E6C027A0D823BFE /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 385D4C28F9DC5CF53BD9ECDB /* DeviceKit */; }; @@ -971,6 +973,7 @@ 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; }; 95B690EA99A3385D4B8A593D /* NotificationToneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94A6A57FE7E71F7605070AA /* NotificationToneManager.swift */; }; 95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; }; + 95ED28978572AD07F0D975C8 /* AccountSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C071F3013E8C6DAC7FB768 /* AccountSettings.swift */; }; 9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.swift */; }; 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; }; 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; }; @@ -1023,6 +1026,7 @@ 9EE71509E6E7519A2B2388B3 /* KnockRequestsListScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD9C9A31D9AB3B6D8128E69 /* KnockRequestsListScreenModels.swift */; }; 9EF72884B74BC39B01640098 /* BugReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DF257F5D968E5DD719583C /* BugReportTests.swift */; }; 9F11B9F347F9E2D236799FB3 /* ElementCallServiceConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */; }; + 9F51563FBDD578F37020C852 /* UserDefaultsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07204157037AB27CE47AA66C /* UserDefaultsProtocol.swift */; }; 9F8BEA86540D8980BDD7C176 /* AuthenticationStartScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE3AB4D399D39FBD78119011 /* AuthenticationStartScreenModels.swift */; }; 9FB41B0E8B2AA9B404E52C8B /* AppLockSetupBiometricsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */; }; 9FBE1FB20171012260A32492 /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53FCCE44F96E0BC411A6CF0 /* TimelineSenderAvatarView.swift */; }; @@ -1112,13 +1116,13 @@ AAF0BBED840DF4A53EE85E77 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = C2C69B8BA5A9702E7A8BC08F /* MatrixRustSDK */; }; ABD29E06DD1224812E750AF8 /* ReadReceiptCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D75941CBD7D336F831924EC /* ReadReceiptCell.swift */; }; AC1DB27A4134470846BE49F6 /* UserProfileScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD116096CAA9139B95EEA9C /* UserProfileScreenViewModel.swift */; }; + AC23453E4BBDA87031F6E478 /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004397AC54CAE108B6E2385D /* TrackedUserDefaults.swift */; }; AC3C3D6D4AD31F13EE987390 /* TraceLogPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7149BDDE47F8AD104E644E2 /* TraceLogPack.swift */; }; AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */; }; AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127A57D053CE8C87B5EFB089 /* Consumable.swift */; }; AC90434798E7894370E80E66 /* SecureBackupScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79BB714D28C9F588DD69353 /* SecureBackupScreenViewModelProtocol.swift */; }; AD2A81B65A9F6163012086F1 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B698739E3410E2CDB7144 /* MXLog.swift */; }; AD55E245FE686D7DB4C86406 /* RoomTimelineItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */; }; - ADC820847A91C9939E269B24 /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */; }; AE07F215EBC2B9CBF17AA54B /* TimelineItemMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1C3CBBC62C566DDF5E84C1 /* TimelineItemMenuAction.swift */; }; AE1160076F663BF14E0E893A /* EffectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4548A9BDE5CB3AB864BCA9F /* EffectsView.swift */; }; AE1A73B24D63DA3D63DC4EE3 /* SessionVerificationControllerProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */; }; @@ -1128,10 +1132,12 @@ AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */; }; AF4510502727E2ABC326C5FD /* test_animated_image.gif in Resources */ = {isa = PBXBuildFile; fileRef = BDB0DD6E869B4764958CD7AB /* test_animated_image.gif */; }; AF640383EC85741D33F7558F /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100DA305FE28BB3B6AEDE1FD /* UserSettings.swift */; }; + AF75E5CF932D80EAD578E02E /* VolatileUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3994D186AC9893BEE458ECF /* VolatileUserDefaults.swift */; }; AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */; }; AFE2AB612A1460E49578D746 /* JoinRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */; }; B004A08BE32F06832BF41CFC /* RoomDetailsScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3674212CAB6C5143DD437AC4 /* RoomDetailsScreenHook.swift */; }; B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; }; + B050F3280B97071EC0158CD2 /* AccountUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F0A9118FF25E18D5CD6526 /* AccountUserDefaults.swift */; }; B0CB16349B96262AA65A04AF /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; }; B1088417801A962BA861C13F /* RoomPowerLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E247E29C7A0E421DB839D7 /* RoomPowerLevel.swift */; }; B10F7D5C237417DA160F4603 /* LongPressWithFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9F148717D74F73BE724434 /* LongPressWithFeedback.swift */; }; @@ -1189,6 +1195,7 @@ BA43D782BE85C7F5F20C624A /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; BA48D6AFF6421D199148C0A1 /* KnockRequestsListScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9AC2CC94FA06F728883B694 /* KnockRequestsListScreenViewModelTests.swift */; }; BA4C9049BC96DED3A2F3B82E /* RoomNotificationSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */; }; + BB5467A56070EBD1C9A8FCF0 /* AccountSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C071F3013E8C6DAC7FB768 /* AccountSettings.swift */; }; BB6BF528BC7F5B87E08C4F18 /* CameraPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A3B7637DDBD6AA97AC2545 /* CameraPicker.swift */; }; BB784A02BADB03C820617A46 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */; }; BB9B800C6094E34860E89DC5 /* AppLockSetupBiometricsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */; }; @@ -1308,7 +1315,6 @@ CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */; }; CDAE3A37D4DF136F9D07DB61 /* RoomChangeRolesScreenSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF710CB1C31F8938EAA3A7D /* RoomChangeRolesScreenSection.swift */; }; CDCA8A559E098503DDE29477 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; }; - CDD76A7D42E1B47F7E423AA8 /* VolatileUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F56C9FFE8D55375CED9741 /* VolatileUserDefaults.swift */; }; CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; }; CE23FE7F463AD5C7D80353AA /* RoomThreadListScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 008D864B3F51B41DF483B860 /* RoomThreadListScreenViewModelProtocol.swift */; }; CE3B7FC34FB2C279AAA5EA01 /* AVMetadataMachineReadableCodeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */; }; @@ -1337,6 +1343,7 @@ D16B3134A7FF4FBA0749014A /* AuthenticationClassicAppAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C45578F406698388B97C07 /* AuthenticationClassicAppAccountView.swift */; }; D18B70975644C24F60656C0D /* KnockRequestProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07851F4EA81AA3339806A7B /* KnockRequestProxyProtocol.swift */; }; D19A748E95E2FAB2940570F0 /* CallScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4103AB4340F2974D690A12A /* CallScreen.swift */; }; + D19E6DA46819E0807E7F00B4 /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004397AC54CAE108B6E2385D /* TrackedUserDefaults.swift */; }; D1C942421F607285C50FB792 /* SearchServiceSDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF5721148A5D59B476BA976 /* SearchServiceSDKMock.swift */; }; D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; }; D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */; }; @@ -1488,7 +1495,6 @@ E9D2ED1C4186931E3D5FDA4E /* QRCodeLoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718D8767035D37E2DB5CC550 /* QRCodeLoginScreenViewModelProtocol.swift */; }; EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; EA2FECCD9E00D9784AC6017D /* PhishingDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB07F03461023BC39C730922 /* PhishingDetector.swift */; }; - EA4848108A6FE53A4085C9B1 /* VolatileUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F56C9FFE8D55375CED9741 /* VolatileUserDefaults.swift */; }; EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */; }; EA78A7512AFB1E5451744EB1 /* AppRouteURLParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */; }; EA8D941771E762A5D3D7FA0D /* FileMediaEventsTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430C73079A84654BF46A7FF5 /* FileMediaEventsTimelineView.swift */; }; @@ -1527,6 +1533,7 @@ EF47D802A404A53F15D5D4B6 /* JoinRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD7C0A2750998C2D77AD00F /* JoinRoomScreenViewModel.swift */; }; EF5009AC03212227131C8AF2 /* RoomNotificationSettingsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */; }; EF890DEF0479E66548F2BA23 /* AppLockTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490BEADEFB2D6B7C9F618AE8 /* AppLockTimer.swift */; }; + EFB3033FE782773D4F105681 /* AccountUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F0A9118FF25E18D5CD6526 /* AccountUserDefaults.swift */; }; EFBBD44C0A16F017C32D2099 /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72614BFF35B8394C6E13F55A /* TimelineItemStatusView.swift */; }; EFC4CCCF9D22FFA7D542693A /* NotificationTone.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08D6FC1919CB804A9191E8 /* NotificationTone.swift */; }; EFF735EC040BEB669AFBAB50 /* LeaveSpaceHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B3CE05643C7791D46AC54B /* LeaveSpaceHandleProxy.swift */; }; @@ -1621,6 +1628,7 @@ FDE47D4686BA0F86BB584633 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 3262F08E1C3483C22A7A319F /* Compound */; }; FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; }; FE578B12750E5E602D62B365 /* ComposerDisabledView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC4B431B0117BDEE697DB4A /* ComposerDisabledView.swift */; }; + FE81E0ADE56876A78FA3F2D2 /* VolatileUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3994D186AC9893BEE458ECF /* VolatileUserDefaults.swift */; }; FE9A5D4715C7AB68682C030C /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDCAC6CAAD65A2C24EA9C4B /* Dictionary.swift */; }; FEC03105D1BDE0F49BD7F243 /* PinnedEventsTimelineScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B6572E6EF5D5F4B0C338A40 /* PinnedEventsTimelineScreenModels.swift */; }; FEF0E0D2EDB8A4A9FE1842EE /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = D48D415C7A4284DEBEB34924 /* portrait_test_video.mp4 */; }; @@ -1763,6 +1771,7 @@ 002399C6CB875C4EBB01CBC0 /* MediaEventsTimelineScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreen.swift; sourceTree = ""; }; 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = ""; }; 002DE5E9625122F37DE5F020 /* OAuthConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthConfiguration.swift; sourceTree = ""; }; + 004397AC54CAE108B6E2385D /* TrackedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedUserDefaults.swift; sourceTree = ""; }; 007C16779FDCF10DA4F1A510 /* LinkNewDeviceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceService.swift; sourceTree = ""; }; 008D864B3F51B41DF483B860 /* RoomThreadListScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomThreadListScreenViewModelProtocol.swift; sourceTree = ""; }; 00AFC5F08734C2EA4EE79C59 /* IdentityConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreen.swift; sourceTree = ""; }; @@ -1811,6 +1820,7 @@ 06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenModels.swift; sourceTree = ""; }; 06F27F588F9059128E17C669 /* WindowManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManagerProtocol.swift; sourceTree = ""; }; 06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenCoordinator.swift; sourceTree = ""; }; + 07204157037AB27CE47AA66C /* UserDefaultsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsProtocol.swift; sourceTree = ""; }; 0724EBDFE8BB4C9E5547C57D /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; 07579F9C29001E40715F3014 /* NotificationSettingsChatType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsChatType.swift; sourceTree = ""; }; 077B01C13BBA2996272C5FB5 /* ProcessInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessInfo.swift; sourceTree = ""; }; @@ -1844,7 +1854,6 @@ 0C62E07C1164F5120727A2A8 /* AppLockSetupBiometricsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenCoordinator.swift; sourceTree = ""; }; 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; 0CBB0B65E5C1686700EF1276 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/SAS.strings; sourceTree = ""; }; - 0CCA4547BE58E17899E95CDE /* UserDefaultsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsProtocol.swift; sourceTree = ""; }; 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = ""; }; 0CD63A653BCA2C8112654B19 /* SearchScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScreenModels.swift; sourceTree = ""; }; 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = ""; }; @@ -1872,7 +1881,6 @@ 1059E2AE7878CF7820592637 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = ""; }; 10CC626F97AD70FF0420C115 /* RoomSummaryProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderProtocol.swift; sourceTree = ""; }; - 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedUserDefaults.swift; sourceTree = ""; }; 10F130DF775CE6BC51A4E392 /* AppLockSetupBiometricsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenModels.swift; sourceTree = ""; }; 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenSelectedItem.swift; sourceTree = ""; }; 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModelProtocol.swift; sourceTree = ""; }; @@ -2107,6 +2115,7 @@ 3674212CAB6C5143DD437AC4 /* RoomDetailsScreenHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenHook.swift; sourceTree = ""; }; 369236171C351AB8C70BB161 /* preview_video.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = preview_video.jpg; sourceTree = ""; }; 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 36F0A9118FF25E18D5CD6526 /* AccountUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUserDefaults.swift; sourceTree = ""; }; 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyMock.swift; sourceTree = ""; }; 3747C96188856006F784BF49 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ko.lproj/Localizable.stringsdict; sourceTree = ""; }; 37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenViewModel.swift; sourceTree = ""; }; @@ -2946,6 +2955,7 @@ C3285BD95B564CA2A948E511 /* OnboardingFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFlowCoordinator.swift; sourceTree = ""; }; C33B3F17996DFDF5F0181512 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; C352359663A0E52BA20761EE /* LoadableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableImage.swift; sourceTree = ""; }; + C3994D186AC9893BEE458ECF /* VolatileUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolatileUserDefaults.swift; sourceTree = ""; }; C3C2316BADD6C1119C1FA229 /* NativeCallSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeCallSystem.swift; sourceTree = ""; }; C454F35D8884C0A952FF2F84 /* LiveLocationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSheet.swift; sourceTree = ""; }; C4756240773D26AB74C22668 /* OrientationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManagerProtocol.swift; sourceTree = ""; }; @@ -3163,6 +3173,7 @@ E8A1F98AE670377B20679FF5 /* MediaPlayerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProvider.swift; sourceTree = ""; }; E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = ""; }; E944F717FC10A428D027074D /* RoomPowerLevelsProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelsProxyMock.swift; sourceTree = ""; }; + E96792E7EE6742172DA3A35E /* AccountUserDefaultsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUserDefaultsTests.swift; sourceTree = ""; }; E9773983346838AABFAA914D /* UserSessionHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionHook.swift; sourceTree = ""; }; E992D7B8BE54B2AB454613AF /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = ""; }; E994322F10D20ECE68683683 /* GalleryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryRoomTimelineItem.swift; sourceTree = ""; }; @@ -3200,6 +3211,7 @@ ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = ""; }; ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = ""; }; EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceFlowCoordinator.swift; sourceTree = ""; }; + EDE997F400545F3265935B53 /* UserSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsTests.swift; sourceTree = ""; }; EDF6E8208542BE7A02A94939 /* PresentationColorSchemeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationColorSchemeModifier.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; EE61568A225F7AA068583AA7 /* SignallingTimelineItemProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignallingTimelineItemProviderMock.swift; sourceTree = ""; }; @@ -3231,7 +3243,6 @@ F2B94F1B0B5D9D42B15AA6E8 /* ChatsTabFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsTabFlowCoordinatorStateMachine.swift; sourceTree = ""; }; F2DC502B1A566E99969D34DD /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; F2E4EF80DFB8FE7C4469B15D /* RoomDirectorySearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreen.swift; sourceTree = ""; }; - F2F56C9FFE8D55375CED9741 /* VolatileUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolatileUserDefaults.swift; sourceTree = ""; }; F3082001D373607455CB08A1 /* QRCodeErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeErrorView.swift; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F320003F490B11F808ECC5E9 /* JoinedMembersBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedMembersBadgeView.swift; sourceTree = ""; }; @@ -3264,6 +3275,7 @@ F7E247E29C7A0E421DB839D7 /* RoomPowerLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevel.swift; sourceTree = ""; }; F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRootCoordinatorTests.swift; sourceTree = ""; }; F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionScreenTests.swift; sourceTree = ""; }; + F8C071F3013E8C6DAC7FB768 /* AccountSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettings.swift; sourceTree = ""; }; F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreen.swift; sourceTree = ""; }; F91AC45CDDF85E946391CA5C /* RoomThreadListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomThreadListScreenViewModel.swift; sourceTree = ""; }; F933BB25FC426D5C67E1D812 /* TimelineControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineControllerMock.swift; sourceTree = ""; }; @@ -5267,6 +5279,7 @@ isa = PBXGroup; children = ( 12D0FBA1FE29833A915B77AE /* AccountProviderTests.swift */, + E96792E7EE6742172DA3A35E /* AccountUserDefaultsTests.swift */, 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */, C687844F60BFF532D49A994C /* AnalyticsTests.swift */, E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */, @@ -5399,6 +5412,7 @@ 71E2E5103702D13361D09100 /* UserProfileScreenViewModelTests.swift */, 4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */, F5F7E6F5D5A54C68DC2EDD71 /* UserSessionStoreTests.swift */, + EDE997F400545F3265935B53 /* UserSettingsTests.swift */, 283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */, AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */, D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */, @@ -5908,6 +5922,17 @@ path = HTMLParsing; sourceTree = ""; }; + 8FB4475AADA10756AFB4370D /* Storage */ = { + isa = PBXGroup; + children = ( + 36F0A9118FF25E18D5CD6526 /* AccountUserDefaults.swift */, + 004397AC54CAE108B6E2385D /* TrackedUserDefaults.swift */, + 07204157037AB27CE47AA66C /* UserDefaultsProtocol.swift */, + C3994D186AC9893BEE458ECF /* VolatileUserDefaults.swift */, + ); + path = Storage; + sourceTree = ""; + }; 90DC2E28718955ED87AD1456 /* CreatePollScreen */ = { isa = PBXGroup; children = ( @@ -6929,6 +6954,7 @@ isa = PBXGroup; children = ( A67BCEFE35F8D00CADF108C9 /* AccountProvider.swift */, + F8C071F3013E8C6DAC7FB768 /* AccountSettings.swift */, FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */, 353024006CB726E9F9187B3A /* AnalyticsConsentState.swift */, 0FD22AEFFA20065494ED2333 /* AppAppearance.swift */, @@ -6941,10 +6967,8 @@ 8FC598338E7CF41107293AB5 /* RageshakeConfiguration.swift */, 69A05E85E4872C3221C5C287 /* RemotePreference.swift */, 095B8ED0CA87F4A0A4858719 /* SearchBreadcrumb.swift */, - 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */, - 0CCA4547BE58E17899E95CDE /* UserDefaultsProtocol.swift */, 100DA305FE28BB3B6AEDE1FD /* UserSettings.swift */, - F2F56C9FFE8D55375CED9741 /* VolatileUserDefaults.swift */, + 8FB4475AADA10756AFB4370D /* Storage */, ); path = Settings; sourceTree = ""; @@ -8275,6 +8299,8 @@ buildActionMask = 2147483647; files = ( 75C1AFACE6C0DEA18F16456C /* AccountProvider.swift in Sources */, + BB5467A56070EBD1C9A8FCF0 /* AccountSettings.swift in Sources */, + B050F3280B97071EC0158CD2 /* AccountUserDefaults.swift in Sources */, CA2BFC5504AD91A028CFA45B /* AnalyticsConfiguration.swift in Sources */, 4E9ED3E6E979C6C7FEC1D011 /* AnalyticsConsentState.swift in Sources */, 68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */, @@ -8352,15 +8378,15 @@ AC3C3D6D4AD31F13EE987390 /* TraceLogPack.swift in Sources */, B81840E45D8746A4692DA774 /* Tracing.swift in Sources */, 2D45A04699BB6BA3B3A0CB9A /* TracingHook.swift in Sources */, - 6BEB10499149444909EBE42F /* TrackedUserDefaults.swift in Sources */, + D19E6DA46819E0807E7F00B4 /* TrackedUserDefaults.swift in Sources */, 45D6DC594816288983627484 /* UITestsScreenIdentifier.swift in Sources */, 281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */, 06B55882911B4BF5B14E9851 /* URL.swift in Sources */, D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */, 34357B287357BC0B9715DD51 /* UserAgentBuilder.swift in Sources */, - 1B1404DD4BB2C5079B074210 /* UserDefaultsProtocol.swift in Sources */, + 90F7C2C3DF76B3B00B58CB57 /* UserDefaultsProtocol.swift in Sources */, D61FCF6ABC1490F630E76AEC /* UserSettings.swift in Sources */, - 28A494A66D0F21EC1EC4EB11 /* VolatileUserDefaults.swift in Sources */, + AF75E5CF932D80EAD578E02E /* VolatileUserDefaults.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8370,6 +8396,7 @@ files = ( C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */, A1A16FEF565ED7972591988B /* AccountProviderTests.swift in Sources */, + 106E3126B0C49231381B5B80 /* AccountUserDefaultsTests.swift in Sources */, A9A5801D5EE3D4D91F6DDADB /* AnalyticsSettingsScreenViewModelTests.swift in Sources */, 890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */, 8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */, @@ -8525,6 +8552,7 @@ 73F547BEB41D3DAFAAF6E0AF /* UserProfileScreenViewModelTests.swift in Sources */, 627139A3D79F032BA81E3A53 /* UserSessionFlowCoordinatorTests.swift in Sources */, E3E1B656F2E8F2D8075F6558 /* UserSessionStoreTests.swift in Sources */, + 51DBB7D725080FDAA4AB1412 /* UserSettingsTests.swift in Sources */, 21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */, 44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */, A3D7110C1E75E7B4A73BE71C /* VoiceMessageRecorderTests.swift in Sources */, @@ -8568,6 +8596,8 @@ buildActionMask = 2147483647; files = ( 3A363617757E6838AC3D15FB /* AccountProvider.swift in Sources */, + 95ED28978572AD07F0D975C8 /* AccountSettings.swift in Sources */, + 708B77D5E65BB48D44732236 /* AccountUserDefaults.swift in Sources */, C6AC34B731F4F853E9D15146 /* AnalyticsConfiguration.swift in Sources */, 585DCA0487A0A6F4E59EF5CA /* AnalyticsConsentState.swift in Sources */, 88887E40301FF0543ED5B59F /* AppAppearance.swift in Sources */, @@ -8606,13 +8636,13 @@ A5FD8284744E2FECFC842FC1 /* TraceLogPack.swift in Sources */, 89DF67AECBF9D0EE0DDB7737 /* Tracing.swift in Sources */, 51263FC33CC961A14F5D6BCA /* TracingHook.swift in Sources */, - 9331608392D42EF465A8E710 /* TrackedUserDefaults.swift in Sources */, + 879013B1A7DA779C7E0C56A4 /* TrackedUserDefaults.swift in Sources */, 03BD83E8BDD23AE059802E0D /* UITestsScreenIdentifier.swift in Sources */, 26252AA9AED64010788F4C26 /* UIView.swift in Sources */, 66E9202BED03B5BB00E812A1 /* URL.swift in Sources */, - 29E411E37D3FBF3899B3ED69 /* UserDefaultsProtocol.swift in Sources */, + 9F51563FBDD578F37020C852 /* UserDefaultsProtocol.swift in Sources */, 452507BCE97B3EA0F24E206E /* UserSettings.swift in Sources */, - CDD76A7D42E1B47F7E423AA8 /* VolatileUserDefaults.swift in Sources */, + 0B30DC7D9D445C405ADCB5FA /* VolatileUserDefaults.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8641,6 +8671,8 @@ 28E8C44DD6E39BEB2A1599C8 /* AccessibilityTestsAppCoordinator.swift in Sources */, 2538350683F421AB620AA950 /* AccessibleLongPress.swift in Sources */, 03E9750B2F994A79FA1296A1 /* AccountProvider.swift in Sources */, + 4A7957D034F5D96909127881 /* AccountSettings.swift in Sources */, + EFB3033FE782773D4F105681 /* AccountUserDefaults.swift in Sources */, 21215E17C9908F1EBF621BE0 /* ActiveCallTimelineItemView.swift in Sources */, 09E1A358EA4205480AEF4698 /* AdvancedSettingsScreen.swift in Sources */, 34ADC506C929D10B138EBA51 /* AdvancedSettingsScreenCoordinator.swift in Sources */, @@ -9678,7 +9710,7 @@ 6E44638FDF7D4B0F80EFA7EA /* TraceLogPack.swift in Sources */, 126CBCF5B0145FA1377C1316 /* Tracing.swift in Sources */, E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */, - ADC820847A91C9939E269B24 /* TrackedUserDefaults.swift in Sources */, + AC23453E4BBDA87031F6E478 /* TrackedUserDefaults.swift in Sources */, 298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */, 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */, A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */, @@ -9696,7 +9728,7 @@ 34C752A73717C691582DC6C7 /* UnsupportedRoomTimelineItem.swift in Sources */, D8459AAD6969B1431ECBE990 /* UnsupportedRoomTimelineView.swift in Sources */, 7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */, - 8CFB8BB99F25FA34B6C6BE86 /* UserDefaultsProtocol.swift in Sources */, + 780C0394F8A10C4BCAC85DDC /* UserDefaultsProtocol.swift in Sources */, 7D58B4F46CAA9A7C3E4C6A30 /* UserDetailsEditScreen.swift in Sources */, A64B52D9F73F9A6B95AF24FE /* UserDetailsEditScreenCoordinator.swift in Sources */, 3982C505960006B341CFD0C6 /* UserDetailsEditScreenModels.swift in Sources */, @@ -9759,7 +9791,7 @@ 024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */, 1224084B7E289E0830BA2C54 /* VoiceMessageRoomTimelineView.swift in Sources */, 1CA094038D4D036A6F0A1314 /* VoiceMessageTrashButton.swift in Sources */, - EA4848108A6FE53A4085C9B1 /* VolatileUserDefaults.swift in Sources */, + FE81E0ADE56876A78FA3F2D2 /* VolatileUserDefaults.swift in Sources */, CA12AE0DCD57D49CD96C699A /* WaveformCursorView.swift in Sources */, 63CDC201A5980F304F6D0A1C /* WaveformInteractionModifier.swift in Sources */, B773ACD8881DB18E876D950C /* WaveformSource.swift in Sources */, @@ -10438,7 +10470,7 @@ IS_NIGHTLY_BUILD = NO; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 14.5; - MARKETING_VERSION = 26.10.0; + MARKETING_VERSION = 26.10.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCTION_APP_NAME = Element; @@ -10520,7 +10552,7 @@ IS_NIGHTLY_BUILD = NO; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 14.5; - MARKETING_VERSION = 26.10.0; + MARKETING_VERSION = 26.10.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index ed0813488..3f19dec50 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -467,14 +467,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg MXLog.info("The app was upgraded from \(oldVersion) to \(newVersion)") - if oldVersion < Version(1, 6, 0) { - MXLog.info("Migrating to v1.6.0, marking identity confirmation onboarding as ran.") - if !userSessionStore.userIDs.isEmpty { - appSettings.hasRunIdentityConfirmationOnboarding = true - appSettings.hasRunNotificationPermissionsOnboarding = true - } - } - if oldVersion < Version(1, 6, 7) { Tracing.deleteLogFiles(in: Tracing.legacyLogsDirectory) MXLog.info("Migrating to v1.6.7, log files have been wiped") @@ -498,6 +490,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg await userSession.clientProxy.optimizeStores() MXLog.info("Finished optimizing client stores.") + 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 + } + if oldVersion < Version(25, 6, 0) { MXLog.info("Migrating to version 25.06.0, migrating timeline media settings to account data.") performSettingsToAccountDataMigration(userSession: userSession) @@ -518,6 +516,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg appSettings.hasSignedInBefore = true } + 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) + } + userSessionMigrationsOldVersion = nil } @@ -863,7 +867,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg userSessionStore.logout(userSession: userSession) tearDownUserSession() - appSettings.resetSessionSpecificSettings() + userSession.userSettings.resetSessionSpecificSettings() appHooks.remoteSettingsHook.reset(appSettings) // Reset analytics diff --git a/ElementX/Sources/Application/Settings/AccountSettings.swift b/ElementX/Sources/Application/Settings/AccountSettings.swift new file mode 100644 index 000000000..73fead295 --- /dev/null +++ b/ElementX/Sources/Application/Settings/AccountSettings.swift @@ -0,0 +1,42 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import Foundation +import Macros + +final nonisolated class AccountSettings: @unchecked Sendable { + /// UserDefaults to be used on reads and writes. + private let store: AccountUserDefaults + private let userID: String + + // MARK: Session + + @UserPreference(defaultValue: false) + var hasRunIdentityConfirmationOnboarding: Bool + + // MARK: Search + + /// The queries the user searched for and the rooms they opened from the results, most recent first. + @UserPreference(defaultValue: [SearchBreadcrumb]()) + var searchBreadcrumbs: [SearchBreadcrumb] + + init(userID: String, store: UserDefaultsProtocol) { + self.store = .init(userID: userID, store: store) + self.userID = userID + } + + func migrateAppSettingsValue(_ keyPath: KeyPath) { + let storeKey = self[keyPath: keyPath].rawValue + store.migrateAppSettingsValue(forKey: storeKey) + } + + func reset() { + MXLog.warning("Resetting AccountSettings for \(userID).") + store.reset() + } +} diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index f97d0ec98..37262ad3b 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -69,12 +69,6 @@ final nonisolated class AppSettings: @unchecked Sendable { store.reset() } - func resetSessionSpecificSettings() { - MXLog.warning("Resetting the user session specific AppSettings.") - resetHasRunIdentityConfirmationOnboarding() - resetSearchBreadcrumbs() - } - // MARK: - Hooks // swiftlint:disable:next function_parameter_count @@ -338,9 +332,6 @@ final nonisolated class AppSettings: @unchecked Sendable { @UserPreference(defaultValue: false) var hasRunNotificationPermissionsOnboarding: Bool - @UserPreference(defaultValue: false) - var hasRunIdentityConfirmationOnboarding: Bool - @UserPreference(defaultValue: false) var hasRequestedLocationAlwaysLocationAuthorization: Bool @@ -363,12 +354,6 @@ final nonisolated class AppSettings: @unchecked Sendable { @UserPreference(defaultValue: true) var showAllRoomListActivity: Bool - // MARK: - Search Screen - - /// The queries the user searched for and the rooms they opened from the results, most recent first. - @UserPreference(defaultValue: [SearchBreadcrumb]()) - var searchBreadcrumbs: [SearchBreadcrumb] - // MARK: - Room Screen @UserPreference(defaultValue: AppBuildType.current == .debug) @@ -482,6 +467,10 @@ final nonisolated class AppSettings: @unchecked Sendable { self.store = store } + func userSettings(for userID: String) -> UserSettings { + UserSettings(appSettings: self, accountSettings: .init(userID: userID, store: store)) + } + static func volatile() -> AppSettings { AppSettings(store: VolatileUserDefaults()) } diff --git a/ElementX/Sources/Application/Settings/Storage/AccountUserDefaults.swift b/ElementX/Sources/Application/Settings/Storage/AccountUserDefaults.swift new file mode 100644 index 000000000..2ca62f6b2 --- /dev/null +++ b/ElementX/Sources/Application/Settings/Storage/AccountUserDefaults.swift @@ -0,0 +1,66 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Foundation + +/// A store that scopes all of its values to a single account. +/// +/// Each value keeps its own key in the parent store, prefixed with the user ID so that writes +/// remain atomic across the app and its extensions. +final nonisolated class AccountUserDefaults: UserDefaultsProtocol, @unchecked Sendable { + private let keyPrefix: String + private let store: UserDefaultsProtocol + + init(userID: String, store: UserDefaultsProtocol) { + keyPrefix = "account[\(userID)]." + self.store = store + } + + // MARK: Storage + + func data(forKey key: String) -> Data? { + store.data(forKey: keyPrefix + key) + } + + func object(forKey key: String) -> Any? { + store.object(forKey: keyPrefix + key) + } + + func removeObject(forKey key: String) { + store.removeObject(forKey: keyPrefix + key) + } + + func set(_ value: Any?, forKey key: String) { + store.set(value, forKey: keyPrefix + key) + } + + // MARK: - Management + + func migrateAppSettingsValue(forKey key: String) { + guard let value = store.object(forKey: key) else { + MXLog.info("Ignoring \(key), not set.") + return + } + + store.set(value, forKey: keyPrefix + key) + store.removeObject(forKey: key) + } + + func reset() { + for key in dictionaryRepresentation().keys { + removeObject(forKey: key) + } + } + + func dictionaryRepresentation() -> [String: Any] { + var representation = [String: Any]() + for (key, value) in store.dictionaryRepresentation() where key.hasPrefix(keyPrefix) { + representation[String(key.dropFirst(keyPrefix.count))] = value + } + return representation + } +} diff --git a/ElementX/Sources/Application/Settings/TrackedUserDefaults.swift b/ElementX/Sources/Application/Settings/Storage/TrackedUserDefaults.swift similarity index 100% rename from ElementX/Sources/Application/Settings/TrackedUserDefaults.swift rename to ElementX/Sources/Application/Settings/Storage/TrackedUserDefaults.swift diff --git a/ElementX/Sources/Application/Settings/UserDefaultsProtocol.swift b/ElementX/Sources/Application/Settings/Storage/UserDefaultsProtocol.swift similarity index 98% rename from ElementX/Sources/Application/Settings/UserDefaultsProtocol.swift rename to ElementX/Sources/Application/Settings/Storage/UserDefaultsProtocol.swift index 85f4685a5..be3068ec6 100644 --- a/ElementX/Sources/Application/Settings/UserDefaultsProtocol.swift +++ b/ElementX/Sources/Application/Settings/Storage/UserDefaultsProtocol.swift @@ -15,6 +15,7 @@ nonisolated protocol UserDefaultsProtocol: AnyObject { func set(_ value: Any?, forKey key: String) func reset() + func dictionaryRepresentation() -> [String: Any] } // MARK: - Codable handling diff --git a/ElementX/Sources/Application/Settings/VolatileUserDefaults.swift b/ElementX/Sources/Application/Settings/Storage/VolatileUserDefaults.swift similarity index 93% rename from ElementX/Sources/Application/Settings/VolatileUserDefaults.swift rename to ElementX/Sources/Application/Settings/Storage/VolatileUserDefaults.swift index f0f43a08f..ef863f930 100644 --- a/ElementX/Sources/Application/Settings/VolatileUserDefaults.swift +++ b/ElementX/Sources/Application/Settings/Storage/VolatileUserDefaults.swift @@ -42,4 +42,8 @@ final nonisolated class VolatileUserDefaults: UserDefaultsProtocol, Sendable { func reset() { storage = [:] } + + func dictionaryRepresentation() -> [String: Any] { + storage + } } diff --git a/ElementX/Sources/Application/Settings/UserSettings.swift b/ElementX/Sources/Application/Settings/UserSettings.swift index 04394b2ab..9965bfcc1 100644 --- a/ElementX/Sources/Application/Settings/UserSettings.swift +++ b/ElementX/Sources/Application/Settings/UserSettings.swift @@ -6,22 +6,23 @@ // import Foundation +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 - let account: AccountSettings + private let account: AccountSettings init(appSettings: AppSettings, accountSettings: AccountSettings) { self.app = appSettings self.account = accountSettings } - static func volatile() -> UserSettings { - let appSettings = AppSettings.volatile() - return UserSettings(appSettings: appSettings, accountSettings: .init()) + static func mock(userID: String = "@me:matrix.org") -> UserSettings { + AppSettings.volatile().userSettings(for: userID) } subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> Value { @@ -41,9 +42,12 @@ final nonisolated class UserSettings: Sendable { subscript(dynamicMember keyPath: KeyPath) -> Value { account[keyPath: keyPath] } -} - -/// Settings specific to an account. -struct AccountSettings { - // Placeholder for now, implementation to follow with a dedicated user defaults store scoped to a specific user ID. + + func migrateAppSettingsValueToAccountSettings(_ keyPath: KeyPath) { + account.migrateAppSettingsValue(keyPath) + } + + func resetSessionSpecificSettings() { + account.reset() + } } diff --git a/ElementX/Sources/Mocks/UserSessionMock.swift b/ElementX/Sources/Mocks/UserSessionMock.swift index 24d556bc8..8518aa144 100644 --- a/ElementX/Sources/Mocks/UserSessionMock.swift +++ b/ElementX/Sources/Mocks/UserSessionMock.swift @@ -11,7 +11,7 @@ import Foundation struct UserSessionMockConfiguration { var clientProxy: ClientProxyProtocol = ClientProxyMock(.init()) - var userSettings: UserSettings = .volatile() + var userSettings: UserSettings = .mock() var contentScannerService: ContentScannerServiceProtocol? } diff --git a/ElementX/Sources/Other/SwiftUI/Views/VisualListItem.swift b/ElementX/Sources/Other/SwiftUI/Views/VisualListItem.swift index bf948fdc2..bb54147e2 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.volatile().analyticsTermsURL) + static let strings = AnalyticsPromptScreenStrings(termsURL: UserSettings.mock().analyticsTermsURL) static var testImage1: some View { Image(systemName: "circle") diff --git a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift index e90d1f9a3..c9b4ef871 100644 --- a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift +++ b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift @@ -405,7 +405,7 @@ struct CallScreen_Previews: PreviewProvider { voiceOnly: false, colorScheme: .light), allowPictureInPicture: false, - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init())) } } diff --git a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift index 5039dfc7a..ebe792968 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomScreen.swift @@ -451,7 +451,7 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview { isSpace: Bool = false, selectionMode: CreateRoomScreenSpaceSelectionMode = .editableSpacesList(preSelectedSpace: nil), isAliasAvailable: Bool = true) -> CreateRoomScreenViewModel { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.knockingEnabled = isKnockingEnabled let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org", diff --git a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomSpaceSelectionSheet.swift b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomSpaceSelectionSheet.swift index ad258afdf..43e12c563 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomSpaceSelectionSheet.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/View/CreateRoomSpaceSelectionSheet.swift @@ -65,7 +65,7 @@ struct CreateRoomSpaceSelectionSheet_Previews: PreviewProvider, TestablePreview clientProxy.spaceService = SpaceServiceProxyMock(.init(editableSpaces: .mockJoinedSpaces2)) let userSession = UserSessionMock(.init(clientProxy: clientProxy)) - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() return CreateRoomScreenViewModel(isSpace: false, spaceSelectionMode: .editableSpacesList(preSelectedSpace: nil), diff --git a/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift b/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift index 1f0dd12e1..05262dc5a 100644 --- a/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift +++ b/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift @@ -103,7 +103,7 @@ struct EmojiPickerScreen: View { @available(iOS 26.0, *) struct EmojiPickerScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = EmojiPickerScreenViewModel(selectedEmojis: ["😀", "😄"], - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), continuation: AsyncStream.makeStream().continuation) static var previews: some View { @@ -115,7 +115,7 @@ struct EmojiPickerScreen_Previews: PreviewProvider, TestablePreview { struct EmojiPickerScreenSheet_Previews: PreviewProvider { static let viewModel = EmojiPickerScreenViewModel(selectedEmojis: ["😀", "😄"], - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), continuation: AsyncStream.makeStream().continuation) static var previews: some View { diff --git a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersEmptyStateView.swift b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersEmptyStateView.swift index 414da9004..8802eb330 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersEmptyStateView.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersEmptyStateView.swift @@ -61,10 +61,10 @@ struct RoomListFiltersEmptyStateView_Previews: PreviewProvider, TestablePreview VStack(spacing: 24) { ForEach(RoomListFilter.allCases) { filter in RoomListFiltersEmptyStateView(state: .init(activeFilters: [filter], - userSettings: .volatile())) + userSettings: .mock())) } RoomListFiltersEmptyStateView(state: .init(activeFilters: [.people, .favourites], - userSettings: .volatile())) + userSettings: .mock())) } .padding(.bottom) .previewLayout(.sizeThatFits) diff --git a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift index f71c0bdf1..a11e25731 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/Filters/RoomListFiltersView.swift @@ -92,10 +92,10 @@ struct RoomListFiltersView: View { struct RoomListFiltersView_Previews: PreviewProvider, TestablePreview { static var previews: some View { - RoomListFiltersView(state: .constant(.init(userSettings: .volatile()))) + RoomListFiltersView(state: .constant(.init(userSettings: .mock()))) RoomListFiltersView(state: .constant(.init(activeFilters: [.rooms, .favourites], - userSettings: .volatile()))) + userSettings: .mock()))) RoomListFiltersView(state: .constant(.init(activeFilters: [.lowPriority], - userSettings: .volatile()))) + userSettings: .mock()))) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index d919f7d73..fbd9b0a17 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -232,7 +232,7 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview { return HomeScreenViewModel(userSession: userSession, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init()), bugReportService: BugReportServiceMock(.init()), notificationManager: NotificationManagerMock(), diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift index 1483cfb33..dd7c40acf 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift @@ -145,7 +145,7 @@ struct HomeScreenEmptyStateView_Previews: PreviewProvider, TestablePreview { roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))))))) return HomeScreenViewModel(userSession: userSession, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init()), bugReportService: BugReportServiceMock(.init()), notificationManager: NotificationManagerMock(), diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift index 6d620b8b8..aafa02359 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift @@ -201,7 +201,7 @@ struct HomeScreenInviteCell_Previews: PreviewProvider, TestablePreview { return HomeScreenViewModel(userSession: userSession, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init()), bugReportService: BugReportServiceMock(.init()), notificationManager: NotificationManagerMock(), diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift index f78fa8225..35f1ebd37 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenKnockedCell.swift @@ -133,7 +133,7 @@ struct HomeScreenKnockedCell_Previews: PreviewProvider, TestablePreview { return HomeScreenViewModel(userSession: userSession, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init()), bugReportService: BugReportServiceMock(.init()), notificationManager: NotificationManagerMock(), diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift index d3d181079..b0959d2b7 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRecoveryKeyConfirmationBanner.swift @@ -122,7 +122,7 @@ struct HomeScreenRecoveryKeyConfirmationBanner_Previews: PreviewProvider, Testab return HomeScreenViewModel(userSession: userSession, selectedRoomPublisher: CurrentValueSubject(nil).asCurrentValuePublisher(), - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init()), bugReportService: BugReportServiceMock(.init()), notificationManager: NotificationManagerMock(), diff --git a/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift b/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift index f3ead23b9..7b85c1849 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift @@ -402,7 +402,7 @@ struct JoinRoomScreenPreviewWrapper: Identifiable { self.isSpace = isSpace self.customPreviewName = customPreviewName - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let clientProxy = ClientProxyMock(.init(hideInviteAvatars: hideInviteAvatars)) clientProxy.canJoinRoomWithReturnValue = canJoinRoom diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift index e5e5e798c..c8d512456 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift @@ -432,7 +432,7 @@ extension LocationSharingScreenViewModel { let roomProxy = JoinedRoomProxyMock(.init(members: .allMembers, ownUserID: RoomMemberProxyMock.mockMe.userID)) roomProxy.makeLiveLocationServiceReturnValue = liveLocationServiceMock - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() return LocationSharingScreenViewModel(interactionMode: interactionMode, mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, diff --git a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift index 5ab8a5c3f..d63fc195f 100644 --- a/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/MediaEventsTimelineScreen/View/MediaEventsTimelineScreen.swift @@ -314,7 +314,7 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) } diff --git a/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift b/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift index b9f034846..f8ebf518c 100644 --- a/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift +++ b/ElementX/Sources/Screens/MediaUploadPreviewScreen/View/MediaUploadPreviewScreen.swift @@ -393,7 +393,7 @@ struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview { title: "App Icon.png", shouldShowCaptionWarning: true, galleryEnabled: true, - mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .volatile()), + mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .mock()), timelineController: TimelineControllerMock(.init()), clientProxy: ClientProxyMock(.init()), userIndicatorController: UserIndicatorControllerMock()) diff --git a/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift b/ElementX/Sources/Screens/Onboarding/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift index b5c19445e..3a2689611 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.volatile().analyticsTermsURL : nil) + AnalyticsPromptScreenViewModel(termsURL: showTerms ? UserSettings.mock().analyticsTermsURL : nil) } } diff --git a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift index 3bcd5987f..073669f5c 100644 --- a/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/IdentityConfirmationScreen/View/IdentityConfirmationScreen.swift @@ -142,7 +142,7 @@ struct IdentityConfirmationScreen_Previews: PreviewProvider, TestablePreview { userSession.sessionSecurityStatePublisher = CurrentValuePublisher(.init(verificationState: .unverified, recoveryState: recoveryState)) return IdentityConfirmationScreenViewModel(userSession: userSession, - userSettings: .volatile(), + userSettings: .mock(), userIndicatorController: UserIndicatorControllerMock()) } } diff --git a/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift index 0eca669e5..788822e80 100644 --- a/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift +++ b/ElementX/Sources/Screens/Onboarding/SessionVerificationScreen/View/SessionVerificationScreen.swift @@ -289,7 +289,7 @@ struct SessionVerification_Previews: PreviewProvider, TestablePreview { flow: SessionVerificationScreenFlow = .deviceInitiator) -> some View { let viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(), flow: flow, - userSettings: .volatile(), + userSettings: .mock(), mediaProvider: MediaProviderMock(.init()), verificationState: state) diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift index 095afb438..f4b980f75 100644 --- a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift @@ -83,7 +83,7 @@ struct PinnedEventsTimelineScreen: View { struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = PinnedEventsTimelineScreenViewModel(roomProxy: JoinedRoomProxyMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init())) static let emptyTimelineViewModel = makeTimelineViewModel(timelineItems: []) @@ -106,7 +106,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { let eventIDs = timelineItems.compactMap { ($0 as? EventBasedTimelineItemProtocol)?.id.eventID } let timelineController = TimelineControllerMock(.init(timelineKind: .pinned, timelineItems: timelineItems)) - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.messageMultiSelectEnabled = isSelecting let userSession = UserSessionMock(.init(userSettings: userSettings)) @@ -117,7 +117,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift index be99da3d8..a3aa07276 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift @@ -177,7 +177,7 @@ struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { return RoomDetailsEditScreenViewModel(roomProxy: roomProxy, userSession: UserSessionMock(.init()), - mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .volatile()), + mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .mock()), userIndicatorController: UserIndicatorControllerMock()) } } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index afbe48c22..ad28c803c 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -445,7 +445,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { return .init(roomProxy: roomProxy, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -474,7 +474,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { return .init(roomProxy: roomProxy, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -513,7 +513,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { return .init(roomProxy: roomProxy, userSession: UserSessionMock(.init(clientProxy: clientProxyMock)), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift index ae351b53a..0f30aeef2 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift @@ -852,7 +852,7 @@ extension ComposerToolbarViewModel { completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)), mediaProvider: MediaProviderMock(.init()), mentionDisplayHelper: ComposerMentionDisplayHelper.mock, - userSettings: .volatile(), + userSettings: .mock(), analyticsService: AnalyticsServiceMock(.init()), composerDraftService: ComposerDraftServiceMock(.init())) viewModel.state.bindings.composerFocused = focused diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/RoomAttachmentPicker.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/RoomAttachmentPicker.swift index ae9172ea7..f39fdfeac 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/RoomAttachmentPicker.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/RoomAttachmentPicker.swift @@ -81,7 +81,7 @@ struct RoomAttachmentPicker_Previews: PreviewProvider, TestablePreview { static let viewModel = makeViewModel() static func makeViewModel() -> ComposerToolbarViewModel { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() return ComposerToolbarViewModel(roomProxy: JoinedRoomProxyMock(.init()), wysiwygViewModel: WysiwygComposerViewModel(), diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 8b4d90953..4bc67e182 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -395,7 +395,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview { powerLevelsConfiguration: .init(canUserSendMessage: canSendMessage))) let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock) - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.messageMultiSelectEnabled = isSelecting let userSession = UserSessionMock(.init(userSettings: userSettings)) diff --git a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift index 96246365a..610a77c07 100644 --- a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift +++ b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift @@ -629,31 +629,31 @@ struct SearchScreen_Previews: PreviewProvider, TestablePreview { clientProxy: makeClientProxy(), mediaProvider: MediaProviderMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: UserSettings.volatile()) + userSettings: UserSettings.mock()) static let noResultsViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))), clientProxy: makeClientProxy(), mediaProvider: MediaProviderMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), initialSearchQuery: "John Doe") static let roomsViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))), clientProxy: makeClientProxy(), mediaProvider: MediaProviderMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), initialSearchQuery: "Foundation") static let messagesViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))), clientProxy: makeClientProxy(searchService: makeSearchService(results: .mockResults)), mediaProvider: MediaProviderMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), initialSearchQuery: "Foundation", initialSearchMode: .messages) static let loadingMessagesViewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([]))), clientProxy: makeClientProxy(searchService: makeSearchService(paginationState: .loading)), mediaProvider: MediaProviderMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), initialSearchQuery: "Foundation", initialSearchMode: .messages) @@ -708,7 +708,7 @@ struct SearchScreen_Previews: PreviewProvider, TestablePreview { } private static func makeUserSettings(breadcrumbs: [SearchBreadcrumb]) -> UserSettings { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.searchBreadcrumbs = breadcrumbs return userSettings } diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift index 2e896e7c8..8d665c1c9 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift @@ -235,7 +235,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { joinRule: .invite)), clientProxy: ClientProxyMock(.init()), userIndicatorController: UserIndicatorControllerMock(), - userSettings: .volatile(), + userSettings: .mock(), appHooks: AppHooks()) static let publicViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, @@ -245,7 +245,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { isVisibleInPublicDirectory: true)), clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), userIndicatorController: UserIndicatorControllerMock(), - userSettings: .volatile(), + userSettings: .mock(), appHooks: AppHooks()) static let publicNoAddressViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, @@ -253,11 +253,11 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { joinRule: .public)), clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), userIndicatorController: UserIndicatorControllerMock(), - userSettings: .volatile(), + userSettings: .mock(), appHooks: AppHooks()) static let singleSpaceMembersViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let space = [SpaceServiceRoom].mockSingleRoom[0] @@ -274,7 +274,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { }() static let multipleSpacesMembersViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let spaces = [SpaceServiceRoom].mockJoinedSpaces @@ -291,7 +291,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { }() static let askToJoinViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.knockingEnabled = true return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, @@ -306,7 +306,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { }() static let singleAskToJoinSpaceMembersViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.knockingEnabled = true let space = [SpaceServiceRoom].mockSingleRoom[0] @@ -324,7 +324,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { }() static let multipleAskToJoinSpacesMembersViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.knockingEnabled = true let spaces = [SpaceServiceRoom].mockJoinedSpaces @@ -349,7 +349,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview { isVisibleInPublicDirectory: true)), clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")), userIndicatorController: UserIndicatorControllerMock(), - userSettings: .volatile(), + userSettings: .mock(), appHooks: AppHooks()) static var previews: some View { diff --git a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift index 18f1e5c35..89dc584f0 100644 --- a/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/AnalyticsSettingsScreen/View/AnalyticsSettingsScreen.swift @@ -39,7 +39,7 @@ struct AnalyticsSettingsScreen: View { struct AnalyticsSettingsScreen_Previews: PreviewProvider, TestablePreview { static var previews: some View { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let viewModel = AnalyticsSettingsScreenViewModel(userSettings: userSettings, analytics: AnalyticsServiceMock(.init())) AnalyticsSettingsScreen(context: viewModel.context) diff --git a/ElementX/Sources/Screens/Settings/LegalInformationScreen/View/LegalInformationScreen.swift b/ElementX/Sources/Screens/Settings/LegalInformationScreen/View/LegalInformationScreen.swift index ac875d059..7e2ee2d49 100644 --- a/ElementX/Sources/Screens/Settings/LegalInformationScreen/View/LegalInformationScreen.swift +++ b/ElementX/Sources/Screens/Settings/LegalInformationScreen/View/LegalInformationScreen.swift @@ -33,7 +33,7 @@ struct LegalInformationScreen: View { // MARK: - Previews struct LegalInformationScreen_Previews: PreviewProvider, TestablePreview { - static let viewModel = LegalInformationScreenViewModel(userSettings: .volatile()) + static let viewModel = LegalInformationScreenViewModel(userSettings: .mock()) static var previews: some View { LegalInformationScreen(context: viewModel.context) } diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift index 713a61f51..8f5d1064e 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsScreen/View/NotificationSettingsScreen.swift @@ -262,7 +262,7 @@ struct NotificationSettingsScreen: View { @available(iOS 26.0, *) struct NotificationSettingsScreen_Previews: PreviewProvider, TestablePreview { static let viewModel: NotificationSettingsScreenViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let notificationCenter = UserNotificationCenterMock() notificationCenter.authorizationStatusReturnValue = .notDetermined let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) @@ -290,7 +290,7 @@ struct NotificationSettingsScreen_Previews: PreviewProvider, TestablePreview { }() static let viewModelConfigurationMismatch: NotificationSettingsScreenViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let notificationCenter = UserNotificationCenterMock() notificationCenter.authorizationStatusReturnValue = .notDetermined let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index b3fed9794..31a025ab9 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -321,7 +321,7 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview { deviceID: "AAAAAAAAAAA", displayName: "Alice Liddell", status: .mockFocussing)))) - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.multiAccountEnabled = true return SettingsScreenViewModel(userSession: userSession, userSettings: userSettings, diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift index c7ced1676..20725ad83 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift @@ -145,7 +145,7 @@ struct UserDetailsEditScreen_Previews: PreviewProvider, TestablePreview { canChangeDisplayName: canChangeProfile)))) return UserDetailsEditScreenViewModel(userSession: userSession, - mediaUploadingPreprocessor: .init(userSettings: .volatile()), + mediaUploadingPreprocessor: .init(userSettings: .mock()), userIndicatorController: UserIndicatorControllerMock()) } } diff --git a/ElementX/Sources/Screens/Spaces/SpaceSettingsScreen/View/SpaceSettingsScreen.swift b/ElementX/Sources/Screens/Spaces/SpaceSettingsScreen/View/SpaceSettingsScreen.swift index 38e5b2500..f39301a97 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceSettingsScreen/View/SpaceSettingsScreen.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceSettingsScreen/View/SpaceSettingsScreen.swift @@ -132,7 +132,7 @@ struct SpaceSettingsScreen_Previews: PreviewProvider, TestablePreview { canonicalAlias: "#space:matrix.org", members: members)), userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), diff --git a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift index edb6f6814..872fe2c2a 100644 --- a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift +++ b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift @@ -154,7 +154,7 @@ struct StartChatScreen: View { struct StartChatScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com")))) let userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 30f837a5c..e476aa79e 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -1306,7 +1306,7 @@ extension TimelineViewModel { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) } diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift index 09611dccc..c0fedbfd4 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift @@ -340,7 +340,7 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview { isViewSourceEnabled: true, areThreadsEnabled: true, timelineKind: .live, - emojiProvider: EmojiProvider(userSettings: .volatile())) + emojiProvider: EmojiProvider(userSettings: .mock())) guard let actions = provider.makeActions() else { return nil } if var textItem = item as? TextRoomTimelineItem { diff --git a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift index b309ce8e4..76b6eb2ba 100644 --- a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift +++ b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift @@ -52,7 +52,7 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) }() diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index 8c80e8fc4..cdd1419dc 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -426,7 +426,7 @@ private extension TimelineItemKeyForwarder { struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview { static let viewModel: TimelineViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.threadsEnabled = true let userSession = UserSessionMock(.init(userSettings: userSettings)) @@ -445,7 +445,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview }() static let viewModelWithPins: TimelineViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.threadsEnabled = true let userSession = UserSessionMock(.init(userSettings: userSettings)) @@ -466,7 +466,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static let selectingViewModel: TimelineViewModel = { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.messageMultiSelectEnabled = true let userSession = UserSessionMock(.init(userSettings: userSettings)) diff --git a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift index cc9965a19..31bbea897 100644 --- a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift @@ -86,7 +86,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift index 39d415b1a..96fac1adf 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/HighlightedTimelineItemModifier.swift @@ -97,7 +97,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift index f4220f11c..20d0f5f17 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift @@ -162,7 +162,7 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) diff --git a/ElementX/Sources/Services/Authentication/AuthenticationService.swift b/ElementX/Sources/Services/Authentication/AuthenticationService.swift index b822348f6..85e2d1f76 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationService.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationService.swift @@ -232,11 +232,10 @@ class AuthenticationService: AuthenticationServiceProtocol { let qrCodeHandler = client.newLoginWithQrCodeHandler(oauthConfiguration: appSettings.oAuthConfiguration.rustValue) try await qrCodeHandler.scan(qrCodeData: qrData, progressListener: listener) - // Since the QR code login flow includes verification. - appSettings.hasRunIdentityConfirmationOnboarding = true - switch await userSession(for: client) { case .success(let userSession): + // Since the QR code login flow includes verification. + userSession.userSettings.hasRunIdentityConfirmationOnboarding = true progressSubject.send(.signedIn(userSession)) case .failure(let error): progressSubject.send(completion: .failure(error)) @@ -367,7 +366,7 @@ class AuthenticationService: AuthenticationServiceProtocol { MXLog.info("Classic app account secrets imported.") // Importing the secrets automatically verifies the session. - appSettings.hasRunIdentityConfirmationOnboarding = true + appSettings.userSettings(for: classicAppAccount.userID).hasRunIdentityConfirmationOnboarding = true } catch { MXLog.error("Failed to import secrets for Classic app account: \(error)") } diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index e9ff464ff..bc4413d93 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -59,7 +59,7 @@ class UserSessionStore: UserSessionStoreProtocol { return .failure(.missingCredentials) } - let userSettings = UserSettings(appSettings: appSettings, accountSettings: .init()) + let userSettings = appSettings.userSettings(for: credentials.userID) switch await restorePreviousLogin(credentials, userSettings: userSettings) { case .success(let clientProxy): @@ -83,7 +83,7 @@ class UserSessionStore: UserSessionStoreProtocol { do { let session = try client.session() let userID = try client.userId() - let userSettings = UserSettings(appSettings: appSettings, accountSettings: .init()) + let userSettings = appSettings.userSettings(for: userID) let clientProxy = try await setupProxyForClient(client, userSettings: userSettings) keychainController.setRestorationToken(RestorationToken(session: session, diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 67bf0b204..cc0e956c7 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -298,7 +298,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "Some room name", avatarURL: nil)), timelineController: TimelineControllerMock(.init()), mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -318,7 +318,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -338,7 +338,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -357,7 +357,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "New room", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -382,7 +382,7 @@ class MockScreen: Identifiable { roomProxy: roomProxy, timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -407,7 +407,7 @@ class MockScreen: Identifiable { roomProxy: roomProxy, timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -431,7 +431,7 @@ class MockScreen: Identifiable { roomProxy: roomProxy, timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -457,7 +457,7 @@ class MockScreen: Identifiable { roomProxy: roomProxy, timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -482,7 +482,7 @@ class MockScreen: Identifiable { roomProxy: roomProxy, timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -503,7 +503,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "Timeline highlight", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -537,7 +537,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -558,7 +558,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -579,7 +579,7 @@ class MockScreen: Identifiable { roomProxy: JoinedRoomProxyMock(.init(name: "Polls timeline", avatarURL: .mockMXCAvatar)), timelineController: timelineController, mediaPlayerProvider: MediaPlayerProviderMock(), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()), ongoingCallRoomIDPublisher: .init(.init(nil)), @@ -597,14 +597,15 @@ class MockScreen: Identifiable { requestDelay: .seconds(5)) let parameters = SessionVerificationScreenCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy, flow: .deviceInitiator, - userSettings: .volatile(), + userSettings: .mock(), mediaProvider: MediaProviderMock(.init())) return SessionVerificationScreenCoordinator(parameters: parameters) case .userSessionScreen, .userSessionScreenReply, .userSessionSpacesFlow: - let appSettings: AppSettings = appSettings - appSettings.hasRunIdentityConfirmationOnboarding = true - appSettings.hasRunNotificationPermissionsOnboarding = true - appSettings.analyticsConsentState = .optedOut + let userID = "@mock:client.com" + let userSettings = appSettings.userSettings(for: userID) + userSettings.hasRunIdentityConfirmationOnboarding = true + userSettings.hasRunNotificationPermissionsOnboarding = true + userSettings.analyticsConsentState = .optedOut let roomSummaries: [RoomSummary] = if id == .userSessionSpacesFlow { [[RoomSummary].mockSpaceInvites[0]] + .mockRooms @@ -612,7 +613,7 @@ class MockScreen: Identifiable { .mockRooms } - let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", + let clientProxy = ClientProxyMock(.init(userID: userID, deviceID: "MOCKCLIENT", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(roomSummaries))), spaceServiceConfiguration: .init(topLevelSpaces: .mockSpaceList.filter(\.isSpace) + .mockSingleRoom), @@ -634,8 +635,7 @@ class MockScreen: Identifiable { let timelineControllerFactory = TimelineControllerFactoryMock(.init(timelineController: timelineController)) - let userSession = UserSessionMock(.init(clientProxy: clientProxy, - userSettings: UserSettings(appSettings: appSettings, accountSettings: .init()))) + let userSession = UserSessionMock(.init(clientProxy: clientProxy, userSettings: userSettings)) let flowCoordinator = UserSessionFlowCoordinator(isNewLogin: false, navigationRootCoordinator: navigationRootCoordinator, appLockService: AppLockService(keychainController: KeychainControllerMock(), @@ -826,14 +826,15 @@ class MockScreen: Identifiable { navigationRootCoordinator.setSheetCoordinator(navigationStackCoordinator) return PlaceholderScreenCoordinator(hideBrandChrome: false) case .autoUpdatingTimeline: - let userSettings = UserSettings(appSettings: appSettings, accountSettings: .init()) - appSettings.hasRunIdentityConfirmationOnboarding = true - appSettings.hasRunNotificationPermissionsOnboarding = true - appSettings.analyticsConsentState = .optedOut + let userID = "@mock:client.com" + let userSettings = appSettings.userSettings(for: userID) + userSettings.hasRunIdentityConfirmationOnboarding = true + userSettings.hasRunNotificationPermissionsOnboarding = true + userSettings.analyticsConsentState = .optedOut let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator(hideBrandChrome: false)) navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator) - let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))))) + let clientProxy = ClientProxyMock(.init(userID: userID, roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))))) let userSession = UserSessionMock(.init(clientProxy: clientProxy, userSettings: userSettings)) let roomProxy = JoinedRoomProxyMock(.init(id: "whatever", name: "okay", shouldUseAutoUpdatingTimeline: true)) @@ -905,6 +906,6 @@ class MockScreen: Identifiable { attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")), mediaProvider: MediaProviderMock(.init()), - userSettings: .volatile()) + userSettings: .mock()) } } diff --git a/Enterprise b/Enterprise index 4c72bf375..5628ab611 160000 --- a/Enterprise +++ b/Enterprise @@ -1 +1 @@ -Subproject commit 4c72bf3758485fae2e5251e7f283f07a85569111 +Subproject commit 5628ab611fe8119e0fe22f72900e408e5ef396f3 diff --git a/UnitTests/Sources/AccountUserDefaultsTests.swift b/UnitTests/Sources/AccountUserDefaultsTests.swift new file mode 100644 index 000000000..fb81f7310 --- /dev/null +++ b/UnitTests/Sources/AccountUserDefaultsTests.swift @@ -0,0 +1,93 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +@testable import ElementX +import Testing + +struct AccountUserDefaultsTests { + private let store = VolatileUserDefaults() + private let aliceDefaults: AccountUserDefaults + private let bobDefaults: AccountUserDefaults + + init() { + aliceDefaults = AccountUserDefaults(userID: "@alice:example.com", store: store) + bobDefaults = AccountUserDefaults(userID: "@bob:example.com", store: store) + } + + @Test + func dictionaryRepresentation() { + // Given a store containing values for Alice, Bob and the app, all sharing a key. + aliceDefaults.set("one", forKey: "first") + aliceDefaults.set(2, forKey: "second") + bobDefaults.set("bob", forKey: "first") + store.set("global", forKey: "first") + #expect(aliceDefaults.object(forKey: "first") as? String == "one") + #expect(aliceDefaults.object(forKey: "second") as? Int == 2) + #expect(bobDefaults.object(forKey: "first") as? String == "bob") + #expect(store.object(forKey: "first") as? String == "global") + + // When getting the dictionary representation of Alice's defaults. + let representation = aliceDefaults.dictionaryRepresentation() + + // Then it should only contain Alice's values. + #expect(representation.count == 2) + #expect(representation["first"] as? String == "one") + #expect(representation["second"] as? Int == 2) + } + + @Test + func reset() { + // Given a store containing values for Alice, Bob and the app, all sharing a key. + aliceDefaults.set("one", forKey: "first") + aliceDefaults.set(2, forKey: "second") + bobDefaults.set("bob", forKey: "first") + store.set("global", forKey: "first") + #expect(aliceDefaults.object(forKey: "first") as? String == "one") + #expect(aliceDefaults.object(forKey: "second") as? Int == 2) + #expect(bobDefaults.object(forKey: "first") as? String == "bob") + #expect(store.object(forKey: "first") as? String == "global") + + // When resetting Alice's defaults. + aliceDefaults.reset() + + // Then only Alice's values should be removed. + #expect(aliceDefaults.object(forKey: "first") == nil) + #expect(aliceDefaults.object(forKey: "second") == nil) + #expect(bobDefaults.object(forKey: "first") as? String == "bob") + #expect(store.object(forKey: "first") as? String == "global") + } + + @Test + func migrateAppSettingsValue() { + // Given a store containing an app value. + store.set("global", forKey: "first") + #expect(store.object(forKey: "first") as? String == "global") + #expect(aliceDefaults.object(forKey: "first") == nil) + #expect(bobDefaults.object(forKey: "first") == nil) + + // When migrating that value into Alice's defaults. + aliceDefaults.migrateAppSettingsValue(forKey: "first") + + // Then the value should be moved to Alice's defaults only. + #expect(aliceDefaults.object(forKey: "first") as? String == "global") + #expect(store.object(forKey: "first") == nil) + #expect(bobDefaults.object(forKey: "first") == nil) + } + + @Test + func migrateUnsetAppSettingsValue() { + // Given an empty store. + #expect(store.storage.isEmpty) + + // When migrating a value that isn't set into Alice's defaults. + aliceDefaults.migrateAppSettingsValue(forKey: "first") + + // Then nothing should be written to the store. + #expect(aliceDefaults.object(forKey: "first") == nil) + #expect(store.storage.isEmpty) + } +} diff --git a/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift b/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift index 86ce54456..f9a31c887 100644 --- a/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/AnalyticsSettingsScreenViewModelTests.swift @@ -23,7 +23,7 @@ final class AnalyticsSettingsScreenViewModelTests { client.isRunning = false analytics = AnalyticsService(client: client, appSettings: appSettings) - viewModel = AnalyticsSettingsScreenViewModel(userSettings: UserSettings(appSettings: appSettings, accountSettings: .init()), + viewModel = AnalyticsSettingsScreenViewModel(userSettings: appSettings.userSettings(for: "@me:matrix.org"), analytics: analytics) context = viewModel.context } diff --git a/UnitTests/Sources/BugReportServiceTests.swift b/UnitTests/Sources/BugReportServiceTests.swift index 858f6d840..5aa06e695 100644 --- a/UnitTests/Sources/BugReportServiceTests.swift +++ b/UnitTests/Sources/BugReportServiceTests.swift @@ -17,7 +17,7 @@ final class BugReportServiceTests { var bugReportService: BugReportServiceProtocol! init() throws { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() userSettings.bugReportRageshakeURL.reset() let bugReportServiceMock = BugReportServiceMock() diff --git a/UnitTests/Sources/ChatsTabFlowCoordinatorTests.swift b/UnitTests/Sources/ChatsTabFlowCoordinatorTests.swift index 9e4f77e83..e707ce810 100644 --- a/UnitTests/Sources/ChatsTabFlowCoordinatorTests.swift +++ b/UnitTests/Sources/ChatsTabFlowCoordinatorTests.swift @@ -29,7 +29,7 @@ struct ChatsTabFlowCoordinatorTests { } init() async throws { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))))) timelineControllerFactory = TimelineControllerFactoryMock(.init()) diff --git a/UnitTests/Sources/ComposerToolbarViewModelTests.swift b/UnitTests/Sources/ComposerToolbarViewModelTests.swift index bc1bb60ae..96d192c62 100644 --- a/UnitTests/Sources/ComposerToolbarViewModelTests.swift +++ b/UnitTests/Sources/ComposerToolbarViewModelTests.swift @@ -84,7 +84,7 @@ final class ComposerToolbarViewModelTests { .init(suggestionType: .user(.init(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: nil)), range: .init(), rawSuggestionText: "")] let mockCompletionSuggestionService = CompletionSuggestionServiceMock(configuration: .init(suggestions: suggestions)) - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() viewModel = ComposerToolbarViewModel(roomProxy: JoinedRoomProxyMock(.init()), wysiwygViewModel: wysiwygViewModel, @@ -803,7 +803,7 @@ final class ComposerToolbarViewModelTests { let mockSubject = CurrentValueSubject<[IdentityStatusChange], Never>([]) roomProxyMock.identityStatusChangesPublisher = mockSubject.asCurrentValuePublisher() - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() viewModel = ComposerToolbarViewModel(roomProxy: roomProxyMock, wysiwygViewModel: wysiwygViewModel, @@ -846,7 +846,7 @@ final class ComposerToolbarViewModelTests { roomProxyMock.identityStatusChangesPublisher = mockSubject.asCurrentValuePublisher() - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() viewModel = ComposerToolbarViewModel(roomProxy: roomProxyMock, wysiwygViewModel: wysiwygViewModel, @@ -886,7 +886,7 @@ final class ComposerToolbarViewModelTests { } roomProxyMock.identityStatusChangesPublisher = CurrentValueSubject([IdentityStatusChange(userId: "@alice:localhost", changedTo: .pinViolation)]).asCurrentValuePublisher() - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() viewModel = ComposerToolbarViewModel(roomProxy: roomProxyMock, wysiwygViewModel: wysiwygViewModel, @@ -923,7 +923,7 @@ final class ComposerToolbarViewModelTests { draftServiceMock.loadDraftClosure = loadDraftClosure } - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() viewModel = ComposerToolbarViewModel(initialText: initialText, roomProxy: JoinedRoomProxyMock(.init()), diff --git a/UnitTests/Sources/CreateRoomViewModelTests.swift b/UnitTests/Sources/CreateRoomViewModelTests.swift index 6dcc32e5a..7021d5439 100644 --- a/UnitTests/Sources/CreateRoomViewModelTests.swift +++ b/UnitTests/Sources/CreateRoomViewModelTests.swift @@ -24,7 +24,7 @@ final class CreateRoomScreenViewModelTests { } init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() } isolated deinit { diff --git a/UnitTests/Sources/EmojiPickerScreenViewModelTests.swift b/UnitTests/Sources/EmojiPickerScreenViewModelTests.swift index b869dd14f..f9f82b71c 100644 --- a/UnitTests/Sources/EmojiPickerScreenViewModelTests.swift +++ b/UnitTests/Sources/EmojiPickerScreenViewModelTests.swift @@ -55,7 +55,7 @@ struct EmojiPickerScreenViewModelTests { emojiPickerStream = stream viewModel = EmojiPickerScreenViewModel(selectedEmojis: selectedEmojis, - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), continuation: continuation) } } diff --git a/UnitTests/Sources/EmojiProviderTests.swift b/UnitTests/Sources/EmojiProviderTests.swift index 4fedb5fc0..c2657223e 100644 --- a/UnitTests/Sources/EmojiProviderTests.swift +++ b/UnitTests/Sources/EmojiProviderTests.swift @@ -19,7 +19,7 @@ struct EmojiProviderTests { let emojiLoaderMock = EmojiLoaderMock() emojiLoaderMock.categories = [category] - let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .volatile()) + let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .mock()) let categories = await emojiProvider.categories() #expect(emojiLoaderMock.categories == categories) @@ -33,7 +33,7 @@ struct EmojiProviderTests { let emojiLoaderMock = EmojiLoaderMock() emojiLoaderMock.categories = [category] - let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .volatile()) + let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .mock()) let categories = await emojiProvider.categories(searchString: "") #expect(emojiLoaderMock.categories == categories) @@ -51,7 +51,7 @@ struct EmojiProviderTests { let emojiLoaderMock = EmojiLoaderMock() emojiLoaderMock.categories = categoriesForFirstLoad - let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .volatile()) + let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .mock()) _ = await emojiProvider.categories() emojiLoaderMock.categories = categoriesForSecondLoad @@ -82,7 +82,7 @@ struct EmojiProviderTests { let emojiLoaderMock = EmojiLoaderMock() emojiLoaderMock.categories = categories - let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .volatile()) + let emojiProvider = EmojiProvider(loader: emojiLoaderMock, userSettings: .mock()) _ = await emojiProvider.categories() let result = await emojiProvider.categories(searchString: searchString) diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 15d30bd0c..8ed6465df 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -25,7 +25,7 @@ final class HomeScreenViewModelTests { var cancellables = Set() init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() } @Test diff --git a/UnitTests/Sources/IdentityConfirmationScreenViewModelTests.swift b/UnitTests/Sources/IdentityConfirmationScreenViewModelTests.swift index 47e91edb4..7b6bbc5f5 100644 --- a/UnitTests/Sources/IdentityConfirmationScreenViewModelTests.swift +++ b/UnitTests/Sources/IdentityConfirmationScreenViewModelTests.swift @@ -109,7 +109,7 @@ struct IdentityConfirmationScreenViewModelTests { userSession.sessionSecurityStatePublisher = securityStateSubject.asCurrentValuePublisher() viewModel = IdentityConfirmationScreenViewModel(userSession: userSession, - userSettings: .volatile(), + userSettings: .mock(), userIndicatorController: UserIndicatorControllerMock()) } } diff --git a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift index 3005328f3..b402bb92d 100644 --- a/UnitTests/Sources/JoinRoomScreenViewModelTests.swift +++ b/UnitTests/Sources/JoinRoomScreenViewModelTests.swift @@ -34,7 +34,7 @@ final class JoinRoomScreenViewModelTests { private let userSettings: UserSettings init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() } isolated deinit { diff --git a/UnitTests/Sources/LiveLocationManagerTests.swift b/UnitTests/Sources/LiveLocationManagerTests.swift index b220d4b81..ec623263b 100644 --- a/UnitTests/Sources/LiveLocationManagerTests.swift +++ b/UnitTests/Sources/LiveLocationManagerTests.swift @@ -228,7 +228,7 @@ final class LiveLocationManagerTests { } private func setUp(accuracyAuthorization: CLAccuracyAuthorization = .fullAccuracy) { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() clientProxy = ClientProxyMock(.init()) beaconInfoSubject = PassthroughSubject() clientProxy.liveLocationOwnInfoUpdatesPublisher = beaconInfoSubject.eraseToAnyPublisher() diff --git a/UnitTests/Sources/LocationSharingScreenViewModelTests.swift b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift index ea53d5da5..c041de97f 100644 --- a/UnitTests/Sources/LocationSharingScreenViewModelTests.swift +++ b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift @@ -417,7 +417,7 @@ struct LocationSharingScreenViewModelTests { let roomProxyMock = JoinedRoomProxyMock(.init(members: .allMembers)) roomProxyMock.makeLiveLocationServiceReturnValue = liveLocationServiceMock - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() viewModel = LocationSharingScreenViewModel(interactionMode: .viewLive(sender: nil, initialLiveLocationShare: nil), mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, @@ -630,7 +630,7 @@ struct LocationSharingScreenViewModelTests { private mutating func setupViewModel(liveLocationManagerMock: LiveLocationManagerMock, members: [RoomMemberProxyMock] = .allMembersAsAdmin) { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() timelineProxy = TimelineProxyMock(.init()) viewModel = LocationSharingScreenViewModel(interactionMode: .picker(shouldShowLiveLocationOption: true), mapURLBuilder: userSettings.mapTilerConfiguration.publisher.value, @@ -649,7 +649,7 @@ struct LocationSharingScreenViewModelTests { liveLocationsSubject: CurrentValueSubject<[LiveLocationShare], Never>, members: [RoomMemberProxyMock] = .allMembers, isSharingLiveLocationFromThisDevice: Bool = false) { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() if isSharingLiveLocationFromThisDevice { userSettings.liveLocationSharingSessionsByRoomID[Self.roomID] = .init(eventID: "$event:matrix.org", expirationDate: .distantFuture) } diff --git a/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift b/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift index 63c68d96e..a8635e0e5 100644 --- a/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift +++ b/UnitTests/Sources/MediaUploadPreviewScreenViewModelTests.swift @@ -28,7 +28,7 @@ final class MediaUploadPreviewScreenViewModelTests { } init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() userSettings.optimizeMediaUploads = false userIndicatorController = UserIndicatorControllerMock() diff --git a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift index ae5cf6148..063feb76a 100644 --- a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift +++ b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift @@ -17,7 +17,7 @@ final class MediaUploadingPreprocessorTests { var mediaUploadingPreprocessor: MediaUploadingPreprocessor! init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() userSettings.optimizeMediaUploads = false mediaUploadingPreprocessor = MediaUploadingPreprocessor(userSettings: userSettings) } diff --git a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift index c831f4e4a..14cf1f311 100644 --- a/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/NotificationSettingsScreenViewModelTests.swift @@ -23,7 +23,7 @@ struct NotificationSettingsScreenViewModelTests { init() throws { userNotificationCenter = UserNotificationCenterMock() userNotificationCenter.authorizationStatusReturnValue = .authorized - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() notificationSettingsProxy = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()) notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .allMessages notificationSettingsProxy.isRoomMentionEnabledReturnValue = true diff --git a/UnitTests/Sources/NotificationToneManagerTests.swift b/UnitTests/Sources/NotificationToneManagerTests.swift index 71a9243c3..ab88b1842 100644 --- a/UnitTests/Sources/NotificationToneManagerTests.swift +++ b/UnitTests/Sources/NotificationToneManagerTests.swift @@ -18,7 +18,7 @@ struct NotificationToneManagerTests { private let manager: NotificationToneManager init() { - manager = NotificationToneManager(userSettings: .volatile()) + manager = NotificationToneManager(userSettings: .mock()) } // MARK: - Deletion diff --git a/UnitTests/Sources/PillContextTests.swift b/UnitTests/Sources/PillContextTests.swift index 5068739cd..734b32c11 100644 --- a/UnitTests/Sources/PillContextTests.swift +++ b/UnitTests/Sources/PillContextTests.swift @@ -28,7 +28,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body))) @@ -60,7 +60,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .user(userID: id), font: .preferredFont(forTextStyle: .body))) @@ -84,7 +84,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body))) @@ -108,7 +108,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomID("1"), font: .preferredFont(forTextStyle: .body))) @@ -131,7 +131,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomID("1"), font: .preferredFont(forTextStyle: .body))) @@ -158,7 +158,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomAlias("#foundation-and-empire:matrix.org"), font: .preferredFont(forTextStyle: .body))) @@ -181,7 +181,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .roomAlias("#foundation-and-empire:matrix.org"), font: .preferredFont(forTextStyle: .body))) @@ -206,7 +206,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomID("1")), font: .preferredFont(forTextStyle: .body))) @@ -229,7 +229,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomID("1")), font: .preferredFont(forTextStyle: .body))) @@ -256,7 +256,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomAlias("#foundation-and-empire:matrix.org")), font: .preferredFont(forTextStyle: .body))) @@ -279,7 +279,7 @@ struct PillContextTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) let context = PillContext(timelineContext: mock.context, data: PillTextAttachmentData(type: .event(room: .roomAlias("#foundation-and-empire:matrix.org")), font: .preferredFont(forTextStyle: .body))) diff --git a/UnitTests/Sources/PresenceServiceTests.swift b/UnitTests/Sources/PresenceServiceTests.swift index 9430545fd..b4996a36a 100644 --- a/UnitTests/Sources/PresenceServiceTests.swift +++ b/UnitTests/Sources/PresenceServiceTests.swift @@ -21,7 +21,7 @@ struct PresenceServiceTests { init() { clientProxy = ClientProxyMock(.init()) - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() notificationCenter = NotificationCenter() clientProxy.configurePresenceSendImmediatelyClosure = { [sendsSubject] presence, sendImmediately in diff --git a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift index dcfc95de1..27cceaec0 100644 --- a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift @@ -167,7 +167,7 @@ struct RoomDetailsEditScreenViewModelTests { userIndicatorController = UserIndicatorControllerMock() viewModel = .init(roomProxy: JoinedRoomProxyMock(roomProxyConfiguration), userSession: UserSessionMock(.init()), - mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .volatile()), + mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .mock()), userIndicatorController: userIndicatorController) } } diff --git a/UnitTests/Sources/RoomDetailsScreenViewModelTests.swift b/UnitTests/Sources/RoomDetailsScreenViewModelTests.swift index 572a1ec08..c109fffa3 100644 --- a/UnitTests/Sources/RoomDetailsScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsScreenViewModelTests.swift @@ -30,7 +30,7 @@ struct RoomDetailsScreenViewModelTests { notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -44,7 +44,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers, joinRule: .public)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -65,7 +65,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -87,7 +87,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -146,7 +146,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -168,7 +168,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -201,7 +201,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init(clientProxy: clientProxy)), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -233,7 +233,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -266,7 +266,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init(clientProxy: clientProxy)), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -300,7 +300,7 @@ struct RoomDetailsScreenViewModelTests { powerLevelsConfiguration: .init(canUserInvite: false))) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -318,7 +318,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", members: mockedMembers, joinRule: .public)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -367,7 +367,7 @@ struct RoomDetailsScreenViewModelTests { viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -403,7 +403,7 @@ struct RoomDetailsScreenViewModelTests { viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -439,7 +439,7 @@ struct RoomDetailsScreenViewModelTests { viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -460,7 +460,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -481,7 +481,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -500,7 +500,7 @@ struct RoomDetailsScreenViewModelTests { notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneThrowableError = NotificationSettingsError.Generic(msg: "error") viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -709,7 +709,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, knockRequestsState: .loaded(mockedRequests), joinRule: .knock)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -731,7 +731,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, knockRequestsState: .loading, joinRule: .knock)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -755,7 +755,7 @@ struct RoomDetailsScreenViewModelTests { powerLevelsConfiguration: .init(canUserInvite: false))) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -779,7 +779,7 @@ struct RoomDetailsScreenViewModelTests { roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: true, members: mockedMembers, knockRequestsState: .loaded(mockedRequests), joinRule: .knock)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), @@ -807,7 +807,7 @@ struct RoomDetailsScreenViewModelTests { viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, userSession: UserSessionMock(.init()), - userSettings: UserSettings.volatile(), + userSettings: UserSettings.mock(), appHooks: AppHooks(), analyticsService: AnalyticsServiceMock(.init()), userIndicatorController: UserIndicatorControllerMock(), diff --git a/UnitTests/Sources/RoomFlowCoordinatorTests.swift b/UnitTests/Sources/RoomFlowCoordinatorTests.swift index 19b4680b6..8111dd1f5 100644 --- a/UnitTests/Sources/RoomFlowCoordinatorTests.swift +++ b/UnitTests/Sources/RoomFlowCoordinatorTests.swift @@ -22,7 +22,7 @@ final class RoomFlowCoordinatorTests { private let userSettings: UserSettings init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() } @Test diff --git a/UnitTests/Sources/RoomListFiltersStateTests.swift b/UnitTests/Sources/RoomListFiltersStateTests.swift index 1e662209a..28f6a5570 100644 --- a/UnitTests/Sources/RoomListFiltersStateTests.swift +++ b/UnitTests/Sources/RoomListFiltersStateTests.swift @@ -17,7 +17,7 @@ final class RoomListFiltersStateTests { let defaultFilters = RoomListFilter.allCases.filter { $0 != .mentions && $0 != .lowPriority } init() { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() state = RoomListFiltersState(userSettings: userSettings) } diff --git a/UnitTests/Sources/RoomMembersFlowCoordinatorTests.swift b/UnitTests/Sources/RoomMembersFlowCoordinatorTests.swift index 021ffb06b..f89fb52ee 100644 --- a/UnitTests/Sources/RoomMembersFlowCoordinatorTests.swift +++ b/UnitTests/Sources/RoomMembersFlowCoordinatorTests.swift @@ -51,7 +51,7 @@ struct RoomMembersFlowCoordinatorTests { let clientProxy = ClientProxyMock(.init()) clientProxy.directRoomForUserIDReturnValue = .success(nil) - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let flowParameters = CommonFlowParameters(userSession: UserSessionMock(.init(clientProxy: clientProxy)), bugReportService: BugReportServiceMock(.init()), diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index a601d9192..78d00084b 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -153,7 +153,7 @@ final class RoomScreenViewModelTests { @Test func pinnedEventsBannerThreadedSelection() async throws { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.threadsEnabled = true let userSession = UserSessionMock(.init(userSettings: userSettings)) diff --git a/UnitTests/Sources/RoomSummaryProviderTests.swift b/UnitTests/Sources/RoomSummaryProviderTests.swift index 663c0cf93..5cb5b3dc2 100644 --- a/UnitTests/Sources/RoomSummaryProviderTests.swift +++ b/UnitTests/Sources/RoomSummaryProviderTests.swift @@ -122,7 +122,7 @@ final class RoomSummaryProviderTests { // MARK: - Helpers private func setup(isLowPriorityFilterEnabled: Bool = false, includesOnlyJoinedRooms: Bool = false, setsRoomList: Bool = true) { - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() userSettings.lowPriorityFilterEnabled = isLowPriorityFilterEnabled let stateEventStringBuilder = RoomStateEventStringBuilder(userID: "@me:matrix.org") diff --git a/UnitTests/Sources/SearchScreenViewModelTests.swift b/UnitTests/Sources/SearchScreenViewModelTests.swift index 7f8eb1401..6623c6765 100644 --- a/UnitTests/Sources/SearchScreenViewModelTests.swift +++ b/UnitTests/Sources/SearchScreenViewModelTests.swift @@ -54,7 +54,7 @@ struct SearchScreenViewModelTests { staticRoomListSubject.value.first { $0.id == identifier } } - userSettings = UserSettings.volatile() + userSettings = UserSettings.mock() viewModel = SearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))), clientProxy: clientProxy, diff --git a/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift b/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift index 42819743c..a1e149223 100644 --- a/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift +++ b/UnitTests/Sources/SecurityAndPrivacyScreenViewModelTests.swift @@ -457,7 +457,7 @@ final class SecurityAndPrivacyScreenViewModelTests { topLevelSpaces: [SpaceServiceRoom] = [], joinRule: ElementX.JoinRule, forceDisableE2EE: Bool = false) { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.knockingEnabled = true userSettings.forceDisableE2EE.applyRemoteValue(forceDisableE2EE) roomProxy = JoinedRoomProxyMock(.init(isEncrypted: false, diff --git a/UnitTests/Sources/SessionVerificationViewModelTests.swift b/UnitTests/Sources/SessionVerificationViewModelTests.swift index 0dda7b4a2..3fb7be9e2 100644 --- a/UnitTests/Sources/SessionVerificationViewModelTests.swift +++ b/UnitTests/Sources/SessionVerificationViewModelTests.swift @@ -21,7 +21,7 @@ struct SessionVerificationViewModelTests { sessionVerificationController = SessionVerificationControllerProxyMock.configureMock() viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: sessionVerificationController, flow: .deviceInitiator, - userSettings: .volatile(), + userSettings: .mock(), mediaProvider: MediaProviderMock(.init())) context = viewModel.context } diff --git a/UnitTests/Sources/SettingsScreenViewModelTests.swift b/UnitTests/Sources/SettingsScreenViewModelTests.swift index 5344f309b..48ac0d011 100644 --- a/UnitTests/Sources/SettingsScreenViewModelTests.swift +++ b/UnitTests/Sources/SettingsScreenViewModelTests.swift @@ -176,7 +176,7 @@ struct SettingsScreenViewModelTests { @Test mutating func addAccountButtonFollowsTheFeatureFlag() async throws { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() setupViewModel(userSettings: userSettings) #expect(!context.viewState.showAddAccountButton) @@ -200,7 +200,7 @@ struct SettingsScreenViewModelTests { // MARK: - Helpers - private mutating func setupViewModel(status: UserStatus = .init(), userSettings: UserSettings = .volatile()) { + private mutating func setupViewModel(status: UserStatus = .init(), userSettings: UserSettings = .mock()) { clientProxy = ClientProxyMock(.init(userID: "", status: status)) viewModel = SettingsScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)), userSettings: userSettings, diff --git a/UnitTests/Sources/TimelineControllerTests.swift b/UnitTests/Sources/TimelineControllerTests.swift index c52bf1d09..d423e3b31 100644 --- a/UnitTests/Sources/TimelineControllerTests.swift +++ b/UnitTests/Sources/TimelineControllerTests.swift @@ -55,7 +55,7 @@ struct TimelineControllerTests { attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()), stateEventStringBuilder: RoomStateEventStringBuilder(userID: "@alice:matrix.org")), mediaProvider: MediaProviderMock(.init()), - userSettings: .volatile()) + userSettings: .mock()) } /// The live timeline's items are only published once any initial focus attempt has finished. diff --git a/UnitTests/Sources/TimelineInteractionHandlerTests.swift b/UnitTests/Sources/TimelineInteractionHandlerTests.swift index ce6ff0183..06fd0d6b4 100644 --- a/UnitTests/Sources/TimelineInteractionHandlerTests.swift +++ b/UnitTests/Sources/TimelineInteractionHandlerTests.swift @@ -218,7 +218,7 @@ struct TimelineInteractionHandlerTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) } diff --git a/UnitTests/Sources/TimelineItemMenuActionProviderTests.swift b/UnitTests/Sources/TimelineItemMenuActionProviderTests.swift index 01d576539..df4f7a40e 100644 --- a/UnitTests/Sources/TimelineItemMenuActionProviderTests.swift +++ b/UnitTests/Sources/TimelineItemMenuActionProviderTests.swift @@ -123,7 +123,7 @@ struct TimelineItemMenuActionProviderTests { isViewSourceEnabled: true, areThreadsEnabled: true, timelineKind: timelineKind, - emojiProvider: EmojiProvider(userSettings: .volatile())) + emojiProvider: EmojiProvider(userSettings: .mock())) .makeActions() } } diff --git a/UnitTests/Sources/TimelineViewModelTests.swift b/UnitTests/Sources/TimelineViewModelTests.swift index b8a0bd84b..e3529ffb8 100644 --- a/UnitTests/Sources/TimelineViewModelTests.swift +++ b/UnitTests/Sources/TimelineViewModelTests.swift @@ -301,7 +301,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) return (viewModel, roomProxy, timelineProxy, timelineController) @@ -326,7 +326,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) @@ -350,7 +350,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) @@ -386,7 +386,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) @@ -422,7 +422,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) @@ -464,7 +464,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) #expect(configuration.pinnedEventIDs == viewModel.context.viewState.pinnedEventIDs) @@ -492,7 +492,7 @@ final class TimelineViewModelTests { userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) @@ -899,7 +899,7 @@ final class TimelineViewModelTests { @Test func disablingTheFlagClearsTheSelection() async throws { let items = [TextRoomTimelineItem(eventID: "$1")] - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.messageMultiSelectEnabled = true let viewModel = makeViewModel(timelineController: TimelineControllerMock(.init(timelineItems: items)), userSettings: userSettings) @@ -917,7 +917,7 @@ final class TimelineViewModelTests { focussedEventID: String? = nil, timelineController: TimelineControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol = UserIndicatorControllerMock(), - userSettings: UserSettings = .volatile()) -> TimelineViewModel { + userSettings: UserSettings = .mock()) -> TimelineViewModel { TimelineViewModel(roomProxy: roomProxy ?? JoinedRoomProxyMock(.init(name: "")), focussedEventID: focussedEventID, timelineController: timelineController, @@ -926,7 +926,7 @@ final class TimelineViewModelTests { userIndicatorController: userIndicatorController, appMediator: AppMediatorMock(.init()), analyticsService: AnalyticsServiceMock(.init()), - emojiProvider: EmojiProvider(userSettings: .volatile()), + emojiProvider: EmojiProvider(userSettings: .mock()), linkMetadataProvider: LinkMetadataProvider(), timelineControllerFactory: TimelineControllerFactoryMock(.init())) } @@ -944,7 +944,7 @@ final class TimelineViewModelTests { private func makeSelectionViewModel(timelineController: TimelineControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol = UserIndicatorControllerMock()) -> TimelineViewModel { - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() userSettings.messageMultiSelectEnabled = true return makeViewModel(timelineController: timelineController, userIndicatorController: userIndicatorController, diff --git a/UnitTests/Sources/UserDetailsEditScreenViewModelTests.swift b/UnitTests/Sources/UserDetailsEditScreenViewModelTests.swift index 6eeb0a3cb..c0832ff7f 100644 --- a/UnitTests/Sources/UserDetailsEditScreenViewModelTests.swift +++ b/UnitTests/Sources/UserDetailsEditScreenViewModelTests.swift @@ -20,7 +20,7 @@ struct UserDetailsEditScreenViewModelTests { init() { viewModel = .init(userSession: UserSessionMock(.init()), - mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .volatile()), + mediaUploadingPreprocessor: MediaUploadingPreprocessor(userSettings: .mock()), userIndicatorController: UserIndicatorControllerMock()) } diff --git a/UnitTests/Sources/UserSessionFlowCoordinatorTests.swift b/UnitTests/Sources/UserSessionFlowCoordinatorTests.swift index b8b66e775..f3d1c661b 100644 --- a/UnitTests/Sources/UserSessionFlowCoordinatorTests.swift +++ b/UnitTests/Sources/UserSessionFlowCoordinatorTests.swift @@ -48,7 +48,7 @@ struct UserSessionFlowCoordinatorTests { appMediator.networkMonitor = networkMonitor userIndicatorController = UserIndicatorControllerMock() - let userSettings = UserSettings.volatile() + let userSettings = UserSettings.mock() let flowParameters = CommonFlowParameters(userSession: UserSessionMock(.init(clientProxy: clientProxy)), bugReportService: BugReportServiceMock(.init()), diff --git a/UnitTests/Sources/UserSettingsTests.swift b/UnitTests/Sources/UserSettingsTests.swift new file mode 100644 index 000000000..3b2564ad6 --- /dev/null +++ b/UnitTests/Sources/UserSettingsTests.swift @@ -0,0 +1,102 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +@testable import ElementX +import Foundation +import Testing + +struct UserSettingsTests { + private let store = VolatileUserDefaults() + private let appSettings: AppSettings + private let aliceSettings: UserSettings + private let bobSettings: UserSettings + + init() { + appSettings = AppSettings(store: store) + aliceSettings = appSettings.userSettings(for: "@alice:example.com") + bobSettings = appSettings.userSettings(for: "@bob:example.com") + } + + @Test + func accountSettingsAreScopedToAccount() { + // Given settings for two different accounts. + #expect(aliceSettings.searchBreadcrumbs.isEmpty) + #expect(bobSettings.searchBreadcrumbs.isEmpty) + + // When updating an account setting for Alice. + aliceSettings.searchBreadcrumbs = [.query("Alice")] + + // Then only Alice should see the change. + #expect(aliceSettings.searchBreadcrumbs == [.query("Alice")]) + #expect(bobSettings.searchBreadcrumbs.isEmpty) + } + + @Test + func appSettingsAreShared() { + // Given settings for two different accounts. + #expect(!appSettings.hasSignedInBefore) + #expect(!aliceSettings.hasSignedInBefore) + #expect(!bobSettings.hasSignedInBefore) + + // When updating an app setting through Alice's settings. + aliceSettings.hasSignedInBefore = true + + // Then the change should be seen by the app and every account. + #expect(appSettings.hasSignedInBefore) + #expect(aliceSettings.hasSignedInBefore) + #expect(bobSettings.hasSignedInBefore) + } + + @Test + func migrateAppSettingsValueToAccountSettings() throws { + // Given a store containing values saved under the keys previously used by the app settings. + // These keys are hardcoded so that renaming a property breaks this test instead of the migration. + store.set(true, forKey: "hasRunIdentityConfirmationOnboarding") + 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) + + // When migrating those values into Alice's account settings. + aliceSettings.migrateAppSettingsValueToAccountSettings(\.hasRunIdentityConfirmationOnboardingKey) + aliceSettings.migrateAppSettingsValueToAccountSettings(\.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(store.object(forKey: "hasRunIdentityConfirmationOnboarding") == nil) + #expect(store.object(forKey: "searchBreadcrumbs") == nil) + } + + @Test + 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")] + #expect(appSettings.hasSignedInBefore) + #expect(aliceSettings.hasRunIdentityConfirmationOnboarding) + #expect(aliceSettings.searchBreadcrumbs == [.query("Alice")]) + #expect(bobSettings.hasRunIdentityConfirmationOnboarding) + #expect(bobSettings.searchBreadcrumbs == [.query("Bob")]) + + // When resetting Alice's session specific settings. + aliceSettings.resetSessionSpecificSettings() + + // 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(appSettings.hasSignedInBefore) + } +} diff --git a/project.yml b/project.yml index 05faa5251..6ab463a69 100644 --- a/project.yml +++ b/project.yml @@ -51,7 +51,7 @@ settings: ENABLE_BITCODE: false APP_NAME: ElementX KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)" - MARKETING_VERSION: 26.10.0 + MARKETING_VERSION: 26.10.1 CURRENT_PROJECT_VERSION: 1 SUPPORTS_MACCATALYST: false ARCHS: arm64