mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
Add a Link New Device screen (behind a feature flag). (#4811)
This commit is contained in:
@@ -279,6 +279,10 @@ extension AccessibilityTests {
|
||||
try await performAccessibilityAudit(named: "LegalInformationScreen_Previews")
|
||||
}
|
||||
|
||||
func testLinkNewDeviceScreen() async throws {
|
||||
try await performAccessibilityAudit(named: "LinkNewDeviceScreen_Previews")
|
||||
}
|
||||
|
||||
func testLoadableImage() async throws {
|
||||
try await performAccessibilityAudit(named: "LoadableImage_Previews")
|
||||
}
|
||||
|
||||
@@ -597,6 +597,7 @@
|
||||
695BE6A2337A634F48B5DBC8 /* RoomMembersFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC666DAE98245269775329B2 /* RoomMembersFlowCoordinatorTests.swift */; };
|
||||
69A9B430397C15075D86193F /* UserPropertiesExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AFD800AF033D8B0D11191A /* UserPropertiesExt.swift */; };
|
||||
69B3C6010B42010F591FC3CB /* RoomRolesAndPermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1AF829F12FDC99717082D9 /* RoomRolesAndPermissionsScreenViewModel.swift */; };
|
||||
69B9CC733A880E1BB097C113 /* LinkNewDeviceScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */; };
|
||||
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; };
|
||||
69DE29C3E3180BB17D840690 /* ProgressCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97C8E13A1FBA717B0C277ECC /* ProgressCursorModifier.swift */; };
|
||||
6A38D0A2BC3943A92D82576E /* EditRoomAddressScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B18089ED50324583BB2FB7 /* EditRoomAddressScreenViewModelProtocol.swift */; };
|
||||
@@ -834,6 +835,7 @@
|
||||
9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */; };
|
||||
9295F1F5E04484E10780BCE8 /* CharacterSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8C01DEEA83903D45069BBD /* CharacterSet.swift */; };
|
||||
92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */; };
|
||||
92FE657CDFAFE3031576EB43 /* LinkNewDeviceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */; };
|
||||
9312F5A17AE59A9E910C51D6 /* NotificationItemProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840182D7A61402D5947DE094 /* NotificationItemProxyMock.swift */; };
|
||||
9322949BFCA6278921085862 /* DeactivateAccountScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664D3C710B4A12CE4E623645 /* DeactivateAccountScreenModels.swift */; };
|
||||
934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */; };
|
||||
@@ -1019,6 +1021,7 @@
|
||||
B3D8AA9988F8A000B162DCB5 /* TombstonedAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */; };
|
||||
B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */; };
|
||||
B444F9C184A377C1B481F07F /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; };
|
||||
B466827F3766FF8E0CD0D34F /* LinkNewDeviceScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA49F233E193590274FD19F /* LinkNewDeviceScreenCoordinator.swift */; };
|
||||
B47213F07A67CE3A8D49CEC9 /* TimelineControllerFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC1E3FE9B59EA094867863E /* TimelineControllerFactoryProtocol.swift */; };
|
||||
B4A0C69370E6008A971463E7 /* BugReportScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */; };
|
||||
B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; };
|
||||
@@ -1088,6 +1091,7 @@
|
||||
C11D4A49DC29D89CE2BB31B8 /* MediaEventsTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976ED77B772F50C4BAD757E7 /* MediaEventsTimelineScreenViewModel.swift */; };
|
||||
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */; };
|
||||
C1429699A6A5BB09A25775C1 /* AudioPlayerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */; };
|
||||
C16E25C41B858BF27E0C4FC6 /* LinkNewDeviceScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D3DA6959C041E212A718DD3 /* LinkNewDeviceScreenViewModel.swift */; };
|
||||
C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */; };
|
||||
C1A5C386319835FB0C77736B /* ReportContentScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */; };
|
||||
C1D0AB8222D7BAFC9AF9C8C0 /* MapLibreMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622D09D4ECE759189009AEAF /* MapLibreMapView.swift */; };
|
||||
@@ -1237,6 +1241,7 @@
|
||||
DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; };
|
||||
DDE7B4771452300C103B1EB8 /* RoomDirectoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F6210134203BE1F2DD5C679 /* RoomDirectoryCell.swift */; };
|
||||
DDFBDEE1DC32BDD5488F898C /* ClientProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */; };
|
||||
DE3BF0ED68E56BF625591D49 /* LinkNewDeviceScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */; };
|
||||
DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; };
|
||||
DEDBD3E9CFCC9F20CAC79881 /* JoinRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */; };
|
||||
DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */; };
|
||||
@@ -1844,6 +1849,7 @@
|
||||
3C3ADF21BE301D0DA48F2A7E /* ShareExtensionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionView.swift; sourceTree = "<group>"; };
|
||||
3C3E67E09FE5A35D73818C39 /* AppLockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenModels.swift; sourceTree = "<group>"; };
|
||||
3C8A63F1458D97D832C127DD /* app.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = app.yml; sourceTree = "<group>"; };
|
||||
3CA49F233E193590274FD19F /* LinkNewDeviceScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenModels.swift; sourceTree = "<group>"; };
|
||||
3CFD5EB0B0EEA4549FB49784 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
||||
3D1D4A6D451F43A03CACD01D /* PINTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextField.swift; sourceTree = "<group>"; };
|
||||
@@ -2121,6 +2127,7 @@
|
||||
723B055A57857BFF0F18D9CB /* test_rotated_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = test_rotated_image.jpg; sourceTree = "<group>"; };
|
||||
72614BFF35B8394C6E13F55A /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = "<group>"; };
|
||||
726E901DF76393E335FD7E8E /* ManageAuthorizedSpacesScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAuthorizedSpacesScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreen.swift; sourceTree = "<group>"; };
|
||||
72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = "<group>"; };
|
||||
7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = "<group>"; };
|
||||
7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -2352,6 +2359,7 @@
|
||||
9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachment.swift; sourceTree = "<group>"; };
|
||||
9D3DA6959C041E212A718DD3 /* LinkNewDeviceScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
9E3B41C36800DD4558D7BDA7 /* VoiceMessageRoomPlaybackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRoomPlaybackView.swift; sourceTree = "<group>"; };
|
||||
9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = "<group>"; };
|
||||
9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockedRoomProxyMock.swift; sourceTree = "<group>"; };
|
||||
@@ -2388,6 +2396,7 @@
|
||||
A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreen.swift; sourceTree = "<group>"; };
|
||||
A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = "<group>"; };
|
||||
A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenModels.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>"; };
|
||||
A6B19D10B102956066AF117B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
|
||||
@@ -2778,6 +2787,7 @@
|
||||
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
|
||||
ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
ED0AD0C652385F69FA90FAF5 /* TimelineMediaPreviewDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewDataSourceTests.swift; sourceTree = "<group>"; };
|
||||
ED0CBEAB5F796BEFBAF7BB6A /* VideoRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -4426,6 +4436,18 @@
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5E5152617DFA7CCA3F4FA683 /* LinkNewDeviceScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3CA49F233E193590274FD19F /* LinkNewDeviceScreenCoordinator.swift */,
|
||||
A4D09290C6791D6EF04F569E /* LinkNewDeviceScreenModels.swift */,
|
||||
9D3DA6959C041E212A718DD3 /* LinkNewDeviceScreenViewModel.swift */,
|
||||
ED096460D7F26F10168FA33B /* LinkNewDeviceScreenViewModelProtocol.swift */,
|
||||
BB11CF5847CA0B3BC3A7F8DA /* View */,
|
||||
);
|
||||
path = LinkNewDeviceScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5EC4A8482DA110602FE6DF42 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -5796,6 +5818,14 @@
|
||||
path = ServerConfirmationScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB11CF5847CA0B3BC3A7F8DA /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BC8F2F197AFDABE63B2E2CA7 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -6303,6 +6333,7 @@
|
||||
FFD7C58CA6A7D6BBC2F584B5 /* JoinRoomScreen */,
|
||||
BF0415BE807CA2BCFC210008 /* KnockRequestsListScreen */,
|
||||
4D963F50D7AA8FE302CA8ACF /* LabsScreen */,
|
||||
5E5152617DFA7CCA3F4FA683 /* LinkNewDeviceScreen */,
|
||||
948DD12A5533BE1BC260E437 /* LocationSharing */,
|
||||
73E032ADD008D63812791D97 /* LogViewerScreen */,
|
||||
24FBB8F76D6435033CD07747 /* ManageAuthorizedSpacesScreen */,
|
||||
@@ -7990,6 +8021,11 @@
|
||||
A9D349478F7D4A2B1E40CEF9 /* LegalInformationScreenViewModelProtocol.swift in Sources */,
|
||||
5E597B9959BDAE7A67DBD5B2 /* LinkMetadataProvider.swift in Sources */,
|
||||
112C6F1C493B3F26AB22716A /* LinkMetadataProviderProtocol.swift in Sources */,
|
||||
DE3BF0ED68E56BF625591D49 /* LinkNewDeviceScreen.swift in Sources */,
|
||||
B466827F3766FF8E0CD0D34F /* LinkNewDeviceScreenCoordinator.swift in Sources */,
|
||||
69B9CC733A880E1BB097C113 /* LinkNewDeviceScreenModels.swift in Sources */,
|
||||
C16E25C41B858BF27E0C4FC6 /* LinkNewDeviceScreenViewModel.swift in Sources */,
|
||||
92FE657CDFAFE3031576EB43 /* LinkNewDeviceScreenViewModelProtocol.swift in Sources */,
|
||||
866FA35E7A2339EF8B6D91CA /* LinkPreviewView.swift in Sources */,
|
||||
6E47D126DD7585E8F8237CE7 /* LoadableAvatarImage.swift in Sources */,
|
||||
D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"Notification" = "Notification";
|
||||
"a11y_add_reaction" = "Add reaction: %1$@";
|
||||
"a11y_avatar" = "Avatar";
|
||||
"a11y_collapse_message_text_field" = "Minimise message text field";
|
||||
"a11y_collapse_message_text_field" = "Minimize message text field";
|
||||
"a11y_delete" = "Delete";
|
||||
"a11y_edit_avatar" = "Edit avatar";
|
||||
"a11y_edit_room_address_hint" = "The full address will be %1$@";
|
||||
@@ -148,6 +148,7 @@
|
||||
"action_skip" = "Skip";
|
||||
"action_start" = "Start";
|
||||
"action_start_chat" = "Start chat";
|
||||
"action_start_over" = "Start over";
|
||||
"action_start_verification" = "Start verification";
|
||||
"action_static_map_load" = "Tap to load map";
|
||||
"action_take_photo" = "Take photo";
|
||||
@@ -579,7 +580,7 @@
|
||||
"screen_knock_requests_list_initial_loading_title" = "Loading requests to join…";
|
||||
"screen_labs_enable_threads" = "Enable thread replies";
|
||||
"screen_labs_enable_threads_description" = "The app will restart to apply this change.";
|
||||
"screen_labs_header_description" = "Try out our latest ideas in development. These features are not finalised; they may be unstable, may change.";
|
||||
"screen_labs_header_description" = "Try out our latest ideas in development. These features are not finalized; they may be unstable, may change.";
|
||||
"screen_labs_header_title" = "Feeling experimental?";
|
||||
"screen_labs_title" = "Labs";
|
||||
"screen_leave_space_last_admin_info" = "%1$@ (Admin)";
|
||||
@@ -588,9 +589,32 @@
|
||||
"screen_leave_space_subtitle_only_last_admin" = "You will not be removed from the following room(s) because you're the only administrator:";
|
||||
"screen_leave_space_title" = "Leave %1$@?";
|
||||
"screen_leave_space_title_last_admin" = "You are the only admin for %1$@";
|
||||
"screen_link_new_device_desktop_computer" = "Desktop computer";
|
||||
"screen_link_new_device_mobile_device" = "Mobile device";
|
||||
"screen_link_new_device_title" = "What type of device do you want to link?";
|
||||
"screen_link_new_device_desktop_scanning_title" = "Scan the QR code";
|
||||
"screen_link_new_device_desktop_step1" = "Open %1$@ on a laptop or desktop computer";
|
||||
"screen_link_new_device_desktop_step3" = "Scan the QR code with this device";
|
||||
"screen_link_new_device_desktop_submit" = "Ready to scan";
|
||||
"screen_link_new_device_desktop_title" = "Open %1$@ on a desktop computer to get the QR code";
|
||||
"screen_link_new_device_enter_number_error_numbers_do_not_match" = "The numbers don’t match";
|
||||
"screen_link_new_device_enter_number_notice" = "Enter 2-digit code";
|
||||
"screen_link_new_device_enter_number_subtitle" = "This will verify that the connection to your other device is secure.";
|
||||
"screen_link_new_device_enter_number_title" = "Enter the number shown on your other device";
|
||||
"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@.";
|
||||
"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported";
|
||||
"screen_link_new_device_error_not_supported_subtitle" = "Your account provider doesn’t support signing into a new device with a QR code.";
|
||||
"screen_link_new_device_error_not_supported_title" = "QR code not supported";
|
||||
"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device.";
|
||||
"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled";
|
||||
"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again.";
|
||||
"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time";
|
||||
"screen_link_new_device_mobile_step1" = "Open %1$@ on the other device";
|
||||
"screen_link_new_device_mobile_step2" = "Select %1$@";
|
||||
"screen_link_new_device_mobile_step2_action" = "“Sign in with QR code”";
|
||||
"screen_link_new_device_mobile_step3" = "Scan the QR code shown here with the other device";
|
||||
"screen_link_new_device_mobile_title" = "Open %1$@ on the other device";
|
||||
"screen_link_new_device_root_desktop_computer" = "Desktop computer";
|
||||
"screen_link_new_device_root_loading_qr_code" = "Loading QR code…";
|
||||
"screen_link_new_device_root_mobile_device" = "Mobile device";
|
||||
"screen_link_new_device_root_title" = "What type of device do you want to link?";
|
||||
"screen_manage_authorized_spaces_header" = "Spaces where members can join the room without an invitation.";
|
||||
"screen_manage_authorized_spaces_title" = "Manage spaces";
|
||||
"screen_manage_authorized_spaces_unknown_space" = "(Unknown space)";
|
||||
|
||||
@@ -148,6 +148,7 @@
|
||||
"action_skip" = "Skip";
|
||||
"action_start" = "Start";
|
||||
"action_start_chat" = "Start chat";
|
||||
"action_start_over" = "Start over";
|
||||
"action_start_verification" = "Start verification";
|
||||
"action_static_map_load" = "Tap to load map";
|
||||
"action_take_photo" = "Take photo";
|
||||
@@ -588,9 +589,32 @@
|
||||
"screen_leave_space_subtitle_only_last_admin" = "You will not be removed from the following room(s) because you're the only administrator:";
|
||||
"screen_leave_space_title" = "Leave %1$@?";
|
||||
"screen_leave_space_title_last_admin" = "You are the only admin for %1$@";
|
||||
"screen_link_new_device_desktop_computer" = "Desktop computer";
|
||||
"screen_link_new_device_mobile_device" = "Mobile device";
|
||||
"screen_link_new_device_title" = "What type of device do you want to link?";
|
||||
"screen_link_new_device_desktop_scanning_title" = "Scan the QR code";
|
||||
"screen_link_new_device_desktop_step1" = "Open %1$@ on a laptop or desktop computer";
|
||||
"screen_link_new_device_desktop_step3" = "Scan the QR code with this device";
|
||||
"screen_link_new_device_desktop_submit" = "Ready to scan";
|
||||
"screen_link_new_device_desktop_title" = "Open %1$@ on a desktop computer to get the QR code";
|
||||
"screen_link_new_device_enter_number_error_numbers_do_not_match" = "The numbers don’t match";
|
||||
"screen_link_new_device_enter_number_notice" = "Enter 2-digit code";
|
||||
"screen_link_new_device_enter_number_subtitle" = "This will verify that the connection to your other device is secure.";
|
||||
"screen_link_new_device_enter_number_title" = "Enter the number shown on your other device";
|
||||
"screen_link_new_device_error_app_not_supported_subtitle" = "Your account provider does not support %1$@.";
|
||||
"screen_link_new_device_error_app_not_supported_title" = "%1$@ not supported";
|
||||
"screen_link_new_device_error_not_supported_subtitle" = "Your account provider doesn’t support signing into a new device with a QR code.";
|
||||
"screen_link_new_device_error_not_supported_title" = "QR code not supported";
|
||||
"screen_link_new_device_error_request_cancelled_subtitle" = "The sign in was cancelled on the other device.";
|
||||
"screen_link_new_device_error_request_cancelled_title" = "Sign in request cancelled";
|
||||
"screen_link_new_device_error_request_timeout_subtitle" = "Sign in expired. Please try again.";
|
||||
"screen_link_new_device_error_request_timeout_title" = "The sign in was not completed in time";
|
||||
"screen_link_new_device_mobile_step1" = "Open %1$@ on the other device";
|
||||
"screen_link_new_device_mobile_step2" = "Select %1$@";
|
||||
"screen_link_new_device_mobile_step2_action" = "“Sign in with QR code”";
|
||||
"screen_link_new_device_mobile_step3" = "Scan the QR code shown here with the other device";
|
||||
"screen_link_new_device_mobile_title" = "Open %1$@ on the other device";
|
||||
"screen_link_new_device_root_desktop_computer" = "Desktop computer";
|
||||
"screen_link_new_device_root_loading_qr_code" = "Loading QR code…";
|
||||
"screen_link_new_device_root_mobile_device" = "Mobile device";
|
||||
"screen_link_new_device_root_title" = "What type of device do you want to link?";
|
||||
"screen_manage_authorized_spaces_header" = "Spaces where members can join the room without an invitation.";
|
||||
"screen_manage_authorized_spaces_title" = "Manage spaces";
|
||||
"screen_manage_authorized_spaces_unknown_space" = "(Unknown space)";
|
||||
@@ -657,7 +681,7 @@
|
||||
"screen_roomlist_clear_filters" = "Clear filters";
|
||||
"screen_roomlist_tombstoned_room_description" = "This room has been upgraded";
|
||||
"screen_security_and_privacy_add_room_address_action" = "Add address";
|
||||
"screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description" = "Anyone in authorized spaces can join, but everyone else must request access.";
|
||||
"screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description" = "Anyone in authorised spaces can join, but everyone else must request access.";
|
||||
"screen_security_and_privacy_ask_to_join_option_description" = "Everyone must request access.";
|
||||
"screen_security_and_privacy_ask_to_join_single_space_members_option_description" = "Anyone in %1$@ can join, but everyone else must request access.";
|
||||
"screen_security_and_privacy_enable_encryption_alert_confirm_button_title" = "Yes, enable encryption";
|
||||
@@ -670,7 +694,7 @@
|
||||
"screen_security_and_privacy_room_access_invite_only_option_description" = "Only invited people can join.";
|
||||
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
|
||||
"screen_security_and_privacy_room_access_section_header" = "Access";
|
||||
"screen_security_and_privacy_room_access_space_members_option_multiple_parents_description" = "Anyone in authorized spaces can join.";
|
||||
"screen_security_and_privacy_room_access_space_members_option_multiple_parents_description" = "Anyone in authorised spaces can join.";
|
||||
"screen_security_and_privacy_room_access_space_members_option_single_parent_description" = "Anyone in %1$@ can join.";
|
||||
"screen_security_and_privacy_room_access_space_members_option_title" = "Space members";
|
||||
"screen_security_and_privacy_room_access_space_members_option_unavailable_description" = "Spaces are not currently supported";
|
||||
|
||||
@@ -66,6 +66,7 @@ final class AppSettings {
|
||||
case linkPreviewsEnabled
|
||||
case spaceSettingsEnabled
|
||||
case focusEventOnNotificationTap
|
||||
case linkNewDeviceEnabled
|
||||
|
||||
// Doug's tweaks 🔧
|
||||
case hideUnreadMessagesBadge
|
||||
@@ -399,6 +400,9 @@ final class AppSettings {
|
||||
@UserPreference(key: UserDefaultsKeys.linkPreviewsEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var linkPreviewsEnabled
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.linkNewDeviceEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||
var linkNewDeviceEnabled
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: isDevelopmentBuild, storageType: .userDefaults(store))
|
||||
var developerOptionsEnabled
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
startEncryptionSettingsFlow(animated: true)
|
||||
case .userDetails:
|
||||
presentUserDetailsEditScreen()
|
||||
case .linkNewDevice:
|
||||
presentLinkNewDeviceScreen()
|
||||
case let .manageAccount(url):
|
||||
presentAccountManagementURL(url)
|
||||
case .analytics:
|
||||
@@ -269,4 +271,27 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
appSettings: flowParameters.appSettings)
|
||||
accountSettingsPresenter?.start()
|
||||
}
|
||||
|
||||
// MARK: - Link New Device
|
||||
|
||||
private func presentLinkNewDeviceScreen() {
|
||||
let parameters = LinkNewDeviceScreenCoordinatorParameters(clientProxy: flowParameters.userSession.clientProxy)
|
||||
let coordinator = LinkNewDeviceScreenCoordinator(parameters: parameters)
|
||||
coordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .linkMobileDevice:
|
||||
break
|
||||
case .linkDesktopComputer:
|
||||
break
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.pop()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +334,8 @@ internal enum L10n {
|
||||
internal static var actionStart: String { return L10n.tr("Localizable", "action_start") }
|
||||
/// Start chat
|
||||
internal static var actionStartChat: String { return L10n.tr("Localizable", "action_start_chat") }
|
||||
/// Start over
|
||||
internal static var actionStartOver: String { return L10n.tr("Localizable", "action_start_over") }
|
||||
/// Start verification
|
||||
internal static var actionStartVerification: String { return L10n.tr("Localizable", "action_start_verification") }
|
||||
/// Tap to load map
|
||||
@@ -1888,12 +1890,72 @@ internal enum L10n {
|
||||
internal static func screenLeaveSpaceTitleLastAdmin(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_leave_space_title_last_admin", String(describing: p1))
|
||||
}
|
||||
/// Scan the QR code
|
||||
internal static var screenLinkNewDeviceDesktopScanningTitle: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_scanning_title") }
|
||||
/// Open %1$@ on a laptop or desktop computer
|
||||
internal static func screenLinkNewDeviceDesktopStep1(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_desktop_step1", String(describing: p1))
|
||||
}
|
||||
/// Scan the QR code with this device
|
||||
internal static var screenLinkNewDeviceDesktopStep3: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_step3") }
|
||||
/// Ready to scan
|
||||
internal static var screenLinkNewDeviceDesktopSubmit: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_submit") }
|
||||
/// Open %1$@ on a desktop computer to get the QR code
|
||||
internal static func screenLinkNewDeviceDesktopTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_desktop_title", String(describing: p1))
|
||||
}
|
||||
/// The numbers don’t match
|
||||
internal static var screenLinkNewDeviceEnterNumberErrorNumbersDoNotMatch: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_error_numbers_do_not_match") }
|
||||
/// Enter 2-digit code
|
||||
internal static var screenLinkNewDeviceEnterNumberNotice: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_notice") }
|
||||
/// This will verify that the connection to your other device is secure.
|
||||
internal static var screenLinkNewDeviceEnterNumberSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_subtitle") }
|
||||
/// Enter the number shown on your other device
|
||||
internal static var screenLinkNewDeviceEnterNumberTitle: String { return L10n.tr("Localizable", "screen_link_new_device_enter_number_title") }
|
||||
/// Your account provider does not support %1$@.
|
||||
internal static func screenLinkNewDeviceErrorAppNotSupportedSubtitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_error_app_not_supported_subtitle", String(describing: p1))
|
||||
}
|
||||
/// %1$@ not supported
|
||||
internal static func screenLinkNewDeviceErrorAppNotSupportedTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_error_app_not_supported_title", String(describing: p1))
|
||||
}
|
||||
/// Your account provider doesn’t support signing into a new device with a QR code.
|
||||
internal static var screenLinkNewDeviceErrorNotSupportedSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_not_supported_subtitle") }
|
||||
/// QR code not supported
|
||||
internal static var screenLinkNewDeviceErrorNotSupportedTitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_not_supported_title") }
|
||||
/// The sign in was cancelled on the other device.
|
||||
internal static var screenLinkNewDeviceErrorRequestCancelledSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_cancelled_subtitle") }
|
||||
/// Sign in request cancelled
|
||||
internal static var screenLinkNewDeviceErrorRequestCancelledTitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_cancelled_title") }
|
||||
/// Sign in expired. Please try again.
|
||||
internal static var screenLinkNewDeviceErrorRequestTimeoutSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_timeout_subtitle") }
|
||||
/// The sign in was not completed in time
|
||||
internal static var screenLinkNewDeviceErrorRequestTimeoutTitle: String { return L10n.tr("Localizable", "screen_link_new_device_error_request_timeout_title") }
|
||||
/// Open %1$@ on the other device
|
||||
internal static func screenLinkNewDeviceMobileStep1(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_mobile_step1", String(describing: p1))
|
||||
}
|
||||
/// Select %1$@
|
||||
internal static func screenLinkNewDeviceMobileStep2(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_mobile_step2", String(describing: p1))
|
||||
}
|
||||
/// “Sign in with QR code”
|
||||
internal static var screenLinkNewDeviceMobileStep2Action: String { return L10n.tr("Localizable", "screen_link_new_device_mobile_step2_action") }
|
||||
/// Scan the QR code shown here with the other device
|
||||
internal static var screenLinkNewDeviceMobileStep3: String { return L10n.tr("Localizable", "screen_link_new_device_mobile_step3") }
|
||||
/// Open %1$@ on the other device
|
||||
internal static func screenLinkNewDeviceMobileTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_link_new_device_mobile_title", String(describing: p1))
|
||||
}
|
||||
/// Desktop computer
|
||||
internal static var screenLinkNewDeviceDesktopComputer: String { return L10n.tr("Localizable", "screen_link_new_device_desktop_computer") }
|
||||
internal static var screenLinkNewDeviceRootDesktopComputer: String { return L10n.tr("Localizable", "screen_link_new_device_root_desktop_computer") }
|
||||
/// Loading QR code…
|
||||
internal static var screenLinkNewDeviceRootLoadingQrCode: String { return L10n.tr("Localizable", "screen_link_new_device_root_loading_qr_code") }
|
||||
/// Mobile device
|
||||
internal static var screenLinkNewDeviceMobileDevice: String { return L10n.tr("Localizable", "screen_link_new_device_mobile_device") }
|
||||
internal static var screenLinkNewDeviceRootMobileDevice: String { return L10n.tr("Localizable", "screen_link_new_device_root_mobile_device") }
|
||||
/// What type of device do you want to link?
|
||||
internal static var screenLinkNewDeviceTitle: String { return L10n.tr("Localizable", "screen_link_new_device_title") }
|
||||
internal static var screenLinkNewDeviceRootTitle: String { return L10n.tr("Localizable", "screen_link_new_device_root_title") }
|
||||
/// This account has been deactivated.
|
||||
internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
||||
/// Incorrect username and/or password
|
||||
@@ -2782,7 +2844,7 @@ internal enum L10n {
|
||||
internal static var screenRoomlistTombstonedRoomDescription: String { return L10n.tr("Localizable", "screen_roomlist_tombstoned_room_description") }
|
||||
/// Add address
|
||||
internal static var screenSecurityAndPrivacyAddRoomAddressAction: String { return L10n.tr("Localizable", "screen_security_and_privacy_add_room_address_action") }
|
||||
/// Anyone in authorized spaces can join, but everyone else must request access.
|
||||
/// Anyone in authorised spaces can join, but everyone else must request access.
|
||||
internal static var screenSecurityAndPrivacyAskToJoinMultipleSpacesMembersOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description") }
|
||||
/// Everyone must request access.
|
||||
internal static var screenSecurityAndPrivacyAskToJoinOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_ask_to_join_option_description") }
|
||||
@@ -2822,7 +2884,7 @@ internal enum L10n {
|
||||
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") }
|
||||
/// Access
|
||||
internal static var screenSecurityAndPrivacyRoomAccessSectionHeader: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_header") }
|
||||
/// Anyone in authorized spaces can join.
|
||||
/// Anyone in authorised spaces can join.
|
||||
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionMultipleParentsDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_multiple_parents_description") }
|
||||
/// Anyone in %1$@ can join.
|
||||
internal static func screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(_ p1: Any) -> String {
|
||||
|
||||
@@ -144,6 +144,7 @@ extension ClientProxyMock {
|
||||
|
||||
underlyingIsReportRoomSupported = true
|
||||
underlyingIsLiveKitRTCSupported = true
|
||||
underlyingIsLoginWithQRCodeSupported = true
|
||||
|
||||
underlyingTimelineMediaVisibilityPublisher = CurrentValueSubject<TimelineMediaVisibility, Never>(configuration.timelineMediaVisibility).asCurrentValuePublisher()
|
||||
underlyingHideInviteAvatarsPublisher = CurrentValueSubject<Bool, Never>(configuration.hideInviteAvatars).asCurrentValuePublisher()
|
||||
|
||||
@@ -2332,6 +2332,23 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
}
|
||||
var underlyingIsLiveKitRTCSupported: Bool!
|
||||
var isLiveKitRTCSupportedClosure: (() async -> Bool)?
|
||||
var isLoginWithQRCodeSupportedCallsCount = 0
|
||||
var isLoginWithQRCodeSupportedCalled: Bool {
|
||||
return isLoginWithQRCodeSupportedCallsCount > 0
|
||||
}
|
||||
|
||||
var isLoginWithQRCodeSupported: Bool {
|
||||
get async {
|
||||
isLoginWithQRCodeSupportedCallsCount += 1
|
||||
if let isLoginWithQRCodeSupportedClosure = isLoginWithQRCodeSupportedClosure {
|
||||
return await isLoginWithQRCodeSupportedClosure()
|
||||
} else {
|
||||
return underlyingIsLoginWithQRCodeSupported
|
||||
}
|
||||
}
|
||||
}
|
||||
var underlyingIsLoginWithQRCodeSupported: Bool!
|
||||
var isLoginWithQRCodeSupportedClosure: (() async -> Bool)?
|
||||
var maxMediaUploadSizeCallsCount = 0
|
||||
var maxMediaUploadSizeCalled: Bool {
|
||||
return maxMediaUploadSizeCallsCount > 0
|
||||
|
||||
@@ -77,6 +77,7 @@ enum TestablePreviewsDictionary {
|
||||
"LeaveSpaceRoomDetailsCell_Previews" : LeaveSpaceRoomDetailsCell_Previews.self,
|
||||
"LeaveSpaceView_Previews" : LeaveSpaceView_Previews.self,
|
||||
"LegalInformationScreen_Previews" : LegalInformationScreen_Previews.self,
|
||||
"LinkNewDeviceScreen_Previews" : LinkNewDeviceScreen_Previews.self,
|
||||
"LoadableImage_Previews" : LoadableImage_Previews.self,
|
||||
"LocationMarkerView_Previews" : LocationMarkerView_Previews.self,
|
||||
"LocationRoomTimelineView_Previews" : LocationRoomTimelineView_Previews.self,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// 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 Combine
|
||||
import SwiftUI
|
||||
|
||||
enum LinkNewDeviceScreenCoordinatorAction {
|
||||
case linkMobileDevice
|
||||
case linkDesktopComputer
|
||||
case dismiss
|
||||
}
|
||||
|
||||
struct LinkNewDeviceScreenCoordinatorParameters {
|
||||
let clientProxy: ClientProxyProtocol
|
||||
}
|
||||
|
||||
final class LinkNewDeviceScreenCoordinator: CoordinatorProtocol {
|
||||
private let viewModel: LinkNewDeviceScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<LinkNewDeviceScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<LinkNewDeviceScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: LinkNewDeviceScreenCoordinatorParameters) {
|
||||
viewModel = LinkNewDeviceScreenViewModel(clientProxy: parameters.clientProxy)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
MXLog.info("Coordinator: received view model action: \(action)")
|
||||
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .linkMobileDevice:
|
||||
actionsSubject.send(.linkMobileDevice)
|
||||
case .linkDesktopComputer:
|
||||
actionsSubject.send(.linkDesktopComputer)
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(LinkNewDeviceScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
enum LinkNewDeviceScreenViewModelAction {
|
||||
case linkMobileDevice
|
||||
case linkDesktopComputer
|
||||
case dismiss
|
||||
}
|
||||
|
||||
struct LinkNewDeviceScreenViewState: BindableState {
|
||||
enum Mode: Equatable { case loading, readyToLink(isGeneratingCode: Bool), notSupported }
|
||||
var mode: Mode = .loading
|
||||
}
|
||||
|
||||
enum LinkNewDeviceScreenViewAction {
|
||||
case linkMobileDevice
|
||||
case linkDesktopComputer
|
||||
case dismiss
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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 Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias LinkNewDeviceScreenViewModelType = StateStoreViewModelV2<LinkNewDeviceScreenViewState, LinkNewDeviceScreenViewAction>
|
||||
|
||||
class LinkNewDeviceScreenViewModel: LinkNewDeviceScreenViewModelType, LinkNewDeviceScreenViewModelProtocol {
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<LinkNewDeviceScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<LinkNewDeviceScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(clientProxy: ClientProxyProtocol) {
|
||||
self.clientProxy = clientProxy
|
||||
|
||||
super.init(initialViewState: LinkNewDeviceScreenViewState())
|
||||
|
||||
Task { await checkQRCodeLoginSupport() }
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: LinkNewDeviceScreenViewAction) {
|
||||
MXLog.info("View model: received view action: \(viewAction)")
|
||||
|
||||
switch viewAction {
|
||||
case .linkMobileDevice:
|
||||
linkMobileDevice()
|
||||
case .linkDesktopComputer:
|
||||
actionsSubject.send(.linkDesktopComputer)
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func checkQRCodeLoginSupport() async {
|
||||
if await clientProxy.isLoginWithQRCodeSupported {
|
||||
state.mode = .readyToLink(isGeneratingCode: false)
|
||||
} else {
|
||||
state.mode = .notSupported
|
||||
}
|
||||
}
|
||||
|
||||
private func linkMobileDevice() {
|
||||
state.mode = .readyToLink(isGeneratingCode: true)
|
||||
|
||||
// TODO: Generate a QR code.
|
||||
|
||||
actionsSubject.send(.linkMobileDevice)
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// 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 Combine
|
||||
|
||||
@MainActor
|
||||
protocol LinkNewDeviceScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<LinkNewDeviceScreenViewModelAction, Never> { get }
|
||||
var context: LinkNewDeviceScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// 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 LinkNewDeviceScreen: View {
|
||||
@Bindable var context: LinkNewDeviceScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
FullscreenDialog(topPadding: 24) {
|
||||
mainContent
|
||||
} bottomContent: {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.backgroundStyle(.compound.bgSubtleSecondary)
|
||||
.navigationTitle(L10n.commonLinkNewDevice)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var mainContent: some View {
|
||||
switch context.viewState.mode {
|
||||
case .loading, .readyToLink:
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.computer, style: .default)
|
||||
|
||||
Text(L10n.screenLinkNewDeviceRootTitle)
|
||||
.font(.compound.headingMDBold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
case .notSupported:
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.errorSolid, style: .alertSolid)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.screenLinkNewDeviceErrorNotSupportedTitle)
|
||||
.font(.compound.headingMDBold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(L10n.screenLinkNewDeviceErrorNotSupportedSubtitle)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var buttons: some View {
|
||||
switch context.viewState.mode {
|
||||
case .loading:
|
||||
Button { } label: {
|
||||
Label {
|
||||
Text(L10n.commonLoading)
|
||||
} icon: {
|
||||
ProgressView()
|
||||
.tint(.compound.iconOnSolidPrimary)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.disabled(true)
|
||||
case .readyToLink(let isGeneratingCode):
|
||||
VStack(spacing: 16) {
|
||||
Button { context.send(viewAction: .linkMobileDevice) } label: {
|
||||
Label {
|
||||
Text(isGeneratingCode ? L10n.screenLinkNewDeviceRootLoadingQrCode : L10n.screenLinkNewDeviceRootMobileDevice)
|
||||
} icon: {
|
||||
if isGeneratingCode {
|
||||
ProgressView()
|
||||
.tint(.compound.iconOnSolidPrimary)
|
||||
} else {
|
||||
CompoundIcon(\.mobile)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
|
||||
Button { context.send(viewAction: .linkDesktopComputer) } label: {
|
||||
Label(L10n.screenLinkNewDeviceRootDesktopComputer, icon: \.computer)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
.disabled(isGeneratingCode)
|
||||
case .notSupported:
|
||||
Button(L10n.actionDismiss) {
|
||||
context.send(viewAction: .dismiss)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct LinkNewDeviceScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = makeViewModel(mode: .readyToLink(isGeneratingCode: false))
|
||||
static let generatingViewModel = makeViewModel(mode: .readyToLink(isGeneratingCode: true))
|
||||
static let loadingViewModel = makeViewModel(mode: .loading)
|
||||
static let unsupportedViewModel = makeViewModel(mode: .notSupported)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
LinkNewDeviceScreen(context: viewModel.context)
|
||||
}
|
||||
.previewDisplayName("Ready")
|
||||
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.mode).map { $0 == .readyToLink(isGeneratingCode: false) })
|
||||
|
||||
NavigationStack {
|
||||
LinkNewDeviceScreen(context: generatingViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Generating")
|
||||
.snapshotPreferences(expect: generatingViewModel.context.observe(\.viewState.mode).map { $0 == .readyToLink(isGeneratingCode: true) })
|
||||
|
||||
NavigationStack {
|
||||
LinkNewDeviceScreen(context: loadingViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Loading")
|
||||
|
||||
NavigationStack {
|
||||
LinkNewDeviceScreen(context: unsupportedViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Unsupported")
|
||||
.snapshotPreferences(expect: unsupportedViewModel.context.observe(\.viewState.mode).map { $0 == .notSupported })
|
||||
}
|
||||
|
||||
static func makeViewModel(mode: LinkNewDeviceScreenViewState.Mode) -> LinkNewDeviceScreenViewModel {
|
||||
let clientProxy = ClientProxyMock(.init())
|
||||
clientProxy.isLoginWithQRCodeSupportedClosure = {
|
||||
switch mode {
|
||||
case .loading:
|
||||
try? await Task.sleep(for: .seconds(20))
|
||||
return false
|
||||
case .readyToLink:
|
||||
return true
|
||||
case .notSupported:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let viewModel = LinkNewDeviceScreenViewModel(clientProxy: clientProxy)
|
||||
|
||||
Task {
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
if case .readyToLink(isGeneratingCode: true) = mode {
|
||||
viewModel.context.send(viewAction: .linkMobileDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
+1
@@ -57,6 +57,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
||||
var linkPreviewsEnabled: Bool { get set }
|
||||
|
||||
var spaceSettingsEnabled: Bool { get set }
|
||||
var linkNewDeviceEnabled: Bool { get set }
|
||||
}
|
||||
|
||||
extension AppSettings: DeveloperOptionsProtocol { }
|
||||
|
||||
+3
@@ -34,6 +34,9 @@ struct DeveloperOptionsScreen: View {
|
||||
}
|
||||
|
||||
Section("General") {
|
||||
Toggle(isOn: $context.linkNewDeviceEnabled) {
|
||||
Text("Link new device with QR code")
|
||||
}
|
||||
Toggle(isOn: $context.spaceSettingsEnabled) {
|
||||
Text("Space settings")
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ enum SettingsScreenCoordinatorAction {
|
||||
case bugReport
|
||||
case about
|
||||
case blockedUsers
|
||||
case linkNewDevice
|
||||
case manageAccount(url: URL)
|
||||
case notifications
|
||||
case advancedSettings
|
||||
@@ -59,6 +60,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.dismiss)
|
||||
case .userDetails:
|
||||
actionsSubject.send(.userDetails)
|
||||
case .linkNewDevice:
|
||||
actionsSubject.send(.linkNewDevice)
|
||||
case let .manageAccount(url):
|
||||
actionsSubject.send(.manageAccount(url: url))
|
||||
case .analytics:
|
||||
|
||||
@@ -12,6 +12,7 @@ import UIKit
|
||||
enum SettingsScreenViewModelAction: Equatable {
|
||||
case close
|
||||
case userDetails
|
||||
case linkNewDevice
|
||||
case manageAccount(url: URL)
|
||||
case analytics
|
||||
case appLock
|
||||
@@ -35,6 +36,7 @@ enum SettingsScreenSecuritySectionMode {
|
||||
struct SettingsScreenViewState: BindableState {
|
||||
var deviceID: String?
|
||||
var userID: String
|
||||
var showLinkNewDeviceButton: Bool
|
||||
var accountProfileURL: URL?
|
||||
var accountSessionsListURL: URL?
|
||||
var showAccountDeactivation: Bool
|
||||
@@ -66,6 +68,7 @@ enum SettingsScreenViewAction {
|
||||
case about
|
||||
case blockedUsers
|
||||
case secureBackup
|
||||
case linkNewDevice
|
||||
case manageAccount(url: URL)
|
||||
case notifications
|
||||
case enableDeveloperOptions
|
||||
|
||||
@@ -25,6 +25,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
|
||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||
userID: userSession.clientProxy.userID,
|
||||
showLinkNewDeviceButton: appSettings.linkNewDeviceEnabled,
|
||||
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
||||
showDeveloperOptions: appSettings.developerOptionsEnabled,
|
||||
showAnalyticsSettings: appSettings.canPromptForAnalytics,
|
||||
@@ -35,6 +36,10 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
.weakAssign(to: \.state.showDeveloperOptions, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
appSettings.$linkNewDeviceEnabled
|
||||
.weakAssign(to: \.state.showLinkNewDeviceButton, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSession.clientProxy.userAvatarURLPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||
@@ -93,6 +98,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
actionsSubject.send(.close)
|
||||
case .userDetails:
|
||||
actionsSubject.send(.userDetails)
|
||||
case .linkNewDevice:
|
||||
actionsSubject.send(.linkNewDevice)
|
||||
case let .manageAccount(url):
|
||||
actionsSubject.send(.manageAccount(url: url))
|
||||
case .analytics:
|
||||
|
||||
@@ -108,6 +108,14 @@ struct SettingsScreen: View {
|
||||
|
||||
private var manageAccountSection: some View {
|
||||
Section {
|
||||
if context.viewState.showLinkNewDeviceButton {
|
||||
ListRow(label: .default(title: L10n.commonLinkNewDevice,
|
||||
icon: \.devices),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .linkNewDevice)
|
||||
})
|
||||
}
|
||||
|
||||
if let url = context.viewState.accountProfileURL {
|
||||
ListRow(label: .default(title: L10n.actionManageAccount,
|
||||
icon: \.userProfile),
|
||||
|
||||
@@ -313,6 +313,17 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
var isLoginWithQRCodeSupported: Bool {
|
||||
get async {
|
||||
do {
|
||||
return try await client.isLoginWithQrCodeSupported()
|
||||
} catch {
|
||||
MXLog.error("Failed checking QR code support with error: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var maxMediaUploadSize: Result<UInt, ClientProxyError> {
|
||||
get async {
|
||||
do {
|
||||
|
||||
@@ -132,6 +132,8 @@ protocol ClientProxyProtocol: AnyObject {
|
||||
|
||||
var isLiveKitRTCSupported: Bool { get async }
|
||||
|
||||
var isLoginWithQRCodeSupported: Bool { get async }
|
||||
|
||||
var maxMediaUploadSize: Result<UInt, ClientProxyError> { get async }
|
||||
|
||||
func isOnlyDeviceLeft() async -> Result<Bool, ClientProxyError>
|
||||
|
||||
@@ -419,6 +419,12 @@ extension PreviewTests {
|
||||
}
|
||||
}
|
||||
|
||||
func testLinkNewDeviceScreen() async throws {
|
||||
for (index, preview) in LinkNewDeviceScreen_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
}
|
||||
}
|
||||
|
||||
func testLoadableImage() async throws {
|
||||
for (index, preview) in LoadableImage_Previews._allPreviews.enumerated() {
|
||||
try await assertSnapshots(matching: preview, step: index)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ffbf93956ead19e2ad6a5c1a6bb31f00b35d18861632cb00072ba43f3b3638fe
|
||||
size 130184
|
||||
oid sha256:d7fb0c5b2f541ded6f34cff7d7e45865f34527bd1b64fb8c22943e7161de3ea6
|
||||
size 130468
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ec67b85d818eaf157b5627be843a1556b32500e440bae1c53f525fbcb27b874c
|
||||
size 100439
|
||||
oid sha256:e88af4b4312e444dbba909dc9da21a5c024d58d8244a54875311955e50e3663a
|
||||
size 100603
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad18b20c43c8f8127c485c8b434064de0b237d25ee177c98a989eb65113248e4
|
||||
size 109374
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5645b5827fdf244280e80f6fc16b421a7dc0635b16b6f80b2b50a1907844b4cf
|
||||
size 123395
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cc447ed7e8194814387d698437d3adaeb4b7b6f687842598fb84240d58d4e0fe
|
||||
size 61504
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8510dfa5fe61fc0fe236ccf4bf69d3fec03a3cd5fb14182c93da27fb93b94356
|
||||
size 82818
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5a9223f99dfb8209fc21042a2656ca4a050a4f8f1a1d7723ef613bcf54706b5
|
||||
size 98823
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:79aba2f30b36dc42149b05844746297c3959cce54108fb6e9043d22a1e441b55
|
||||
size 109776
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:54ce632ef35c3ac9c60ee051649cb18ab26aec79c12fd764ea729c81d7897dbf
|
||||
size 52282
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c49ca4038efc3d6059b8f8cd715b67741c4c45e4b817ff61aa5569ea3d76ed38
|
||||
size 65128
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f94dff4155601b73d57df0294c051463ff330fe431a2c46a5a5b198cdc372ea
|
||||
size 109664
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a049866c334facc710309e02999e4869a46839cf0448b68660d21e0cb6842a43
|
||||
size 123411
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c0d7ce68e28cf45d36e04d4f6b9e62722ed67e7fc5be42287b43e1bc68924a67
|
||||
size 61909
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c637c11e208a467b4e4d77a2fb9f1ea0fe6602dcc0788934659328074ed4f3bc
|
||||
size 81263
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d1959686b35a78a6c49ab16e9eb96f5ca879151ecf95969e13acf31d8c61f461
|
||||
size 106107
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f15c62942085467d9e450c48bd5233ba51a3ae3b2c7f7c4cce2428ef66675b87
|
||||
size 120137
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d85de510b52b116591c0e24b0ba647e3aa404a77b5e645f512a3cbf47d5291dd
|
||||
size 60430
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a504b62f6c259b91a9a0a6d8a7b1cf18b513e86a69f5f5f9399aedf69053f65d
|
||||
size 80204
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:beb3917bac09663e50dc7759681e4c74f737789cc52edc08087a41db0e103a1a
|
||||
size 211544
|
||||
oid sha256:734d18a6d289e4b726746bdc4a6447d2d44c07b68b5aa3619e41d20743b2be43
|
||||
size 211278
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c20f5acdf0bea460a75100551817860ab7f3db7d6049b179088bf695dc99c64
|
||||
size 147525
|
||||
oid sha256:7b8c656ed05bb2a51e925ae50156fc786c291451669942fec45ff0877c8d97b6
|
||||
size 147337
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dbe3080f170b7d36524405eb26f14d180bfa543130f55b3265166638b5eb0c28
|
||||
size 169930
|
||||
oid sha256:b3bcd80b001708d7b146832168487813d1523f421cb30e17c8c540017b72db92
|
||||
size 169810
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d2feb5b0aeceb20b979b5930456074fd1a0b4cec6acb9705bdda0d86ac0778fc
|
||||
size 116785
|
||||
oid sha256:3973e132104bbca48b78481b1b625d1cbc322d2346792538ec6f7f7abcc5642c
|
||||
size 116706
|
||||
|
||||
Reference in New Issue
Block a user