mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
DM Details View (#744)
* Basic implementation completed but it requires some bug fixing and polishing (+4 squashed commits) Squashed commits: [43f0064e] now the content of the room details reflects the expected dm details content when the conditions are met [fe6f2b5e] replaced existing headers with a composable and reusable HeaderView [3c00120a] now the members are fetched every time the room is opened [421d539b] refactored the members fetching to be immediate in case the room could be a direct chat * polished the code and fixed some bugs that prevented the updated state of the members to be displayed in the room details view (+1 squashed commit) Squashed commits: [6bf98473] fix for the button not being destructive in room details screen * removed copy button and updated copy * moved a file and renamed header view * addressed all PR comments * sdk bump * UI tests * unit tests * changelog * updated mocks * removed a duplicated signature in a mock
This commit is contained in:
@@ -163,7 +163,7 @@
|
||||
{
|
||||
"identity" : "swift-snapshot-testing",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
|
||||
"location" : "https://github.com/pointfreeco/swift-snapshot-testing.git",
|
||||
"state" : {
|
||||
"revision" : "cef5b3f6f11781dd4591bdd1dd0a3d22bd609334",
|
||||
"version" : "1.11.0"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"action_save" = "Save";
|
||||
"action_search" = "Search";
|
||||
"action_send" = "Send";
|
||||
"action_share_link" = "Share link";
|
||||
"action_share_link" = "Share";
|
||||
"action_skip" = "Skip";
|
||||
"action_start" = "Start";
|
||||
"action_start_chat" = "Start chat";
|
||||
@@ -47,6 +47,7 @@
|
||||
"common_about" = "About";
|
||||
"common_audio" = "Audio";
|
||||
"common_bubbles" = "Bubbles";
|
||||
"common_creating_room" = "Creating room…";
|
||||
"common_decryption_error" = "Decryption error";
|
||||
"common_developer_options" = "Developer options";
|
||||
"common_edited_suffix" = "(edited)";
|
||||
@@ -104,6 +105,7 @@
|
||||
"emoji_picker_category_symbols" = "Symbols";
|
||||
"error_failed_creating_the_permalink" = "Failed creating the permalink";
|
||||
"error_failed_loading_messages" = "Failed loading messages";
|
||||
"error_no_compatible_app_found" = "No compatible app was found to handle this action.";
|
||||
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
|
||||
"error_unknown" = "Sorry, an error occurred";
|
||||
"invite_friends_text" = "Hey, talk to me on %1$@: %2$@";
|
||||
@@ -154,6 +156,12 @@
|
||||
"screen_create_room_action_create_room" = "New room";
|
||||
"screen_create_room_action_invite_people" = "Invite people";
|
||||
"screen_create_room_add_people_title" = "Add people";
|
||||
"screen_dm_details_block_alert_action" = "Block";
|
||||
"screen_dm_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
"screen_dm_details_block_user" = "Block user";
|
||||
"screen_dm_details_unblock_alert_action" = "Unblock";
|
||||
"screen_dm_details_unblock_alert_description" = "On unblocking the user, you will be able to see all messages by them again.";
|
||||
"screen_dm_details_unblock_user" = "Unblock user";
|
||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
||||
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
||||
@@ -174,6 +182,7 @@
|
||||
"screen_room_details_leave_room_title" = "Leave room";
|
||||
"screen_room_details_people_title" = "People";
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_share_room_title" = "Share room";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_member_details_block_alert_action" = "Block";
|
||||
"screen_room_member_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"action_save" = "Save";
|
||||
"action_search" = "Search";
|
||||
"action_send" = "Send";
|
||||
"action_share_link" = "Share link";
|
||||
"action_share_link" = "Share";
|
||||
"action_skip" = "Skip";
|
||||
"action_start" = "Start";
|
||||
"action_start_chat" = "Start chat";
|
||||
@@ -47,6 +47,7 @@
|
||||
"common_about" = "About";
|
||||
"common_audio" = "Audio";
|
||||
"common_bubbles" = "Bubbles";
|
||||
"common_creating_room" = "Creating room…";
|
||||
"common_decryption_error" = "Decryption error";
|
||||
"common_developer_options" = "Developer options";
|
||||
"common_edited_suffix" = "(edited)";
|
||||
@@ -104,6 +105,7 @@
|
||||
"emoji_picker_category_symbols" = "Symbols";
|
||||
"error_failed_creating_the_permalink" = "Failed creating the permalink";
|
||||
"error_failed_loading_messages" = "Failed loading messages";
|
||||
"error_no_compatible_app_found" = "No compatible app was found to handle this action.";
|
||||
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
|
||||
"error_unknown" = "Sorry, an error occurred";
|
||||
"invite_friends_text" = "Hey, talk to me on %1$@: %2$@";
|
||||
@@ -154,6 +156,12 @@
|
||||
"screen_create_room_action_create_room" = "New room";
|
||||
"screen_create_room_action_invite_people" = "Invite people";
|
||||
"screen_create_room_add_people_title" = "Add people";
|
||||
"screen_dm_details_block_alert_action" = "Block";
|
||||
"screen_dm_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
"screen_dm_details_block_user" = "Block user";
|
||||
"screen_dm_details_unblock_alert_action" = "Unblock";
|
||||
"screen_dm_details_unblock_alert_description" = "On unblocking the user, you will be able to see all messages by them again.";
|
||||
"screen_dm_details_unblock_user" = "Unblock user";
|
||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
||||
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
||||
@@ -174,6 +182,7 @@
|
||||
"screen_room_details_leave_room_title" = "Leave room";
|
||||
"screen_room_details_people_title" = "People";
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_share_room_title" = "Share room";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_member_details_block_alert_action" = "Block";
|
||||
"screen_room_member_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
|
||||
@@ -34,5 +34,21 @@
|
||||
<string>%1$d room changes</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>screen_room_member_list_header_title</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@COUNT@</string>
|
||||
<key>COUNT</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>1 person</string>
|
||||
<key>other</key>
|
||||
<string>%1$d people</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -37,7 +37,7 @@
|
||||
"action_save" = "Save";
|
||||
"action_search" = "Search";
|
||||
"action_send" = "Send";
|
||||
"action_share_link" = "Share link";
|
||||
"action_share_link" = "Share";
|
||||
"action_skip" = "Skip";
|
||||
"action_start" = "Start";
|
||||
"action_start_chat" = "Start chat";
|
||||
@@ -47,6 +47,7 @@
|
||||
"common_about" = "About";
|
||||
"common_audio" = "Audio";
|
||||
"common_bubbles" = "Bubbles";
|
||||
"common_creating_room" = "Creating room…";
|
||||
"common_decryption_error" = "Decryption error";
|
||||
"common_developer_options" = "Developer options";
|
||||
"common_edited_suffix" = "(edited)";
|
||||
@@ -104,6 +105,7 @@
|
||||
"emoji_picker_category_symbols" = "Symbols";
|
||||
"error_failed_creating_the_permalink" = "Failed creating the permalink";
|
||||
"error_failed_loading_messages" = "Failed loading messages";
|
||||
"error_no_compatible_app_found" = "No compatible app was found to handle this action.";
|
||||
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
|
||||
"error_unknown" = "Sorry, an error occurred";
|
||||
"invite_friends_text" = "Hey, talk to me on %1$@: %2$@";
|
||||
@@ -154,6 +156,12 @@
|
||||
"screen_create_room_action_create_room" = "New room";
|
||||
"screen_create_room_action_invite_people" = "Invite people";
|
||||
"screen_create_room_add_people_title" = "Add people";
|
||||
"screen_dm_details_block_alert_action" = "Block";
|
||||
"screen_dm_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
"screen_dm_details_block_user" = "Block user";
|
||||
"screen_dm_details_unblock_alert_action" = "Unblock";
|
||||
"screen_dm_details_unblock_alert_description" = "On unblocking the user, you will be able to see all messages by them again.";
|
||||
"screen_dm_details_unblock_user" = "Unblock user";
|
||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
||||
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
||||
@@ -174,6 +182,7 @@
|
||||
"screen_room_details_leave_room_title" = "Leave room";
|
||||
"screen_room_details_people_title" = "People";
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_share_room_title" = "Share room";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_member_details_block_alert_action" = "Block";
|
||||
"screen_room_member_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
"Notification" = "NOTIFICA";
|
||||
"a11y_hide_password" = "Nascondi password";
|
||||
"a11y_send_files" = "Send files";
|
||||
"a11y_show_password" = "Show password";
|
||||
"a11y_user_menu" = "User menu";
|
||||
"action_back" = "Back";
|
||||
"action_cancel" = "Cancel";
|
||||
"action_clear" = "Clear";
|
||||
"action_close" = "Close";
|
||||
"action_complete_verification" = "Complete verification";
|
||||
"action_confirm" = "Confirm";
|
||||
"action_continue" = "Continue";
|
||||
"action_copy" = "Copy";
|
||||
"action_copy_link" = "Copy link";
|
||||
"action_create_a_room" = "Create a room";
|
||||
"action_disable" = "Disable";
|
||||
"action_done" = "Done";
|
||||
"action_edit" = "Edit";
|
||||
"action_enable" = "Enable";
|
||||
"action_invite" = "Invite";
|
||||
"action_invite_friends_to_app" = "Invite friends to %1$@";
|
||||
"action_learn_more" = "Learn more";
|
||||
"action_leave" = "Leave";
|
||||
"action_leave_room" = "Leave room";
|
||||
"action_next" = "Next";
|
||||
"action_no" = "No";
|
||||
"action_not_now" = "Not now";
|
||||
"action_ok" = "OK";
|
||||
"action_quick_reply" = "Quick reply";
|
||||
"action_quote" = "Quote";
|
||||
"action_remove" = "Remove";
|
||||
"action_reply" = "Reply";
|
||||
"action_report_bug" = "Report bug";
|
||||
"action_report_content" = "Report Content";
|
||||
"action_retry" = "Retry";
|
||||
"action_retry_decryption" = "Retry decryption";
|
||||
"action_save" = "Save";
|
||||
"action_search" = "Search";
|
||||
"action_send" = "Send";
|
||||
"action_share_link" = "Share";
|
||||
"action_skip" = "Skip";
|
||||
"action_start" = "Start";
|
||||
"action_start_chat" = "Start chat";
|
||||
"action_start_verification" = "Start verification";
|
||||
"action_view_source" = "View Source";
|
||||
"action_yes" = "Yes";
|
||||
"common_about" = "About";
|
||||
"common_audio" = "Audio";
|
||||
"common_bubbles" = "Bubbles";
|
||||
"common_creating_room" = "Creating room…";
|
||||
"common_decryption_error" = "Decryption error";
|
||||
"common_developer_options" = "Developer options";
|
||||
"common_edited_suffix" = "(edited)";
|
||||
"common_editing" = "Editing";
|
||||
"common_encryption_enabled" = "Encryption enabled";
|
||||
"common_error" = "Error";
|
||||
"common_file" = "File";
|
||||
"common_gif" = "GIF";
|
||||
"common_image" = "Image";
|
||||
"common_link_copied_to_clipboard" = "Link copied to clipboard";
|
||||
"common_loading" = "Loading…";
|
||||
"common_message" = "Message";
|
||||
"common_message_layout" = "Message layout";
|
||||
"common_message_removed" = "Message removed";
|
||||
"common_modern" = "Modern";
|
||||
"common_no_results" = "No results";
|
||||
"common_offline" = "Offline";
|
||||
"common_password" = "Password";
|
||||
"common_people" = "People";
|
||||
"common_permalink" = "Permalink";
|
||||
"common_reactions" = "Reactions";
|
||||
"common_replying_to" = "Replying to %1$@";
|
||||
"common_report_a_bug" = "Report a bug";
|
||||
"common_report_submitted" = "Report submitted";
|
||||
"common_search_for_someone" = "Search for someone";
|
||||
"common_security" = "Security";
|
||||
"common_select_your_server" = "Select your server";
|
||||
"common_sending" = "Sending…";
|
||||
"common_server_not_supported" = "Server not supported";
|
||||
"common_server_url" = "Server URL";
|
||||
"common_settings" = "Settings";
|
||||
"common_sticker" = "Sticker";
|
||||
"common_success" = "Success";
|
||||
"common_suggestions" = "Suggestions";
|
||||
"common_topic" = "Topic";
|
||||
"common_unable_to_decrypt" = "Unable to decrypt";
|
||||
"common_unsupported_event" = "Unsupported event";
|
||||
"common_username" = "Username";
|
||||
"common_verification_cancelled" = "Verification cancelled";
|
||||
"common_verification_complete" = "Verification complete";
|
||||
"common_video" = "Video";
|
||||
"common_waiting" = "Waiting…";
|
||||
"crash_detection_dialog_content" = "%1$@ crashed the last time it was used. Would you like to share a crash report with us?";
|
||||
"dialog_title_confirmation" = "Confirmation";
|
||||
"dialog_title_error" = "Error";
|
||||
"dialog_title_success" = "Success";
|
||||
"dialog_title_warning" = "Warning";
|
||||
"emoji_picker_category_activity" = "Activities";
|
||||
"emoji_picker_category_flags" = "Flags";
|
||||
"emoji_picker_category_foods" = "Food & Drink";
|
||||
"emoji_picker_category_nature" = "Animals & Nature";
|
||||
"emoji_picker_category_objects" = "Objects";
|
||||
"emoji_picker_category_people" = "Smileys & People";
|
||||
"emoji_picker_category_places" = "Travel & Places";
|
||||
"emoji_picker_category_symbols" = "Symbols";
|
||||
"error_failed_creating_the_permalink" = "Failed creating the permalink";
|
||||
"error_failed_loading_messages" = "Failed loading messages";
|
||||
"error_no_compatible_app_found" = "No compatible app was found to handle this action.";
|
||||
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
|
||||
"error_unknown" = "Sorry, an error occurred";
|
||||
"invite_friends_text" = "Hey, talk to me on %1$@: %2$@";
|
||||
"leave_room_alert_empty_subtitle" = "Are you sure that you want to leave this room? You are the only person here. If you leave, no one will be able to join in the future, including you.";
|
||||
"leave_room_alert_private_subtitle" = "Are you sure that you want to leave this room? This room is not public and you will not be able to rejoin without an invite.";
|
||||
"leave_room_alert_subtitle" = "Are you sure that you want to leave the room?";
|
||||
"login_initial_device_name_ios" = "%1$@ iOS";
|
||||
"preference_rageshake" = "Rageshake to report bug";
|
||||
"rageshake_detection_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
|
||||
"rageshake_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
|
||||
"report_content_explanation" = "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages.";
|
||||
"report_content_hint" = "Reason for reporting this content";
|
||||
"rich_text_editor_bullet_list" = "Toggle bullet list";
|
||||
"rich_text_editor_code_block" = "Toggle code block";
|
||||
"rich_text_editor_composer_placeholder" = "Message…";
|
||||
"rich_text_editor_format_bold" = "Apply bold format";
|
||||
"rich_text_editor_format_italic" = "Apply italic format";
|
||||
"rich_text_editor_format_strikethrough" = "Apply strikethrough format";
|
||||
"rich_text_editor_format_underline" = "Apply underline format";
|
||||
"rich_text_editor_full_screen_toggle" = "Toggle full screen mode";
|
||||
"rich_text_editor_indent" = "Indent";
|
||||
"rich_text_editor_inline_code" = "Apply inline code format";
|
||||
"rich_text_editor_link" = "Set link";
|
||||
"rich_text_editor_numbered_list" = "Toggle numbered list";
|
||||
"rich_text_editor_quote" = "Toggle quote";
|
||||
"rich_text_editor_unindent" = "Unindent";
|
||||
"room_timeline_beginning_of_room" = "This is the beginning of %1$@.";
|
||||
"room_timeline_beginning_of_room_no_name" = "This is the beginning of this conversation.";
|
||||
"room_timeline_read_marker_title" = "New";
|
||||
"screen_bug_report_attach_screenshot" = "Attach screenshot";
|
||||
"screen_bug_report_contact_me" = "You may contact me if you have any follow up questions";
|
||||
"screen_bug_report_edit_screenshot" = "Edit screenshot";
|
||||
"screen_bug_report_editor_description" = "Please describe the bug. What did you do? What did you expect to happen? What actually happened. Please go into as much detail as you can.";
|
||||
"screen_bug_report_editor_placeholder" = "Describe the bug…";
|
||||
"screen_bug_report_editor_supporting" = "If possible, please write the description in English.";
|
||||
"screen_bug_report_include_crash_logs" = "Send crash logs";
|
||||
"screen_bug_report_include_logs" = "Send logs to help";
|
||||
"screen_bug_report_include_screenshot" = "Send screenshot";
|
||||
"screen_bug_report_logs_description" = "To check things work as intended, logs will be sent with your message. These will be private. To just send your message, turn off this setting.";
|
||||
"screen_bug_report_rash_logs_alert_title" = "%1$@ crashed the last time it was used. Would you like to share a crash report with us?";
|
||||
"screen_change_server_error_invalid_homeserver" = "We couldn't reach this homeserver. Please check that you have entered the homeserver URL correctly. If the URL is correct, contact your homeserver administrator for further help.";
|
||||
"screen_change_server_error_no_sliding_sync_message" = "This server currently doesn’t support sliding sync.";
|
||||
"screen_change_server_form_header" = "Homeserver URL";
|
||||
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
|
||||
"screen_change_server_submit" = "Continue";
|
||||
"screen_change_server_subtitle" = "What is the address of your server?";
|
||||
"screen_change_server_title" = "Select your server";
|
||||
"screen_create_room_action_create_room" = "New room";
|
||||
"screen_create_room_action_invite_people" = "Invite people";
|
||||
"screen_create_room_add_people_title" = "Add people";
|
||||
"screen_dm_details_block_alert_action" = "Block";
|
||||
"screen_dm_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
"screen_dm_details_block_user" = "Block user";
|
||||
"screen_dm_details_unblock_alert_action" = "Unblock";
|
||||
"screen_dm_details_unblock_alert_description" = "On unblocking the user, you will be able to see all messages by them again.";
|
||||
"screen_dm_details_unblock_user" = "Unblock user";
|
||||
"screen_login_error_deactivated_account" = "This account has been deactivated.";
|
||||
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
|
||||
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
|
||||
"screen_login_error_unsupported_authentication" = "The selected homeserver doesn't support password or OIDC login. Please contact your admin or choose another homeserver.";
|
||||
"screen_login_form_header" = "Enter your details";
|
||||
"screen_login_password_hint" = "Password";
|
||||
"screen_login_server_header" = "Where your conversations live";
|
||||
"screen_login_submit" = "Continue";
|
||||
"screen_login_title" = "Welcome back!";
|
||||
"screen_login_username_hint" = "Username";
|
||||
"screen_onboarding_welcome_subtitle" = "Welcome to the %1$@ Beta. Supercharged, for speed and simplicity.";
|
||||
"screen_onboarding_welcome_title" = "Be in your Element";
|
||||
"screen_report_content_block_user" = "Block user";
|
||||
"screen_report_content_block_user_hint" = "Check if you want to hide all current and future messages from this user";
|
||||
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
|
||||
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
|
||||
"screen_room_details_invite_people_title" = "Invite people";
|
||||
"screen_room_details_leave_room_title" = "Leave room";
|
||||
"screen_room_details_people_title" = "People";
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_share_room_title" = "Share room";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_member_details_block_alert_action" = "Block";
|
||||
"screen_room_member_details_block_alert_description" = "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.";
|
||||
"screen_room_member_details_block_user" = "Block user";
|
||||
"screen_room_member_details_unblock_alert_action" = "Unblock";
|
||||
"screen_room_member_details_unblock_alert_description" = "On unblocking the user, you will be able to see all messages by them again.";
|
||||
"screen_room_member_details_unblock_user" = "Unblock user";
|
||||
"screen_roomlist_a11y_create_message" = "Create a new conversation or room";
|
||||
"screen_roomlist_main_space_title" = "All Chats";
|
||||
"screen_session_verification_cancelled_subtitle" = "Something doesn’t seem right. Either the request timed out or the request was denied.";
|
||||
"screen_session_verification_cancelled_title" = "Verification cancelled";
|
||||
"screen_session_verification_compare_emojis_subtitle" = "Confirm that the emojis below match those shown on your other session.";
|
||||
"screen_session_verification_compare_emojis_title" = "Compare emojis";
|
||||
"screen_session_verification_complete_subtitle" = "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.";
|
||||
"screen_session_verification_open_existing_session_subtitle" = "Prove it’s you in order to access your encrypted message history.";
|
||||
"screen_session_verification_open_existing_session_title" = "Open an existing session";
|
||||
"screen_session_verification_positive_button_canceled" = "Retry verification";
|
||||
"screen_session_verification_positive_button_initial" = "I am ready";
|
||||
"screen_session_verification_positive_button_ready" = "Start";
|
||||
"screen_session_verification_positive_button_verifying_ongoing" = "Waiting to match";
|
||||
"screen_session_verification_request_accepted_subtitle" = "Compare the unique emoji, ensuring they appear in the same order.";
|
||||
"screen_session_verification_they_dont_match" = "They don’t match";
|
||||
"screen_session_verification_they_match" = "They match";
|
||||
"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_signout_confirmation_dialog_content" = "Are you sure you want to sign out?";
|
||||
"screen_signout_confirmation_dialog_submit" = "Sign out";
|
||||
"screen_signout_confirmation_dialog_title" = "Sign out";
|
||||
"screen_signout_in_progress_dialog_content" = "Signing out…";
|
||||
"screen_signout_preference_item" = "Sign out";
|
||||
"screen_start_chat_error_starting_chat" = "An error occurred when trying to start a chat";
|
||||
"session_verification_banner_message" = "Looks like you’re using a new device. Verify it’s you to access your encrypted messages.";
|
||||
"session_verification_banner_title" = "Access your message history";
|
||||
"settings_rageshake" = "Rageshake";
|
||||
"settings_rageshake_detection_threshold" = "Detection threshold";
|
||||
"settings_title_general" = "General";
|
||||
"settings_version_number" = "Version: %1$@ (%2$@)";
|
||||
"state_event_avatar_changed_too" = "(avatar was changed too)";
|
||||
"state_event_avatar_url_changed" = "%1$@ changed their avatar";
|
||||
"state_event_avatar_url_changed_by_you" = "You changed your avatar";
|
||||
"state_event_display_name_changed_from" = "%1$@ changed their display name from %2$@ to %3$@";
|
||||
"state_event_display_name_changed_from_by_you" = "You changed your display name from %1$@ to %2$@";
|
||||
"state_event_display_name_removed" = "%1$@ removed their display name (it was %2$@)";
|
||||
"state_event_display_name_removed_by_you" = "You removed your display name (it was %1$@)";
|
||||
"state_event_display_name_set" = "%1$@ set their display name to %2$@";
|
||||
"state_event_display_name_set_by_you" = "You set your display name to %1$@";
|
||||
"state_event_room_avatar_changed" = "%1$@ changed the room avatar";
|
||||
"state_event_room_avatar_changed_by_you" = "You changed the room avatar";
|
||||
"state_event_room_avatar_removed" = "%1$@ removed the room avatar";
|
||||
"state_event_room_avatar_removed_by_you" = "You removed the room avatar";
|
||||
"state_event_room_ban" = "%1$@ banned %2$@";
|
||||
"state_event_room_ban_by_you" = "You banned %1$@";
|
||||
"state_event_room_created" = "%1$@ created the room";
|
||||
"state_event_room_created_by_you" = "You created the room";
|
||||
"state_event_room_invite" = "%1$@ invited %2$@";
|
||||
"state_event_room_invite_accepted" = "%1$@ accepted the invite";
|
||||
"state_event_room_invite_accepted_by_you" = "You accepted the invite";
|
||||
"state_event_room_invite_by_you" = "You invited %1$@";
|
||||
"state_event_room_invite_you" = "%1$@ invited you";
|
||||
"state_event_room_join" = "%1$@ joined the room";
|
||||
"state_event_room_join_by_you" = "You joined the room";
|
||||
"state_event_room_knock" = "%1$@ requested to join";
|
||||
"state_event_room_knock_accepted" = "%1$@ allowed %2$@ to join";
|
||||
"state_event_room_knock_accepted_by_you" = "%1$@ allowed you to join";
|
||||
"state_event_room_knock_by_you" = "You requested to join";
|
||||
"state_event_room_knock_denied" = "%1$@ rejected %2$@'s request to join";
|
||||
"state_event_room_knock_denied_by_you" = "You rejected %1$@'s request to join";
|
||||
"state_event_room_knock_denied_you" = "%1$@ rejected your request to join";
|
||||
"state_event_room_knock_retracted" = "%1$@ is no longer interested in joining";
|
||||
"state_event_room_knock_retracted_by_you" = "You cancelled your request to join";
|
||||
"state_event_room_leave" = "%1$@ left the room";
|
||||
"state_event_room_leave_by_you" = "You left the room";
|
||||
"state_event_room_name_changed" = "%1$@ changed the room name to: %2$@";
|
||||
"state_event_room_name_changed_by_you" = "You changed the room name to: %1$@";
|
||||
"state_event_room_name_removed" = "%1$@ removed the room name";
|
||||
"state_event_room_name_removed_by_you" = "You removed the room name";
|
||||
"state_event_room_reject" = "%1$@ rejected the invitation";
|
||||
"state_event_room_reject_by_you" = "You rejected the invitation";
|
||||
"state_event_room_remove" = "%1$@ removed %2$@";
|
||||
"state_event_room_remove_by_you" = "You removed %1$@";
|
||||
"state_event_room_third_party_invite" = "%1$@ sent an invitation to %2$@ to join the room";
|
||||
"state_event_room_third_party_invite_by_you" = "You sent an invitation to %1$@ to join the room";
|
||||
"state_event_room_third_party_revoked_invite" = "%1$@ revoked the invitation for %2$@ to join the room";
|
||||
"state_event_room_third_party_revoked_invite_by_you" = "You revoked the invitation for %1$@ to join the room";
|
||||
"state_event_room_topic_changed" = "%1$@ changed the topic to: %2$@";
|
||||
"state_event_room_topic_changed_by_you" = "You changed the topic to: %1$@";
|
||||
"state_event_room_topic_removed" = "%1$@ removed the room topic";
|
||||
"state_event_room_topic_removed_by_you" = "You removed the room topic";
|
||||
"state_event_room_unban" = "%1$@ unbanned %2$@";
|
||||
"state_event_room_unban_by_you" = "You unbanned %1$@";
|
||||
"state_event_room_unknown_membership_change" = "%1$@ made an unknown change to their membership";
|
||||
@@ -0,0 +1,276 @@
|
||||
"Notification" = "Notificare";
|
||||
"a11y_hide_password" = "Ascunde parola";
|
||||
"a11y_send_files" = "Trimite fișiere";
|
||||
"a11y_show_password" = "Arata parola";
|
||||
"a11y_user_menu" = "Meniu utilizator";
|
||||
"action_back" = "Înapoi";
|
||||
"action_cancel" = "Anulează";
|
||||
"action_clear" = "Șterge";
|
||||
"action_close" = "Închide";
|
||||
"action_complete_verification" = "Verificare completă";
|
||||
"action_confirm" = "Confirmă";
|
||||
"action_continue" = "Continua";
|
||||
"action_copy" = "Copiază";
|
||||
"action_copy_link" = "Copiază linkul";
|
||||
"action_create_a_room" = "Crează o cameră";
|
||||
"action_disable" = "Dezactivează";
|
||||
"action_done" = "Gata";
|
||||
"action_edit" = "Editează";
|
||||
"action_enable" = "Activează";
|
||||
"action_invite" = "Invită";
|
||||
"action_invite_friends_to_app" = "Invită prieteni în %1$@";
|
||||
"action_learn_more" = "Află mai multe";
|
||||
"action_leave" = "Părăsește";
|
||||
"action_leave_room" = "Părăsește camera";
|
||||
"action_next" = "Următorul";
|
||||
"action_no" = "Nu";
|
||||
"action_not_now" = "Nu acum";
|
||||
"action_ok" = "OK";
|
||||
"action_quick_reply" = "Raspuns rapid";
|
||||
"action_quote" = "Citează";
|
||||
"action_remove" = "Șterge";
|
||||
"action_reply" = "Răspunde";
|
||||
"action_report_bug" = "Raportează o eroare";
|
||||
"action_report_content" = "Raportează conținutul";
|
||||
"action_retry" = "Reîncearcă";
|
||||
"action_retry_decryption" = "Reîncearcă decriptarea";
|
||||
"action_save" = "Salvează";
|
||||
"action_search" = "Caută";
|
||||
"action_send" = "Trimite";
|
||||
"action_share_link" = "Partajează linkul";
|
||||
"action_skip" = "Omite";
|
||||
"action_start" = "Începe";
|
||||
"action_start_chat" = "Începe discuția";
|
||||
"action_start_verification" = "Începe verificarea";
|
||||
"action_view_source" = "Vezi sursa";
|
||||
"action_yes" = "Da";
|
||||
"common_about" = "Despre";
|
||||
"common_audio" = "Audio";
|
||||
"common_bubbles" = "Baloane";
|
||||
"common_creating_room" = "Se creează camera…";
|
||||
"common_decryption_error" = "Eroare de decriptare";
|
||||
"common_developer_options" = "Opțiuni programator";
|
||||
"common_edited_suffix" = "(editat)";
|
||||
"common_editing" = "Editare";
|
||||
"common_encryption_enabled" = "Criptare activată";
|
||||
"common_error" = "Eroare";
|
||||
"common_file" = "Fişier";
|
||||
"common_gif" = "GIF";
|
||||
"common_image" = "Imagine";
|
||||
"common_link_copied_to_clipboard" = "Linkul a fost copiat în clipboard";
|
||||
"common_loading" = "Se încarcă…";
|
||||
"common_message" = "Mesaj";
|
||||
"common_message_layout" = "Aranjamentul mesajelor";
|
||||
"common_message_removed" = "Mesaj sters";
|
||||
"common_modern" = "Modern";
|
||||
"common_no_results" = "Niciun rezultat";
|
||||
"common_offline" = "Deconectat";
|
||||
"common_password" = "Parola";
|
||||
"common_people" = "Persoane";
|
||||
"common_permalink" = "Permalink";
|
||||
"common_reactions" = "Reacții";
|
||||
"common_replying_to" = "Răspuns pentru %1$@";
|
||||
"common_report_a_bug" = "Raporteaza o eroare";
|
||||
"common_report_submitted" = "Raport trimis";
|
||||
"common_search_for_someone" = "Caută pe cineva";
|
||||
"common_security" = "Securitate";
|
||||
"common_select_your_server" = "Selectează serverul";
|
||||
"common_sending" = "Se trimite…";
|
||||
"common_server_not_supported" = "Serverul nu este compatibil";
|
||||
"common_server_url" = "Adresa URL a serverului";
|
||||
"common_settings" = "Setări";
|
||||
"common_sticker" = "Autocolant";
|
||||
"common_success" = "Succes";
|
||||
"common_suggestions" = "Sugestii";
|
||||
"common_topic" = "Subiect";
|
||||
"common_unable_to_decrypt" = "Nu s-a putut decripta";
|
||||
"common_unsupported_event" = "Eveniment neacceptat";
|
||||
"common_username" = "Utilizator";
|
||||
"common_verification_cancelled" = "Verificare anulată";
|
||||
"common_verification_complete" = "Verificare completă";
|
||||
"common_video" = "Video";
|
||||
"common_waiting" = "Se aşteaptă…";
|
||||
"crash_detection_dialog_content" = "%1$@ s-a blocat ultima dată când a fost folosit. Dorești să ne trimiti un raport?";
|
||||
"dialog_title_confirmation" = "Confirmare";
|
||||
"dialog_title_error" = "Eroare";
|
||||
"dialog_title_success" = "Succes";
|
||||
"dialog_title_warning" = "Avertisment";
|
||||
"emoji_picker_category_activity" = "Activități";
|
||||
"emoji_picker_category_flags" = "Steaguri";
|
||||
"emoji_picker_category_foods" = "Mâncare & Băutură";
|
||||
"emoji_picker_category_nature" = "Animale și Natură";
|
||||
"emoji_picker_category_objects" = "Obiecte";
|
||||
"emoji_picker_category_people" = "Fețe zâmbitoare & Oameni";
|
||||
"emoji_picker_category_places" = "Călătorii & Locuri";
|
||||
"emoji_picker_category_symbols" = "Simboluri";
|
||||
"error_failed_creating_the_permalink" = "Crearea permalink-ului a eșuat";
|
||||
"error_failed_loading_messages" = "Încărcarea mesajelor a eșuat";
|
||||
"error_no_compatible_app_found" = "Nu a fost găsită nicio aplicație capabilă să gestioneze această acțiune.";
|
||||
"error_some_messages_have_not_been_sent" = "Unele mesaje nu au fost trimise";
|
||||
"error_unknown" = "Ne pare rău, a apărut o eroare";
|
||||
"invite_friends_text" = "Hei, vorbește cu mine pe %1$@: %2$@";
|
||||
"leave_room_alert_empty_subtitle" = "Ești sigur că vrei să părăsești această cameră? Ești singura persoană de aici. Dacă o părasești, nimeni nu se va mai putea alătura în viitor, inclusiv tu.";
|
||||
"leave_room_alert_private_subtitle" = "Ești sigur că vrei să părăsești această cameră? Această cameră nu este publică și nu te vei putea alătura din nou fără o invitație.";
|
||||
"leave_room_alert_subtitle" = "Ești sigur că vrei să părăsești camera?";
|
||||
"login_initial_device_name_ios" = "%1$@ iOS";
|
||||
"preference_rageshake" = "Rageshake pentru a raporta erori";
|
||||
"rageshake_detection_dialog_content" = "Se pare că scuturi telefonul de frustrare. Dorești să deschizi ecranul de raportare a unei erori?";
|
||||
"rageshake_dialog_content" = "Se pare că scuturi telefonul de frustrare. Dorești să deschizi ecranul de raportare a unei erori?";
|
||||
"report_content_explanation" = "Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat.";
|
||||
"report_content_hint" = "Motivul raportării acestui conținut";
|
||||
"rich_text_editor_bullet_list" = "Comutează lista cu puncte";
|
||||
"rich_text_editor_code_block" = "Comutează blocul de cod";
|
||||
"rich_text_editor_composer_placeholder" = "Mesaj…";
|
||||
"rich_text_editor_format_bold" = "Aplică formatul aldin";
|
||||
"rich_text_editor_format_italic" = "Aplică formatul italic";
|
||||
"rich_text_editor_format_strikethrough" = "Aplică formatul barat";
|
||||
"rich_text_editor_format_underline" = "Aplică formatul de subliniere";
|
||||
"rich_text_editor_full_screen_toggle" = "Comută modul ecran intreg";
|
||||
"rich_text_editor_indent" = "Indentare";
|
||||
"rich_text_editor_inline_code" = "Aplică formatul de cod in linie";
|
||||
"rich_text_editor_link" = "Setați linkul";
|
||||
"rich_text_editor_numbered_list" = "Comută lista numerotată";
|
||||
"rich_text_editor_quote" = "Aplică citatul";
|
||||
"rich_text_editor_unindent" = "Dez-identare";
|
||||
"room_timeline_beginning_of_room" = "Acesta este începutul conversației %1$@.";
|
||||
"room_timeline_beginning_of_room_no_name" = "Acesta este începutul acestei conversații.";
|
||||
"room_timeline_read_marker_title" = "Nou";
|
||||
"screen_bug_report_attach_screenshot" = "Atașează o captură de ecran";
|
||||
"screen_bug_report_contact_me" = "Puteți să mă contactați dacă aveți întrebări suplimentare";
|
||||
"screen_bug_report_edit_screenshot" = "Editează captura de ecran";
|
||||
"screen_bug_report_editor_description" = "Te rugăm să descrii eroarea. Ce-ai făcut? Ce te aşteptai să se întâmple? Ce s-a întâmplat de fapt. Te rugam să intri în cât mai multe detalii cu putință.";
|
||||
"screen_bug_report_editor_placeholder" = "Descrie eroarea...";
|
||||
"screen_bug_report_editor_supporting" = "Dacă posibil, te rugăm să scrii descrierea în engleză.";
|
||||
"screen_bug_report_include_crash_logs" = "Trimite log-uri";
|
||||
"screen_bug_report_include_logs" = "Trimite log-uri pentru a ajuta";
|
||||
"screen_bug_report_include_screenshot" = "Trimite captură de ecran";
|
||||
"screen_bug_report_logs_description" = "Pentru a verifica că lucrurile funcționează conform așteptărilor, log-uri vor fi trimise împreună cu mesajul. Acestea vor fi private. Pentru a trimite doar mesajul, dezactivați această setare.";
|
||||
"screen_bug_report_rash_logs_alert_title" = "%1$@ s-a blocat ultima dată când a fost folosit. Dorești să ne trimiti un raport?";
|
||||
"screen_change_server_error_invalid_homeserver" = "Nu am putut accesa acest homeserver. Te rugăm să verifici că ai introdus corect adresa URL a homeserver-ului. Dacă adresa URL este corectă, contactează administratorul homeserver-ului pentru ajutor suplimentar.";
|
||||
"screen_change_server_error_no_sliding_sync_message" = "Momentan acest server nu oferă suport pentru sliding sync.";
|
||||
"screen_change_server_form_header" = "Adresa URL a homeserver-ului";
|
||||
"screen_change_server_form_notice" = "Te poți conecta numai la un server existent care oferă suport pentru sliding sync. Administratorul homeserver-ului tău va trebui să îl configureze. %1$@";
|
||||
"screen_change_server_submit" = "Continuă";
|
||||
"screen_change_server_subtitle" = "Care este adresa serverului tău?";
|
||||
"screen_change_server_title" = "Selectează serverul";
|
||||
"screen_create_room_action_create_room" = "Cameră nouă";
|
||||
"screen_create_room_action_invite_people" = "Invită persoane";
|
||||
"screen_create_room_add_people_title" = "Adaugă persoane";
|
||||
"screen_dm_details_block_alert_action" = "Blochează";
|
||||
"screen_dm_details_block_alert_description" = "Utilizatorii blocați nu iți vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Poți anula această acțiune oricând.";
|
||||
"screen_dm_details_block_user" = "Blochează utilizatorul";
|
||||
"screen_dm_details_unblock_alert_action" = "Deblochează";
|
||||
"screen_dm_details_unblock_alert_description" = "La deblocarea utilizatorului, vei putea vedea din nou toate mesajele de la acesta.";
|
||||
"screen_dm_details_unblock_user" = "Deblochează utilizatorul";
|
||||
"screen_login_error_deactivated_account" = "Acest cont a fost dezactivat.";
|
||||
"screen_login_error_invalid_credentials" = "Utilizator și/sau parolă incorecte";
|
||||
"screen_login_error_invalid_user_id" = "Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”";
|
||||
"screen_login_error_unsupported_authentication" = "Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver.";
|
||||
"screen_login_form_header" = "Introdu-ți detaliile";
|
||||
"screen_login_password_hint" = "Parolă";
|
||||
"screen_login_server_header" = "Locul unde trăiesc conversațiile tale";
|
||||
"screen_login_submit" = "Continuă";
|
||||
"screen_login_title" = "Bine ai revenit!";
|
||||
"screen_login_username_hint" = "Utilizator";
|
||||
"screen_onboarding_welcome_subtitle" = "Bun venit la versiunea beta a %1$@. Supraalimentat, pentru viteză și simplitate.";
|
||||
"screen_onboarding_welcome_title" = "Fii în Elementul tău";
|
||||
"screen_report_content_block_user" = "Blochează utilizatorul";
|
||||
"screen_report_content_block_user_hint" = "Verifică că dorești să ascunzi toate mesajele curente și viitoare de la acest utilizator";
|
||||
"screen_room_details_encryption_enabled_subtitle" = "Mesajele sunt securizate cu încuietori. Doar tu și destinatarii aveți cheile unice pentru a le debloca.";
|
||||
"screen_room_details_encryption_enabled_title" = "Criptarea mesajelor este activată";
|
||||
"screen_room_details_invite_people_title" = "Invită persoane";
|
||||
"screen_room_details_leave_room_title" = "Părăsește camera";
|
||||
"screen_room_details_people_title" = "Persoane";
|
||||
"screen_room_details_security_title" = "Securitate";
|
||||
"screen_room_details_share_room_title" = "Partajează camera";
|
||||
"screen_room_details_topic_title" = "Subiect";
|
||||
"screen_room_member_details_block_alert_action" = "Blochează";
|
||||
"screen_room_member_details_block_alert_description" = "Utilizatorii blocați nu iți vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Poți anula această acțiune oricând.";
|
||||
"screen_room_member_details_block_user" = "Blocați utilizatorul";
|
||||
"screen_room_member_details_unblock_alert_action" = "Deblochează";
|
||||
"screen_room_member_details_unblock_alert_description" = "La deblocarea utilizatorului, vei putea vedea din nou toate mesajele de la acesta.";
|
||||
"screen_room_member_details_unblock_user" = "Deblochează utilizatorul";
|
||||
"screen_roomlist_a11y_create_message" = "Crează o conversație sau o cameră nouă";
|
||||
"screen_roomlist_main_space_title" = "Toate conversatiile";
|
||||
"screen_session_verification_cancelled_subtitle" = "Ceva nu este în regulă. Fie cererea a expirat, fie a fost respinsă.";
|
||||
"screen_session_verification_cancelled_title" = "Verificare anulată";
|
||||
"screen_session_verification_compare_emojis_subtitle" = "Confirmă că emoticoanele de mai jos se potrivesc cu cele afișate în cealaltă sesiune.";
|
||||
"screen_session_verification_compare_emojis_title" = "Compară emoticoanele";
|
||||
"screen_session_verification_complete_subtitle" = "Noua ta sesiune este acum verificată. Are acces la mesajele tale criptate, iar alți utilizatori îl te vor vedea ca fiind de încredere.";
|
||||
"screen_session_verification_open_existing_session_subtitle" = "Demonstreaza-ți identitatea pentru a accesa istoricul mesajelor tale criptate.";
|
||||
"screen_session_verification_open_existing_session_title" = "Deschide o sesiune existentă";
|
||||
"screen_session_verification_positive_button_canceled" = "Reîncearcă verificarea";
|
||||
"screen_session_verification_positive_button_initial" = "Sunt pregătit";
|
||||
"screen_session_verification_positive_button_ready" = "Începe";
|
||||
"screen_session_verification_positive_button_verifying_ongoing" = "Se așteaptă confirmarea";
|
||||
"screen_session_verification_request_accepted_subtitle" = "Compară emoticoalene asigurându-te că apar în aceeași ordine.";
|
||||
"screen_session_verification_they_dont_match" = "Nu se potrivesc";
|
||||
"screen_session_verification_they_match" = "Se potrivesc";
|
||||
"screen_session_verification_waiting_to_accept_subtitle" = "Acceptă solicitarea de a începe procesul de verificare în cealaltă sesiune pentru a continua.";
|
||||
"screen_session_verification_waiting_to_accept_title" = "Se așteptă acceptarea cererii";
|
||||
"screen_signout_confirmation_dialog_content" = "Ești sigur că vrei să te deconectezi?";
|
||||
"screen_signout_confirmation_dialog_submit" = "Deconectează-te";
|
||||
"screen_signout_confirmation_dialog_title" = "Deconectează-te";
|
||||
"screen_signout_in_progress_dialog_content" = "Deconectare în curs...";
|
||||
"screen_signout_preference_item" = "Deconectează-te";
|
||||
"screen_start_chat_error_starting_chat" = "A apărut o eroare la încercarea începerii conversației";
|
||||
"session_verification_banner_message" = "Se pare că folosești un dispozitiv nou. Verifică-ți identitatea pentru acces la mesajele tale criptate.";
|
||||
"session_verification_banner_title" = "Accesează istoricul mesajelor";
|
||||
"settings_rageshake" = "Rageshake";
|
||||
"settings_rageshake_detection_threshold" = "Prag de detecție";
|
||||
"settings_title_general" = "General";
|
||||
"settings_version_number" = "Versiunea: %1$@ (%2$@)";
|
||||
"state_event_avatar_changed_too" = "(s-a schimbat si avatarul)";
|
||||
"state_event_avatar_url_changed" = "%1$@ și-a schimbat avatarul";
|
||||
"state_event_avatar_url_changed_by_you" = "Ți-ai schimbat avatarul";
|
||||
"state_event_display_name_changed_from" = "%1$@ și-a schimbat numele din %2$@ în %3$@";
|
||||
"state_event_display_name_changed_from_by_you" = "Ți-ai schimbat numele din %1$@ în %2$@";
|
||||
"state_event_display_name_removed" = "%1$@ și-a sters numele (era %2$@)";
|
||||
"state_event_display_name_removed_by_you" = "Ți-ai sters numele (era %1$@)";
|
||||
"state_event_display_name_set" = "%1$@ și-a schimbat numele %2$@";
|
||||
"state_event_display_name_set_by_you" = "Ți-ai schimbat numele în %1$@";
|
||||
"state_event_room_avatar_changed" = "%1$@ a schimbat avatarul camerei";
|
||||
"state_event_room_avatar_changed_by_you" = "Ai schimbat avatarul camerei";
|
||||
"state_event_room_avatar_removed" = "%1$@ a șters avatarul camerei";
|
||||
"state_event_room_avatar_removed_by_you" = "Ai șters avatarul camerei";
|
||||
"state_event_room_ban" = "%1$@ a adăugat o interdicție pentru %2$@";
|
||||
"state_event_room_ban_by_you" = "Ai adăugat o interdicție pentru %1$@";
|
||||
"state_event_room_created" = "%1$@ a creat camera";
|
||||
"state_event_room_created_by_you" = "Ai creat camera";
|
||||
"state_event_room_invite" = "%1$@ l-a invitat pe %2$@";
|
||||
"state_event_room_invite_accepted" = "%1$@ a acceptat invitația";
|
||||
"state_event_room_invite_accepted_by_you" = "Ai acceptat invitația";
|
||||
"state_event_room_invite_by_you" = "L-ai invitat pe %1$@";
|
||||
"state_event_room_invite_you" = "%1$@ te-a invitat";
|
||||
"state_event_room_join" = "%1$@ a intrat în cameră";
|
||||
"state_event_room_join_by_you" = "Ai intrat în cameră";
|
||||
"state_event_room_knock" = "%1$@ a solicitat să se alăture camerei";
|
||||
"state_event_room_knock_accepted" = "%1$@ i-a permis lui %2$@ să se alăture camerei";
|
||||
"state_event_room_knock_accepted_by_you" = "%1$@ ți-a permis să te alături camerei";
|
||||
"state_event_room_knock_by_you" = "Ai solicitat să te alături camerei";
|
||||
"state_event_room_knock_denied" = "%1$@ a respins solicitarea de alăturare a lui %2$@";
|
||||
"state_event_room_knock_denied_by_you" = "Ai respins solicitarea de alăturare a lui %1$@";
|
||||
"state_event_room_knock_denied_you" = "%1$@ a respins cererea ta de alăturare";
|
||||
"state_event_room_knock_retracted" = "%1$@ nu mai este interesat să se alăture camerei";
|
||||
"state_event_room_knock_retracted_by_you" = "Ți-ai anulat cererea de alăturare";
|
||||
"state_event_room_leave" = "%1$@ a părăsit camera";
|
||||
"state_event_room_leave_by_you" = "Ai părăsit camera";
|
||||
"state_event_room_name_changed" = "%1$@ a schimbat numele camerei în: %2$@";
|
||||
"state_event_room_name_changed_by_you" = "Ai schimbat numele camerei în: %1$@";
|
||||
"state_event_room_name_removed" = "%1$@ a sters numele camerei";
|
||||
"state_event_room_name_removed_by_you" = "Ai șters numele camerei";
|
||||
"state_event_room_reject" = "%1$@ a respins invitația";
|
||||
"state_event_room_reject_by_you" = "Ai respins invitația";
|
||||
"state_event_room_remove" = "%1$@ l-a îndepărtat pe %2$@";
|
||||
"state_event_room_remove_by_you" = "L-ai îndepărtat pe %1$@";
|
||||
"state_event_room_third_party_invite" = "%1$@ a trimis o invitație către %2$@ pentru a se alătura camerei";
|
||||
"state_event_room_third_party_invite_by_you" = "Ai trimis o invitație către %1$@ pentru a se alătura camerei";
|
||||
"state_event_room_third_party_revoked_invite" = "%1$@ a revocat invitația pentru %2$@ de a se alătura camerei";
|
||||
"state_event_room_third_party_revoked_invite_by_you" = "Ai revocat invitația pentru %1$@ de a se alătura camerei";
|
||||
"state_event_room_topic_changed" = "%1$@ a schimbat subiectul în: %2$@";
|
||||
"state_event_room_topic_changed_by_you" = "Ai schimbat subiectul în: %1$@";
|
||||
"state_event_room_topic_removed" = "%1$@ a șters subiectul camerei";
|
||||
"state_event_room_topic_removed_by_you" = "Ai șters subiectul camerei";
|
||||
"state_event_room_unban" = "%1$@ a anulat interdicția pentru %2$@";
|
||||
"state_event_room_unban_by_you" = "Ai anulat interdicția pentru %1$@";
|
||||
"state_event_room_unknown_membership_change" = "%1$@ a făcut o modificare necunoscută asupra calității sale de membru";
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>common_member_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@COUNT@</string>
|
||||
<key>COUNT</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%1$d membru</string>
|
||||
<key>few</key>
|
||||
<string></string>
|
||||
<key>other</key>
|
||||
<string>%1$d membri</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>room_timeline_state_changes</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@COUNT@</string>
|
||||
<key>COUNT</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%1$d schimbare a camerii</string>
|
||||
<key>few</key>
|
||||
<string></string>
|
||||
<key>other</key>
|
||||
<string>%1$d schimbări ale camerei</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>screen_room_member_list_header_title</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@COUNT@</string>
|
||||
<key>COUNT</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>o persoană</string>
|
||||
<key>few</key>
|
||||
<string></string>
|
||||
<key>other</key>
|
||||
<string>%1$d persoane</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -88,7 +88,7 @@ public enum L10n {
|
||||
public static var actionSearch: String { return L10n.tr("Localizable", "action_search") }
|
||||
/// Send
|
||||
public static var actionSend: String { return L10n.tr("Localizable", "action_send") }
|
||||
/// Share link
|
||||
/// Share
|
||||
public static var actionShareLink: String { return L10n.tr("Localizable", "action_share_link") }
|
||||
/// Skip
|
||||
public static var actionSkip: String { return L10n.tr("Localizable", "action_skip") }
|
||||
@@ -108,6 +108,8 @@ public enum L10n {
|
||||
public static var commonAudio: String { return L10n.tr("Localizable", "common_audio") }
|
||||
/// Bubbles
|
||||
public static var commonBubbles: String { return L10n.tr("Localizable", "common_bubbles") }
|
||||
/// Creating room…
|
||||
public static var commonCreatingRoom: String { return L10n.tr("Localizable", "common_creating_room") }
|
||||
/// Decryption error
|
||||
public static var commonDecryptionError: String { return L10n.tr("Localizable", "common_decryption_error") }
|
||||
/// Developer options
|
||||
@@ -230,6 +232,8 @@ public enum L10n {
|
||||
public static var errorFailedCreatingThePermalink: String { return L10n.tr("Localizable", "error_failed_creating_the_permalink") }
|
||||
/// Failed loading messages
|
||||
public static var errorFailedLoadingMessages: String { return L10n.tr("Localizable", "error_failed_loading_messages") }
|
||||
/// No compatible app was found to handle this action.
|
||||
public static var errorNoCompatibleAppFound: String { return L10n.tr("Localizable", "error_no_compatible_app_found") }
|
||||
/// Some messages have not been sent
|
||||
public static var errorSomeMessagesHaveNotBeenSent: String { return L10n.tr("Localizable", "error_some_messages_have_not_been_sent") }
|
||||
/// Sorry, an error occurred
|
||||
@@ -346,6 +350,18 @@ public enum L10n {
|
||||
public static var screenCreateRoomActionInvitePeople: String { return L10n.tr("Localizable", "screen_create_room_action_invite_people") }
|
||||
/// Add people
|
||||
public static var screenCreateRoomAddPeopleTitle: String { return L10n.tr("Localizable", "screen_create_room_add_people_title") }
|
||||
/// Block
|
||||
public static var screenDmDetailsBlockAlertAction: String { return L10n.tr("Localizable", "screen_dm_details_block_alert_action") }
|
||||
/// Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.
|
||||
public static var screenDmDetailsBlockAlertDescription: String { return L10n.tr("Localizable", "screen_dm_details_block_alert_description") }
|
||||
/// Block user
|
||||
public static var screenDmDetailsBlockUser: String { return L10n.tr("Localizable", "screen_dm_details_block_user") }
|
||||
/// Unblock
|
||||
public static var screenDmDetailsUnblockAlertAction: String { return L10n.tr("Localizable", "screen_dm_details_unblock_alert_action") }
|
||||
/// On unblocking the user, you will be able to see all messages by them again.
|
||||
public static var screenDmDetailsUnblockAlertDescription: String { return L10n.tr("Localizable", "screen_dm_details_unblock_alert_description") }
|
||||
/// Unblock user
|
||||
public static var screenDmDetailsUnblockUser: String { return L10n.tr("Localizable", "screen_dm_details_unblock_user") }
|
||||
/// This account has been deactivated.
|
||||
public static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
|
||||
/// Incorrect username and/or password
|
||||
@@ -388,6 +404,8 @@ public enum L10n {
|
||||
public static var screenRoomDetailsPeopleTitle: String { return L10n.tr("Localizable", "screen_room_details_people_title") }
|
||||
/// Security
|
||||
public static var screenRoomDetailsSecurityTitle: String { return L10n.tr("Localizable", "screen_room_details_security_title") }
|
||||
/// Share room
|
||||
public static var screenRoomDetailsShareRoomTitle: String { return L10n.tr("Localizable", "screen_room_details_share_room_title") }
|
||||
/// Topic
|
||||
public static var screenRoomDetailsTopicTitle: String { return L10n.tr("Localizable", "screen_room_details_topic_title") }
|
||||
/// Block
|
||||
@@ -402,6 +420,10 @@ public enum L10n {
|
||||
public static var screenRoomMemberDetailsUnblockAlertDescription: String { return L10n.tr("Localizable", "screen_room_member_details_unblock_alert_description") }
|
||||
/// Unblock user
|
||||
public static var screenRoomMemberDetailsUnblockUser: String { return L10n.tr("Localizable", "screen_room_member_details_unblock_user") }
|
||||
/// Plural format key: "%#@COUNT@"
|
||||
public static func screenRoomMemberListHeaderTitle(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "screen_room_member_list_header_title", p1)
|
||||
}
|
||||
/// Create a new conversation or room
|
||||
public static var screenRoomlistA11yCreateMessage: String { return L10n.tr("Localizable", "screen_roomlist_a11y_create_message") }
|
||||
/// All Chats
|
||||
|
||||
@@ -166,6 +166,11 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
var displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> {
|
||||
get { return underlyingMembersPublisher }
|
||||
set(value) { underlyingMembersPublisher = value }
|
||||
}
|
||||
var underlyingMembersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never>!
|
||||
|
||||
//MARK: - loadAvatarURLForUserId
|
||||
|
||||
@@ -410,23 +415,6 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return reportContentReasonReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - members
|
||||
|
||||
var membersCallsCount = 0
|
||||
var membersCalled: Bool {
|
||||
return membersCallsCount > 0
|
||||
}
|
||||
var membersReturnValue: Result<[RoomMemberProxyProtocol], RoomProxyError>!
|
||||
var membersClosure: (() async -> Result<[RoomMemberProxyProtocol], RoomProxyError>)?
|
||||
|
||||
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError> {
|
||||
membersCallsCount += 1
|
||||
if let membersClosure = membersClosure {
|
||||
return await membersClosure()
|
||||
} else {
|
||||
return membersReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - ignoreUser
|
||||
|
||||
var ignoreUserCallsCount = 0
|
||||
@@ -481,6 +469,18 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return leaveRoomReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - updateMembers
|
||||
|
||||
var updateMembersCallsCount = 0
|
||||
var updateMembersCalled: Bool {
|
||||
return updateMembersCallsCount > 0
|
||||
}
|
||||
var updateMembersClosure: (() async -> Void)?
|
||||
|
||||
func updateMembers() async {
|
||||
updateMembersCallsCount += 1
|
||||
await updateMembersClosure?()
|
||||
}
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
struct RoomProxyMockConfiguration {
|
||||
@@ -51,11 +52,12 @@ extension RoomProxyMock {
|
||||
alternativeAliases = configuration.alternativeAliases
|
||||
hasUnreadNotifications = configuration.hasUnreadNotifications
|
||||
|
||||
membersClosure = {
|
||||
if let members = configuration.members {
|
||||
return .success(members)
|
||||
}
|
||||
return .failure(.failedRetrievingMembers)
|
||||
if let members = configuration.members {
|
||||
membersPublisher = Just(members).eraseToAnyPublisher()
|
||||
} else {
|
||||
membersPublisher = Just([]).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
updateMembersClosure = { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ struct A11yIdentifiers {
|
||||
|
||||
struct RoomDetailsScreen {
|
||||
let avatar = "room_details-avatar"
|
||||
let dmAvatar = "room_details-dm_avatar"
|
||||
let people = "room_details-people"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AvatarHeaderView<Footer: View>: View {
|
||||
let avatarUrl: URL?
|
||||
let name: String?
|
||||
let id: String
|
||||
let avatarSize: AvatarSize
|
||||
let imageProvider: ImageProviderProtocol?
|
||||
let subtitle: String?
|
||||
@ViewBuilder var footer: () -> Footer
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8.0) {
|
||||
LoadableAvatarImage(url: avatarUrl,
|
||||
name: name,
|
||||
contentID: id,
|
||||
avatarSize: avatarSize,
|
||||
imageProvider: imageProvider)
|
||||
|
||||
Text(name ?? id)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.element.title1Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
footer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AvatarHeaderView(avatarUrl: URL.picturesDirectory,
|
||||
name: "Test",
|
||||
id: "test",
|
||||
avatarSize: .room(on: .details),
|
||||
imageProvider: MockMediaProvider(),
|
||||
subtitle: "test") {
|
||||
HStack(spacing: 32) {
|
||||
ShareLink(item: "test") {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: "Test"))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,19 +38,65 @@ struct RoomDetailsViewState: BindableState {
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
let permalink: URL?
|
||||
var members: [RoomDetailsMember]
|
||||
var members: [RoomMemberDetails] = []
|
||||
var isProcessingIgnoreRequest = false
|
||||
|
||||
var isLoadingMembers: Bool {
|
||||
members.isEmpty
|
||||
}
|
||||
|
||||
var bindings: RoomDetailsViewStateBindings
|
||||
|
||||
var dmRecipient: RoomMemberDetails?
|
||||
|
||||
private var isDMRoom: Bool {
|
||||
isEncrypted && isDirect && members.count == 2
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomDetailsViewStateBindings {
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let cancelTitle = L10n.actionCancel
|
||||
|
||||
var title: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenDmDetailsBlockUser
|
||||
case .unignore: return L10n.screenDmDetailsUnblockUser
|
||||
}
|
||||
}
|
||||
|
||||
var confirmationTitle: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenDmDetailsBlockAlertAction
|
||||
case .unignore: return L10n.screenDmDetailsUnblockAlertAction
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenDmDetailsBlockAlertDescription
|
||||
case .unignore: return L10n.screenDmDetailsUnblockAlertDescription
|
||||
}
|
||||
}
|
||||
|
||||
var viewAction: RoomDetailsViewAction {
|
||||
switch action {
|
||||
case .ignore: return .ignoreConfirmed
|
||||
case .unignore: return .unignoreConfirmed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomDetailsErrorType>?
|
||||
var leaveRoomAlertItem: LeaveRoomAlertItem?
|
||||
var ignoreUserRoomAlertItem: IgnoreUserAlertItem?
|
||||
}
|
||||
|
||||
struct LeaveRoomAlertItem: AlertItem {
|
||||
@@ -77,21 +123,11 @@ struct LeaveRoomAlertItem: AlertItem {
|
||||
enum RoomDetailsViewAction {
|
||||
case processTapPeople
|
||||
case processTapLeave
|
||||
case processTapIgnore
|
||||
case processTapUnignore
|
||||
case confirmLeave
|
||||
case copyRoomLink
|
||||
}
|
||||
|
||||
struct RoomDetailsMember: Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String?
|
||||
let avatarURL: URL?
|
||||
|
||||
@MainActor
|
||||
init(withProxy proxy: RoomMemberProxyProtocol) {
|
||||
id = proxy.userID
|
||||
name = proxy.displayName
|
||||
avatarURL = proxy.avatarURL
|
||||
}
|
||||
case ignoreConfirmed
|
||||
case unignoreConfirmed
|
||||
}
|
||||
|
||||
enum RoomDetailsErrorType: Hashable {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias RoomDetailsViewModelType = StateStoreViewModel<RoomDetailsViewState, RoomDetailsViewAction>
|
||||
@@ -22,12 +23,27 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private var members: [RoomMemberProxyProtocol] = [] {
|
||||
didSet {
|
||||
state.members = members.map { RoomDetailsMember(withProxy: $0) }
|
||||
state.members = members.map { RoomMemberDetails(withProxy: $0) }
|
||||
if roomProxy.isDirect, roomProxy.isEncrypted, members.count == 2 {
|
||||
dmRecipient = members.first(where: { !$0.isAccountOwner })
|
||||
} else {
|
||||
dmRecipient = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var dmRecipient: RoomMemberProxyProtocol? {
|
||||
didSet {
|
||||
if let dmRecipient {
|
||||
state.dmRecipient = RoomMemberDetails(withProxy: dmRecipient)
|
||||
} else {
|
||||
state.dmRecipient = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var callback: ((RoomDetailsViewModelAction) -> Void)?
|
||||
|
||||
|
||||
init(roomProxy: RoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
@@ -39,18 +55,16 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
topic: roomProxy.topic,
|
||||
avatarURL: roomProxy.avatarURL,
|
||||
permalink: roomProxy.permalink,
|
||||
members: [],
|
||||
bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
|
||||
roomProxy.membersPublisher.sink { [weak self] members in
|
||||
self?.members = members
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
Task {
|
||||
switch await roomProxy.members() {
|
||||
case .success(let members):
|
||||
self.members = members
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed retrieving room members: \(error)")
|
||||
state.bindings.alertInfo = AlertInfo(id: .alert(L10n.errorUnknown))
|
||||
}
|
||||
await roomProxy.updateMembers()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +74,6 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
switch viewAction {
|
||||
case .processTapPeople:
|
||||
callback?(.requestMemberDetailsPresentation(members))
|
||||
case .copyRoomLink:
|
||||
copyRoomLink()
|
||||
case .processTapLeave:
|
||||
guard members.count > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: .empty)
|
||||
@@ -70,21 +82,20 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
||||
case .confirmLeave:
|
||||
await leaveRoom()
|
||||
case .processTapIgnore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .ignore)
|
||||
case .processTapUnignore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore)
|
||||
case .ignoreConfirmed:
|
||||
await ignore()
|
||||
case .unignoreConfirmed:
|
||||
await unignore()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||
|
||||
private func copyRoomLink() {
|
||||
if let roomLink = state.permalink {
|
||||
UIPasteboard.general.url = roomLink
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.commonLinkCopiedToClipboard))
|
||||
} else {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoom() async {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: L10n.commonLoading, persistent: true))
|
||||
@@ -97,4 +108,28 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
|
||||
private func ignore() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await dmRecipient?.ignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.dmRecipient?.isIgnored = true
|
||||
case .failure, .none:
|
||||
state.bindings.alertInfo = .init(id: .unknown)
|
||||
}
|
||||
}
|
||||
|
||||
private func unignore() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await dmRecipient?.unignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.dmRecipient?.isIgnored = false
|
||||
case .failure, .none:
|
||||
state.bindings.alertInfo = .init(id: .unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,27 @@ struct RoomDetailsScreen: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
if let recipient = context.viewState.dmRecipient {
|
||||
dmHeaderSection(recipient: recipient)
|
||||
} else {
|
||||
normalRoomHeaderSection
|
||||
}
|
||||
|
||||
topicSection
|
||||
|
||||
aboutSection
|
||||
|
||||
|
||||
if context.viewState.dmRecipient == nil {
|
||||
aboutSection
|
||||
}
|
||||
|
||||
if context.viewState.isEncrypted {
|
||||
securitySection
|
||||
}
|
||||
|
||||
leaveRoomSection
|
||||
if let recipient = context.viewState.dmRecipient {
|
||||
ignoreUserSection(user: recipient)
|
||||
} else {
|
||||
leaveRoomSection
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
@@ -39,48 +49,53 @@ struct RoomDetailsScreen: View {
|
||||
.alert(item: $context.leaveRoomAlertItem,
|
||||
actions: leaveRoomAlertActions,
|
||||
message: leaveRoomAlertMessage)
|
||||
.alert(item: $context.ignoreUserRoomAlertItem,
|
||||
actions: blockUserAlertActions,
|
||||
message: blockUserAlertMessage)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var headerSection: some View {
|
||||
VStack(spacing: 8.0) {
|
||||
LoadableAvatarImage(url: context.viewState.avatarURL,
|
||||
name: context.viewState.title,
|
||||
contentID: context.viewState.roomId,
|
||||
avatarSize: .room(on: .details),
|
||||
imageProvider: context.imageProvider)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.avatar)
|
||||
|
||||
Text(context.viewState.title)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.element.title1Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let canonicalAlias = context.viewState.canonicalAlias {
|
||||
Text(canonicalAlias)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var normalRoomHeaderSection: some View {
|
||||
AvatarHeaderView(avatarUrl: context.viewState.avatarURL,
|
||||
name: context.viewState.title,
|
||||
id: context.viewState.roomId,
|
||||
avatarSize: .room(on: .details),
|
||||
imageProvider: context.imageProvider,
|
||||
subtitle: context.viewState.canonicalAlias) {
|
||||
if let permalink = context.viewState.permalink {
|
||||
HStack(spacing: 32) {
|
||||
Button { context.send(viewAction: .copyRoomLink) } label: {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCopyLink))
|
||||
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionInvite))
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShareLink))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.avatar)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func dmHeaderSection(recipient: RoomMemberDetails) -> some View {
|
||||
AvatarHeaderView(avatarUrl: recipient.avatarURL,
|
||||
name: recipient.name,
|
||||
id: recipient.id,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider,
|
||||
subtitle: recipient.id) {
|
||||
if let permalink = recipient.permalink {
|
||||
HStack(spacing: 32) {
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShareLink))
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.dmAvatar)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -158,6 +173,21 @@ struct RoomDetailsScreen: View {
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func ignoreUserSection(user: RoomMemberDetails) -> some View {
|
||||
Section {
|
||||
Button(role: user.isIgnored ? nil : .destructive) {
|
||||
context.send(viewAction: user.isIgnored ? .processTapUnignore : .processTapIgnore)
|
||||
} label: {
|
||||
Label(user.isIgnored ? L10n.screenDmDetailsUnblockUser : L10n.screenDmDetailsBlockUser,
|
||||
systemImage: "slash.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: context.viewState.isProcessingIgnoreRequest ? .progressView : nil))
|
||||
.disabled(context.viewState.isProcessingIgnoreRequest)
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func leaveRoomAlertActions(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
@@ -169,12 +199,25 @@ struct RoomDetailsScreen: View {
|
||||
private func leaveRoomAlertMessage(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Text(item.subtitle)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func blockUserAlertActions(_ item: RoomDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
Button(item.confirmationTitle,
|
||||
role: item.action == .ignore ? .destructive : nil) {
|
||||
context.send(viewAction: item.viewAction)
|
||||
}
|
||||
}
|
||||
|
||||
private func blockUserAlertMessage(_ item: RoomDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Text(item.description)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct RoomDetails_Previews: PreviewProvider {
|
||||
static let viewModel = {
|
||||
static let genericRoomViewModel = {
|
||||
let members: [RoomMemberProxyMock] = [
|
||||
.mockAlice,
|
||||
.mockBob,
|
||||
@@ -190,8 +233,28 @@ struct RoomDetails_Previews: PreviewProvider {
|
||||
return RoomDetailsViewModel(roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
}()
|
||||
|
||||
static let dmRoomViewModel = {
|
||||
let members: [RoomMemberProxyMock] = [
|
||||
.mockMe,
|
||||
.mockDan
|
||||
]
|
||||
|
||||
let roomProxy = RoomProxyMock(with: .init(displayName: "DM Room",
|
||||
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
isDirect: true,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members))
|
||||
|
||||
return RoomDetailsViewModel(roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
RoomDetailsScreen(context: viewModel.context)
|
||||
RoomDetailsScreen(context: genericRoomViewModel.context)
|
||||
.previewDisplayName("Generic Room")
|
||||
RoomDetailsScreen(context: dmRoomViewModel.context)
|
||||
.previewDisplayName("DM Room")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,64 +19,58 @@ import Foundation
|
||||
enum RoomMemberDetailsViewModelAction { }
|
||||
|
||||
struct RoomMemberDetailsViewState: BindableState {
|
||||
let userID: String
|
||||
let name: String?
|
||||
let avatarURL: URL?
|
||||
let isAccountOwner: Bool
|
||||
let permalink: URL?
|
||||
var isIgnored: Bool
|
||||
var details: RoomMemberDetails
|
||||
var isProcessingIgnoreRequest = false
|
||||
|
||||
var bindings: RoomMemberDetailsViewStateBindings
|
||||
}
|
||||
|
||||
struct RoomMemberDetailsViewStateBindings {
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let cancelTitle = L10n.actionCancel
|
||||
|
||||
var title: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockUser
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockUser
|
||||
}
|
||||
}
|
||||
|
||||
var confirmationTitle: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertAction
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertAction
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertDescription
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertDescription
|
||||
}
|
||||
}
|
||||
|
||||
var viewAction: RoomMemberDetailsViewAction {
|
||||
switch action {
|
||||
case .ignore: return .ignoreConfirmed
|
||||
case .unignore: return .unignoreConfirmed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ignoreUserAlert: IgnoreUserAlertItem?
|
||||
var errorAlert: ErrorAlertItem?
|
||||
}
|
||||
|
||||
struct IgnoreUserAlertItem: AlertItem, Equatable {
|
||||
enum Action {
|
||||
case ignore
|
||||
case unignore
|
||||
}
|
||||
|
||||
let action: Action
|
||||
let cancelTitle = L10n.actionCancel
|
||||
|
||||
var title: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockUser
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockUser
|
||||
}
|
||||
}
|
||||
|
||||
var confirmationTitle: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertAction
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertAction
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch action {
|
||||
case .ignore: return L10n.screenRoomMemberDetailsBlockAlertDescription
|
||||
case .unignore: return L10n.screenRoomMemberDetailsUnblockAlertDescription
|
||||
}
|
||||
}
|
||||
|
||||
var viewAction: RoomMemberDetailsViewAction {
|
||||
switch action {
|
||||
case .ignore: return .ignoreConfirmed
|
||||
case .unignore: return .unignoreConfirmed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomMemberDetailsViewAction {
|
||||
case showUnignoreAlert
|
||||
case showIgnoreAlert
|
||||
case ignoreConfirmed
|
||||
case unignoreConfirmed
|
||||
case copyUserLink
|
||||
}
|
||||
|
||||
@@ -25,12 +25,7 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
|
||||
init(roomMemberProxy: RoomMemberProxyProtocol, mediaProvider: MediaProviderProtocol) {
|
||||
self.roomMemberProxy = roomMemberProxy
|
||||
let initialViewState = RoomMemberDetailsViewState(userID: roomMemberProxy.userID,
|
||||
name: roomMemberProxy.displayName,
|
||||
avatarURL: roomMemberProxy.avatarURL,
|
||||
isAccountOwner: roomMemberProxy.isAccountOwner,
|
||||
permalink: roomMemberProxy.permalink,
|
||||
isIgnored: roomMemberProxy.isIgnored,
|
||||
let initialViewState = RoomMemberDetailsViewState(details: RoomMemberDetails(withProxy: roomMemberProxy),
|
||||
bindings: .init())
|
||||
super.init(initialViewState: initialViewState, imageProvider: mediaProvider)
|
||||
}
|
||||
@@ -43,8 +38,6 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
state.bindings.ignoreUserAlert = .init(action: .unignore)
|
||||
case .showIgnoreAlert:
|
||||
state.bindings.ignoreUserAlert = .init(action: .ignore)
|
||||
case .copyUserLink:
|
||||
copyUserLink()
|
||||
case .ignoreConfirmed:
|
||||
await ignoreUser()
|
||||
case .unignoreConfirmed:
|
||||
@@ -54,22 +47,13 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func copyUserLink() {
|
||||
if let userLink = state.permalink {
|
||||
UIPasteboard.general.url = userLink
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.commonLinkCopiedToClipboard))
|
||||
} else {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
|
||||
private func ignoreUser() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await roomMemberProxy.ignoreUser()
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.isIgnored = true
|
||||
state.details.isIgnored = true
|
||||
case .failure:
|
||||
state.bindings.errorAlert = .init()
|
||||
}
|
||||
@@ -81,7 +65,7 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
state.isProcessingIgnoreRequest = false
|
||||
switch result {
|
||||
case .success:
|
||||
state.isIgnored = false
|
||||
state.details.isIgnored = false
|
||||
case .failure:
|
||||
state.bindings.errorAlert = .init()
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct RoomMemberDetailsScreen: View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
if !context.viewState.isAccountOwner {
|
||||
if !context.viewState.details.isAccountOwner {
|
||||
blockUserSection
|
||||
}
|
||||
}
|
||||
@@ -35,31 +35,16 @@ struct RoomMemberDetailsScreen: View {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@ViewBuilder
|
||||
private var headerSection: some View {
|
||||
VStack(spacing: 8.0) {
|
||||
LoadableAvatarImage(url: context.viewState.avatarURL,
|
||||
name: context.viewState.name,
|
||||
contentID: context.viewState.userID,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider)
|
||||
if let name = context.viewState.name {
|
||||
Text(name)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.font(.element.title1Bold)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
Text(context.viewState.userID)
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let permalink = context.viewState.permalink {
|
||||
AvatarHeaderView(avatarUrl: context.viewState.details.avatarURL,
|
||||
name: context.viewState.details.name,
|
||||
id: context.viewState.details.id,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
imageProvider: context.imageProvider,
|
||||
subtitle: context.viewState.details.id) {
|
||||
if let permalink = context.viewState.details.permalink {
|
||||
HStack(spacing: 32) {
|
||||
Button { context.send(viewAction: .copyUserLink) } label: {
|
||||
Image(systemName: "link")
|
||||
}
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionCopyLink))
|
||||
|
||||
ShareLink(item: permalink) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
@@ -68,13 +53,11 @@ struct RoomMemberDetailsScreen: View {
|
||||
.padding(.top, 32)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
|
||||
private var blockUserSection: some View {
|
||||
Section {
|
||||
Button {
|
||||
Button(role: context.viewState.details.isIgnored ? nil : .destructive) {
|
||||
context.send(viewAction: blockUserButtonAction)
|
||||
} label: {
|
||||
Label(blockUserButtonTitle, systemImage: "slash.circle")
|
||||
@@ -87,19 +70,19 @@ struct RoomMemberDetailsScreen: View {
|
||||
}
|
||||
|
||||
private var blockUserButtonAction: RoomMemberDetailsViewAction {
|
||||
context.viewState.isIgnored ? .showUnignoreAlert : .showIgnoreAlert
|
||||
context.viewState.details.isIgnored ? .showUnignoreAlert : .showIgnoreAlert
|
||||
}
|
||||
|
||||
private var blockUserButtonTitle: String {
|
||||
context.viewState.isIgnored ? L10n.screenRoomMemberDetailsUnblockUser : L10n.screenRoomMemberDetailsBlockUser
|
||||
context.viewState.details.isIgnored ? L10n.screenRoomMemberDetailsUnblockUser : L10n.screenRoomMemberDetailsBlockUser
|
||||
}
|
||||
|
||||
private var blockUserButtonAccessibilityIdentifier: String {
|
||||
context.viewState.isIgnored ? A11yIdentifiers.roomMemberDetailsScreen.unignore : A11yIdentifiers.roomMemberDetailsScreen.ignore
|
||||
context.viewState.details.isIgnored ? A11yIdentifiers.roomMemberDetailsScreen.unignore : A11yIdentifiers.roomMemberDetailsScreen.ignore
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func blockUserAlertActions(_ item: IgnoreUserAlertItem) -> some View {
|
||||
private func blockUserAlertActions(_ item: RoomMemberDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
Button(item.confirmationTitle,
|
||||
role: item.action == .ignore ? .destructive : nil) {
|
||||
@@ -107,7 +90,7 @@ struct RoomMemberDetailsScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func blockUserAlertMessage(_ item: IgnoreUserAlertItem) -> some View {
|
||||
private func blockUserAlertMessage(_ item: RoomMemberDetailsViewStateBindings.IgnoreUserAlertItem) -> some View {
|
||||
Text(item.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ enum RoomMembersListViewModelAction {
|
||||
}
|
||||
|
||||
struct RoomMembersListViewState: BindableState {
|
||||
var members: [RoomDetailsMember]
|
||||
var members: [RoomMemberDetails]
|
||||
|
||||
var bindings: RoomMembersListViewStateBindings
|
||||
|
||||
var visibleMembers: [RoomDetailsMember] {
|
||||
var visibleMembers: [RoomMemberDetails] {
|
||||
if bindings.searchQuery.isEmpty {
|
||||
return members
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class RoomMembersListViewModel: RoomMembersListViewModelType, RoomMembersListVie
|
||||
members: [RoomMemberProxyProtocol]) {
|
||||
self.mediaProvider = mediaProvider
|
||||
self.members = members
|
||||
super.init(initialViewState: .init(members: members.map { RoomDetailsMember(withProxy: $0) },
|
||||
super.init(initialViewState: .init(members: members.map { RoomMemberDetails(withProxy: $0) },
|
||||
bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import SwiftUI
|
||||
struct RoomMembersListMemberCell: View {
|
||||
@ScaledMetric private var avatarSize = AvatarSize.user(on: .roomDetails).value
|
||||
|
||||
let member: RoomDetailsMember
|
||||
let member: RoomMemberDetails
|
||||
let context: RoomMembersListViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -34,6 +34,11 @@ class RoomProxy: RoomProxyProtocol {
|
||||
private(set) var displayName: String?
|
||||
|
||||
private var timelineObservationToken: TaskHandle?
|
||||
|
||||
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> {
|
||||
membersSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(slidingSyncRoom: SlidingSyncRoomProtocol,
|
||||
room: RoomProtocol,
|
||||
@@ -140,6 +145,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
timelineObservationToken = result.taskHandle
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
}
|
||||
return .success(result.items)
|
||||
} else {
|
||||
@@ -274,18 +280,16 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError> {
|
||||
|
||||
func updateMembers() async {
|
||||
do {
|
||||
let members = try await Task.dispatch(on: .global()) {
|
||||
let members = try self.room.members()
|
||||
return members
|
||||
let roomMembers = try await Task.dispatch(on: .global()) {
|
||||
try self.room.members()
|
||||
}
|
||||
|
||||
let proxiedMembers = buildRoomMemberProxies(members: members)
|
||||
return .success(proxiedMembers)
|
||||
membersSubject.value = buildRoomMemberProxies(members: roomMembers)
|
||||
} catch {
|
||||
return .failure(.failedRetrievingMembers)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,9 @@ protocol RoomProxyProtocol {
|
||||
var topic: String? { get }
|
||||
|
||||
var avatarURL: URL? { get }
|
||||
|
||||
|
||||
var membersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never> { get }
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError>
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
|
||||
@@ -79,13 +81,13 @@ protocol RoomProxyProtocol {
|
||||
|
||||
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError>
|
||||
|
||||
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func retryDecryption(for sessionID: String) async
|
||||
|
||||
func leaveRoom() async -> Result<Void, RoomProxyError>
|
||||
|
||||
func updateMembers() async
|
||||
}
|
||||
|
||||
extension RoomProxyProtocol {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RoomMemberDetails: Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String?
|
||||
let avatarURL: URL?
|
||||
let permalink: URL?
|
||||
let isAccountOwner: Bool
|
||||
var isIgnored: Bool
|
||||
|
||||
@MainActor
|
||||
init(withProxy proxy: RoomMemberProxyProtocol) {
|
||||
id = proxy.userID
|
||||
name = proxy.displayName
|
||||
avatarURL = proxy.avatarURL
|
||||
permalink = proxy.permalink
|
||||
isAccountOwner = proxy.isAccountOwner
|
||||
isIgnored = proxy.isIgnored
|
||||
}
|
||||
}
|
||||
@@ -337,6 +337,20 @@ class MockScreen: Identifiable {
|
||||
let coordinator = RoomMemberDetailsCoordinator(parameters: .init(roomMemberProxy: RoomMemberProxyMock.mockIgnored, mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomDetailsScreenDmDetails:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let members: [RoomMemberProxyMock] = [.mockMe, .mockDan]
|
||||
let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
topic: "test",
|
||||
isDirect: true,
|
||||
isEncrypted: true,
|
||||
members: members))
|
||||
let coordinator = RoomDetailsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case userSessionScreen
|
||||
case roomDetailsScreen
|
||||
case roomDetailsScreenWithRoomAvatar
|
||||
case roomDetailsScreenDmDetails
|
||||
case roomMembersListScreen
|
||||
case roomMemberDetailsAccountOwner
|
||||
case roomMemberDetails
|
||||
|
||||
@@ -33,4 +33,12 @@ class RoomDetailsScreenUITests: XCTestCase {
|
||||
XCTAssert(app.buttons[A11yIdentifiers.roomDetailsScreen.people].waitForExistence(timeout: 1))
|
||||
app.assertScreenshot(.roomDetailsScreenWithRoomAvatar)
|
||||
}
|
||||
|
||||
func testInitialStateComponentsDmDetails() {
|
||||
let app = Application.launch(.roomDetailsScreenDmDetails)
|
||||
|
||||
XCTAssert(app.images[A11yIdentifiers.roomDetailsScreen.dmAvatar].waitForExistence(timeout: 1))
|
||||
XCTAssertFalse(app.buttons[A11yIdentifiers.roomDetailsScreen.people].exists)
|
||||
app.assertScreenshot(.roomDetailsScreenDmDetails)
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:523804837d4ca7123635a59677e8e50abb4fc3e8bab0d17d817955ea736244b1
|
||||
size 101550
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b5d36a079c35934f91cfa81e1f2a567571cd0c66ff32babb3f83d8aa15ff3afb
|
||||
size 122338
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4328de12a3aefba9bdc8bf52bcdc8c6587a1618452aa6499429d85a80634fe30
|
||||
size 114902
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cb47ad82f2952d130e17fe3c99871e83782bc573f01317fcd55cf1ffbf53f0f0
|
||||
size 154477
|
||||
@@ -82,4 +82,94 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
|
||||
func testInitialDMDetailsState() async {
|
||||
let recipient = RoomMemberProxyMock.mockDan
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
}
|
||||
|
||||
func testIgnoreSuccess() async throws {
|
||||
let recipient = RoomMemberProxyMock.mockDan
|
||||
recipient.ignoreUserClosure = {
|
||||
try? await Task.sleep(for: .milliseconds(10))
|
||||
return .success(())
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||
}
|
||||
|
||||
func testIgnoreFailure() async throws {
|
||||
let recipient = RoomMemberProxyMock.mockDan
|
||||
recipient.ignoreUserClosure = {
|
||||
try? await Task.sleep(for: .milliseconds(10))
|
||||
return .failure(.ignoreUserFailed)
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
|
||||
func testUnignoreSuccess() async throws {
|
||||
let recipient = RoomMemberProxyMock.mockIgnored
|
||||
recipient.unignoreUserClosure = {
|
||||
try? await Task.sleep(for: .milliseconds(10))
|
||||
return .success(())
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||
}
|
||||
|
||||
func testUnignoreFailure() async throws {
|
||||
let recipient = RoomMemberProxyMock.mockIgnored
|
||||
recipient.unignoreUserClosure = {
|
||||
try? await Task.sleep(for: .milliseconds(10))
|
||||
return .failure(.unignoreUserFailed)
|
||||
}
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,7 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
roomMemberProxyMock = RoomMemberProxyMock.mockAlice
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
XCTAssertEqual(context.viewState.name, "Alice")
|
||||
XCTAssertFalse(context.viewState.isAccountOwner)
|
||||
XCTAssertFalse(context.viewState.isIgnored)
|
||||
XCTAssertEqual(context.viewState.userID, "@alice:matrix.org")
|
||||
XCTAssertEqual(context.viewState.permalink, URL(string: "https://matrix.to/#/@alice:matrix.org"))
|
||||
XCTAssertEqual(context.viewState.avatarURL, nil)
|
||||
XCTAssertEqual(context.viewState.details, RoomMemberDetails(withProxy: roomMemberProxyMock))
|
||||
XCTAssertNil(context.ignoreUserAlert)
|
||||
XCTAssertNil(context.errorAlert)
|
||||
}
|
||||
@@ -48,15 +43,15 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, IgnoreUserAlertItem(action: .ignore))
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertFalse(context.viewState.isIgnored)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.isIgnored)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
}
|
||||
|
||||
func testIgnoreFailure() async throws {
|
||||
@@ -69,16 +64,16 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, IgnoreUserAlertItem(action: .ignore))
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertFalse(context.viewState.isIgnored)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertNotNil(context.errorAlert)
|
||||
XCTAssertFalse(context.viewState.isIgnored)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
}
|
||||
|
||||
func testUnignoreSuccess() async throws {
|
||||
@@ -91,15 +86,15 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, IgnoreUserAlertItem(action: .unignore))
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.isIgnored)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertFalse(context.viewState.isIgnored)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
}
|
||||
|
||||
func testUnignoreFailure() async throws {
|
||||
@@ -112,15 +107,15 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, IgnoreUserAlertItem(action: .unignore))
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.isIgnored)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.isIgnored)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
XCTAssertNotNil(context.errorAlert)
|
||||
}
|
||||
|
||||
@@ -128,12 +123,7 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
roomMemberProxyMock = RoomMemberProxyMock.mockMe
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
XCTAssertEqual(context.viewState.name, "Me")
|
||||
XCTAssertTrue(context.viewState.isAccountOwner)
|
||||
XCTAssertFalse(context.viewState.isIgnored)
|
||||
XCTAssertEqual(context.viewState.userID, "@me:matrix.org")
|
||||
XCTAssertEqual(context.viewState.permalink, URL(string: "https://matrix.to/#/@me:matrix.org"))
|
||||
XCTAssertEqual(context.viewState.avatarURL, URL.picturesDirectory)
|
||||
XCTAssertEqual(context.viewState.details, RoomMemberDetails(withProxy: roomMemberProxyMock))
|
||||
XCTAssertNil(context.ignoreUserAlert)
|
||||
XCTAssertNil(context.errorAlert)
|
||||
}
|
||||
@@ -142,12 +132,7 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
roomMemberProxyMock = RoomMemberProxyMock.mockIgnored
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
XCTAssertEqual(context.viewState.name, "Ignored")
|
||||
XCTAssertFalse(context.viewState.isAccountOwner)
|
||||
XCTAssertTrue(context.viewState.isIgnored)
|
||||
XCTAssertEqual(context.viewState.userID, "@ignored:matrix.org")
|
||||
XCTAssertEqual(context.viewState.permalink, URL(string: "https://matrix.to/#/@ignored:matrix.org"))
|
||||
XCTAssertEqual(context.viewState.avatarURL, nil)
|
||||
XCTAssertEqual(context.viewState.details, RoomMemberDetails(withProxy: roomMemberProxyMock))
|
||||
XCTAssertNil(context.ignoreUserAlert)
|
||||
XCTAssertNil(context.errorAlert)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Added DM Details View.
|
||||
Reference in New Issue
Block a user