diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 208f184d1..fa29ae45d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ dependencies = ( ); name = Periphery; + packageProductDependencies = ( + ); productName = Periphery; }; /* End PBXAggregateTarget section */ @@ -68,7 +70,6 @@ 095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; }; 095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */; }; 09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; }; - 09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; }; 09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */; }; 0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; }; 0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; }; @@ -178,7 +179,6 @@ 1F3232BD368DF430AB433907 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; }; 1FE593ECEC40A43789105D80 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */; }; 1FEC0A4EC6E6DF693C16B32A /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEBCB9676FCD1D0F13188DD /* StringTests.swift */; }; - 206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */; }; 208C19811613F9A10F8A7B75 /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; }; 20C16A3F718802B0E4A19C83 /* URLComponentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76310030C831D4610A705603 /* URLComponentsTests.swift */; }; 210DB40676DF2A23E69C2D06 /* AuthenticationClientBuilderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B655A536341D2695158C6664 /* AuthenticationClientBuilderFactory.swift */; }; @@ -377,6 +377,7 @@ 491D62ACD19E6F134B1766AF /* RoomNotificationSettingsUserDefinedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */; }; 492274DA6691EE985C2FCCAA /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 67E7A6F388D3BF85767609D9 /* Sentry */; }; 4940B439681767BE9D78CFDB /* AppLockSetupBiometricsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5EE5DE3B55D59299DB5BC /* AppLockSetupBiometricsScreenViewModelTests.swift */; }; + 494970EA811FE4D93AC68482 /* SettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE8A35B4AD5256F4B562274 /* SettingsScreenViewModelTests.swift */; }; 4949C8C12669D1B5E082366E /* QRCodeLoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA9EA59D5C0DA1BFC7B3621 /* QRCodeLoginScreen.swift */; }; 49500BBA1CD65A5AE252D970 /* RoomDirectorySearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */; }; 4A4110369DBB79E4A314F415 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */; }; @@ -856,6 +857,7 @@ A4C29D373986AFE4559696D5 /* SecureBackupKeyBackupScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4525E8C0FBDD27D1ACE90952 /* SecureBackupKeyBackupScreenViewModelProtocol.swift */; }; A4E885358D7DD5A072A06824 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; }; A51C65E5A3C9F2464A91A380 /* AuthenticationClientBuilderFactoryMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */; }; + A583B70939707197B0B21DFC /* DeferredFulfillmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEBB74427E24AF30CDB131B7 /* DeferredFulfillmentTests.swift */; }; A588572ED0EB18D947B32A5E /* SendInviteConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F276F31C1AEC19E52B951B62 /* SendInviteConfirmationView.swift */; }; A5B455D1A6DADF7476F7B417 /* EmojiProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BCCE3D12B0A9C6E559B5B5A /* EmojiProviderProtocol.swift */; }; A5B9EF45C7B8ACEB4954AE36 /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9780389F8A53E4D26E23DD03 /* LoginScreenViewModelProtocol.swift */; }; @@ -1012,6 +1014,7 @@ C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; }; C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; }; C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; }; + C797C0B4CF45C66CD1921252 /* SoftLogoutScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC43313F21511C853D34544E /* SoftLogoutScreenViewModelTests.swift */; }; C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; }; C7F20DBF873CC72FB482E326 /* test_rotated_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 723B055A57857BFF0F18D9CB /* test_rotated_image.jpg */; }; C80E06ED97CE52704A46C148 /* ClientBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1C33355FFB0F0953C35036 /* ClientBuilder.swift */; }; @@ -1627,7 +1630,6 @@ 3203C6566DC17B7AECC1B7FD /* RoomNotificationSettingsUserDefinedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsUserDefinedScreen.swift; sourceTree = ""; }; 32A1FAE3EEAB27D8D38E3273 /* MapTilerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerConfiguration.swift; sourceTree = ""; }; 32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyProtocol.swift; sourceTree = ""; }; - 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutViewModelTests.swift; sourceTree = ""; }; 33035418BB35754232985871 /* TimelineReadReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReadReceiptsView.swift; sourceTree = ""; }; 330AF4D121C3396F7A14B21D /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/SAS.strings; sourceTree = ""; }; 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObject.swift; sourceTree = ""; }; @@ -1672,7 +1674,6 @@ 3CFD5EB0B0EEA4549FB49784 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = ""; }; 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextField.swift; sourceTree = ""; }; 3D27299A36536DBF91AE8FA6 /* ShareExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionViewController.swift; sourceTree = ""; }; - 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = ""; }; 3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = ""; }; 3D65BCC659FD9087E49B3C25 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = ""; }; 3D75941CBD7D336F831924EC /* ReadReceiptCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceiptCell.swift; sourceTree = ""; }; @@ -2140,6 +2141,7 @@ 9F40FB0A43DAECEC27C73722 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/SAS.strings; sourceTree = ""; }; 9FD40B92FCF20165658296AD /* TimelineMediaPreviewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewModifier.swift; sourceTree = ""; }; 9FD7E851E2BA8C5A8D284B2A /* BannedRoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannedRoomProxyMock.swift; sourceTree = ""; }; + 9FE8A35B4AD5256F4B562274 /* SettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelTests.swift; sourceTree = ""; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModel.swift; sourceTree = ""; }; A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModel.swift; sourceTree = ""; }; @@ -2194,6 +2196,7 @@ AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelTests.swift; sourceTree = ""; }; AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageCache.swift; sourceTree = ""; }; AC3F82523D6F48B926D6AF68 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; + AC43313F21511C853D34544E /* SoftLogoutScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModelTests.swift; sourceTree = ""; }; AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerTests.swift; sourceTree = ""; }; AC5F5209279A752D98AAC4B2 /* CollapsibleFlowLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleFlowLayoutTests.swift; sourceTree = ""; }; AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerProtocol.swift; sourceTree = ""; }; @@ -2449,6 +2452,7 @@ DD955A0380C287C418F1A74D /* PhotoLibraryManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryManagerMock.swift; sourceTree = ""; }; DD97F9661ABF08CE002054A2 /* AppLockServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceTests.swift; sourceTree = ""; }; DE5127D6EA05B2E45D0A7D59 /* JoinRoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenViewModelTests.swift; sourceTree = ""; }; + DEBB74427E24AF30CDB131B7 /* DeferredFulfillmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredFulfillmentTests.swift; sourceTree = ""; }; DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = ""; }; DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = ""; }; DF17EA323AD0205A6AB621AA /* Snapshotting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Snapshotting.swift; sourceTree = ""; }; @@ -4294,6 +4298,7 @@ 3B5E97E9615A158C76B2AB77 /* DateTests.swift */, D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */, 2ADF12A50186B75C68017B61 /* DeclineAndBlockScreenViewModelTests.swift */, + DEBB74427E24AF30CDB131B7 /* DeferredFulfillmentTests.swift */, 6D0A27607AB09784C8501B5C /* DeveloperOptionsScreenViewModelTests.swift */, 906451FB8CF27C628152BF7A /* EditRoomAddressScreenViewModelTests.swift */, 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */, @@ -4359,8 +4364,8 @@ 0825EAFD47332DD459DE893F /* SessionDirectoriesTests.swift */, A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */, DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */, - 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */, - 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */, + 9FE8A35B4AD5256F4B562274 /* SettingsScreenViewModelTests.swift */, + AC43313F21511C853D34544E /* SoftLogoutScreenViewModelTests.swift */, 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */, C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */, 2CEBCB9676FCD1D0F13188DD /* StringTests.swift */, @@ -6409,6 +6414,7 @@ "zh-Hant-TW", ); mainGroup = 405B00F139AEE3994601B36A; + minimizedProjectReferenceProxies = 1; packageReferences = ( E025F19D013D9BA6C58B37F4 /* XCRemoteSwiftPackageReference "swift-algorithms" */, AC3475112CA40C2C6E78D1EB /* XCRemoteSwiftPackageReference "matrix-analytics-events" */, @@ -6435,6 +6441,7 @@ EC6D0C817B1C21D9D096505A /* XCRemoteSwiftPackageReference "Version" */, EE40B0E16A55BD23ECBFFD22 /* XCRemoteSwiftPackageReference "matrix-rich-text-editor-swift" */, ); + preferredProjectObjectVersion = 54; projectDirPath = ""; projectRoot = ""; targets = ( @@ -6776,6 +6783,7 @@ CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */, 80F6C8EFCA4564B67F0D34B0 /* DeactivateAccountScreenViewModelTests.swift in Sources */, 34390DAE0C574DAD30CCA7D9 /* DeclineAndBlockScreenViewModelTests.swift in Sources */, + A583B70939707197B0B21DFC /* DeferredFulfillmentTests.swift in Sources */, 864C69CF951BF36D25BE0C03 /* DeveloperOptionsScreenViewModelTests.swift in Sources */, EDB6915EC953BB2A44AA608E /* EditRoomAddressScreenViewModelTests.swift in Sources */, 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */, @@ -6850,8 +6858,8 @@ CC1C948F67A5510A340FD7F0 /* SessionDirectoriesTests.swift in Sources */, 86675910612A12409262DFBD /* SessionVerificationStateMachineTests.swift in Sources */, 755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */, - 206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */, - 09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */, + 494970EA811FE4D93AC68482 /* SettingsScreenViewModelTests.swift in Sources */, + C797C0B4CF45C66CD1921252 /* SoftLogoutScreenViewModelTests.swift in Sources */, 6189B4ABD535CE526FA1107B /* StartChatViewModelTests.swift in Sources */, 9BB91CABB10D8FE90C491BCD /* StaticLocationScreenViewModelTests.swift in Sources */, 1FEC0A4EC6E6DF693C16B32A /* StringTests.swift in Sources */, diff --git a/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/ServerConfirmationScreenViewModel.swift b/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/ServerConfirmationScreenViewModel.swift index 79782a59a..1277eb6ee 100644 --- a/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/ServerConfirmationScreenViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/ServerConfirmationScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias ServerConfirmationScreenViewModelType = StateStoreViewModel +typealias ServerConfirmationScreenViewModelType = StateStoreViewModelV2 class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType, ServerConfirmationScreenViewModelProtocol { let authenticationService: AuthenticationServiceProtocol diff --git a/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/View/ServerConfirmationScreen.swift b/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/View/ServerConfirmationScreen.swift index 46a0760e6..3e10e28f7 100644 --- a/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/View/ServerConfirmationScreen.swift +++ b/ElementX/Sources/Screens/Authentication/ServerConfirmationScreen/View/ServerConfirmationScreen.swift @@ -9,7 +9,7 @@ import Compound import SwiftUI struct ServerConfirmationScreen: View { - @ObservedObject var context: ServerConfirmationScreenViewModel.Context + @Bindable var context: ServerConfirmationScreenViewModel.Context var body: some View { FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar) { diff --git a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift index fb9ab5522..02deac254 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/ServerSelectionScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias ServerSelectionScreenViewModelType = StateStoreViewModel +typealias ServerSelectionScreenViewModelType = StateStoreViewModelV2 class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, ServerSelectionScreenViewModelProtocol { private let authenticationService: AuthenticationServiceProtocol diff --git a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/View/ServerSelectionScreen.swift b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/View/ServerSelectionScreen.swift index f3ee23eab..560f12074 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/View/ServerSelectionScreen.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelectionScreen/View/ServerSelectionScreen.swift @@ -8,7 +8,7 @@ import SwiftUI struct ServerSelectionScreen: View { - @ObservedObject var context: ServerSelectionScreenViewModel.Context + @Bindable var context: ServerSelectionScreenViewModel.Context var body: some View { ScrollView { @@ -107,9 +107,7 @@ struct ServerSelection_Previews: PreviewProvider, TestablePreview { NavigationStack { ServerSelectionScreen(context: invalidViewModel.context) } - .snapshotPreferences(expect: invalidViewModel.context.$viewState.map { state in - state.hasValidationError == true - }) + .snapshotPreferences(expect: invalidViewModel.context.observe(\.viewState.hasValidationError)) } static func makeViewModel(for homeserverAddress: String) -> ServerSelectionScreenViewModel { diff --git a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenViewModel.swift b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenViewModel.swift index a22a2ec27..80552f39b 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenViewModel.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/SoftLogoutScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias SoftLogoutScreenViewModelType = StateStoreViewModel +typealias SoftLogoutScreenViewModelType = StateStoreViewModelV2 class SoftLogoutScreenViewModel: SoftLogoutScreenViewModelType, SoftLogoutScreenViewModelProtocol { private var actionsSubject: PassthroughSubject = .init() diff --git a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/View/SoftLogoutScreen.swift b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/View/SoftLogoutScreen.swift index f74b9f895..ddd4857c8 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/View/SoftLogoutScreen.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogoutScreen/View/SoftLogoutScreen.swift @@ -13,7 +13,7 @@ struct SoftLogoutScreen: View { /// The focus state of the password text field. @FocusState private var isPasswordFocused: Bool - @ObservedObject var context: SoftLogoutScreenViewModel.Context + @Bindable var context: SoftLogoutScreenViewModel.Context var body: some View { ScrollView { diff --git a/ElementX/Sources/Screens/AuthenticationStartScreen/AuthenticationStartScreenViewModel.swift b/ElementX/Sources/Screens/AuthenticationStartScreen/AuthenticationStartScreenViewModel.swift index 463115010..0e23a654d 100644 --- a/ElementX/Sources/Screens/AuthenticationStartScreen/AuthenticationStartScreenViewModel.swift +++ b/ElementX/Sources/Screens/AuthenticationStartScreen/AuthenticationStartScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias AuthenticationStartScreenViewModelType = StateStoreViewModel +typealias AuthenticationStartScreenViewModelType = StateStoreViewModelV2 class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType, AuthenticationStartScreenViewModelProtocol { private var actionsSubject: PassthroughSubject = .init() diff --git a/ElementX/Sources/Screens/AuthenticationStartScreen/View/AuthenticationStartScreen.swift b/ElementX/Sources/Screens/AuthenticationStartScreen/View/AuthenticationStartScreen.swift index ba4acb059..7014be483 100644 --- a/ElementX/Sources/Screens/AuthenticationStartScreen/View/AuthenticationStartScreen.swift +++ b/ElementX/Sources/Screens/AuthenticationStartScreen/View/AuthenticationStartScreen.swift @@ -12,7 +12,7 @@ import SwiftUI struct AuthenticationStartScreen: View { @Environment(\.verticalSizeClass) private var verticalSizeClass - @ObservedObject var context: AuthenticationStartScreenViewModel.Context + let context: AuthenticationStartScreenViewModel.Context var body: some View { GeometryReader { geometry in diff --git a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift index 5759899bd..9edcd44d0 100644 --- a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias AdvancedSettingsScreenViewModelType = StateStoreViewModel +typealias AdvancedSettingsScreenViewModelType = StateStoreViewModelV2 class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol { private let analytics: AnalyticsService diff --git a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift index b99395655..e313f5b49 100644 --- a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift @@ -9,7 +9,7 @@ import Compound import SwiftUI struct AdvancedSettingsScreen: View { - @ObservedObject var context: AdvancedSettingsScreenViewModel.Context + @Bindable var context: AdvancedSettingsScreenViewModel.Context var body: some View { Form { diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift index a774975dc..93084936e 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias DeveloperOptionsScreenViewModelType = StateStoreViewModel +typealias DeveloperOptionsScreenViewModelType = StateStoreViewModelV2 class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, DeveloperOptionsScreenViewModelProtocol { private var actionsSubject: PassthroughSubject = .init() diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index de89245e0..c6d800238 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -8,7 +8,8 @@ import SwiftUI struct DeveloperOptionsScreen: View { - @ObservedObject var context: DeveloperOptionsScreenViewModel.Context + @Bindable var context: DeveloperOptionsScreenViewModel.Context + @State private var showConfetti = false @State private var elementCallURLOverrideString: String diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index b267a95d3..3de4e676f 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -8,7 +8,7 @@ import Combine import SwiftUI -typealias SettingsScreenViewModelType = StateStoreViewModel +typealias SettingsScreenViewModelType = StateStoreViewModelV2 class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewModelProtocol { private var actionsSubject: PassthroughSubject = .init() diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index 1a996e42d..63e0973d9 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -10,7 +10,7 @@ import SFSafeSymbols import SwiftUI struct SettingsScreen: View { - @ObservedObject var context: SettingsScreenViewModel.Context + let context: SettingsScreenViewModel.Context private var shouldHideManageAccountSection: Bool { context.viewState.accountProfileURL == nil && @@ -241,17 +241,13 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview { NavigationStack { SettingsScreen(context: viewModel.context) } - .snapshotPreferences(expect: viewModel.context.$viewState.map { state in - state.accountSessionsListURL != nil - }) + .snapshotPreferences(expect: viewModel.context.observe(\.viewState.accountSessionsListURL).map { $0 != nil }.eraseToStream()) .previewDisplayName("Default") NavigationStack { SettingsScreen(context: bugReportDisabledViewModel.context) } - .snapshotPreferences(expect: bugReportDisabledViewModel.context.$viewState.map { state in - state.accountSessionsListURL != nil - }) + .snapshotPreferences(expect: bugReportDisabledViewModel.context.observe(\.viewState.accountSessionsListURL).map { $0 != nil }.eraseToStream()) .previewDisplayName("Bug report disabled") } diff --git a/UnitTests/Sources/DeferredFulfillmentTests.swift b/UnitTests/Sources/DeferredFulfillmentTests.swift new file mode 100644 index 000000000..c1394614e --- /dev/null +++ b/UnitTests/Sources/DeferredFulfillmentTests.swift @@ -0,0 +1,81 @@ +// +// Copyright 2025 New Vector 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 XCTest + +class DeferredFulfillmentTests: XCTestCase { + private let observable = SomeObservable() + + func testObservableWithoutUpdate() async throws { + // Given a deferred fulfilment on a value that already matches the expected value. + let initialValue = observable.counter + let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == initialValue } + + // Then the test should be fulfilled by the initial value and shouldn't timeout whilst waiting for a update. + try await deferred.fulfill() + } + + func testObservableWithSynchronousUpdate() async throws { + // Given a deferred fulfilment for an expected value. + let newValue = 100 + let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == newValue } + + // When that value is changed synchronously. + observable.counter = newValue + XCTAssertEqual(observable.counter, newValue) + + // Then the test should be fulfilled. + try await deferred.fulfill() + XCTAssertEqual(observable.counter, newValue) + } + + func testObservableAsynchronousUpdate() async throws { + // Given a deferred fulfilment for an expected value. + let newValue = 100 + let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == newValue } + + // When that value is changed asynchronously. + Task { try await observable.setCounter(newValue, delay: .seconds(1)) } + XCTAssertEqual(observable.counter, 0) + + // Then the test should be fulfilled once the update has taken place. + try await deferred.fulfill() + XCTAssertEqual(observable.counter, newValue) + } + + func testObservableMultipleUpdates() async throws { + // Given a deferred fulfilment for an expected value. + let finalValue = 500 + let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == finalValue } + + // When that value is changed asynchronously with some intermediate values before it is reached. + Task { + try await observable.setCounter(100, delay: .seconds(.random(in: 1.0...2.0))) + try await observable.setCounter(250, delay: .seconds(.random(in: 1.0...2.0))) + try await observable.setCounter(finalValue, delay: .seconds(.random(in: 1.0...2.0))) + } + XCTAssertEqual(observable.counter, 0) + + // Then the test should be fulfilled once the expected update has taken place. + try await deferred.fulfill() + XCTAssertEqual(observable.counter, finalValue) + } +} + +// MARK: - Helpers + +@Observable private class SomeObservable { + var counter = 0 + + func setCounter(_ newValue: Int, delay: Duration? = nil) async throws { + if let delay { + try await Task.sleep(for: delay) + } + counter = newValue + } +} diff --git a/UnitTests/Sources/ServerConfirmationScreenViewModelTests.swift b/UnitTests/Sources/ServerConfirmationScreenViewModelTests.swift index 970764d29..0f62a4c1f 100644 --- a/UnitTests/Sources/ServerConfirmationScreenViewModelTests.swift +++ b/UnitTests/Sources/ServerConfirmationScreenViewModelTests.swift @@ -150,7 +150,7 @@ class ServerConfirmationScreenViewModelTests: XCTestCase { XCTAssertNil(context.alertInfo) // When continuing from the confirmation screen. - let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } + let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil } context.send(viewAction: .confirm) try await deferred.fulfill() @@ -167,7 +167,7 @@ class ServerConfirmationScreenViewModelTests: XCTestCase { XCTAssertNil(context.alertInfo) // When continuing from the confirmation screen. - let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } + let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil } context.send(viewAction: .confirm) try await deferred.fulfill() diff --git a/UnitTests/Sources/ServerSelectionScreenViewModelTests.swift b/UnitTests/Sources/ServerSelectionScreenViewModelTests.swift index c188da3ff..9fb108124 100644 --- a/UnitTests/Sources/ServerSelectionScreenViewModelTests.swift +++ b/UnitTests/Sources/ServerSelectionScreenViewModelTests.swift @@ -43,7 +43,7 @@ class ServerSelectionScreenViewModelTests: XCTestCase { // When selecting a server that doesn't support login. context.homeserverAddress = "server.net" - let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } + let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil } context.send(viewAction: .confirm) try await deferred.fulfill() @@ -78,7 +78,7 @@ class ServerSelectionScreenViewModelTests: XCTestCase { // When selecting a server that doesn't support registration. context.homeserverAddress = "example.com" - let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil } + let deferred = deferFulfillment(context.observe(\.alertInfo)) { $0 != nil } context.send(viewAction: .confirm) try await deferred.fulfill() @@ -96,7 +96,7 @@ class ServerSelectionScreenViewModelTests: XCTestCase { "The standard footer message should be shown.") // When attempting to discover an invalid server - var deferred = deferFulfillment(context.$viewState) { $0.isShowingFooterError } + var deferred = deferFulfillment(context.observe(\.viewState.isShowingFooterError)) { $0 } context.homeserverAddress = "idontexist" context.send(viewAction: .confirm) try await deferred.fulfill() @@ -108,7 +108,7 @@ class ServerSelectionScreenViewModelTests: XCTestCase { "The error message should be shown.") // And when clearing the error. - deferred = deferFulfillment(context.$viewState) { !$0.isShowingFooterError } + deferred = deferFulfillment(context.observe(\.viewState.isShowingFooterError)) { !$0 } context.homeserverAddress = "" context.send(viewAction: .clearFooterError) try await deferred.fulfill() diff --git a/UnitTests/Sources/SettingsViewModelTests.swift b/UnitTests/Sources/SettingsScreenViewModelTests.swift similarity index 57% rename from UnitTests/Sources/SettingsViewModelTests.swift rename to UnitTests/Sources/SettingsScreenViewModelTests.swift index 69633a991..480ce3374 100644 --- a/UnitTests/Sources/SettingsViewModelTests.swift +++ b/UnitTests/Sources/SettingsScreenViewModelTests.swift @@ -26,47 +26,20 @@ class SettingsScreenViewModelTests: XCTestCase { } @MainActor func testLogout() async throws { - var correctResult = false - - viewModel.actions - .sink { action in - switch action { - case .logout: - correctResult = true - default: - break - } - } - .store(in: &cancellables) - + let deferred = deferFulfillment(viewModel.actions) { $0 == .logout } context.send(viewAction: .logout) - await Task.yield() - XCTAssert(correctResult) + try await deferred.fulfill() } func testReportBug() async throws { - var correctResult = false - viewModel.actions - .sink { action in - correctResult = action == .reportBug - } - .store(in: &cancellables) - + let deferred = deferFulfillment(viewModel.actions) { $0 == .reportBug } context.send(viewAction: .reportBug) - await Task.yield() - XCTAssert(correctResult) + try await deferred.fulfill() } func testAnalytics() async throws { - var correctResult = false - viewModel.actions - .sink { action in - correctResult = action == .analytics - } - .store(in: &cancellables) - + let deferred = deferFulfillment(viewModel.actions) { $0 == .analytics } context.send(viewAction: .analytics) - await Task.yield() - XCTAssert(correctResult) + try await deferred.fulfill() } } diff --git a/UnitTests/Sources/SoftLogoutViewModelTests.swift b/UnitTests/Sources/SoftLogoutScreenViewModelTests.swift similarity index 98% rename from UnitTests/Sources/SoftLogoutViewModelTests.swift rename to UnitTests/Sources/SoftLogoutScreenViewModelTests.swift index 8837b038a..9f3998a30 100644 --- a/UnitTests/Sources/SoftLogoutViewModelTests.swift +++ b/UnitTests/Sources/SoftLogoutScreenViewModelTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import ElementX @MainActor -class SoftLogoutViewModelTests: XCTestCase { +class SoftLogoutScreenViewModelTests: XCTestCase { let credentials = SoftLogoutScreenCredentials(userID: "mock_user_id", homeserverName: "https://example.com", userDisplayName: "mock_username",