mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
Add the UI for your own user status in the Settings screen. (#5888)
* Show the user's own Status emoji on the SettingsScreen. * Add the UI for setting your own status. * Add placeholder action for selecting a custom emoji and fix some accessibility issues.
This commit is contained in:
@@ -663,6 +663,14 @@ extension AccessibilityTests {
|
|||||||
try await performAccessibilityAudit(named: "SessionVerification_Previews")
|
try await performAccessibilityAudit(named: "SessionVerification_Previews")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSettingsScreenUserStatusPickerView() async throws {
|
||||||
|
try await performAccessibilityAudit(named: "SettingsScreenUserStatusPickerView_Previews")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSettingsScreenUserStatusRow() async throws {
|
||||||
|
try await performAccessibilityAudit(named: "SettingsScreenUserStatusRow_Previews")
|
||||||
|
}
|
||||||
|
|
||||||
func testSettingsScreen() async throws {
|
func testSettingsScreen() async throws {
|
||||||
try await performAccessibilityAudit(named: "SettingsScreen_Previews")
|
try await performAccessibilityAudit(named: "SettingsScreen_Previews")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -569,6 +569,7 @@
|
|||||||
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
|
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
|
||||||
5DFC2A889D3B39DD47AC63A8 /* PillUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C537DE821FED94D23467B6C4 /* PillUtilities.swift */; };
|
5DFC2A889D3B39DD47AC63A8 /* PillUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C537DE821FED94D23467B6C4 /* PillUtilities.swift */; };
|
||||||
5E597B9959BDAE7A67DBD5B2 /* LinkMetadataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C78111573987B1D79ED0868 /* LinkMetadataProvider.swift */; };
|
5E597B9959BDAE7A67DBD5B2 /* LinkMetadataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C78111573987B1D79ED0868 /* LinkMetadataProvider.swift */; };
|
||||||
|
5E67990F98AF11563EA50080 /* SettingsScreenUserStatusPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06775BFC4D00C62B7E1353A8 /* SettingsScreenUserStatusPickerView.swift */; };
|
||||||
5E73893669BB56307A44AAAD /* RoomListActivityVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DA892E8643240C7BC41900 /* RoomListActivityVisibility.swift */; };
|
5E73893669BB56307A44AAAD /* RoomListActivityVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DA892E8643240C7BC41900 /* RoomListActivityVisibility.swift */; };
|
||||||
5EB116B58533C9A0EBA22717 /* ThreadTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2E7CEE6E9314FD69FE0ED9 /* ThreadTimelineScreen.swift */; };
|
5EB116B58533C9A0EBA22717 /* ThreadTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC2E7CEE6E9314FD69FE0ED9 /* ThreadTimelineScreen.swift */; };
|
||||||
5EDBDE802761B5ECB54E6787 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; };
|
5EDBDE802761B5ECB54E6787 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; };
|
||||||
@@ -1125,6 +1126,7 @@
|
|||||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; };
|
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; };
|
||||||
B6EC2148FA5443C9289BEEBA /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
|
B6EC2148FA5443C9289BEEBA /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
|
||||||
B73E50AF1AB2EB5477E20710 /* RoomDetailsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 166D45E1861A73B232109843 /* RoomDetailsScreenViewModelTests.swift */; };
|
B73E50AF1AB2EB5477E20710 /* RoomDetailsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 166D45E1861A73B232109843 /* RoomDetailsScreenViewModelTests.swift */; };
|
||||||
|
B74CCA58911A0C9C3E277A6B /* SettingsScreenUserStatusRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C816902BB6F450B4225B90C /* SettingsScreenUserStatusRow.swift */; };
|
||||||
B7513A1D8145E7F03B60696B /* sound_01.caf in Resources */ = {isa = PBXBuildFile; fileRef = 7349B71A67B765AA86002723 /* sound_01.caf */; };
|
B7513A1D8145E7F03B60696B /* sound_01.caf in Resources */ = {isa = PBXBuildFile; fileRef = 7349B71A67B765AA86002723 /* sound_01.caf */; };
|
||||||
B773ACD8881DB18E876D950C /* WaveformSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94028A227645FA880B966211 /* WaveformSource.swift */; };
|
B773ACD8881DB18E876D950C /* WaveformSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94028A227645FA880B966211 /* WaveformSource.swift */; };
|
||||||
B7888FC1E1DEF816D175C8D6 /* SecureBackupKeyBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD72A9B720D75DBE60AC299F /* SecureBackupKeyBackupScreenModels.swift */; };
|
B7888FC1E1DEF816D175C8D6 /* SecureBackupKeyBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD72A9B720D75DBE60AC299F /* SecureBackupKeyBackupScreenModels.swift */; };
|
||||||
@@ -1693,6 +1695,7 @@
|
|||||||
05A3E8741D199CD1A37F4CBF /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
|
05A3E8741D199CD1A37F4CBF /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
|
||||||
05AF58372CA884A789EB9C5A /* AppMediatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorProtocol.swift; sourceTree = "<group>"; };
|
05AF58372CA884A789EB9C5A /* AppMediatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediatorProtocol.swift; sourceTree = "<group>"; };
|
||||||
05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = "<group>"; };
|
05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
06775BFC4D00C62B7E1353A8 /* SettingsScreenUserStatusPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUserStatusPickerView.swift; sourceTree = "<group>"; };
|
||||||
0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenModels.swift; sourceTree = "<group>"; };
|
06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenModels.swift; sourceTree = "<group>"; };
|
||||||
06F27F588F9059128E17C669 /* WindowManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManagerProtocol.swift; sourceTree = "<group>"; };
|
06F27F588F9059128E17C669 /* WindowManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManagerProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -2106,6 +2109,7 @@
|
|||||||
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
|
4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = "<group>"; };
|
||||||
4B73675A21580971E9ADF66A /* LocationSharingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
4B73675A21580971E9ADF66A /* LocationSharingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
4C816902BB6F450B4225B90C /* SettingsScreenUserStatusRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUserStatusRow.swift; sourceTree = "<group>"; };
|
||||||
4C8D988E82A8DFA13BE46F7C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = pl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
4C8D988E82A8DFA13BE46F7C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = pl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
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>"; };
|
||||||
@@ -3683,6 +3687,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3CFD5EB0B0EEA4549FB49784 /* SettingsScreen.swift */,
|
3CFD5EB0B0EEA4549FB49784 /* SettingsScreen.swift */,
|
||||||
|
06775BFC4D00C62B7E1353A8 /* SettingsScreenUserStatusPickerView.swift */,
|
||||||
|
4C816902BB6F450B4225B90C /* SettingsScreenUserStatusRow.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -9073,6 +9079,8 @@
|
|||||||
34F1261CEF6D6A00D559B520 /* SettingsScreen.swift in Sources */,
|
34F1261CEF6D6A00D559B520 /* SettingsScreen.swift in Sources */,
|
||||||
AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */,
|
AF8BFA37791E1756EE243E08 /* SettingsScreenCoordinator.swift in Sources */,
|
||||||
B93D7CE520088AD53FA6D53C /* SettingsScreenModels.swift in Sources */,
|
B93D7CE520088AD53FA6D53C /* SettingsScreenModels.swift in Sources */,
|
||||||
|
5E67990F98AF11563EA50080 /* SettingsScreenUserStatusPickerView.swift in Sources */,
|
||||||
|
B74CCA58911A0C9C3E277A6B /* SettingsScreenUserStatusRow.swift in Sources */,
|
||||||
E0B6A569AC3E81D233B43D60 /* SettingsScreenViewModel.swift in Sources */,
|
E0B6A569AC3E81D233B43D60 /* SettingsScreenViewModel.swift in Sources */,
|
||||||
A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */,
|
A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */,
|
||||||
5D99F63CC88BB29383019FC6 /* ShareExtensionModels.swift in Sources */,
|
5D99F63CC88BB29383019FC6 /* ShareExtensionModels.swift in Sources */,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"a11y_collapse_message_text_field" = "Minimize message text field";
|
"a11y_collapse_message_text_field" = "Minimize message text field";
|
||||||
"a11y_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person";
|
"a11y_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person";
|
||||||
"a11y_create_poll_votes_allowed_increase" = "Increase votes allowed per person";
|
"a11y_create_poll_votes_allowed_increase" = "Increase votes allowed per person";
|
||||||
|
"a11y_custom_emoji" = "Custom emoji";
|
||||||
"a11y_delete" = "Delete";
|
"a11y_delete" = "Delete";
|
||||||
"a11y_duration" = "Duration: %1$@";
|
"a11y_duration" = "Duration: %1$@";
|
||||||
"a11y_edit_avatar" = "Edit avatar";
|
"a11y_edit_avatar" = "Edit avatar";
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
"a11y_start_call" = "Start a call";
|
"a11y_start_call" = "Start a call";
|
||||||
"a11y_start_video_call" = "Start a video call";
|
"a11y_start_video_call" = "Start a video call";
|
||||||
"a11y_start_voice_call" = "Start a voice call";
|
"a11y_start_voice_call" = "Start a voice call";
|
||||||
|
"a11y_status" = "Status";
|
||||||
"a11y_tombstoned_room" = "Tombstoned room";
|
"a11y_tombstoned_room" = "Tombstoned room";
|
||||||
"a11y_user_menu" = "User menu";
|
"a11y_user_menu" = "User menu";
|
||||||
"a11y_video_preview" = "Video preview";
|
"a11y_video_preview" = "Video preview";
|
||||||
@@ -387,13 +389,6 @@
|
|||||||
"common_unsupported_call" = "Unsupported call";
|
"common_unsupported_call" = "Unsupported call";
|
||||||
"common_unsupported_event" = "Unsupported event";
|
"common_unsupported_event" = "Unsupported event";
|
||||||
"common_user_started_a_call" = "%1$@ started a call";
|
"common_user_started_a_call" = "%1$@ started a call";
|
||||||
"common_user_status_away" = "Away";
|
|
||||||
"common_user_status_be_right_back" = "Be right back";
|
|
||||||
"common_user_status_custom" = "Custom…";
|
|
||||||
"common_user_status_focus_time" = "Focus time";
|
|
||||||
"common_user_status_in_a_meeting" = "In a meeting";
|
|
||||||
"common_user_status_on_a_call" = "On a call";
|
|
||||||
"common_user_status_on_the_road" = "On the road";
|
|
||||||
"common_username" = "Username";
|
"common_username" = "Username";
|
||||||
"common_verification_cancelled" = "Verification cancelled";
|
"common_verification_cancelled" = "Verification cancelled";
|
||||||
"common_verification_complete" = "Verification complete";
|
"common_verification_complete" = "Verification complete";
|
||||||
@@ -1125,8 +1120,6 @@
|
|||||||
"screen_polls_history_filter_ongoing" = "Ongoing";
|
"screen_polls_history_filter_ongoing" = "Ongoing";
|
||||||
"screen_polls_history_filter_past" = "Past";
|
"screen_polls_history_filter_past" = "Past";
|
||||||
"screen_polls_history_title" = "Polls";
|
"screen_polls_history_title" = "Polls";
|
||||||
"screen_preferences_user_status_custom_hint" = "Custom status…";
|
|
||||||
"screen_preferences_user_status_placeholder" = "What's your status?";
|
|
||||||
"screen_push_history_title" = "Push history";
|
"screen_push_history_title" = "Push history";
|
||||||
"screen_qr_code_login_connecting_subtitle" = "Establishing a secure connection";
|
"screen_qr_code_login_connecting_subtitle" = "Establishing a secure connection";
|
||||||
"screen_qr_code_login_connection_note_secure_state_description" = "A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.";
|
"screen_qr_code_login_connection_note_secure_state_description" = "A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.";
|
||||||
@@ -1515,6 +1508,14 @@
|
|||||||
"screen_session_verification_waiting_subtitle" = "Once accepted you’ll be able to continue with the verification.";
|
"screen_session_verification_waiting_subtitle" = "Once accepted you’ll be able to continue with the verification.";
|
||||||
"screen_session_verification_waiting_to_accept_subtitle" = "Accept the request to start the verification process in your other session to continue.";
|
"screen_session_verification_waiting_to_accept_subtitle" = "Accept the request to start the verification process in your other session to continue.";
|
||||||
"screen_session_verification_waiting_to_accept_title" = "Waiting to accept request";
|
"screen_session_verification_waiting_to_accept_title" = "Waiting to accept request";
|
||||||
|
"screen_settings_user_status_away" = "Away";
|
||||||
|
"screen_settings_user_status_be_right_back" = "Be right back";
|
||||||
|
"screen_settings_user_status_custom" = "Custom…";
|
||||||
|
"screen_settings_user_status_custom_hint" = "Custom status…";
|
||||||
|
"screen_settings_user_status_focus_time" = "Focus time";
|
||||||
|
"screen_settings_user_status_in_a_meeting" = "In a meeting";
|
||||||
|
"screen_settings_user_status_on_the_road" = "On the road";
|
||||||
|
"screen_settings_user_status_placeholder" = "What's your status?";
|
||||||
"screen_share_location_live_location_disclaimer_title" = "Your live location history will be stored in the room and visible to members after the session ends.";
|
"screen_share_location_live_location_disclaimer_title" = "Your live location history will be stored in the room and visible to members after the session ends.";
|
||||||
"screen_share_location_live_location_duration_picker_title" = "Choose how long to share your live location.";
|
"screen_share_location_live_location_duration_picker_title" = "Choose how long to share your live location.";
|
||||||
"screen_share_location_live_location_missing_permissions" = "You do not have permissions to share your live location in this room";
|
"screen_share_location_live_location_missing_permissions" = "You do not have permissions to share your live location in this room";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"a11y_collapse_message_text_field" = "Minimise message text field";
|
"a11y_collapse_message_text_field" = "Minimise message text field";
|
||||||
"a11y_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person";
|
"a11y_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person";
|
||||||
"a11y_create_poll_votes_allowed_increase" = "Increase votes allowed per person";
|
"a11y_create_poll_votes_allowed_increase" = "Increase votes allowed per person";
|
||||||
|
"a11y_custom_emoji" = "Custom emoji";
|
||||||
"a11y_delete" = "Delete";
|
"a11y_delete" = "Delete";
|
||||||
"a11y_duration" = "Duration: %1$@";
|
"a11y_duration" = "Duration: %1$@";
|
||||||
"a11y_edit_avatar" = "Edit avatar";
|
"a11y_edit_avatar" = "Edit avatar";
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
"a11y_start_call" = "Start a call";
|
"a11y_start_call" = "Start a call";
|
||||||
"a11y_start_video_call" = "Start a video call";
|
"a11y_start_video_call" = "Start a video call";
|
||||||
"a11y_start_voice_call" = "Start a voice call";
|
"a11y_start_voice_call" = "Start a voice call";
|
||||||
|
"a11y_status" = "Status";
|
||||||
"a11y_tombstoned_room" = "Tombstoned room";
|
"a11y_tombstoned_room" = "Tombstoned room";
|
||||||
"a11y_user_menu" = "User menu";
|
"a11y_user_menu" = "User menu";
|
||||||
"a11y_video_preview" = "Video preview";
|
"a11y_video_preview" = "Video preview";
|
||||||
@@ -387,13 +389,6 @@
|
|||||||
"common_unsupported_call" = "Unsupported call";
|
"common_unsupported_call" = "Unsupported call";
|
||||||
"common_unsupported_event" = "Unsupported event";
|
"common_unsupported_event" = "Unsupported event";
|
||||||
"common_user_started_a_call" = "%1$@ started a call";
|
"common_user_started_a_call" = "%1$@ started a call";
|
||||||
"common_user_status_away" = "Away";
|
|
||||||
"common_user_status_be_right_back" = "Be right back";
|
|
||||||
"common_user_status_custom" = "Custom…";
|
|
||||||
"common_user_status_focus_time" = "Focus time";
|
|
||||||
"common_user_status_in_a_meeting" = "In a meeting";
|
|
||||||
"common_user_status_on_a_call" = "On a call";
|
|
||||||
"common_user_status_on_the_road" = "On the road";
|
|
||||||
"common_username" = "Username";
|
"common_username" = "Username";
|
||||||
"common_verification_cancelled" = "Verification cancelled";
|
"common_verification_cancelled" = "Verification cancelled";
|
||||||
"common_verification_complete" = "Verification complete";
|
"common_verification_complete" = "Verification complete";
|
||||||
@@ -1125,8 +1120,6 @@
|
|||||||
"screen_polls_history_filter_ongoing" = "Ongoing";
|
"screen_polls_history_filter_ongoing" = "Ongoing";
|
||||||
"screen_polls_history_filter_past" = "Past";
|
"screen_polls_history_filter_past" = "Past";
|
||||||
"screen_polls_history_title" = "Polls";
|
"screen_polls_history_title" = "Polls";
|
||||||
"screen_preferences_user_status_custom_hint" = "Custom status…";
|
|
||||||
"screen_preferences_user_status_placeholder" = "What's your status?";
|
|
||||||
"screen_push_history_title" = "Push history";
|
"screen_push_history_title" = "Push history";
|
||||||
"screen_qr_code_login_connecting_subtitle" = "Establishing a secure connection";
|
"screen_qr_code_login_connecting_subtitle" = "Establishing a secure connection";
|
||||||
"screen_qr_code_login_connection_note_secure_state_description" = "A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.";
|
"screen_qr_code_login_connection_note_secure_state_description" = "A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.";
|
||||||
@@ -1515,6 +1508,14 @@
|
|||||||
"screen_session_verification_waiting_subtitle" = "Once accepted you’ll be able to continue with the verification.";
|
"screen_session_verification_waiting_subtitle" = "Once accepted you’ll be able to continue with the verification.";
|
||||||
"screen_session_verification_waiting_to_accept_subtitle" = "Accept the request to start the verification process in your other session to continue.";
|
"screen_session_verification_waiting_to_accept_subtitle" = "Accept the request to start the verification process in your other session to continue.";
|
||||||
"screen_session_verification_waiting_to_accept_title" = "Waiting to accept request";
|
"screen_session_verification_waiting_to_accept_title" = "Waiting to accept request";
|
||||||
|
"screen_settings_user_status_away" = "Away";
|
||||||
|
"screen_settings_user_status_be_right_back" = "Be right back";
|
||||||
|
"screen_settings_user_status_custom" = "Custom…";
|
||||||
|
"screen_settings_user_status_custom_hint" = "Custom status…";
|
||||||
|
"screen_settings_user_status_focus_time" = "Focus time";
|
||||||
|
"screen_settings_user_status_in_a_meeting" = "In a meeting";
|
||||||
|
"screen_settings_user_status_on_the_road" = "On the road";
|
||||||
|
"screen_settings_user_status_placeholder" = "What's your status?";
|
||||||
"screen_share_location_live_location_disclaimer_title" = "Your live location history will be stored in the room and visible to members after the session ends.";
|
"screen_share_location_live_location_disclaimer_title" = "Your live location history will be stored in the room and visible to members after the session ends.";
|
||||||
"screen_share_location_live_location_duration_picker_title" = "Choose how long to share your live location.";
|
"screen_share_location_live_location_duration_picker_title" = "Choose how long to share your live location.";
|
||||||
"screen_share_location_live_location_missing_permissions" = "You do not have permissions to share your live location in this room";
|
"screen_share_location_live_location_missing_permissions" = "You do not have permissions to share your live location in this room";
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: flowParameters.userSession,
|
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: flowParameters.userSession,
|
||||||
appSettings: flowParameters.appSettings,
|
appSettings: flowParameters.appSettings,
|
||||||
isBugReportServiceEnabled: flowParameters.bugReportService.isEnabled,
|
isBugReportServiceEnabled: flowParameters.bugReportService.isEnabled,
|
||||||
isInSecondaryWindow: isInSecondaryWindow))
|
isInSecondaryWindow: isInSecondaryWindow,
|
||||||
|
userIndicatorController: flowParameters.userIndicatorController))
|
||||||
|
|
||||||
settingsScreenCoordinator.actions
|
settingsScreenCoordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ internal nonisolated enum L10n {
|
|||||||
internal static var a11yCreatePollVotesAllowedDecrease: String { return L10n.tr("Localizable", "a11y_create_poll_votes_allowed_decrease") }
|
internal static var a11yCreatePollVotesAllowedDecrease: String { return L10n.tr("Localizable", "a11y_create_poll_votes_allowed_decrease") }
|
||||||
/// Increase votes allowed per person
|
/// Increase votes allowed per person
|
||||||
internal static var a11yCreatePollVotesAllowedIncrease: String { return L10n.tr("Localizable", "a11y_create_poll_votes_allowed_increase") }
|
internal static var a11yCreatePollVotesAllowedIncrease: String { return L10n.tr("Localizable", "a11y_create_poll_votes_allowed_increase") }
|
||||||
|
/// Custom emoji
|
||||||
|
internal static var a11yCustomEmoji: String { return L10n.tr("Localizable", "a11y_custom_emoji") }
|
||||||
/// Delete
|
/// Delete
|
||||||
internal static var a11yDelete: String { return L10n.tr("Localizable", "a11y_delete") }
|
internal static var a11yDelete: String { return L10n.tr("Localizable", "a11y_delete") }
|
||||||
/// Plural format key: "%#@COUNT@"
|
/// Plural format key: "%#@COUNT@"
|
||||||
@@ -150,6 +152,8 @@ internal nonisolated enum L10n {
|
|||||||
internal static var a11yStartVideoCall: String { return L10n.tr("Localizable", "a11y_start_video_call") }
|
internal static var a11yStartVideoCall: String { return L10n.tr("Localizable", "a11y_start_video_call") }
|
||||||
/// Start a voice call
|
/// Start a voice call
|
||||||
internal static var a11yStartVoiceCall: String { return L10n.tr("Localizable", "a11y_start_voice_call") }
|
internal static var a11yStartVoiceCall: String { return L10n.tr("Localizable", "a11y_start_voice_call") }
|
||||||
|
/// Status
|
||||||
|
internal static var a11yStatus: String { return L10n.tr("Localizable", "a11y_status") }
|
||||||
/// Tombstoned room
|
/// Tombstoned room
|
||||||
internal static var a11yTombstonedRoom: String { return L10n.tr("Localizable", "a11y_tombstoned_room") }
|
internal static var a11yTombstonedRoom: String { return L10n.tr("Localizable", "a11y_tombstoned_room") }
|
||||||
/// User menu
|
/// User menu
|
||||||
@@ -866,20 +870,6 @@ internal nonisolated enum L10n {
|
|||||||
internal static func commonUserStartedACall(_ p1: Any) -> String {
|
internal static func commonUserStartedACall(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "common_user_started_a_call", String(describing: p1))
|
return L10n.tr("Localizable", "common_user_started_a_call", String(describing: p1))
|
||||||
}
|
}
|
||||||
/// Away
|
|
||||||
internal static var commonUserStatusAway: String { return L10n.tr("Localizable", "common_user_status_away") }
|
|
||||||
/// Be right back
|
|
||||||
internal static var commonUserStatusBeRightBack: String { return L10n.tr("Localizable", "common_user_status_be_right_back") }
|
|
||||||
/// Custom…
|
|
||||||
internal static var commonUserStatusCustom: String { return L10n.tr("Localizable", "common_user_status_custom") }
|
|
||||||
/// Focus time
|
|
||||||
internal static var commonUserStatusFocusTime: String { return L10n.tr("Localizable", "common_user_status_focus_time") }
|
|
||||||
/// In a meeting
|
|
||||||
internal static var commonUserStatusInAMeeting: String { return L10n.tr("Localizable", "common_user_status_in_a_meeting") }
|
|
||||||
/// On a call
|
|
||||||
internal static var commonUserStatusOnACall: String { return L10n.tr("Localizable", "common_user_status_on_a_call") }
|
|
||||||
/// On the road
|
|
||||||
internal static var commonUserStatusOnTheRoad: String { return L10n.tr("Localizable", "common_user_status_on_the_road") }
|
|
||||||
/// Username
|
/// Username
|
||||||
internal static var commonUsername: String { return L10n.tr("Localizable", "common_username") }
|
internal static var commonUsername: String { return L10n.tr("Localizable", "common_username") }
|
||||||
/// Verification cancelled
|
/// Verification cancelled
|
||||||
@@ -2612,10 +2602,6 @@ internal nonisolated enum L10n {
|
|||||||
internal static var screenPollsHistoryFilterPast: String { return L10n.tr("Localizable", "screen_polls_history_filter_past") }
|
internal static var screenPollsHistoryFilterPast: String { return L10n.tr("Localizable", "screen_polls_history_filter_past") }
|
||||||
/// Polls
|
/// Polls
|
||||||
internal static var screenPollsHistoryTitle: String { return L10n.tr("Localizable", "screen_polls_history_title") }
|
internal static var screenPollsHistoryTitle: String { return L10n.tr("Localizable", "screen_polls_history_title") }
|
||||||
/// Custom status…
|
|
||||||
internal static var screenPreferencesUserStatusCustomHint: String { return L10n.tr("Localizable", "screen_preferences_user_status_custom_hint") }
|
|
||||||
/// What's your status?
|
|
||||||
internal static var screenPreferencesUserStatusPlaceholder: String { return L10n.tr("Localizable", "screen_preferences_user_status_placeholder") }
|
|
||||||
/// Push history
|
/// Push history
|
||||||
internal static var screenPushHistoryTitle: String { return L10n.tr("Localizable", "screen_push_history_title") }
|
internal static var screenPushHistoryTitle: String { return L10n.tr("Localizable", "screen_push_history_title") }
|
||||||
/// Establishing a secure connection
|
/// Establishing a secure connection
|
||||||
@@ -3499,6 +3485,22 @@ internal nonisolated enum L10n {
|
|||||||
internal static var screenSessionVerificationWaitingToAcceptSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_waiting_to_accept_subtitle") }
|
internal static var screenSessionVerificationWaitingToAcceptSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_waiting_to_accept_subtitle") }
|
||||||
/// Waiting to accept request
|
/// Waiting to accept request
|
||||||
internal static var screenSessionVerificationWaitingToAcceptTitle: String { return L10n.tr("Localizable", "screen_session_verification_waiting_to_accept_title") }
|
internal static var screenSessionVerificationWaitingToAcceptTitle: String { return L10n.tr("Localizable", "screen_session_verification_waiting_to_accept_title") }
|
||||||
|
/// Away
|
||||||
|
internal static var screenSettingsUserStatusAway: String { return L10n.tr("Localizable", "screen_settings_user_status_away") }
|
||||||
|
/// Be right back
|
||||||
|
internal static var screenSettingsUserStatusBeRightBack: String { return L10n.tr("Localizable", "screen_settings_user_status_be_right_back") }
|
||||||
|
/// Custom…
|
||||||
|
internal static var screenSettingsUserStatusCustom: String { return L10n.tr("Localizable", "screen_settings_user_status_custom") }
|
||||||
|
/// Custom status…
|
||||||
|
internal static var screenSettingsUserStatusCustomHint: String { return L10n.tr("Localizable", "screen_settings_user_status_custom_hint") }
|
||||||
|
/// Focus time
|
||||||
|
internal static var screenSettingsUserStatusFocusTime: String { return L10n.tr("Localizable", "screen_settings_user_status_focus_time") }
|
||||||
|
/// In a meeting
|
||||||
|
internal static var screenSettingsUserStatusInAMeeting: String { return L10n.tr("Localizable", "screen_settings_user_status_in_a_meeting") }
|
||||||
|
/// On the road
|
||||||
|
internal static var screenSettingsUserStatusOnTheRoad: String { return L10n.tr("Localizable", "screen_settings_user_status_on_the_road") }
|
||||||
|
/// What's your status?
|
||||||
|
internal static var screenSettingsUserStatusPlaceholder: String { return L10n.tr("Localizable", "screen_settings_user_status_placeholder") }
|
||||||
/// Your live location history will be stored in the room and visible to members after the session ends.
|
/// Your live location history will be stored in the room and visible to members after the session ends.
|
||||||
internal static var screenShareLocationLiveLocationDisclaimerTitle: String { return L10n.tr("Localizable", "screen_share_location_live_location_disclaimer_title") }
|
internal static var screenShareLocationLiveLocationDisclaimerTitle: String { return L10n.tr("Localizable", "screen_share_location_live_location_disclaimer_title") }
|
||||||
/// Choose how long to share your live location.
|
/// Choose how long to share your live location.
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ struct ClientProxyMockConfiguration {
|
|||||||
var homeserver = ""
|
var homeserver = ""
|
||||||
var userIDServerName: String?
|
var userIDServerName: String?
|
||||||
var userID: String = RoomMemberProxyMock.mockMe.userID
|
var userID: String = RoomMemberProxyMock.mockMe.userID
|
||||||
var displayName: String? = "User display name"
|
|
||||||
var deviceID: String?
|
var deviceID: String?
|
||||||
|
var displayName: String? = "User display name"
|
||||||
|
var status: UserStatus = .init()
|
||||||
var roomSummaryProvider: RoomSummaryProviderProtocol = RoomSummaryProviderMock(.init())
|
var roomSummaryProvider: RoomSummaryProviderProtocol = RoomSummaryProviderMock(.init())
|
||||||
var spaceServiceConfiguration: SpaceServiceProxyMock.Configuration = .init()
|
var spaceServiceConfiguration: SpaceServiceProxyMock.Configuration = .init()
|
||||||
var roomPreviews: [RoomPreviewProxyProtocol]?
|
var roomPreviews: [RoomPreviewProxyProtocol]?
|
||||||
@@ -65,7 +66,9 @@ extension ClientProxyMock {
|
|||||||
verificationStatePublisher = .init(.unknown)
|
verificationStatePublisher = .init(.unknown)
|
||||||
homeserverReachabilityPublisher = .init(.reachable)
|
homeserverReachabilityPublisher = .init(.reachable)
|
||||||
|
|
||||||
userProfilePublisher = .init(UserProfile(userID: configuration.userID, displayName: configuration.displayName))
|
userProfilePublisher = .init(UserProfile(userID: configuration.userID,
|
||||||
|
displayName: configuration.displayName,
|
||||||
|
status: configuration.status))
|
||||||
|
|
||||||
ignoredUsersPublisher = .init([RoomMemberProxyMock].allMembers.map(\.userID))
|
ignoredUsersPublisher = .init([RoomMemberProxyMock].allMembers.map(\.userID))
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ enum TestablePreviewsDictionary {
|
|||||||
"ServerConfirmationScreen_Previews" : ServerConfirmationScreen_Previews.self,
|
"ServerConfirmationScreen_Previews" : ServerConfirmationScreen_Previews.self,
|
||||||
"SessionVerificationRequestDetailsView_Previews" : SessionVerificationRequestDetailsView_Previews.self,
|
"SessionVerificationRequestDetailsView_Previews" : SessionVerificationRequestDetailsView_Previews.self,
|
||||||
"SessionVerification_Previews" : SessionVerification_Previews.self,
|
"SessionVerification_Previews" : SessionVerification_Previews.self,
|
||||||
|
"SettingsScreenUserStatusPickerView_Previews" : SettingsScreenUserStatusPickerView_Previews.self,
|
||||||
|
"SettingsScreenUserStatusRow_Previews" : SettingsScreenUserStatusRow_Previews.self,
|
||||||
"ShimmerOverlay_Previews" : ShimmerOverlay_Previews.self,
|
"ShimmerOverlay_Previews" : ShimmerOverlay_Previews.self,
|
||||||
"SoftLogoutScreen_Previews" : SoftLogoutScreen_Previews.self,
|
"SoftLogoutScreen_Previews" : SoftLogoutScreen_Previews.self,
|
||||||
"SpaceAddRoomsScreenSelectedItem_Previews" : SpaceAddRoomsScreenSelectedItem_Previews.self,
|
"SpaceAddRoomsScreenSelectedItem_Previews" : SpaceAddRoomsScreenSelectedItem_Previews.self,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ struct SettingsScreenCoordinatorParameters {
|
|||||||
let appSettings: AppSettings
|
let appSettings: AppSettings
|
||||||
let isBugReportServiceEnabled: Bool
|
let isBugReportServiceEnabled: Bool
|
||||||
let isInSecondaryWindow: Bool
|
let isInSecondaryWindow: Bool
|
||||||
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SettingsScreenCoordinatorAction {
|
enum SettingsScreenCoordinatorAction {
|
||||||
@@ -51,7 +52,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
||||||
appSettings: parameters.appSettings,
|
appSettings: parameters.appSettings,
|
||||||
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled,
|
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled,
|
||||||
isInSecondaryWindow: parameters.isInSecondaryWindow)
|
isInSecondaryWindow: parameters.isInSecondaryWindow,
|
||||||
|
userIndicatorController: parameters.userIndicatorController)
|
||||||
|
|
||||||
viewModel.actions
|
viewModel.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ enum SettingsScreenSecuritySectionMode {
|
|||||||
struct SettingsScreenViewState: BindableState {
|
struct SettingsScreenViewState: BindableState {
|
||||||
var deviceID: String?
|
var deviceID: String?
|
||||||
var userProfile: UserProfile
|
var userProfile: UserProfile
|
||||||
|
var showUserStatus: Bool
|
||||||
var showLinkNewDeviceButton: Bool
|
var showLinkNewDeviceButton: Bool
|
||||||
var accountProfileURL: URL?
|
var accountProfileURL: URL?
|
||||||
var showAccountDeactivation: Bool
|
var showAccountDeactivation: Bool
|
||||||
@@ -51,15 +52,28 @@ struct SettingsScreenViewState: BindableState {
|
|||||||
let navigationBarVisibility: Visibility
|
let navigationBarVisibility: Visibility
|
||||||
|
|
||||||
var bindings = SettingsScreenViewStateBindings()
|
var bindings = SettingsScreenViewStateBindings()
|
||||||
|
|
||||||
|
var userStatusRowMode: SettingsScreenUserStatusRow.Mode {
|
||||||
|
if bindings.isShowingCustomStatusField {
|
||||||
|
.custom
|
||||||
|
} else if let rawStatus = userProfile.status.raw {
|
||||||
|
.show(rawStatus)
|
||||||
|
} else {
|
||||||
|
.pick
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SettingsScreenViewStateBindings {
|
struct SettingsScreenViewStateBindings {
|
||||||
|
var isPresentingStatusPicker = false
|
||||||
|
var isShowingCustomStatusField = false
|
||||||
var isPresentingAccountDeactivationConfirmation = false
|
var isPresentingAccountDeactivationConfirmation = false
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SettingsScreenViewAction {
|
enum SettingsScreenViewAction {
|
||||||
case close
|
case close
|
||||||
case userDetails
|
case userDetails
|
||||||
|
case userStatus(UserStatusAction)
|
||||||
case analytics
|
case analytics
|
||||||
case appLock
|
case appLock
|
||||||
case reportBug
|
case reportBug
|
||||||
@@ -75,4 +89,17 @@ enum SettingsScreenViewAction {
|
|||||||
case labs
|
case labs
|
||||||
case logout
|
case logout
|
||||||
case deactivateAccount
|
case deactivateAccount
|
||||||
|
|
||||||
|
enum UserStatusAction {
|
||||||
|
/// Show status picker sheet to select a preset status.
|
||||||
|
case pickStatus
|
||||||
|
/// Dismiss the picker sheet and show the custom status input.
|
||||||
|
case customStatus
|
||||||
|
/// Show the emoji picker to select the emoji for the custom status.
|
||||||
|
case pickCustomEmoji
|
||||||
|
/// Set the user's status to the provided value.
|
||||||
|
case set(UserStatus.Raw?)
|
||||||
|
/// Cancel user status picking/input.
|
||||||
|
case cancel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ typealias SettingsScreenViewModelType = StateStoreViewModelV2<SettingsScreenView
|
|||||||
|
|
||||||
class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewModelProtocol {
|
class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewModelProtocol {
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
|
private let clientProxy: ClientProxyProtocol
|
||||||
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
|
||||||
private var actionsSubject: PassthroughSubject<SettingsScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<SettingsScreenViewModelAction, Never> = .init()
|
||||||
|
|
||||||
@@ -20,11 +22,14 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool, isInSecondaryWindow: Bool) {
|
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool, isInSecondaryWindow: Bool, userIndicatorController: UserIndicatorControllerProtocol) {
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
clientProxy = userSession.clientProxy
|
||||||
|
self.userIndicatorController = userIndicatorController
|
||||||
|
|
||||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||||
userProfile: userSession.clientProxy.userProfilePublisher.value,
|
userProfile: userSession.clientProxy.userProfilePublisher.value,
|
||||||
|
showUserStatus: appSettings.userStatusEnabled,
|
||||||
showLinkNewDeviceButton: appSettings.linkNewDeviceEnabled,
|
showLinkNewDeviceButton: appSettings.linkNewDeviceEnabled,
|
||||||
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
||||||
showDeveloperOptions: appSettings.developerOptionsEnabled,
|
showDeveloperOptions: appSettings.developerOptionsEnabled,
|
||||||
@@ -92,6 +97,18 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.send(.close)
|
actionsSubject.send(.close)
|
||||||
case .userDetails:
|
case .userDetails:
|
||||||
actionsSubject.send(.userDetails)
|
actionsSubject.send(.userDetails)
|
||||||
|
case .userStatus(.pickStatus):
|
||||||
|
state.bindings.isPresentingStatusPicker = true
|
||||||
|
case .userStatus(.customStatus):
|
||||||
|
state.bindings.isPresentingStatusPicker = false
|
||||||
|
state.bindings.isShowingCustomStatusField = true
|
||||||
|
case .userStatus(.pickCustomEmoji):
|
||||||
|
break // Hook-up the emoji picker here.
|
||||||
|
case .userStatus(.set(let status)):
|
||||||
|
Task { await setUserStatus(status) }
|
||||||
|
case .userStatus(.cancel):
|
||||||
|
state.bindings.isPresentingStatusPicker = false
|
||||||
|
state.bindings.isShowingCustomStatusField = false
|
||||||
case .linkNewDevice:
|
case .linkNewDevice:
|
||||||
actionsSubject.send(.linkNewDevice)
|
actionsSubject.send(.linkNewDevice)
|
||||||
case let .manageAccount(url):
|
case let .manageAccount(url):
|
||||||
@@ -124,4 +141,28 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.send(.deactivateAccount)
|
actionsSubject.send(.deactivateAccount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
func setUserStatus(_ status: UserStatus.Raw?) async {
|
||||||
|
// Loading state tbc
|
||||||
|
state.bindings.isPresentingStatusPicker = false
|
||||||
|
state.bindings.isShowingCustomStatusField = false
|
||||||
|
|
||||||
|
let result = if let status {
|
||||||
|
await clientProxy.setUserStatus(status)
|
||||||
|
} else {
|
||||||
|
await clientProxy.removeUserStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
break // Loading/error state tbc
|
||||||
|
case .failure:
|
||||||
|
userIndicatorController.submitIndicator(.init(id: UUID().uuidString,
|
||||||
|
type: .toast,
|
||||||
|
title: L10n.errorUnknown,
|
||||||
|
icon: \.close))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SFSafeSymbols
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsScreen: View {
|
struct SettingsScreen: View {
|
||||||
let context: SettingsScreenViewModel.Context
|
@Bindable var context: SettingsScreenViewModel.Context
|
||||||
|
|
||||||
private var shouldHideManageAccountSection: Bool {
|
private var shouldHideManageAccountSection: Bool {
|
||||||
context.viewState.accountProfileURL == nil &&
|
context.viewState.accountProfileURL == nil &&
|
||||||
@@ -23,6 +23,10 @@ struct SettingsScreen: View {
|
|||||||
Form {
|
Form {
|
||||||
userSection
|
userSection
|
||||||
|
|
||||||
|
if context.viewState.showUserStatus {
|
||||||
|
userStatusSection
|
||||||
|
}
|
||||||
|
|
||||||
if !shouldHideManageAccountSection {
|
if !shouldHideManageAccountSection {
|
||||||
manageAccountSection
|
manageAccountSection
|
||||||
}
|
}
|
||||||
@@ -59,9 +63,16 @@ struct SettingsScreen: View {
|
|||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(context.viewState.userProfile.displayName ?? "")
|
HStack(spacing: 6) {
|
||||||
.font(.compound.headingMD)
|
Text(context.viewState.userProfile.displayName ?? "")
|
||||||
.foregroundColor(.compound.textPrimary)
|
|
||||||
|
if let statusEmoji = context.viewState.userProfile.status.displayed?.emoji {
|
||||||
|
Text(String(statusEmoji))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.compound.headingMD)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
|
||||||
Text(context.viewState.userProfile.id)
|
Text(context.viewState.userProfile.id)
|
||||||
.font(.compound.bodySM)
|
.font(.compound.bodySM)
|
||||||
.foregroundColor(.compound.textSecondary)
|
.foregroundColor(.compound.textSecondary)
|
||||||
@@ -78,6 +89,19 @@ struct SettingsScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var userStatusSection: some View {
|
||||||
|
Section {
|
||||||
|
SettingsScreenUserStatusRow(mode: context.viewState.userStatusRowMode) { action in
|
||||||
|
context.send(viewAction: .userStatus(action))
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $context.isPresentingStatusPicker) {
|
||||||
|
SettingsScreenUserStatusPickerView { action in
|
||||||
|
context.send(viewAction: .userStatus(action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var manageMyAppSection: some View {
|
private var manageMyAppSection: some View {
|
||||||
Section {
|
Section {
|
||||||
ListRow(label: .default(title: L10n.screenNotificationSettingsTitle,
|
ListRow(label: .default(title: L10n.screenNotificationSettingsTitle,
|
||||||
@@ -275,11 +299,14 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func makeViewModel(isBugReportServiceEnabled: Bool = true) -> SettingsScreenViewModel {
|
static func makeViewModel(isBugReportServiceEnabled: Bool = true) -> SettingsScreenViewModel {
|
||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com",
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@alice:example.com",
|
||||||
deviceID: "AAAAAAAAAAA"))))
|
deviceID: "AAAAAAAAAAA",
|
||||||
|
displayName: "Alice Liddell",
|
||||||
|
status: .mockFocussing))))
|
||||||
return SettingsScreenViewModel(userSession: userSession,
|
return SettingsScreenViewModel(userSession: userSession,
|
||||||
appSettings: .volatile(),
|
appSettings: .volatile(),
|
||||||
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||||
isInSecondaryWindow: false)
|
isInSecondaryWindow: false,
|
||||||
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// 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 Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SettingsScreenUserStatusPickerView: View {
|
||||||
|
let action: (SettingsScreenViewAction.UserStatusAction) -> Void
|
||||||
|
|
||||||
|
@State private var selection: UserStatusPreset?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ElementNavigationStack {
|
||||||
|
list
|
||||||
|
.navigationTitle(L10n.screenSettingsUserStatusPlaceholder)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar { toolbar }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var list: some View {
|
||||||
|
List {
|
||||||
|
ListRow(label: .plain(title: ""),
|
||||||
|
kind: .inlinePicker(selection: $selection,
|
||||||
|
items: UserStatusPreset.allCases.map { (title: "\($0.rawStatus.emoji) \($0.rawStatus.text)", tag: $0) }))
|
||||||
|
|
||||||
|
ListRow(label: .plain(title: "✍️ \(L10n.screenSettingsUserStatusCustom)"),
|
||||||
|
kind: .button { action(.customStatus) })
|
||||||
|
}
|
||||||
|
.compoundList(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToolbarContentBuilder
|
||||||
|
var toolbar: some ToolbarContent {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
ToolbarButton(role: .cancel) { action(.cancel) }
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
ToolbarButton(role: .save) {
|
||||||
|
if let selection {
|
||||||
|
action(.set(selection.rawStatus))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(selection == nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum UserStatusPreset: CaseIterable {
|
||||||
|
case inAMeeting
|
||||||
|
case focusTime
|
||||||
|
case onTheRoad
|
||||||
|
case beRightBack
|
||||||
|
case away
|
||||||
|
|
||||||
|
var rawStatus: UserStatus.Raw {
|
||||||
|
switch self {
|
||||||
|
case .inAMeeting: .init(text: L10n.screenSettingsUserStatusInAMeeting, emoji: "💬")
|
||||||
|
case .focusTime: .init(text: L10n.screenSettingsUserStatusFocusTime, emoji: "💡")
|
||||||
|
case .onTheRoad: .init(text: L10n.screenSettingsUserStatusOnTheRoad, emoji: "🚙")
|
||||||
|
case .beRightBack: .init(text: L10n.screenSettingsUserStatusBeRightBack, emoji: "☕️")
|
||||||
|
case .away: .init(text: L10n.screenSettingsUserStatusAway, emoji: "🌴")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct SettingsScreenUserStatusPickerView_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static var previews: some View {
|
||||||
|
SettingsScreenUserStatusPickerView { _ in }
|
||||||
|
}
|
||||||
|
}
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
//
|
||||||
|
// 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 Compound
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SettingsScreenUserStatusRow: View {
|
||||||
|
enum Mode: Equatable { case pick, custom, show(UserStatus.Raw) }
|
||||||
|
let mode: Mode
|
||||||
|
let action: (SettingsScreenViewAction.UserStatusAction) -> Void
|
||||||
|
|
||||||
|
@State private var customEmoji = "😄"
|
||||||
|
@State private var customText = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
switch mode {
|
||||||
|
case .pick:
|
||||||
|
ListRow(label: .default(title: L10n.screenSettingsUserStatusPlaceholder, icon: \.reaction),
|
||||||
|
kind: .button { action(.pickStatus) })
|
||||||
|
case .custom:
|
||||||
|
ListRow(kind: .custom {
|
||||||
|
HStack(spacing: ListRowPadding.labelIconSpacing) {
|
||||||
|
Button { action(.pickCustomEmoji) } label: {
|
||||||
|
Text(customEmoji)
|
||||||
|
.font(.compound.headingSM)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.accessibilityLabel(L10n.a11yCustomEmoji)
|
||||||
|
.accessibilityValue(customEmoji)
|
||||||
|
}
|
||||||
|
.buttonStyle(EditEmojiButtonStyle())
|
||||||
|
|
||||||
|
TextField(L10n.screenSettingsUserStatusCustomHint, text: $customText)
|
||||||
|
.textFieldStyle(.compound(.raised))
|
||||||
|
.accessibilityLabel(L10n.screenSettingsUserStatusCustomHint)
|
||||||
|
.padding(.vertical, 3)
|
||||||
|
|
||||||
|
ZStack { // ZStack to stop the text field from resizing.
|
||||||
|
Button(L10n.actionCancel) { action(.cancel) }
|
||||||
|
.opacity(customText.isEmpty ? 1 : 0)
|
||||||
|
Button(L10n.actionSave, action: saveCustomStatus)
|
||||||
|
.opacity(customText.isEmpty ? 0 : 1)
|
||||||
|
}
|
||||||
|
.buttonStyle(.compound(.textLink))
|
||||||
|
}
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
|
})
|
||||||
|
case .show(let status):
|
||||||
|
ListRow(kind: .custom {
|
||||||
|
HStack(spacing: ListRowPadding.labelIconSpacing) {
|
||||||
|
Button { action(.pickStatus) } label: {
|
||||||
|
HStack(spacing: ListRowPadding.labelIconSpacing) {
|
||||||
|
CompoundIcon(\.reaction)
|
||||||
|
.hidden()
|
||||||
|
.overlay {
|
||||||
|
Text(String(status.emoji))
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(status.text)
|
||||||
|
.font(.compound.bodyLG)
|
||||||
|
.foregroundStyle(.compound.textPrimary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.a11yStatus)
|
||||||
|
.accessibilityValue(Text(String(status.emoji)) + Text(status.text))
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
|
Button(L10n.actionClear) { action(.set(nil)) }
|
||||||
|
.buttonStyle(.compound(.textLink))
|
||||||
|
}
|
||||||
|
.padding(ListRowPadding.insets)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveCustomStatus() {
|
||||||
|
guard let emoji = customEmoji.first else { return }
|
||||||
|
action(.set(.init(text: customText, emoji: emoji)))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EditEmojiButtonStyle: ButtonStyle {
|
||||||
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
|
configuration.label
|
||||||
|
.padding(12)
|
||||||
|
.background {
|
||||||
|
ZStack {
|
||||||
|
Circle().fill(.compound.bgCanvasDefaultLevel1)
|
||||||
|
Circle().inset(by: 0.5).stroke(.compound.borderInteractiveSecondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawingGroup()
|
||||||
|
.opacity(configuration.isPressed ? 0.6 : 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SettingsScreenUserStatusRow_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static var previews: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
SettingsScreenUserStatusRow(mode: .pick) { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
SettingsScreenUserStatusRow(mode: .custom) { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
SettingsScreenUserStatusRow(mode: .show(.init(text: "Away", emoji: "🌴"))) { _ in }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.compoundList()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1159,6 +1159,20 @@ extension PreviewTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func settingsScreenUserStatusPickerView() async throws {
|
||||||
|
for (index, preview) in SettingsScreenUserStatusPickerView_Previews._allPreviews.enumerated() {
|
||||||
|
try await assertSnapshots(matching: preview, step: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func settingsScreenUserStatusRow() async throws {
|
||||||
|
for (index, preview) in SettingsScreenUserStatusRow_Previews._allPreviews.enumerated() {
|
||||||
|
try await assertSnapshots(matching: preview, step: index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func settingsScreen() async throws {
|
func settingsScreen() async throws {
|
||||||
for (index, preview) in SettingsScreen_Previews._allPreviews.enumerated() {
|
for (index, preview) in SettingsScreen_Previews._allPreviews.enumerated() {
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f2d77bf89b1d8f32769fb344350d506385d285b8243d2e68e6a927526ec859f6
|
oid sha256:5001fc410195d974fceb85fb9c8e03a25bd7614fe9ffd7d35d1ca3791b71df4d
|
||||||
size 148915
|
size 150756
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:be915ee13c944b772a0e57afebc00a89c025da30a2b9752f63355d439da7cddb
|
oid sha256:da52953ab4cf8a4b09f00ee3c5942f8bb946b108b1613fad1e906e833cf6fa00
|
||||||
size 155044
|
size 156816
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a05f66738a862f6131e63903c87c9b1b94dfdf0a64cbb222185c30dc13d76dc3
|
oid sha256:f0b3e32358f640201ad8efb3f397287c3b8f1286eb8f4b3510d521a104db5180
|
||||||
size 95770
|
size 97973
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:504ba2c922526a08812f1df453b7a3f7edb56c3e78666d75272f6da887965926
|
oid sha256:834144dc590660e7553cb94e24464d5eab47d096641c4e435190c731233db3d1
|
||||||
size 111928
|
size 114090
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e580af0e0cd966cf0fecec76f46412cfc258e7d2f354c3b9d0a08c072cef0a4b
|
oid sha256:49948b289947884a323d3d9e5af9b2d2b98689d57c6b15426cb56c9bab661da5
|
||||||
size 149158
|
size 150999
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a2e654f118059094b2245e8e6574a1c26b9debdc470453e938e27fed680f799c
|
oid sha256:c9a15bdedcbd5957ecf376e56a443dfaa55c08a486ab21df53b8def37df37e60
|
||||||
size 155305
|
size 157077
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:fd8270f52ee525489e71ecdb3eefaf07879654d71adfccb71fe018fd8a3f01a6
|
oid sha256:c63dec2f3675ac96551c66cc5398cdfaa87ed600c267c92267bb50b8cf83120d
|
||||||
size 96176
|
size 98379
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b067e9231ded487380ca1bc6af2d17410fcabf349afb7fc81fd0ae9cf0fb7c04
|
oid sha256:b0107312537fe0627c61fc799f91009b36b39464e64545cb7280bf2b67fa3cb5
|
||||||
size 115309
|
size 117471
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9c2dfd8d6bc2a475e5a77cf004b46dac6f4f56591315bd2a0399a8e6b3fb842e
|
||||||
|
size 106801
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cfc43abc39bc53dccf09bac730b1a5415d0a1b3ac9b6e94127edc0d46604ed4f
|
||||||
|
size 109600
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5d0773ec07993bea5963f0d18526090b4d380b86634a6826edd97f910cd01946
|
||||||
|
size 61378
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9a943da9248522547ff9cd82d6545c316636003799d3c5971d837f1ba3311d12
|
||||||
|
size 68219
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c3ac8e7e786f9769e382d8fc59f5ed7b702542d93dd4ce4f28cd683f6feb7cc8
|
||||||
|
size 108146
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:bf0f284e5e2fc6a942e6990dd0639c29c176a5824fc16f74e24a185fdea3f6bc
|
||||||
|
size 110210
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:93ee4b791e7b40f6ec0b21616806b749ac3dee51202e643b4b54800fd1d4c266
|
||||||
|
size 60165
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8068d0b7d33018645ee13ae808e1f52c99e35198c3222567cd096705f4b2d41e
|
||||||
|
size 66395
|
||||||
@@ -12,37 +12,130 @@ import Testing
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct SettingsScreenViewModelTests {
|
struct SettingsScreenViewModelTests {
|
||||||
private var viewModel: SettingsScreenViewModelProtocol
|
private var clientProxy: ClientProxyMock!
|
||||||
private var context: SettingsScreenViewModelType.Context
|
private var viewModel: SettingsScreenViewModelProtocol!
|
||||||
|
private var context: SettingsScreenViewModelType.Context {
|
||||||
|
viewModel.context
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
// MARK: - User status
|
||||||
let appSettings = AppSettings.volatile()
|
|
||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
@Test
|
||||||
viewModel = SettingsScreenViewModel(userSession: userSession,
|
mutating func statusPickerFlow() {
|
||||||
appSettings: appSettings,
|
// Given a screen where no status has been set.
|
||||||
isBugReportServiceEnabled: true,
|
setupViewModel()
|
||||||
isInSecondaryWindow: false)
|
#expect(!context.viewState.bindings.isPresentingStatusPicker)
|
||||||
context = viewModel.context
|
#expect(!context.viewState.bindings.isShowingCustomStatusField)
|
||||||
|
#expect(context.viewState.userStatusRowMode == .pick)
|
||||||
|
|
||||||
|
// When choosing to pick a status.
|
||||||
|
context.send(viewAction: .userStatus(.pickStatus))
|
||||||
|
|
||||||
|
// Then the status picker should be presented.
|
||||||
|
#expect(context.viewState.bindings.isPresentingStatusPicker)
|
||||||
|
#expect(!context.viewState.bindings.isShowingCustomStatusField)
|
||||||
|
#expect(context.viewState.userStatusRowMode == .pick)
|
||||||
|
|
||||||
|
// When choosing to enter a custom status.
|
||||||
|
context.send(viewAction: .userStatus(.customStatus))
|
||||||
|
|
||||||
|
// Then the picker should be dismissed and the custom status field shown.
|
||||||
|
#expect(!context.viewState.bindings.isPresentingStatusPicker)
|
||||||
|
#expect(context.viewState.bindings.isShowingCustomStatusField)
|
||||||
|
#expect(context.viewState.userStatusRowMode == .custom)
|
||||||
|
|
||||||
|
// When cancelling.
|
||||||
|
context.send(viewAction: .userStatus(.cancel))
|
||||||
|
|
||||||
|
// Then all status editing should be dismissed.
|
||||||
|
#expect(!context.viewState.bindings.isPresentingStatusPicker)
|
||||||
|
#expect(!context.viewState.bindings.isShowingCustomStatusField)
|
||||||
|
#expect(context.viewState.userStatusRowMode == .pick)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func logout() async throws {
|
mutating func persistingStatus() async throws {
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .logout }
|
// Given a screen where no status has been set or removed yet.
|
||||||
context.send(viewAction: .logout)
|
setupViewModel()
|
||||||
try await deferred.fulfill()
|
#expect(clientProxy.setUserStatusCallsCount == 0)
|
||||||
|
#expect(clientProxy.removeUserStatusCallsCount == 0)
|
||||||
|
|
||||||
|
// When setting a status.
|
||||||
|
let status = UserStatus.Raw(text: "Away", emoji: "🌴")
|
||||||
|
let (setStream, setContinuation) = AsyncStream<UserStatus.Raw>.makeStream()
|
||||||
|
clientProxy.setUserStatusClosure = {
|
||||||
|
setContinuation.yield($0)
|
||||||
|
return .success(())
|
||||||
|
}
|
||||||
|
let statusSet = deferFulfillment(setStream) { _ in true }
|
||||||
|
context.send(viewAction: .userStatus(.set(status)))
|
||||||
|
|
||||||
|
// Then only the set endpoint should be called, with the chosen status.
|
||||||
|
#expect(try await statusSet.fulfill() == status)
|
||||||
|
#expect(clientProxy.setUserStatusCallsCount == 1)
|
||||||
|
#expect(clientProxy.removeUserStatusCallsCount == 0)
|
||||||
|
|
||||||
|
// When clearing the status.
|
||||||
|
let (removeStream, removeContinuation) = AsyncStream<Void>.makeStream()
|
||||||
|
clientProxy.removeUserStatusClosure = {
|
||||||
|
removeContinuation.yield()
|
||||||
|
return .success(())
|
||||||
|
}
|
||||||
|
let statusRemoved = deferFulfillment(removeStream) { _ in true }
|
||||||
|
context.send(viewAction: .userStatus(.set(nil)))
|
||||||
|
|
||||||
|
// Then only the remove endpoint should be called, leaving the set call untouched.
|
||||||
|
try await statusRemoved.fulfill()
|
||||||
|
#expect(clientProxy.removeUserStatusCallsCount == 1)
|
||||||
|
#expect(clientProxy.setUserStatusCallsCount == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func reportBug() async throws {
|
mutating func showsExistingStatus() throws {
|
||||||
|
// Given a screen where the user has a status set.
|
||||||
|
let status = UserStatus.mockHoliday
|
||||||
|
setupViewModel(status: status)
|
||||||
|
|
||||||
|
// Then that status should be shown.
|
||||||
|
let rawStatus = try #require(status.raw)
|
||||||
|
#expect(context.viewState.userStatusRowMode == .show(rawStatus))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func reportBug() async throws {
|
||||||
|
setupViewModel()
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .reportBug }
|
let deferred = deferFulfillment(viewModel.actions) { $0 == .reportBug }
|
||||||
context.send(viewAction: .reportBug)
|
context.send(viewAction: .reportBug)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func analytics() async throws {
|
mutating func analytics() async throws {
|
||||||
|
setupViewModel()
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .analytics }
|
let deferred = deferFulfillment(viewModel.actions) { $0 == .analytics }
|
||||||
context.send(viewAction: .analytics)
|
context.send(viewAction: .analytics)
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func logout() async throws {
|
||||||
|
setupViewModel()
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(viewModel.actions) { $0 == .logout }
|
||||||
|
context.send(viewAction: .logout)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private mutating func setupViewModel(status: UserStatus = .init()) {
|
||||||
|
clientProxy = ClientProxyMock(.init(userID: "", status: status))
|
||||||
|
viewModel = SettingsScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
|
appSettings: AppSettings.volatile(),
|
||||||
|
isBugReportServiceEnabled: true,
|
||||||
|
isInSecondaryWindow: false,
|
||||||
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user