mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Add a view to show the user's account from Element Classic. (#5361)
No presentation logic yet.
This commit is contained in:
@@ -158,6 +158,7 @@
|
|||||||
1801F1467ABCEA080419E150 /* preview_avatar_user.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87FC42213E86E8182CFD3A49 /* preview_avatar_user.jpg */; };
|
1801F1467ABCEA080419E150 /* preview_avatar_user.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87FC42213E86E8182CFD3A49 /* preview_avatar_user.jpg */; };
|
||||||
182D532B736178A1DED9F76E /* ReportRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FCAE847556719BBE7A0882 /* ReportRoomScreenModels.swift */; };
|
182D532B736178A1DED9F76E /* ReportRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FCAE847556719BBE7A0882 /* ReportRoomScreenModels.swift */; };
|
||||||
18386B777FDA74E4B3282D4F /* TimelineItemThreadSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98C6A082F2B2A15E1B9BE280 /* TimelineItemThreadSummary.swift */; };
|
18386B777FDA74E4B3282D4F /* TimelineItemThreadSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98C6A082F2B2A15E1B9BE280 /* TimelineItemThreadSummary.swift */; };
|
||||||
|
1866A34DBAD0F730058CB3C3 /* ClassicAppMediaLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC3F28DECDF8665E8EBC76E /* ClassicAppMediaLoaderTests.swift */; };
|
||||||
18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
|
18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
|
||||||
18978C9438206828C1D5AF2A /* test_animated_image.gif in Resources */ = {isa = PBXBuildFile; fileRef = 53FD6D3D38F556CEAA280C58 /* test_animated_image.gif */; };
|
18978C9438206828C1D5AF2A /* test_animated_image.gif in Resources */ = {isa = PBXBuildFile; fileRef = 53FD6D3D38F556CEAA280C58 /* test_animated_image.gif */; };
|
||||||
18E3786918486D4C9726BC84 /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */; };
|
18E3786918486D4C9726BC84 /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */; };
|
||||||
@@ -254,6 +255,7 @@
|
|||||||
2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; };
|
2955F4C160CFD7794D819C64 /* EffectsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024F7398C5FC12586FB10E9D /* EffectsScene.swift */; };
|
||||||
298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */; };
|
298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */; };
|
||||||
29EE1791E0AFA1ABB7F23D2F /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
|
29EE1791E0AFA1ABB7F23D2F /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
|
||||||
|
2A4F8B76E1F99DE95D5867E6 /* ClassicAppManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633BAD3D9BB44B2AED7CBB93 /* ClassicAppManagerMock.swift */; };
|
||||||
2A56B00B070F83E0FE571193 /* TimelineMediaPreviewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A454132A5A5247802821E /* TimelineMediaPreviewDataSource.swift */; };
|
2A56B00B070F83E0FE571193 /* TimelineMediaPreviewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A454132A5A5247802821E /* TimelineMediaPreviewDataSource.swift */; };
|
||||||
2A61D2B4A225332CECA3B937 /* ReportRoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */; };
|
2A61D2B4A225332CECA3B937 /* ReportRoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */; };
|
||||||
2AAB2A77F1762A2648078A30 /* InteractiveQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */; };
|
2AAB2A77F1762A2648078A30 /* InteractiveQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */; };
|
||||||
@@ -615,6 +617,7 @@
|
|||||||
68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD22AEFFA20065494ED2333 /* AppAppearance.swift */; };
|
68C3AF257678F6E7BB238C3F /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD22AEFFA20065494ED2333 /* AppAppearance.swift */; };
|
||||||
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
|
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
|
||||||
695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */; };
|
695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */; };
|
||||||
|
696D34FAA9A4E00435DDAF39 /* ClassicAppMediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D57D1C26306256332BA070F /* ClassicAppMediaLoader.swift */; };
|
||||||
69A9B430397C15075D86193F /* UserPropertiesExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AFD800AF033D8B0D11191A /* UserPropertiesExt.swift */; };
|
69A9B430397C15075D86193F /* UserPropertiesExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AFD800AF033D8B0D11191A /* UserPropertiesExt.swift */; };
|
||||||
69B3C6010B42010F591FC3CB /* RoomRolesAndPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */; };
|
69B3C6010B42010F591FC3CB /* RoomRolesAndPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */; };
|
||||||
69B9CC733A880E1BB097C113 /* LinkNewDeviceScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */; };
|
69B9CC733A880E1BB097C113 /* LinkNewDeviceScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */; };
|
||||||
@@ -1224,6 +1227,7 @@
|
|||||||
D10BA4F041DC58580A440A32 /* RoomRolesAndPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B1DC3B3FB40A7F4AE9B7BF /* RoomRolesAndPermissionsScreen.swift */; };
|
D10BA4F041DC58580A440A32 /* RoomRolesAndPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B1DC3B3FB40A7F4AE9B7BF /* RoomRolesAndPermissionsScreen.swift */; };
|
||||||
D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */; };
|
D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */; };
|
||||||
D150D6E96CA6CA09FA50E13C /* LabsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */; };
|
D150D6E96CA6CA09FA50E13C /* LabsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */; };
|
||||||
|
D16B3134A7FF4FBA0749014A /* AuthenticationClassicAppAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C45578F406698388B97C07 /* AuthenticationClassicAppAccountView.swift */; };
|
||||||
D18B70975644C24F60656C0D /* KnockRequestProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07851F4EA81AA3339806A7B /* KnockRequestProxyProtocol.swift */; };
|
D18B70975644C24F60656C0D /* KnockRequestProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07851F4EA81AA3339806A7B /* KnockRequestProxyProtocol.swift */; };
|
||||||
D19A748E95E2FAB2940570F0 /* CallScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4103AB4340F2974D690A12A /* CallScreen.swift */; };
|
D19A748E95E2FAB2940570F0 /* CallScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4103AB4340F2974D690A12A /* CallScreen.swift */; };
|
||||||
D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; };
|
D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; };
|
||||||
@@ -2015,6 +2019,7 @@
|
|||||||
4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
|
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
|
||||||
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = "<group>"; };
|
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = "<group>"; };
|
||||||
|
4AC3F28DECDF8665E8EBC76E /* ClassicAppMediaLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassicAppMediaLoaderTests.swift; sourceTree = "<group>"; };
|
||||||
4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = wrapper.icon; path = AppIcon.icon; sourceTree = "<group>"; };
|
4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = wrapper.icon; path = AppIcon.icon; sourceTree = "<group>"; };
|
||||||
4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomListProxy.swift; sourceTree = "<group>"; };
|
4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomListProxy.swift; sourceTree = "<group>"; };
|
||||||
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
|
4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
@@ -2026,6 +2031,7 @@
|
|||||||
4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
|
4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
|
||||||
4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenCoordinator.swift; sourceTree = "<group>"; };
|
4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
4D3A7375AB22721C436EB056 /* ComposerToolbarModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarModels.swift; sourceTree = "<group>"; };
|
4D3A7375AB22721C436EB056 /* ComposerToolbarModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarModels.swift; sourceTree = "<group>"; };
|
||||||
|
4D57D1C26306256332BA070F /* ClassicAppMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassicAppMediaLoader.swift; sourceTree = "<group>"; };
|
||||||
4D635709C1D6D37C225AD40E /* RoomPowerLevelProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelProxyProtocol.swift; sourceTree = "<group>"; };
|
4D635709C1D6D37C225AD40E /* RoomPowerLevelProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
4E2245243369B99216C7D84E /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
4E2245243369B99216C7D84E /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceMock.swift; sourceTree = "<group>"; };
|
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerDraftServiceMock.swift; sourceTree = "<group>"; };
|
||||||
@@ -2133,6 +2139,7 @@
|
|||||||
62B07B296D7A9D2F09120853 /* OrderedSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSet.swift; sourceTree = "<group>"; };
|
62B07B296D7A9D2F09120853 /* OrderedSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSet.swift; sourceTree = "<group>"; };
|
||||||
62EACAFB3F3E017060F9F1C5 /* TimelineProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProviderMock.swift; sourceTree = "<group>"; };
|
62EACAFB3F3E017060F9F1C5 /* TimelineProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProviderMock.swift; sourceTree = "<group>"; };
|
||||||
633924B26ACCD29C18BEF4E8 /* DeactivateAccountScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
633924B26ACCD29C18BEF4E8 /* DeactivateAccountScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
633BAD3D9BB44B2AED7CBB93 /* ClassicAppManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassicAppManagerMock.swift; sourceTree = "<group>"; };
|
||||||
638790D3F915F0909315C47A /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
638790D3F915F0909315C47A /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
||||||
638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveQuickLook.swift; sourceTree = "<group>"; };
|
638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveQuickLook.swift; sourceTree = "<group>"; };
|
||||||
63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
63E8A1E8EE094F570573B6E8 /* RoomDetailsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -2497,6 +2504,7 @@
|
|||||||
A4D9DF4F2DF3507F99B5B97B /* LabsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModel.swift; sourceTree = "<group>"; };
|
A4D9DF4F2DF3507F99B5B97B /* LabsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineFlowCoordinator.swift; sourceTree = "<group>"; };
|
A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||||
A566F8F2C27B99E1FB80C69B /* JoinRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRule.swift; sourceTree = "<group>"; };
|
A566F8F2C27B99E1FB80C69B /* JoinRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRule.swift; sourceTree = "<group>"; };
|
||||||
|
A5C45578F406698388B97C07 /* AuthenticationClassicAppAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationClassicAppAccountView.swift; sourceTree = "<group>"; };
|
||||||
A6B19D10B102956066AF117B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
|
A6B19D10B102956066AF117B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
|
||||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
|
||||||
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
|
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -3708,6 +3716,7 @@
|
|||||||
0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */,
|
0554FEA301486A8CFA475D5A /* AuthenticationClientBuilderFactoryMock.swift */,
|
||||||
9FD7E851E2BA8C5A8D284B2A /* BannedRoomProxyMock.swift */,
|
9FD7E851E2BA8C5A8D284B2A /* BannedRoomProxyMock.swift */,
|
||||||
8F7FC9580CABF797A2E6213A /* BugReportServiceMock.swift */,
|
8F7FC9580CABF797A2E6213A /* BugReportServiceMock.swift */,
|
||||||
|
633BAD3D9BB44B2AED7CBB93 /* ClassicAppManagerMock.swift */,
|
||||||
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
||||||
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */,
|
4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */,
|
||||||
86E1BAA7232081635662A83F /* CXProviderMock.swift */,
|
86E1BAA7232081635662A83F /* CXProviderMock.swift */,
|
||||||
@@ -4363,6 +4372,7 @@
|
|||||||
98F4DFFFC62187D9A4D2030D /* ClassicAppAccountManager.swift */,
|
98F4DFFFC62187D9A4D2030D /* ClassicAppAccountManager.swift */,
|
||||||
384A744714571BAF138C6B86 /* ClassicAppAES.swift */,
|
384A744714571BAF138C6B86 /* ClassicAppAES.swift */,
|
||||||
39C78D5FF15343724902EB20 /* ClassicAppManager.swift */,
|
39C78D5FF15343724902EB20 /* ClassicAppManager.swift */,
|
||||||
|
4D57D1C26306256332BA070F /* ClassicAppMediaLoader.swift */,
|
||||||
74240BE69DACAD01AA670730 /* ClassicAppMXAccount.swift */,
|
74240BE69DACAD01AA670730 /* ClassicAppMXAccount.swift */,
|
||||||
);
|
);
|
||||||
path = ClassicApp;
|
path = ClassicApp;
|
||||||
@@ -4834,6 +4844,7 @@
|
|||||||
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
|
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
|
||||||
0328F54E0C3AAEDDF3E05D9D /* ChatsTabFlowCoordinatorTests.swift */,
|
0328F54E0C3AAEDDF3E05D9D /* ChatsTabFlowCoordinatorTests.swift */,
|
||||||
80935ADC7ED867226225F965 /* ClassicAppAccountManagerTests.swift */,
|
80935ADC7ED867226225F965 /* ClassicAppAccountManagerTests.swift */,
|
||||||
|
4AC3F28DECDF8665E8EBC76E /* ClassicAppMediaLoaderTests.swift */,
|
||||||
D5EA0312A6262484AA393AC9 /* CompletionSuggestionServiceTests.swift */,
|
D5EA0312A6262484AA393AC9 /* CompletionSuggestionServiceTests.swift */,
|
||||||
CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */,
|
CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */,
|
||||||
69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */,
|
69D42EE0102D2857933625DD /* CreateRoomViewModelTests.swift */,
|
||||||
@@ -5705,6 +5716,7 @@
|
|||||||
A18A0DA2E598A7E76C50E53D /* View */ = {
|
A18A0DA2E598A7E76C50E53D /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A5C45578F406698388B97C07 /* AuthenticationClassicAppAccountView.swift */,
|
||||||
D2B0E0A2F16603E7C827C295 /* AuthenticationClassicAppBackupInstructionsView.swift */,
|
D2B0E0A2F16603E7C827C295 /* AuthenticationClassicAppBackupInstructionsView.swift */,
|
||||||
98784280D98C852727BE0111 /* AuthenticationStartLogo.swift */,
|
98784280D98C852727BE0111 /* AuthenticationStartLogo.swift */,
|
||||||
A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */,
|
A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */,
|
||||||
@@ -7765,6 +7777,7 @@
|
|||||||
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
|
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
|
||||||
4BD5AB54A6982CF19F5CC7C4 /* ChatsTabFlowCoordinatorTests.swift in Sources */,
|
4BD5AB54A6982CF19F5CC7C4 /* ChatsTabFlowCoordinatorTests.swift in Sources */,
|
||||||
1F1BCEE81056FD9F344F3B0E /* ClassicAppAccountManagerTests.swift in Sources */,
|
1F1BCEE81056FD9F344F3B0E /* ClassicAppAccountManagerTests.swift in Sources */,
|
||||||
|
1866A34DBAD0F730058CB3C3 /* ClassicAppMediaLoaderTests.swift in Sources */,
|
||||||
B5321A1F5B26A0F3EC54909E /* CollapsibleFlowLayoutTests.swift in Sources */,
|
B5321A1F5B26A0F3EC54909E /* CollapsibleFlowLayoutTests.swift in Sources */,
|
||||||
3A164187907DA43B7858F9EC /* CompletionSuggestionServiceTests.swift in Sources */,
|
3A164187907DA43B7858F9EC /* CompletionSuggestionServiceTests.swift in Sources */,
|
||||||
0C932A5158C1D0604DFC5750 /* ComposerToolbarViewModelTests.swift in Sources */,
|
0C932A5158C1D0604DFC5750 /* ComposerToolbarViewModelTests.swift in Sources */,
|
||||||
@@ -8046,6 +8059,7 @@
|
|||||||
88F348E2CB14FF71CBBB665D /* AudioRoomTimelineItemContent.swift in Sources */,
|
88F348E2CB14FF71CBBB665D /* AudioRoomTimelineItemContent.swift in Sources */,
|
||||||
7BD2123144A32F082CECC108 /* AudioRoomTimelineView.swift in Sources */,
|
7BD2123144A32F082CECC108 /* AudioRoomTimelineView.swift in Sources */,
|
||||||
9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */,
|
9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */,
|
||||||
|
D16B3134A7FF4FBA0749014A /* AuthenticationClassicAppAccountView.swift in Sources */,
|
||||||
90C683C87BF6D39419402E5B /* AuthenticationClassicAppBackupInstructionsView.swift in Sources */,
|
90C683C87BF6D39419402E5B /* AuthenticationClassicAppBackupInstructionsView.swift in Sources */,
|
||||||
A51C65E5A3C9F2464A91A380 /* AuthenticationClientBuilderFactoryMock.swift in Sources */,
|
A51C65E5A3C9F2464A91A380 /* AuthenticationClientBuilderFactoryMock.swift in Sources */,
|
||||||
AE066FC93E7B707C826B335A /* AuthenticationClientFactory.swift in Sources */,
|
AE066FC93E7B707C826B335A /* AuthenticationClientFactory.swift in Sources */,
|
||||||
@@ -8115,6 +8129,8 @@
|
|||||||
F58BF3BD3233A03F013816E4 /* ClassicAppAccountManager.swift in Sources */,
|
F58BF3BD3233A03F013816E4 /* ClassicAppAccountManager.swift in Sources */,
|
||||||
2C289BECCCBEDBA48DC9AC67 /* ClassicAppMXAccount.swift in Sources */,
|
2C289BECCCBEDBA48DC9AC67 /* ClassicAppMXAccount.swift in Sources */,
|
||||||
E8BBCFF3B1380F56C73690BC /* ClassicAppManager.swift in Sources */,
|
E8BBCFF3B1380F56C73690BC /* ClassicAppManager.swift in Sources */,
|
||||||
|
2A4F8B76E1F99DE95D5867E6 /* ClassicAppManagerMock.swift in Sources */,
|
||||||
|
696D34FAA9A4E00435DDAF39 /* ClassicAppMediaLoader.swift in Sources */,
|
||||||
A52090A4FE0DB826578DFC03 /* Client.swift in Sources */,
|
A52090A4FE0DB826578DFC03 /* Client.swift in Sources */,
|
||||||
C80E06ED97CE52704A46C148 /* ClientBuilder.swift in Sources */,
|
C80E06ED97CE52704A46C148 /* ClientBuilder.swift in Sources */,
|
||||||
87CEA3E07B602705BC2D2A20 /* ClientBuilderHook.swift in Sources */,
|
87CEA3E07B602705BC2D2A20 /* ClientBuilderHook.swift in Sources */,
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
provisioningParameters: provisioningParameters,
|
provisioningParameters: provisioningParameters,
|
||||||
isBugReportServiceEnabled: bugReportService.isEnabled,
|
isBugReportServiceEnabled: bugReportService.isEnabled,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
|
mediaProvider: nil, // Currently unused.
|
||||||
userIndicatorController: userIndicatorController)
|
userIndicatorController: userIndicatorController)
|
||||||
let coordinator = AuthenticationStartScreenCoordinator(parameters: parameters)
|
let coordinator = AuthenticationStartScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
extension ClassicAppManagerMock {
|
||||||
|
struct Configuration { }
|
||||||
|
|
||||||
|
convenience init(_ configuration: Configuration) {
|
||||||
|
self.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ClassicAppAccount {
|
||||||
|
static var mockAlice: ClassicAppAccount {
|
||||||
|
ClassicAppAccount(userID: "@alice:matrix.org",
|
||||||
|
displayName: "Alice",
|
||||||
|
avatarURL: nil,
|
||||||
|
serverName: "matrix.org",
|
||||||
|
homeserverURL: "https://matrix-client.matrix.org/",
|
||||||
|
cryptoStoreURL: .cachesDirectory,
|
||||||
|
cryptoStorePassphrase: "1234567890",
|
||||||
|
accessToken: "accessToken")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockDan: ClassicAppAccount {
|
||||||
|
ClassicAppAccount(userID: "@dan:matrix.org",
|
||||||
|
displayName: "Dan",
|
||||||
|
avatarURL: .mockMXCUserAvatar,
|
||||||
|
serverName: "matrix.org",
|
||||||
|
homeserverURL: "https://matrix-client.matrix.org/",
|
||||||
|
cryptoStoreURL: .cachesDirectory,
|
||||||
|
cryptoStorePassphrase: "1234567890",
|
||||||
|
accessToken: "accessToken")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2239,6 +2239,77 @@ class CXProviderMock: CXProviderProtocol, @unchecked Sendable {
|
|||||||
reportCallWithEndedAtReasonClosure?(uuid, endedAt, reason)
|
reportCallWithEndedAtReasonClosure?(uuid, endedAt, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class ClassicAppManagerMock: ClassicAppManagerProtocol, @unchecked Sendable {
|
||||||
|
|
||||||
|
//MARK: - loadAccounts
|
||||||
|
|
||||||
|
var loadAccountsThrowableError: Error?
|
||||||
|
var loadAccountsUnderlyingCallsCount = 0
|
||||||
|
var loadAccountsCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return loadAccountsUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = loadAccountsUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
loadAccountsUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
loadAccountsUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var loadAccountsCalled: Bool {
|
||||||
|
return loadAccountsCallsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadAccountsUnderlyingReturnValue: [ClassicAppAccount]!
|
||||||
|
var loadAccountsReturnValue: [ClassicAppAccount]! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return loadAccountsUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: [ClassicAppAccount]? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = loadAccountsUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
loadAccountsUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
loadAccountsUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var loadAccountsClosure: (() throws -> [ClassicAppAccount])?
|
||||||
|
|
||||||
|
func loadAccounts() throws -> [ClassicAppAccount] {
|
||||||
|
if let error = loadAccountsThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
loadAccountsCallsCount += 1
|
||||||
|
if let loadAccountsClosure = loadAccountsClosure {
|
||||||
|
return try loadAccountsClosure()
|
||||||
|
} else {
|
||||||
|
return loadAccountsReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||||
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> {
|
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> {
|
||||||
get { return underlyingActionsPublisher }
|
get { return underlyingActionsPublisher }
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ enum UserAvatarSizeOnScreen {
|
|||||||
case threadList
|
case threadList
|
||||||
case threadSummary
|
case threadSummary
|
||||||
case map
|
case map
|
||||||
|
case classicAppAccount
|
||||||
|
|
||||||
var value: CGFloat {
|
var value: CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -115,7 +116,7 @@ enum UserAvatarSizeOnScreen {
|
|||||||
64
|
64
|
||||||
case .dmDetails:
|
case .dmDetails:
|
||||||
75
|
75
|
||||||
case .memberDetails, .editUserDetails:
|
case .memberDetails, .editUserDetails, .classicAppAccount:
|
||||||
96
|
96
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -14,9 +14,20 @@ struct AuthenticationStartScreenParameters {
|
|||||||
let provisioningParameters: AccountProvisioningParameters?
|
let provisioningParameters: AccountProvisioningParameters?
|
||||||
let isBugReportServiceEnabled: Bool
|
let isBugReportServiceEnabled: Bool
|
||||||
let appSettings: AppSettings
|
let appSettings: AppSettings
|
||||||
|
let mediaProvider: MediaProviderProtocol?
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AuthenticationStartScreenCoordinatorAction {
|
||||||
|
case loginWithQR
|
||||||
|
case login
|
||||||
|
case register
|
||||||
|
case reportProblem
|
||||||
|
|
||||||
|
case loginDirectlyWithOIDC(data: OIDCAuthorizationDataProxy, window: UIWindow)
|
||||||
|
case loginDirectlyWithPassword(loginHint: String?)
|
||||||
|
}
|
||||||
|
|
||||||
final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
||||||
private var viewModel: AuthenticationStartScreenViewModelProtocol
|
private var viewModel: AuthenticationStartScreenViewModelProtocol
|
||||||
private let actionsSubject: PassthroughSubject<AuthenticationStartScreenCoordinatorAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<AuthenticationStartScreenCoordinatorAction, Never> = .init()
|
||||||
@@ -31,6 +42,7 @@ final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
|||||||
provisioningParameters: parameters.provisioningParameters,
|
provisioningParameters: parameters.provisioningParameters,
|
||||||
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled,
|
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled,
|
||||||
appSettings: parameters.appSettings,
|
appSettings: parameters.appSettings,
|
||||||
|
mediaProvider: parameters.mediaProvider,
|
||||||
userIndicatorController: parameters.userIndicatorController)
|
userIndicatorController: parameters.userIndicatorController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-12
@@ -8,18 +8,6 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// MARK: - Coordinator
|
|
||||||
|
|
||||||
enum AuthenticationStartScreenCoordinatorAction {
|
|
||||||
case loginWithQR
|
|
||||||
case login
|
|
||||||
case register
|
|
||||||
case reportProblem
|
|
||||||
|
|
||||||
case loginDirectlyWithOIDC(data: OIDCAuthorizationDataProxy, window: UIWindow)
|
|
||||||
case loginDirectlyWithPassword(loginHint: String?)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AuthenticationStartScreenViewModelAction: Equatable {
|
enum AuthenticationStartScreenViewModelAction: Equatable {
|
||||||
case loginWithQR
|
case loginWithQR
|
||||||
case login
|
case login
|
||||||
@@ -70,4 +58,8 @@ enum AuthenticationStartScreenViewAction {
|
|||||||
case login
|
case login
|
||||||
case register
|
case register
|
||||||
case reportProblem
|
case reportProblem
|
||||||
|
|
||||||
|
case continueWithClassic(ClassicAppAccount)
|
||||||
|
case otherOptions(ClassicAppAccount)
|
||||||
|
case closeOtherOptions(ClassicAppAccount)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -24,11 +24,12 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
var actions: AnyPublisher<AuthenticationStartScreenViewModelAction, Never> {
|
var actions: AnyPublisher<AuthenticationStartScreenViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(authenticationService: AuthenticationServiceProtocol,
|
init(authenticationService: AuthenticationServiceProtocol,
|
||||||
provisioningParameters: AccountProvisioningParameters?,
|
provisioningParameters: AccountProvisioningParameters?,
|
||||||
isBugReportServiceEnabled: Bool,
|
isBugReportServiceEnabled: Bool,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
|
mediaProvider: MediaProviderProtocol?,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||||
self.authenticationService = authenticationService
|
self.authenticationService = authenticationService
|
||||||
self.provisioningParameters = provisioningParameters
|
self.provisioningParameters = provisioningParameters
|
||||||
@@ -59,9 +60,9 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
hideBrandChrome: appSettings.hideBrandChrome)
|
hideBrandChrome: appSettings.hideBrandChrome)
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init(initialViewState: initialViewState)
|
super.init(initialViewState: initialViewState, mediaProvider: mediaProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: AuthenticationStartScreenViewAction) {
|
override func process(viewAction: AuthenticationStartScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .updateWindow(let window):
|
case .updateWindow(let window):
|
||||||
@@ -77,6 +78,8 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
if canReportProblem {
|
if canReportProblem {
|
||||||
actionsSubject.send(.reportProblem)
|
actionsSubject.send(.reportProblem)
|
||||||
}
|
}
|
||||||
|
case .continueWithClassic, .otherOptions, .closeOtherOptions:
|
||||||
|
break // To follow.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 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 Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AuthenticationClassicAppAccountView: View {
|
||||||
|
@Bindable var context: AuthenticationStartScreenViewModel.Context
|
||||||
|
|
||||||
|
let classicAppAccount: ClassicAppAccount
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
FullscreenDialog(topPadding: 25, background: .gradient) {
|
||||||
|
VStack(spacing: 38) {
|
||||||
|
header
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
profile
|
||||||
|
|
||||||
|
buttons
|
||||||
|
}
|
||||||
|
} bottomContent: {
|
||||||
|
// Buttons are intentionally shown inline on this screen.
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.alert(item: $context.alertInfo)
|
||||||
|
.introspect(.window, on: .supportedVersions) { window in
|
||||||
|
context.send(viewAction: .updateWindow(window))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var header: some View {
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
AuthenticationStartLogo(size: 54, hideBrandChrome: false, isOnGradient: false)
|
||||||
|
|
||||||
|
Text(L10n.screenOnboardingWelcomeTitle)
|
||||||
|
.font(.compound.headingMDBold)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile: some View {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
LoadableAvatarImage(url: classicAppAccount.avatarURL,
|
||||||
|
name: classicAppAccount.displayName,
|
||||||
|
contentID: classicAppAccount.userID,
|
||||||
|
avatarSize: .user(on: .classicAppAccount),
|
||||||
|
mediaProvider: context.mediaProvider)
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Text(L10n.screenOnboardingWelcomeBack)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.foregroundStyle(.compound.textSecondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
Text(classicAppAccount.displayableName)
|
||||||
|
.font(.compound.headingLGBold)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(classicAppAccount.userID)
|
||||||
|
.font(.compound.bodyLGSemibold)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttons: some View {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Button(L10n.actionContinue) {
|
||||||
|
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.primary))
|
||||||
|
|
||||||
|
Button(L10n.commonOtherOptions) {
|
||||||
|
context.send(viewAction: .otherOptions(classicAppAccount))
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.secondary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ClassicAppAccount {
|
||||||
|
var displayableName: String {
|
||||||
|
if let displayName, !displayName.isEmpty {
|
||||||
|
displayName
|
||||||
|
} else if let localPart = userID.dropFirst().split(separator: ":").first, !localPart.isEmpty {
|
||||||
|
String(localPart)
|
||||||
|
} else {
|
||||||
|
userID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct AuthenticationClassicAppAccountView_Previews: PreviewProvider { // Not Testable – snapshots generated by main screen.
|
||||||
|
static let viewModel = makeViewModel()
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
ElementNavigationStack {
|
||||||
|
AuthenticationClassicAppAccountView(context: viewModel.context, classicAppAccount: .mockDan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeViewModel() -> AuthenticationStartScreenViewModel {
|
||||||
|
AuthenticationStartScreenViewModel(authenticationService: AuthenticationService.mock,
|
||||||
|
provisioningParameters: nil,
|
||||||
|
isBugReportServiceEnabled: false,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -69,6 +69,7 @@ struct AuthenticationClassicAppBackupInstructionsView_Previews: PreviewProvider,
|
|||||||
provisioningParameters: nil,
|
provisioningParameters: nil,
|
||||||
isBugReportServiceEnabled: false,
|
isBugReportServiceEnabled: false,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
userIndicatorController: UserIndicatorControllerMock())
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -152,6 +152,7 @@ struct AuthenticationStartScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
provisioningParameters: provisionedServerName.map { .init(accountProvider: $0, loginHint: nil) },
|
provisioningParameters: provisionedServerName.map { .init(accountProvider: $0, loginHint: nil) },
|
||||||
isBugReportServiceEnabled: true,
|
isBugReportServiceEnabled: true,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
userIndicatorController: UserIndicatorControllerMock())
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,10 +68,12 @@ class ClassicAppAccountManager {
|
|||||||
|
|
||||||
return ClassicAppAccount(userID: userID,
|
return ClassicAppAccount(userID: userID,
|
||||||
displayName: user?.displayName,
|
displayName: user?.displayName,
|
||||||
avatarURL: user?.avatarURL.flatMap(URL.init(string:)),
|
avatarURL: user?.avatarURL,
|
||||||
serverName: serverName,
|
serverName: serverName,
|
||||||
|
homeserverURL: mxAccount.homeserverURL,
|
||||||
cryptoStoreURL: cryptoStoreURL(for: userID),
|
cryptoStoreURL: cryptoStoreURL(for: userID),
|
||||||
cryptoStorePassphrase: cryptoStorePassphrase)
|
cryptoStorePassphrase: cryptoStorePassphrase.base64EncodedString(),
|
||||||
|
accessToken: mxAccount.accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadUser(for mxAccount: ClassicAppMXAccount) -> ClassicAppMXUser? {
|
private func loadUser(for mxAccount: ClassicAppMXAccount) -> ClassicAppMXUser? {
|
||||||
|
|||||||
@@ -7,13 +7,23 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ClassicAppAccount: Equatable {
|
struct ClassicAppAccount: Equatable, CustomStringConvertible {
|
||||||
let userID: String
|
let userID: String
|
||||||
let displayName: String?
|
let displayName: String?
|
||||||
let avatarURL: URL?
|
let avatarURL: URL?
|
||||||
|
|
||||||
let serverName: String
|
let serverName: String
|
||||||
|
let homeserverURL: URL
|
||||||
|
|
||||||
let cryptoStoreURL: URL
|
let cryptoStoreURL: URL
|
||||||
let cryptoStorePassphrase: Data
|
let cryptoStorePassphrase: String
|
||||||
|
|
||||||
|
let accessToken: String // For avatar loading and key backup detection.
|
||||||
|
|
||||||
|
/// Custom `CustomStringConvertible` without the access token.
|
||||||
|
var description: String {
|
||||||
|
"ClassicAppAccount(userID: \(userID), homeserverURL: \(homeserverURL))"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: NSCoding Types
|
// MARK: NSCoding Types
|
||||||
@@ -21,8 +31,10 @@ struct ClassicAppAccount: Equatable {
|
|||||||
final class ClassicAppMXAccount: NSObject, NSCoding {
|
final class ClassicAppMXAccount: NSObject, NSCoding {
|
||||||
/// The obtained user ID.
|
/// The obtained user ID.
|
||||||
var userID: String
|
var userID: String
|
||||||
|
/// The access token to create a MXRestClient.
|
||||||
|
var accessToken: String
|
||||||
/// The homeserver url (ex: "https://matrix.org").
|
/// The homeserver url (ex: "https://matrix.org").
|
||||||
var homeserver: String?
|
var homeserverURL: URL
|
||||||
|
|
||||||
/// Disable the account without logging out (NO by default).
|
/// Disable the account without logging out (NO by default).
|
||||||
///
|
///
|
||||||
@@ -37,6 +49,11 @@ final class ClassicAppMXAccount: NSObject, NSCoding {
|
|||||||
!isDisabled && !isSoftLogout
|
!isDisabled && !isSoftLogout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override the existing `CustomStringConvertible` conformance.
|
||||||
|
override var description: String {
|
||||||
|
"ClassicAppMXAccount(userID: \(userID), homeserverURL: \(homeserverURL), isDisabled: \(isDisabled), isSoftLogout: \(isSoftLogout))"
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: NSCoding
|
// MARK: NSCoding
|
||||||
|
|
||||||
enum Keys {
|
enum Keys {
|
||||||
@@ -64,12 +81,15 @@ final class ClassicAppMXAccount: NSObject, NSCoding {
|
|||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
guard let userID = coder.decodeObject(forKey: Keys.userID) as? String,
|
guard let userID = coder.decodeObject(forKey: Keys.userID) as? String,
|
||||||
let homeserver = coder.decodeObject(forKey: Keys.homeserverURL) as? String else {
|
let accessToken = coder.decodeObject(forKey: Keys.accessToken) as? String,
|
||||||
|
let homeserver = coder.decodeObject(forKey: Keys.homeserverURL) as? String,
|
||||||
|
let homeserverURL = URL(string: homeserver) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.userID = userID
|
self.userID = userID
|
||||||
self.homeserver = homeserver
|
self.accessToken = accessToken
|
||||||
|
self.homeserverURL = homeserverURL
|
||||||
|
|
||||||
isDisabled = coder.decodeBool(forKey: Keys.isDisabled)
|
isDisabled = coder.decodeBool(forKey: Keys.isDisabled)
|
||||||
isSoftLogout = coder.decodeBool(forKey: Keys.isSoftLogout)
|
isSoftLogout = coder.decodeBool(forKey: Keys.isSoftLogout)
|
||||||
@@ -89,7 +109,7 @@ final class ClassicAppMXUser: NSObject, NSCoding {
|
|||||||
/// The user display name.
|
/// The user display name.
|
||||||
let displayName: String?
|
let displayName: String?
|
||||||
/// The url of the user of the avatar.
|
/// The url of the user of the avatar.
|
||||||
let avatarURL: String?
|
let avatarURL: URL?
|
||||||
|
|
||||||
// MARK: NSCoding
|
// MARK: NSCoding
|
||||||
|
|
||||||
@@ -110,7 +130,9 @@ final class ClassicAppMXUser: NSObject, NSCoding {
|
|||||||
|
|
||||||
self.userID = userID
|
self.userID = userID
|
||||||
displayName = aDecoder.decodeObject(forKey: Keys.displayName) as? String
|
displayName = aDecoder.decodeObject(forKey: Keys.displayName) as? String
|
||||||
avatarURL = aDecoder.decodeObject(forKey: Keys.avatarURL) as? String
|
|
||||||
|
let avatarURLString = aDecoder.decodeObject(forKey: Keys.avatarURL) as? String
|
||||||
|
avatarURL = avatarURLString.flatMap { URL(string: $0) }
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Foundation
|
|||||||
import KeychainAccess
|
import KeychainAccess
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
// sourcery: AutoMockable
|
||||||
protocol ClassicAppManagerProtocol {
|
protocol ClassicAppManagerProtocol {
|
||||||
func loadAccounts() throws -> [ClassicAppAccount]
|
func loadAccounts() throws -> [ClassicAppAccount]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// 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 SwiftUI
|
||||||
|
|
||||||
|
/// A media provider that can download a `ClassicAppAccount`'s avatar image.
|
||||||
|
class ClassicAppMediaLoader: MediaLoaderProtocol {
|
||||||
|
let classicAppAccount: ClassicAppAccount
|
||||||
|
let urlSession: URLSession
|
||||||
|
|
||||||
|
init(classicAppAccount: ClassicAppAccount, urlSession: URLSession = .shared) {
|
||||||
|
self.classicAppAccount = classicAppAccount
|
||||||
|
self.urlSession = urlSession
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
|
||||||
|
try await loadMedia(source: source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data {
|
||||||
|
try await loadMedia(source: source, width: width, height: height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy {
|
||||||
|
throw MediaLoaderError.notSupported // Not needed for LoadableImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func loadMedia(source: MediaSourceProxy, width: UInt? = nil, height: UInt? = nil) async throws -> Data {
|
||||||
|
guard let mxcURL = source.url else {
|
||||||
|
MXLog.error("The provided media source is missing the URL")
|
||||||
|
throw MediaLoaderError.invalidURL
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let request = mediaURLRequest(from: mxcURL, width: width, height: height) else {
|
||||||
|
MXLog.error("Failed to construct media URL for source: \(mxcURL)")
|
||||||
|
throw MediaLoaderError.invalidURL
|
||||||
|
}
|
||||||
|
|
||||||
|
let (data, response) = try await urlSession.data(for: request)
|
||||||
|
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
|
||||||
|
MXLog.error("Unexpected response fetching ClassicAppAccount media: \(source.url?.absoluteString ?? "nil")")
|
||||||
|
throw MediaLoaderError.unexpectedResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs an authenticated `URLRequest` to download Matrix media from the homeserver.
|
||||||
|
///
|
||||||
|
/// - When `width` and `height` are provided, converts `mxc://{serverName}/{mediaId}` to
|
||||||
|
/// `{homeserverURL}/_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}?width=…&height=…`
|
||||||
|
/// - Otherwise converts to `{homeserverURL}/_matrix/client/v1/media/download/{serverName}/{mediaId}`
|
||||||
|
///
|
||||||
|
/// Sets the `Authorization: Bearer` header when an access token is provided.
|
||||||
|
private func mediaURLRequest(from mxcURL: URL, width: UInt?, height: UInt?) -> URLRequest? {
|
||||||
|
guard mxcURL.scheme == "mxc" else { return nil }
|
||||||
|
|
||||||
|
let serverName = mxcURL.host() ?? ""
|
||||||
|
let mediaID = mxcURL.path().trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||||
|
|
||||||
|
guard !serverName.isEmpty, !mediaID.isEmpty else { return nil }
|
||||||
|
|
||||||
|
let isThumbnail = width != nil && height != nil
|
||||||
|
let endpoint = isThumbnail ? "_matrix/client/v1/media/thumbnail" : "_matrix/client/v1/media/download"
|
||||||
|
|
||||||
|
var components = URLComponents(url: classicAppAccount.homeserverURL
|
||||||
|
.appending(path: endpoint)
|
||||||
|
.appending(path: serverName)
|
||||||
|
.appending(path: mediaID), resolvingAgainstBaseURL: false)
|
||||||
|
|
||||||
|
if let width, let height {
|
||||||
|
components?.queryItems = [
|
||||||
|
URLQueryItem(name: "width", value: String(width)),
|
||||||
|
URLQueryItem(name: "height", value: String(height))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let url = components?.url else { return nil }
|
||||||
|
|
||||||
|
var request = URLRequest(url: url)
|
||||||
|
request.setValue("Bearer \(classicAppAccount.accessToken)", forHTTPHeaderField: "Authorization")
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,9 @@ import Foundation
|
|||||||
|
|
||||||
enum MediaLoaderError: Error {
|
enum MediaLoaderError: Error {
|
||||||
case missingClient
|
case missingClient
|
||||||
|
case notSupported
|
||||||
|
case invalidURL
|
||||||
|
case unexpectedResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
provisioningParameters: provisioningParameters,
|
provisioningParameters: provisioningParameters,
|
||||||
isBugReportServiceEnabled: true,
|
isBugReportServiceEnabled: true,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||||
userIndicatorController: UserIndicatorControllerMock())
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
|
|
||||||
// Add a fake window in order for the OIDC flow to continue
|
// Add a fake window in order for the OIDC flow to continue
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ extension ClassicAppAccount {
|
|||||||
displayName: "Classic App Account",
|
displayName: "Classic App Account",
|
||||||
avatarURL: "mxc://matrix.org/LYIzLOiILkjQJCqsgzAOUirs",
|
avatarURL: "mxc://matrix.org/LYIzLOiILkjQJCqsgzAOUirs",
|
||||||
serverName: "matrix.org",
|
serverName: "matrix.org",
|
||||||
|
homeserverURL: "https://matrix-client.matrix.org",
|
||||||
cryptoStoreURL: classicAppAccountManager.cryptoStoreURL(for: userID),
|
cryptoStoreURL: classicAppAccountManager.cryptoStoreURL(for: userID),
|
||||||
cryptoStorePassphrase: cryptoStorePassphrase)
|
cryptoStorePassphrase: cryptoStorePassphrase.base64EncodedString(),
|
||||||
|
accessToken: "mct_6luZquERViQxGSXqzdxDeMpQkEjHpk_ISvHO2") // Note: Deactivated account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
final class ClassicAppMediaLoaderTests {
|
||||||
|
let account: ClassicAppAccount
|
||||||
|
let mediaLoader: ClassicAppMediaLoader
|
||||||
|
let urlSession: URLSession
|
||||||
|
|
||||||
|
init() throws {
|
||||||
|
account = ClassicAppAccount(userID: "@alice:matrix.org",
|
||||||
|
displayName: nil,
|
||||||
|
avatarURL: nil,
|
||||||
|
serverName: "matrix.org",
|
||||||
|
homeserverURL: "https://matrix-client.matrix.org",
|
||||||
|
cryptoStoreURL: .temporaryDirectory,
|
||||||
|
cryptoStorePassphrase: "",
|
||||||
|
accessToken: MockURLProtocol.accessToken)
|
||||||
|
|
||||||
|
let configuration = URLSessionConfiguration.ephemeral
|
||||||
|
configuration.protocolClasses = [MockURLProtocol.self]
|
||||||
|
urlSession = URLSession(configuration: configuration)
|
||||||
|
|
||||||
|
mediaLoader = ClassicAppMediaLoader(classicAppAccount: account, urlSession: urlSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Images
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaContent() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
||||||
|
let data = try await mediaLoader.loadMediaContentForSource(source)
|
||||||
|
#expect(data == MockURLProtocol.downloadData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaContentWithInvalidToken() async throws {
|
||||||
|
let accountWithoutToken = ClassicAppAccount(userID: "@bob:matrix.org",
|
||||||
|
displayName: nil,
|
||||||
|
avatarURL: nil,
|
||||||
|
serverName: "matrix.org",
|
||||||
|
homeserverURL: "https://matrix-client.matrix.org",
|
||||||
|
cryptoStoreURL: .temporaryDirectory,
|
||||||
|
cryptoStorePassphrase: "",
|
||||||
|
accessToken: "wrongToken")
|
||||||
|
let loaderWithoutToken = ClassicAppMediaLoader(classicAppAccount: accountWithoutToken, urlSession: urlSession)
|
||||||
|
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
||||||
|
await #expect(throws: MediaLoaderError.unexpectedResponse) {
|
||||||
|
try await loaderWithoutToken.loadMediaContentForSource(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaContentWith404() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.notFoundMXCURL, mimeType: nil)
|
||||||
|
await #expect(throws: MediaLoaderError.unexpectedResponse) {
|
||||||
|
try await mediaLoader.loadMediaContentForSource(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Thumbnails
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaThumbnail() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
||||||
|
let data = try await mediaLoader.loadMediaThumbnailForSource(source, width: 100, height: 100)
|
||||||
|
#expect(data == MockURLProtocol.thumbnailData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaThumbnailWith404() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.notFoundMXCURL, mimeType: nil)
|
||||||
|
await #expect(throws: MediaLoaderError.unexpectedResponse) {
|
||||||
|
try await mediaLoader.loadMediaThumbnailForSource(source, width: 100, height: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Files (unsupported)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaFileIsNotSupported() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
||||||
|
await #expect(throws: MediaLoaderError.notSupported) {
|
||||||
|
try await mediaLoader.loadMediaFileForSource(source, filename: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - URL construction
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaContentBuildsCorrectDownloadURL() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
||||||
|
let capturedRequest = try await mediaLoader.loadMediaContentForSource(source)
|
||||||
|
|
||||||
|
// The download path must use the download endpoint with no query string.
|
||||||
|
#expect(MockURLProtocol.lastRequest?.url?.path() == "/_matrix/client/v1/media/download/matrix.org/testmediaid")
|
||||||
|
#expect(MockURLProtocol.lastRequest?.url?.query() == nil)
|
||||||
|
#expect(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockURLProtocol.accessToken)")
|
||||||
|
_ = capturedRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func loadMediaThumbnailBuildsCorrectThumbnailURL() async throws {
|
||||||
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
||||||
|
_ = try await mediaLoader.loadMediaThumbnailForSource(source, width: 320, height: 240)
|
||||||
|
|
||||||
|
// The thumbnail path must use the thumbnail endpoint with width/height query items.
|
||||||
|
let url = try #require(MockURLProtocol.lastRequest?.url)
|
||||||
|
#expect(url.path() == "/_matrix/client/v1/media/thumbnail/matrix.org/testmediaid")
|
||||||
|
let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
|
||||||
|
#expect(queryItems?.contains(URLQueryItem(name: "width", value: "320")) == true)
|
||||||
|
#expect(queryItems?.contains(URLQueryItem(name: "height", value: "240")) == true)
|
||||||
|
#expect(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockURLProtocol.accessToken)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - MockURLProtocol
|
||||||
|
|
||||||
|
private class MockURLProtocol: URLProtocol {
|
||||||
|
/// The MXC URL whose media requests will be served successfully.
|
||||||
|
static let mxcURL: URL = "mxc://matrix.org/testmediaid"
|
||||||
|
/// The MXC URL whose media requests will return a 404.
|
||||||
|
static let notFoundMXCURL: URL = "mxc://matrix.org/notfound"
|
||||||
|
|
||||||
|
/// The access token expected on all authenticated requests.
|
||||||
|
static let accessToken = "testAccessToken"
|
||||||
|
|
||||||
|
/// The data returned for a full-size download of `mxcURL`.
|
||||||
|
static let downloadData = Data("download data".utf8)
|
||||||
|
/// The data returned for a thumbnail download of `mxcURL`.
|
||||||
|
static let thumbnailData = Data("thumbnail data".utf8)
|
||||||
|
|
||||||
|
/// The last request handled, for URL/header inspection in tests.
|
||||||
|
static var lastRequest: URLRequest?
|
||||||
|
|
||||||
|
/// Maps a URL path to a fixed `(statusCode, Data)` response.
|
||||||
|
private static let responses: [String: (Int, Data)] = [
|
||||||
|
"/_matrix/client/v1/media/download/matrix.org/testmediaid": (200, downloadData),
|
||||||
|
"/_matrix/client/v1/media/thumbnail/matrix.org/testmediaid": (200, thumbnailData)
|
||||||
|
]
|
||||||
|
|
||||||
|
override func startLoading() {
|
||||||
|
MockURLProtocol.lastRequest = request
|
||||||
|
|
||||||
|
guard let url = request.url else {
|
||||||
|
client?.urlProtocol(self, didFailWithError: URLError(.badURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isAuthenticated = request.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockURLProtocol.accessToken)"
|
||||||
|
let path = url.path()
|
||||||
|
let (statusCode, data) = isAuthenticated ? MockURLProtocol.responses[path] ?? (404, Data()) : (401, Data())
|
||||||
|
|
||||||
|
guard let response = HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: nil, headerFields: nil) else {
|
||||||
|
client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
||||||
|
client?.urlProtocol(self, didLoad: data)
|
||||||
|
client?.urlProtocolDidFinishLoading(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func stopLoading() { }
|
||||||
|
|
||||||
|
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
||||||
|
request
|
||||||
|
}
|
||||||
|
|
||||||
|
override class func canInit(with request: URLRequest) -> Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user