mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
refactor: manage member sheet in timeline
better implementation updated tests Update ElementX/Sources/Screens/Timeline/TimelineViewModel.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> fix
This commit is contained in:
@@ -399,9 +399,26 @@
|
||||
"screen_advanced_settings_hide_invite_avatars_toggle_title" = "Hide avatars in room invite requests";
|
||||
"screen_advanced_settings_hide_timeline_media_toggle_title" = "Hide media previews in timeline";
|
||||
"screen_advanced_settings_moderation_and_safety_section_title" = "Moderation and Safety";
|
||||
"screen_advanced_settings_show_media_timeline_always_hide" = "Always hide";
|
||||
"screen_advanced_settings_show_media_timeline_always_show" = "Always show";
|
||||
"screen_advanced_settings_show_media_timeline_private_rooms" = "In private rooms";
|
||||
"screen_advanced_settings_show_media_timeline_subtitle" = "A hidden media can always be shown by tapping on it";
|
||||
"screen_advanced_settings_show_media_timeline_title" = "Show media in timeline";
|
||||
"screen_bottom_sheet,manage_room_member_remove" = "Remove from room";
|
||||
"screen_bottom_sheet_create_dm_confirmation_button_title" = "Send invite";
|
||||
"screen_bottom_sheet_create_dm_message" = "Would you like to start a chat with %1$@?";
|
||||
"screen_bottom_sheet_create_dm_title" = "Send invite?";
|
||||
"screen_bottom_sheet_manage_room_member_ban" = "Ban from room";
|
||||
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_action" = "Ban";
|
||||
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_description" = "They won’t be able to join this room again if invited.";
|
||||
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_title" = "Are you sure you want to ban this member?";
|
||||
"screen_bottom_sheet_manage_room_member_banning_user" = "Banning %1$@";
|
||||
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_action" = "Remove";
|
||||
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_description" = "They will be able to join this room again if invited.";
|
||||
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_title" = "Are you sure you want to remove this member?";
|
||||
"screen_bottom_sheet_manage_room_member_member_user_info" = "View profile";
|
||||
"screen_bottom_sheet_manage_room_member_remove_confirmation_title" = "Remove member and ban from joining in the future?";
|
||||
"screen_bottom_sheet_manage_room_member_removing_user" = "Removing %1$@…";
|
||||
"screen_create_room_room_access_section_anyone_option_description" = "Anyone can join this room";
|
||||
"screen_create_room_room_access_section_anyone_option_title" = "Anyone";
|
||||
"screen_create_room_room_access_section_header" = "Room Access";
|
||||
@@ -886,26 +903,14 @@
|
||||
"screen_room_member_details_unblock_user" = "Unblock user";
|
||||
"screen_room_member_details_verify_button_subtitle" = "Use the web app to verify this user.";
|
||||
"screen_room_member_details_verify_button_title" = "Verify %1$@";
|
||||
"screen_room_member_list_ban_member_confirmation_action" = "Ban";
|
||||
"screen_room_member_list_ban_member_confirmation_description" = "They won’t be able to join this room again if invited.";
|
||||
"screen_room_member_list_ban_member_confirmation_title" = "Are you sure you want to ban this member?";
|
||||
"screen_room_member_list_banned_empty" = "There are no banned users in this room.";
|
||||
"screen_room_member_list_banning_user" = "Banning %1$@";
|
||||
"screen_room_member_list_kick_member_confirmation_action" = "Remove";
|
||||
"screen_room_member_list_kick_member_confirmation_description" = "They will be able to join this room again if invited.";
|
||||
"screen_room_member_list_kick_member_confirmation_title" = "Are you sure you want to remove this member?";
|
||||
"screen_room_member_list_manage_member_ban" = "Remove and ban member";
|
||||
"screen_room_member_list_manage_member_remove" = "Remove from room";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_kick" = "Only remove member";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_title" = "Remove member and ban from joining in the future?";
|
||||
"screen_room_member_list_manage_member_unban_action" = "Unban";
|
||||
"screen_room_member_list_manage_member_unban_message" = "They will be able to join this room again if invited.";
|
||||
"screen_room_member_list_manage_member_unban_title" = "Unban user";
|
||||
"screen_room_member_list_manage_member_user_info" = "View profile";
|
||||
"screen_room_member_list_mode_banned" = "Banned";
|
||||
"screen_room_member_list_mode_members" = "Members";
|
||||
"screen_room_member_list_pending_header_title" = "Pending";
|
||||
"screen_room_member_list_removing_user" = "Removing %1$@…";
|
||||
"screen_room_member_list_role_administrator" = "Admin";
|
||||
"screen_room_member_list_role_moderator" = "Moderator";
|
||||
"screen_room_member_list_room_members_header_title" = "Room members";
|
||||
@@ -1134,9 +1139,10 @@
|
||||
"troubleshoot_notifications_test_current_push_provider_failure" = "No push providers selected.";
|
||||
"troubleshoot_notifications_test_current_push_provider_success" = "Current push provider: %1$@.";
|
||||
"troubleshoot_notifications_test_current_push_provider_title" = "Current push provider";
|
||||
"troubleshoot_notifications_test_detect_push_provider_description" = "Ensure that the application has at least one push provider.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_failure" = "No push providers found.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_title" = "Detect push providers";
|
||||
"troubleshoot_notifications_test_detect_push_provider_description" = "Ensure that the application supports at least one push provider.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_failure" = "No push provider support found.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_success_2" = "The application was built with support for: %1$@";
|
||||
"troubleshoot_notifications_test_detect_push_provider_title" = "Push provider support";
|
||||
"troubleshoot_notifications_test_display_notification_description" = "Check that the application can display notification.";
|
||||
"troubleshoot_notifications_test_display_notification_failure" = "The notification has not been clicked.";
|
||||
"troubleshoot_notifications_test_display_notification_permission_failure" = "Cannot display the notification.";
|
||||
@@ -1237,7 +1243,7 @@
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_error_failed_processing_media" = "Failed processing media to upload, please try again.";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_ban" = "Remove and ban member";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_ban" = "Ban from room";
|
||||
"screen_room_notification_settings_mode_all_messages" = "All messages";
|
||||
"screen_room_notification_settings_mode_mentions_and_keywords" = "Mentions and Keywords only";
|
||||
"screen_room_timeline_reactions_show_less" = "Show less";
|
||||
|
||||
@@ -399,9 +399,26 @@
|
||||
"screen_advanced_settings_hide_invite_avatars_toggle_title" = "Hide avatars in room invite requests";
|
||||
"screen_advanced_settings_hide_timeline_media_toggle_title" = "Hide media previews in timeline";
|
||||
"screen_advanced_settings_moderation_and_safety_section_title" = "Moderation and Safety";
|
||||
"screen_advanced_settings_show_media_timeline_always_hide" = "Always hide";
|
||||
"screen_advanced_settings_show_media_timeline_always_show" = "Always show";
|
||||
"screen_advanced_settings_show_media_timeline_private_rooms" = "In private rooms";
|
||||
"screen_advanced_settings_show_media_timeline_subtitle" = "A hidden media can always be shown by tapping on it";
|
||||
"screen_advanced_settings_show_media_timeline_title" = "Show media in timeline";
|
||||
"screen_bottom_sheet,manage_room_member_remove" = "Remove from room";
|
||||
"screen_bottom_sheet_create_dm_confirmation_button_title" = "Send invite";
|
||||
"screen_bottom_sheet_create_dm_message" = "Would you like to start a chat with %1$@?";
|
||||
"screen_bottom_sheet_create_dm_title" = "Send invite?";
|
||||
"screen_bottom_sheet_manage_room_member_ban" = "Ban from room";
|
||||
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_action" = "Ban";
|
||||
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_description" = "They won’t be able to join this room again if invited.";
|
||||
"screen_bottom_sheet_manage_room_member_ban_member_confirmation_title" = "Are you sure you want to ban this member?";
|
||||
"screen_bottom_sheet_manage_room_member_banning_user" = "Banning %1$@";
|
||||
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_action" = "Remove";
|
||||
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_description" = "They will be able to join this room again if invited.";
|
||||
"screen_bottom_sheet_manage_room_member_kick_member_confirmation_title" = "Are you sure you want to remove this member?";
|
||||
"screen_bottom_sheet_manage_room_member_member_user_info" = "View profile";
|
||||
"screen_bottom_sheet_manage_room_member_remove_confirmation_title" = "Remove member and ban from joining in the future?";
|
||||
"screen_bottom_sheet_manage_room_member_removing_user" = "Removing %1$@…";
|
||||
"screen_create_room_room_access_section_anyone_option_description" = "Anyone can join this room";
|
||||
"screen_create_room_room_access_section_anyone_option_title" = "Anyone";
|
||||
"screen_create_room_room_access_section_header" = "Room Access";
|
||||
@@ -886,26 +903,14 @@
|
||||
"screen_room_member_details_unblock_user" = "Unblock user";
|
||||
"screen_room_member_details_verify_button_subtitle" = "Use the web app to verify this user.";
|
||||
"screen_room_member_details_verify_button_title" = "Verify %1$@";
|
||||
"screen_room_member_list_ban_member_confirmation_action" = "Ban";
|
||||
"screen_room_member_list_ban_member_confirmation_description" = "They won’t be able to join this room again if invited.";
|
||||
"screen_room_member_list_ban_member_confirmation_title" = "Are you sure you want to ban this member?";
|
||||
"screen_room_member_list_banned_empty" = "There are no banned users in this room.";
|
||||
"screen_room_member_list_banning_user" = "Banning %1$@";
|
||||
"screen_room_member_list_kick_member_confirmation_action" = "Remove";
|
||||
"screen_room_member_list_kick_member_confirmation_description" = "They will be able to join this room again if invited.";
|
||||
"screen_room_member_list_kick_member_confirmation_title" = "Are you sure you want to remove this member?";
|
||||
"screen_room_member_list_manage_member_ban" = "Remove and ban member";
|
||||
"screen_room_member_list_manage_member_remove" = "Remove from room";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_kick" = "Only remove member";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_title" = "Remove member and ban from joining in the future?";
|
||||
"screen_room_member_list_manage_member_unban_action" = "Unban";
|
||||
"screen_room_member_list_manage_member_unban_message" = "They will be able to join this room again if invited.";
|
||||
"screen_room_member_list_manage_member_unban_title" = "Unban user";
|
||||
"screen_room_member_list_manage_member_user_info" = "View profile";
|
||||
"screen_room_member_list_mode_banned" = "Banned";
|
||||
"screen_room_member_list_mode_members" = "Members";
|
||||
"screen_room_member_list_pending_header_title" = "Pending";
|
||||
"screen_room_member_list_removing_user" = "Removing %1$@…";
|
||||
"screen_room_member_list_role_administrator" = "Admin";
|
||||
"screen_room_member_list_role_moderator" = "Moderator";
|
||||
"screen_room_member_list_room_members_header_title" = "Room members";
|
||||
@@ -1134,9 +1139,10 @@
|
||||
"troubleshoot_notifications_test_current_push_provider_failure" = "No push providers selected.";
|
||||
"troubleshoot_notifications_test_current_push_provider_success" = "Current push provider: %1$@.";
|
||||
"troubleshoot_notifications_test_current_push_provider_title" = "Current push provider";
|
||||
"troubleshoot_notifications_test_detect_push_provider_description" = "Ensure that the application has at least one push provider.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_failure" = "No push providers found.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_title" = "Detect push providers";
|
||||
"troubleshoot_notifications_test_detect_push_provider_description" = "Ensure that the application supports at least one push provider.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_failure" = "No push provider support found.";
|
||||
"troubleshoot_notifications_test_detect_push_provider_success_2" = "The application was built with support for: %1$@";
|
||||
"troubleshoot_notifications_test_detect_push_provider_title" = "Push provider support";
|
||||
"troubleshoot_notifications_test_display_notification_description" = "Check that the application can display notification.";
|
||||
"troubleshoot_notifications_test_display_notification_failure" = "The notification has not been clicked.";
|
||||
"troubleshoot_notifications_test_display_notification_permission_failure" = "Cannot display the notification.";
|
||||
@@ -1237,7 +1243,7 @@
|
||||
"screen_room_details_security_title" = "Security";
|
||||
"screen_room_details_topic_title" = "Topic";
|
||||
"screen_room_error_failed_processing_media" = "Failed processing media to upload, please try again.";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_ban" = "Remove and ban member";
|
||||
"screen_room_member_list_manage_member_remove_confirmation_ban" = "Ban from room";
|
||||
"screen_room_notification_settings_mode_all_messages" = "All messages";
|
||||
"screen_room_notification_settings_mode_mentions_and_keywords" = "Mentions and Keywords only";
|
||||
"screen_room_timeline_reactions_show_less" = "Show less";
|
||||
|
||||
@@ -978,6 +978,16 @@ internal enum L10n {
|
||||
internal static var screenAdvancedSettingsSharePresence: String { return L10n.tr("Localizable", "screen_advanced_settings_share_presence") }
|
||||
/// If turned off, you won’t be able to send or receive read receipts or typing notifications.
|
||||
internal static var screenAdvancedSettingsSharePresenceDescription: String { return L10n.tr("Localizable", "screen_advanced_settings_share_presence_description") }
|
||||
/// Always hide
|
||||
internal static var screenAdvancedSettingsShowMediaTimelineAlwaysHide: String { return L10n.tr("Localizable", "screen_advanced_settings_show_media_timeline_always_hide") }
|
||||
/// Always show
|
||||
internal static var screenAdvancedSettingsShowMediaTimelineAlwaysShow: String { return L10n.tr("Localizable", "screen_advanced_settings_show_media_timeline_always_show") }
|
||||
/// In private rooms
|
||||
internal static var screenAdvancedSettingsShowMediaTimelinePrivateRooms: String { return L10n.tr("Localizable", "screen_advanced_settings_show_media_timeline_private_rooms") }
|
||||
/// A hidden media can always be shown by tapping on it
|
||||
internal static var screenAdvancedSettingsShowMediaTimelineSubtitle: String { return L10n.tr("Localizable", "screen_advanced_settings_show_media_timeline_subtitle") }
|
||||
/// Show media in timeline
|
||||
internal static var screenAdvancedSettingsShowMediaTimelineTitle: String { return L10n.tr("Localizable", "screen_advanced_settings_show_media_timeline_title") }
|
||||
/// Enable option to view message source in the timeline.
|
||||
internal static var screenAdvancedSettingsViewSourceDescription: String { return L10n.tr("Localizable", "screen_advanced_settings_view_source_description") }
|
||||
/// We won't record or profile any personal data
|
||||
@@ -1082,6 +1092,8 @@ internal enum L10n {
|
||||
internal static var screenBlockedUsersUnblockAlertTitle: String { return L10n.tr("Localizable", "screen_blocked_users_unblock_alert_title") }
|
||||
/// Unblocking…
|
||||
internal static var screenBlockedUsersUnblocking: String { return L10n.tr("Localizable", "screen_blocked_users_unblocking") }
|
||||
/// Remove from room
|
||||
internal static var screenBottomSheetManageRoomMemberRemove: String { return L10n.tr("Localizable", "screen_bottom_sheet,manage_room_member_remove") }
|
||||
/// Send invite
|
||||
internal static var screenBottomSheetCreateDmConfirmationButtonTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_create_dm_confirmation_button_title") }
|
||||
/// Would you like to start a chat with %1$@?
|
||||
@@ -1090,6 +1102,32 @@ internal enum L10n {
|
||||
}
|
||||
/// Send invite?
|
||||
internal static var screenBottomSheetCreateDmTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_create_dm_title") }
|
||||
/// Ban from room
|
||||
internal static var screenBottomSheetManageRoomMemberBan: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban") }
|
||||
/// Ban
|
||||
internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationAction: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_action") }
|
||||
/// They won’t be able to join this room again if invited.
|
||||
internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_description") }
|
||||
/// Are you sure you want to ban this member?
|
||||
internal static var screenBottomSheetManageRoomMemberBanMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_ban_member_confirmation_title") }
|
||||
/// Banning %1$@
|
||||
internal static func screenBottomSheetManageRoomMemberBanningUser(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_banning_user", String(describing: p1))
|
||||
}
|
||||
/// Remove
|
||||
internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationAction: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_action") }
|
||||
/// They will be able to join this room again if invited.
|
||||
internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_description") }
|
||||
/// Are you sure you want to remove this member?
|
||||
internal static var screenBottomSheetManageRoomMemberKickMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_kick_member_confirmation_title") }
|
||||
/// View profile
|
||||
internal static var screenBottomSheetManageRoomMemberMemberUserInfo: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_member_user_info") }
|
||||
/// Remove member and ban from joining in the future?
|
||||
internal static var screenBottomSheetManageRoomMemberRemoveConfirmationTitle: String { return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_remove_confirmation_title") }
|
||||
/// Removing %1$@…
|
||||
internal static func screenBottomSheetManageRoomMemberRemovingUser(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_bottom_sheet_manage_room_member_removing_user", String(describing: p1))
|
||||
}
|
||||
/// Attach screenshot
|
||||
internal static var screenBugReportAttachScreenshot: String { return L10n.tr("Localizable", "screen_bug_report_attach_screenshot") }
|
||||
/// You may contact me if you have any follow up questions.
|
||||
@@ -2066,56 +2104,28 @@ internal enum L10n {
|
||||
internal static func screenRoomMemberDetailsVerifyButtonTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_room_member_details_verify_button_title", String(describing: p1))
|
||||
}
|
||||
/// Ban
|
||||
internal static var screenRoomMemberListBanMemberConfirmationAction: String { return L10n.tr("Localizable", "screen_room_member_list_ban_member_confirmation_action") }
|
||||
/// They won’t be able to join this room again if invited.
|
||||
internal static var screenRoomMemberListBanMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_room_member_list_ban_member_confirmation_description") }
|
||||
/// Are you sure you want to ban this member?
|
||||
internal static var screenRoomMemberListBanMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_room_member_list_ban_member_confirmation_title") }
|
||||
/// There are no banned users in this room.
|
||||
internal static var screenRoomMemberListBannedEmpty: String { return L10n.tr("Localizable", "screen_room_member_list_banned_empty") }
|
||||
/// Banning %1$@
|
||||
internal static func screenRoomMemberListBanningUser(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_room_member_list_banning_user", String(describing: p1))
|
||||
}
|
||||
/// Plural format key: "%#@COUNT@"
|
||||
internal static func screenRoomMemberListHeaderTitle(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "screen_room_member_list_header_title", p1)
|
||||
}
|
||||
/// Remove
|
||||
internal static var screenRoomMemberListKickMemberConfirmationAction: String { return L10n.tr("Localizable", "screen_room_member_list_kick_member_confirmation_action") }
|
||||
/// They will be able to join this room again if invited.
|
||||
internal static var screenRoomMemberListKickMemberConfirmationDescription: String { return L10n.tr("Localizable", "screen_room_member_list_kick_member_confirmation_description") }
|
||||
/// Are you sure you want to remove this member?
|
||||
internal static var screenRoomMemberListKickMemberConfirmationTitle: String { return L10n.tr("Localizable", "screen_room_member_list_kick_member_confirmation_title") }
|
||||
/// Remove and ban member
|
||||
internal static var screenRoomMemberListManageMemberBan: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_ban") }
|
||||
/// Remove from room
|
||||
internal static var screenRoomMemberListManageMemberRemove: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_remove") }
|
||||
/// Remove and ban member
|
||||
/// Ban from room
|
||||
internal static var screenRoomMemberListManageMemberRemoveConfirmationBan: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_remove_confirmation_ban") }
|
||||
/// Only remove member
|
||||
internal static var screenRoomMemberListManageMemberRemoveConfirmationKick: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_remove_confirmation_kick") }
|
||||
/// Remove member and ban from joining in the future?
|
||||
internal static var screenRoomMemberListManageMemberRemoveConfirmationTitle: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_remove_confirmation_title") }
|
||||
/// Unban
|
||||
internal static var screenRoomMemberListManageMemberUnbanAction: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_unban_action") }
|
||||
/// They will be able to join this room again if invited.
|
||||
internal static var screenRoomMemberListManageMemberUnbanMessage: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_unban_message") }
|
||||
/// Unban user
|
||||
internal static var screenRoomMemberListManageMemberUnbanTitle: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_unban_title") }
|
||||
/// View profile
|
||||
internal static var screenRoomMemberListManageMemberUserInfo: String { return L10n.tr("Localizable", "screen_room_member_list_manage_member_user_info") }
|
||||
/// Banned
|
||||
internal static var screenRoomMemberListModeBanned: String { return L10n.tr("Localizable", "screen_room_member_list_mode_banned") }
|
||||
/// Members
|
||||
internal static var screenRoomMemberListModeMembers: String { return L10n.tr("Localizable", "screen_room_member_list_mode_members") }
|
||||
/// Pending
|
||||
internal static var screenRoomMemberListPendingHeaderTitle: String { return L10n.tr("Localizable", "screen_room_member_list_pending_header_title") }
|
||||
/// Removing %1$@…
|
||||
internal static func screenRoomMemberListRemovingUser(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_room_member_list_removing_user", String(describing: p1))
|
||||
}
|
||||
/// Admin
|
||||
internal static var screenRoomMemberListRoleAdministrator: String { return L10n.tr("Localizable", "screen_room_member_list_role_administrator") }
|
||||
/// Moderator
|
||||
@@ -2860,15 +2870,19 @@ internal enum L10n {
|
||||
}
|
||||
/// Current push provider
|
||||
internal static var troubleshootNotificationsTestCurrentPushProviderTitle: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_current_push_provider_title") }
|
||||
/// Ensure that the application has at least one push provider.
|
||||
/// Ensure that the application supports at least one push provider.
|
||||
internal static var troubleshootNotificationsTestDetectPushProviderDescription: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_detect_push_provider_description") }
|
||||
/// No push providers found.
|
||||
/// No push provider support found.
|
||||
internal static var troubleshootNotificationsTestDetectPushProviderFailure: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_detect_push_provider_failure") }
|
||||
/// Plural format key: "%#@COUNT@"
|
||||
internal static func troubleshootNotificationsTestDetectPushProviderSuccess(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "troubleshoot_notifications_test_detect_push_provider_success", p1)
|
||||
}
|
||||
/// Detect push providers
|
||||
/// The application was built with support for: %1$@
|
||||
internal static func troubleshootNotificationsTestDetectPushProviderSuccess2(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "troubleshoot_notifications_test_detect_push_provider_success_2", String(describing: p1))
|
||||
}
|
||||
/// Push provider support
|
||||
internal static var troubleshootNotificationsTestDetectPushProviderTitle: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_detect_push_provider_title") }
|
||||
/// Check that the application can display notification.
|
||||
internal static var troubleshootNotificationsTestDisplayNotificationDescription: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_display_notification_description") }
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
enum ManageRoomMemberSheetViewModelAction: Equatable {
|
||||
case dismiss(shouldShowDetails: Bool)
|
||||
}
|
||||
|
||||
struct ManageRoomMemberSheetViewState: BindableState {
|
||||
let member: RoomMemberDetails
|
||||
let canKick: Bool
|
||||
let canBan: Bool
|
||||
|
||||
var bindings = ManageRoomMemberSheetViewStateBindings()
|
||||
}
|
||||
|
||||
struct ManageRoomMemberSheetViewStateBindings {
|
||||
var alertInfo: AlertInfo<ManageRoomMemberSheetViewAlertType>?
|
||||
}
|
||||
|
||||
enum ManageRoomMemberSheetViewAlertType {
|
||||
case kick
|
||||
case ban
|
||||
}
|
||||
|
||||
enum ManageRoomMemberSheetViewAction {
|
||||
case kick
|
||||
case ban
|
||||
case displayDetails
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
typealias ManageRoomMemberSheetViewModelType = StateStoreViewModel<ManageRoomMemberSheetViewState, ManageRoomMemberSheetViewAction>
|
||||
|
||||
class ManageRoomMemberSheetViewModel: ManageRoomMemberSheetViewModelType, ManageRoomMemberSheetViewModelProtocol {
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let analyticsService: AnalyticsService
|
||||
|
||||
private var actionsSubject: PassthroughSubject<ManageRoomMemberSheetViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<ManageRoomMemberSheetViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(member: RoomMemberDetails,
|
||||
canKick: Bool,
|
||||
canBan: Bool,
|
||||
roomProxy: JoinedRoomProxyProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
analyticsService: AnalyticsService,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.roomProxy = roomProxy
|
||||
self.analyticsService = analyticsService
|
||||
super.init(initialViewState: .init(member: member, canKick: canKick, canBan: canBan), mediaProvider: mediaProvider)
|
||||
}
|
||||
|
||||
override func process(viewAction: ManageRoomMemberSheetViewAction) {
|
||||
switch viewAction {
|
||||
case .kick:
|
||||
displayAlert(.kick)
|
||||
case .ban:
|
||||
displayAlert(.ban)
|
||||
case .displayDetails:
|
||||
actionsSubject.send(.dismiss(shouldShowDetails: true))
|
||||
}
|
||||
}
|
||||
|
||||
private func displayAlert(_ alertType: ManageRoomMemberSheetViewAlertType) {
|
||||
let member = state.member
|
||||
var reason: String?
|
||||
let binding: Binding<String> = .init(get: { reason ?? "" },
|
||||
set: { reason = $0.isBlank ? nil : $0 })
|
||||
switch alertType {
|
||||
case .kick:
|
||||
state.bindings.alertInfo = .init(id: alertType,
|
||||
title: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationTitle,
|
||||
message: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationDescription,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel) { },
|
||||
secondaryButton: .init(title: L10n.screenBottomSheetManageRoomMemberKickMemberConfirmationAction) { [weak self] in Task { await self?.kickMember(member, reason: reason) } },
|
||||
textFields: [.init(placeholder: L10n.commonReason,
|
||||
text: binding,
|
||||
autoCapitalization: .sentences,
|
||||
autoCorrectionDisabled: false)])
|
||||
case .ban:
|
||||
state.bindings.alertInfo = .init(id: alertType,
|
||||
title: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationTitle,
|
||||
message: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationDescription,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel) { },
|
||||
secondaryButton: .init(title: L10n.screenBottomSheetManageRoomMemberBanMemberConfirmationAction) { [weak self] in Task { await self?.banMember(member, reason: reason) } },
|
||||
textFields: [.init(placeholder: L10n.commonReason,
|
||||
text: binding,
|
||||
autoCapitalization: .sentences,
|
||||
autoCorrectionDisabled: false)])
|
||||
}
|
||||
}
|
||||
|
||||
private func kickMember(_ member: RoomMemberDetails, reason: String?) async {
|
||||
let indicatorTitle = L10n.screenBottomSheetManageRoomMemberRemovingUser(member.name ?? member.id)
|
||||
showManageMemberIndicator(title: indicatorTitle)
|
||||
|
||||
switch await roomProxy.kickUser(member.id, reason: reason) {
|
||||
case .success:
|
||||
hideManageMemberIndicator(title: indicatorTitle)
|
||||
analyticsService.trackRoomModeration(action: .KickMember, role: nil)
|
||||
actionsSubject.send(.dismiss(shouldShowDetails: false))
|
||||
case .failure:
|
||||
showManageMemberFailure(title: indicatorTitle)
|
||||
}
|
||||
}
|
||||
|
||||
private func banMember(_ member: RoomMemberDetails, reason: String?) async {
|
||||
let indicatorTitle = L10n.screenBottomSheetManageRoomMemberBanningUser(member.name ?? member.id)
|
||||
showManageMemberIndicator(title: indicatorTitle)
|
||||
|
||||
switch await roomProxy.banUser(member.id, reason: reason) {
|
||||
case .success:
|
||||
hideManageMemberIndicator(title: indicatorTitle)
|
||||
analyticsService.trackRoomModeration(action: .BanMember, role: nil)
|
||||
actionsSubject.send(.dismiss(shouldShowDetails: false))
|
||||
case .failure:
|
||||
showManageMemberFailure(title: indicatorTitle)
|
||||
}
|
||||
}
|
||||
|
||||
private func showManageMemberIndicator(title: String) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: title,
|
||||
type: .toast(progress: .indeterminate),
|
||||
title: title,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
private func hideManageMemberIndicator(title: String) {
|
||||
userIndicatorController.retractIndicatorWithId(title)
|
||||
}
|
||||
|
||||
private func showManageMemberFailure(title: String) {
|
||||
userIndicatorController.retractIndicatorWithId(title)
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.commonFailed, iconName: "xmark"))
|
||||
}
|
||||
}
|
||||
|
||||
extension ManageRoomMemberSheetViewModel: Identifiable {
|
||||
var id: String { state.member.id }
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ManageRoomMemberSheetViewModelProtocol {
|
||||
var actions: AnyPublisher<ManageRoomMemberSheetViewModelAction, Never> { get }
|
||||
var context: ManageRoomMemberSheetViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct ManageRoomMemberSheetView: View {
|
||||
@ObservedObject var context: ManageRoomMemberSheetViewModelType.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
AvatarHeaderView(member: context.viewState.member,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
mediaProvider: context.mediaProvider) {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.screenBottomSheetManageRoomMemberMemberUserInfo,
|
||||
icon: \.userProfileSolid),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .displayDetails)
|
||||
})
|
||||
}
|
||||
|
||||
Section {
|
||||
if context.viewState.canKick {
|
||||
ListRow(label: .default(title: L10n.screenBottomSheetManageRoomMemberRemove,
|
||||
icon: \.close,
|
||||
role: .destructive),
|
||||
kind: .button {
|
||||
context.send(viewAction: .kick)
|
||||
})
|
||||
}
|
||||
|
||||
if context.viewState.canBan {
|
||||
ListRow(label: .default(title: L10n.screenBottomSheetManageRoomMemberBan,
|
||||
icon: \.block,
|
||||
role: .destructive),
|
||||
kind: .button {
|
||||
context.send(viewAction: .ban)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.compoundList()
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationDetents([.large, .fraction(0.67)]) // Maybe find a way to use the ideal height somehow?
|
||||
.alert(item: $context.alertInfo)
|
||||
}
|
||||
}
|
||||
|
||||
struct ManageRoomMemberSheetView_Previews: PreviewProvider, TestablePreview {
|
||||
static let allActionsViewModel = ManageRoomMemberSheetViewModel.mock()
|
||||
|
||||
static let kickOnlyViewModel = ManageRoomMemberSheetViewModel.mock(canBan: false)
|
||||
|
||||
static let banOnlyViewModel = ManageRoomMemberSheetViewModel.mock(canKick: false)
|
||||
|
||||
static var previews: some View {
|
||||
ManageRoomMemberSheetView(context: allActionsViewModel.context)
|
||||
.previewDisplayName("All Actions")
|
||||
ManageRoomMemberSheetView(context: kickOnlyViewModel.context)
|
||||
.previewDisplayName("Kick Only")
|
||||
ManageRoomMemberSheetView(context: banOnlyViewModel.context)
|
||||
.previewDisplayName("Ban Only")
|
||||
}
|
||||
}
|
||||
|
||||
private extension ManageRoomMemberSheetViewModel {
|
||||
static func mock(canKick: Bool = true,
|
||||
canBan: Bool = true) -> ManageRoomMemberSheetViewModel {
|
||||
let member = RoomMemberDetails(withProxy: RoomMemberProxyMock.mockDan)
|
||||
return ManageRoomMemberSheetViewModel(member: member,
|
||||
canKick: canKick,
|
||||
canBan: canBan,
|
||||
roomProxy: JoinedRoomProxyMock(.init()),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -80,7 +80,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
self?.displayMediaPreview(mediaPreviewViewModel)
|
||||
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
||||
.tappedOnSenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
||||
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
||||
.composer, .hasScrolled, .viewInRoomTimeline:
|
||||
break
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class MediaEventsTimelineScreenViewModel: MediaEventsTimelineScreenViewModelType
|
||||
self?.displayMediaPreview(mediaPreviewViewModel)
|
||||
case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker,
|
||||
.displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaUploadPreviewScreen,
|
||||
.tappedOnSenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
||||
.displaySenderDetails, .displayMessageForwarding, .displayLocation, .displayResolveSendFailure,
|
||||
.composer, .hasScrolled, .viewInRoomTimeline:
|
||||
break
|
||||
}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .tappedOnSenderDetails(let userID):
|
||||
case .displaySenderDetails(let userID):
|
||||
actionsSubject.send(.displayUser(userID: userID))
|
||||
case .displayMessageForwarding(let forwardingItem):
|
||||
actionsSubject.send(.displayMessageForwarding(forwardingItem: forwardingItem))
|
||||
|
||||
+3
@@ -28,6 +28,9 @@ struct PinnedEventsTimelineScreen: View {
|
||||
.background(.compound.bgCanvasDefault)
|
||||
.interactiveDismissDisabled()
|
||||
.timelineMediaPreview(viewModel: $context.mediaPreviewViewModel)
|
||||
.sheet(item: $timelineContext.manageMemberViewModel) {
|
||||
ManageRoomMemberSheetView(context: $0.context)
|
||||
}
|
||||
.sheet(item: $timelineContext.debugInfo) { TimelineItemDebugView(info: $0) }
|
||||
.sheet(item: $timelineContext.actionMenuInfo) { info in
|
||||
let actions = TimelineItemMenuActionProvider(timelineItem: info.item,
|
||||
|
||||
@@ -80,31 +80,15 @@ struct RoomMembersListScreenViewStateBindings {
|
||||
var searchQuery = ""
|
||||
/// The current mode the screen is in.
|
||||
var mode: RoomMembersListScreenMode = .members
|
||||
/// A selected member to kick, ban, promote etc.
|
||||
var memberToManage: RoomMembersListScreenManagementDetails?
|
||||
/// A sheet model for the selected member to kick, ban, promote etc.
|
||||
var manageMemeberViewModel: ManageRoomMemberSheetViewModel?
|
||||
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomMembersListScreenAlertType>?
|
||||
}
|
||||
|
||||
/// Information about managing a particular room member.
|
||||
struct RoomMembersListScreenManagementDetails: Identifiable {
|
||||
var id: String { member.id }
|
||||
|
||||
/// The member that is being managed.
|
||||
let member: RoomMemberDetails
|
||||
|
||||
/// A management action that can be performed on the member.
|
||||
enum Action { case kick, ban }
|
||||
/// The management actions available for `member`.
|
||||
let actions: [Action]
|
||||
}
|
||||
|
||||
enum RoomMembersListScreenViewAction {
|
||||
case selectMember(RoomMemberDetails)
|
||||
case showMemberDetails(RoomMemberDetails)
|
||||
case kickMember(RoomMemberDetails)
|
||||
case banMember(RoomMemberDetails)
|
||||
case unbanMember(RoomMemberDetails)
|
||||
case invite
|
||||
}
|
||||
|
||||
+30
-68
@@ -15,8 +15,10 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let analytics: AnalyticsService
|
||||
private let mediaProvider: MediaProviderProtocol
|
||||
|
||||
private var members: [RoomMemberProxyProtocol] = []
|
||||
private var currentUserProxy: RoomMemberProxyProtocol?
|
||||
|
||||
private var actionsSubject: PassthroughSubject<RoomMembersListScreenViewModelAction, Never> = .init()
|
||||
|
||||
@@ -34,6 +36,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.analytics = analytics
|
||||
self.mediaProvider = mediaProvider
|
||||
|
||||
super.init(initialViewState: .init(joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
|
||||
bindings: .init(mode: initialMode)),
|
||||
@@ -48,34 +51,6 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
switch viewAction {
|
||||
case .selectMember(let member):
|
||||
selectMember(member)
|
||||
case .showMemberDetails(let member):
|
||||
showMemberDetails(member)
|
||||
case .kickMember(let member):
|
||||
var reason: String?
|
||||
let binding: Binding<String> = .init(get: { reason ?? "" },
|
||||
set: { reason = $0.isBlank ? nil : $0 })
|
||||
state.bindings.alertInfo = .init(id: .kickConfirmation,
|
||||
title: L10n.screenRoomMemberListKickMemberConfirmationTitle,
|
||||
message: L10n.screenRoomMemberListKickMemberConfirmationDescription,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel) { },
|
||||
secondaryButton: .init(title: L10n.screenRoomMemberListKickMemberConfirmationAction) { [weak self] in Task { await self?.kickMember(member, reason: reason) } },
|
||||
textFields: [.init(placeholder: L10n.commonReason,
|
||||
text: binding,
|
||||
autoCapitalization: .sentences,
|
||||
autoCorrectionDisabled: false)])
|
||||
case .banMember(let member):
|
||||
var reason: String?
|
||||
let binding: Binding<String> = .init(get: { reason ?? "" },
|
||||
set: { reason = $0.isBlank ? nil : $0 })
|
||||
state.bindings.alertInfo = .init(id: .banConfirmation,
|
||||
title: L10n.screenRoomMemberListBanMemberConfirmationTitle,
|
||||
message: L10n.screenRoomMemberListBanMemberConfirmationDescription,
|
||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel) { },
|
||||
secondaryButton: .init(title: L10n.screenRoomMemberListBanMemberConfirmationAction) { [weak self] in Task { await self?.banMember(member, reason: reason) } },
|
||||
textFields: [.init(placeholder: L10n.commonReason,
|
||||
text: binding,
|
||||
autoCapitalization: .sentences,
|
||||
autoCorrectionDisabled: false)])
|
||||
case .unbanMember(let member):
|
||||
Task { await unbanMember(member) }
|
||||
case .invite:
|
||||
@@ -118,6 +93,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
let members = members.sorted()
|
||||
let roomMembersDetails = await buildMembersDetails(members: members)
|
||||
self.members = members
|
||||
self.currentUserProxy = members.first { $0.userID == roomProxy.ownUserID }
|
||||
|
||||
self.state = .init(joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
|
||||
joinedMembers: roomMembersDetails.joinedMembers,
|
||||
@@ -168,6 +144,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
}
|
||||
|
||||
private func selectMember(_ member: RoomMemberDetails) {
|
||||
guard let currentUserProxy,
|
||||
currentUserProxy.powerLevel > member.powerLevel else {
|
||||
showMemberDetails(member)
|
||||
return
|
||||
}
|
||||
|
||||
if member.isBanned { // No need to check canBan here, banned users are only shown when it is true.
|
||||
state.bindings.alertInfo = AlertInfo(id: .unbanConfirmation(member),
|
||||
title: L10n.screenRoomMemberListManageMemberUnbanTitle,
|
||||
@@ -179,16 +161,26 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
return
|
||||
}
|
||||
|
||||
var actions = [RoomMembersListScreenManagementDetails.Action]()
|
||||
if state.canKickUsers, member.role != .administrator {
|
||||
actions.append(.kick)
|
||||
}
|
||||
if state.canBanUsers, member.role != .administrator {
|
||||
actions.append(.ban)
|
||||
}
|
||||
|
||||
if !actions.isEmpty {
|
||||
state.bindings.memberToManage = .init(member: member, actions: actions)
|
||||
if state.canKickUsers || state.canBanUsers {
|
||||
let manageMemeberViewModel = ManageRoomMemberSheetViewModel(member: member,
|
||||
canKick: state.canKickUsers,
|
||||
canBan: state.canBanUsers,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: userIndicatorController,
|
||||
analyticsService: analytics,
|
||||
mediaProvider: mediaProvider)
|
||||
manageMemeberViewModel.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss(let shouldShowDetails):
|
||||
state.bindings.manageMemeberViewModel = nil
|
||||
if shouldShowDetails {
|
||||
showMemberDetails(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
state.bindings.manageMemeberViewModel = manageMemeberViewModel
|
||||
} else {
|
||||
showMemberDetails(member)
|
||||
}
|
||||
@@ -200,46 +192,16 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
return
|
||||
}
|
||||
actionsSubject.send(.selectMember(member))
|
||||
state.bindings.memberToManage = nil
|
||||
}
|
||||
|
||||
// MARK: - Member Management
|
||||
|
||||
private func kickMember(_ member: RoomMemberDetails, reason: String?) async {
|
||||
let indicatorTitle = L10n.screenRoomMemberListRemovingUser(member.name ?? member.id)
|
||||
showManageMemberIndicator(title: indicatorTitle)
|
||||
|
||||
switch await roomProxy.kickUser(member.id, reason: reason) {
|
||||
case .success:
|
||||
state.bindings.memberToManage = nil
|
||||
hideManageMemberIndicator(title: indicatorTitle)
|
||||
analytics.trackRoomModeration(action: .KickMember, role: nil)
|
||||
case .failure:
|
||||
showManageMemberFailure(title: indicatorTitle)
|
||||
}
|
||||
}
|
||||
|
||||
private func banMember(_ member: RoomMemberDetails, reason: String?) async {
|
||||
let indicatorTitle = L10n.screenRoomMemberListBanningUser(member.name ?? member.id)
|
||||
showManageMemberIndicator(title: indicatorTitle)
|
||||
|
||||
switch await roomProxy.banUser(member.id, reason: reason) {
|
||||
case .success:
|
||||
state.bindings.memberToManage = nil
|
||||
hideManageMemberIndicator(title: indicatorTitle)
|
||||
analytics.trackRoomModeration(action: .BanMember, role: nil)
|
||||
case .failure:
|
||||
showManageMemberFailure(title: indicatorTitle)
|
||||
}
|
||||
}
|
||||
|
||||
private func unbanMember(_ member: RoomMemberDetails) async {
|
||||
let indicatorTitle = L10n.screenRoomMemberListUnbanningUser(member.name ?? member.id)
|
||||
showManageMemberIndicator(title: indicatorTitle)
|
||||
|
||||
switch await roomProxy.unbanUser(member.id) {
|
||||
case .success:
|
||||
state.bindings.memberToManage = nil
|
||||
hideManageMemberIndicator(title: indicatorTitle)
|
||||
analytics.trackRoomModeration(action: .UnbanMember, role: nil)
|
||||
case .failure:
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct RoomMembersListManageMemberSheet: View {
|
||||
let member: RoomMemberDetails
|
||||
let actions: [RoomMembersListScreenManagementDetails.Action]
|
||||
|
||||
@ObservedObject var context: RoomMembersListScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
AvatarHeaderView(member: member,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
mediaProvider: context.mediaProvider) {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberUserInfo,
|
||||
icon: \.userProfileSolid),
|
||||
kind: .button {
|
||||
context.send(viewAction: .showMemberDetails(member))
|
||||
})
|
||||
|
||||
if actions.contains(.kick) {
|
||||
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberRemove,
|
||||
icon: \.close),
|
||||
kind: .button {
|
||||
context.send(viewAction: .kickMember(member))
|
||||
})
|
||||
}
|
||||
|
||||
if actions.contains(.ban) {
|
||||
ListRow(label: .default(title: L10n.screenRoomMemberListManageMemberBan,
|
||||
icon: \.block,
|
||||
role: .destructive),
|
||||
kind: .button {
|
||||
context.send(viewAction: .banMember(member))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.compoundList()
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationDetents([.large, .fraction(0.54)]) // Maybe find a way to use the ideal height somehow?
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomMembersListManageMemberSheet_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = RoomMembersListScreenViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
|
||||
actions: [.kick, .ban],
|
||||
context: viewModel.context)
|
||||
.previewDisplayName("Joined")
|
||||
|
||||
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockBanned[3]),
|
||||
actions: [],
|
||||
context: viewModel.context)
|
||||
.previewDisplayName("Banned")
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomMembersListManageMemberSheetLive_Previews: PreviewProvider {
|
||||
static let viewModel = RoomMembersListScreenViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
Color.clear
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
RoomMembersListManageMemberSheet(member: .init(withProxy: RoomMemberProxyMock.mockDan),
|
||||
actions: [.kick, .ban],
|
||||
context: viewModel.context)
|
||||
}
|
||||
.previewDisplayName("Sheet")
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomMembersListScreenViewModel {
|
||||
static var mock: RoomMembersListScreenViewModel {
|
||||
RoomMembersListScreenViewModel(initialMode: .members,
|
||||
clientProxy: ClientProxyMock(.init()),
|
||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,8 @@ struct RoomMembersListScreen: View {
|
||||
.autocorrectionDisabled()
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.navigationTitle(L10n.commonPeople)
|
||||
.sheet(item: $context.memberToManage) {
|
||||
RoomMembersListManageMemberSheet(member: $0.member, actions: $0.actions, context: context)
|
||||
.sheet(item: $context.manageMemeberViewModel) {
|
||||
ManageRoomMemberSheetView(context: $0.context)
|
||||
}
|
||||
.alert(item: $context.alertInfo)
|
||||
.toolbar { toolbar }
|
||||
|
||||
@@ -136,7 +136,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentPollForm(mode: mode))
|
||||
case .displayMediaUploadPreviewScreen(let url):
|
||||
actionsSubject.send(.presentMediaUploadPreviewScreen(url))
|
||||
case .tappedOnSenderDetails(userID: let userID):
|
||||
case .displaySenderDetails(userID: let userID):
|
||||
actionsSubject.send(.presentRoomMemberDetails(userID: userID))
|
||||
case .displayMessageForwarding(let forwardingItem):
|
||||
actionsSubject.send(.presentMessageForwarding(forwardingItem: forwardingItem))
|
||||
|
||||
@@ -66,6 +66,9 @@ struct RoomScreen: View {
|
||||
.toolbarBackground(.visible, for: .navigationBar) // Fix the toolbar's background.
|
||||
.overlay { loadingIndicator }
|
||||
.alert(item: $timelineContext.alertInfo)
|
||||
.sheet(item: $timelineContext.manageMemberViewModel) {
|
||||
ManageRoomMemberSheetView(context: $0.context)
|
||||
}
|
||||
.sheet(item: $timelineContext.debugInfo) { TimelineItemDebugView(info: $0) }
|
||||
.sheet(item: $timelineContext.actionMenuInfo) { info in
|
||||
let actions = TimelineItemMenuActionProvider(timelineItem: info.item,
|
||||
|
||||
@@ -19,7 +19,7 @@ enum TimelineViewModelAction {
|
||||
case displayLocationPicker
|
||||
case displayPollForm(mode: PollFormMode)
|
||||
case displayMediaUploadPreviewScreen(url: URL)
|
||||
case tappedOnSenderDetails(userID: String)
|
||||
case displaySenderDetails(userID: String)
|
||||
case displayMessageForwarding(forwardingItem: MessageForwardingItem)
|
||||
case displayMediaPreview(TimelineMediaPreviewViewModel)
|
||||
case displayLocation(body: String, geoURI: GeoURI, description: String?)
|
||||
@@ -99,6 +99,8 @@ struct TimelineViewState: BindableState {
|
||||
var canCurrentUserRedactOthers = false
|
||||
var canCurrentUserRedactSelf = false
|
||||
var canCurrentUserPin = false
|
||||
var canCurrentUserKick = false
|
||||
var canCurrentUserBan = false
|
||||
var isViewSourceEnabled: Bool
|
||||
var hideTimelineMedia: Bool
|
||||
|
||||
@@ -135,7 +137,7 @@ struct TimelineViewStateBindings {
|
||||
/// Key is itemID, value is the collapsed state.
|
||||
var reactionsCollapsed: [TimelineItemIdentifier: Bool]
|
||||
|
||||
var alertInfo: AlertInfo<RoomScreenAlertInfoType>?
|
||||
var alertInfo: AlertInfo<TimelineAlertInfoType>?
|
||||
|
||||
var debugInfo: TimelineItemDebugInfo?
|
||||
|
||||
@@ -144,6 +146,8 @@ struct TimelineViewStateBindings {
|
||||
var reactionSummaryInfo: ReactionSummaryInfo?
|
||||
|
||||
var readReceiptsSummaryInfo: ReadReceiptSummaryInfo?
|
||||
|
||||
var manageMemberViewModel: ManageRoomMemberSheetViewModel?
|
||||
}
|
||||
|
||||
struct TimelineItemActionMenuInfo: Equatable, Identifiable {
|
||||
@@ -172,7 +176,7 @@ struct ReadReceiptSummaryInfo: Identifiable {
|
||||
let id: TimelineItemIdentifier
|
||||
}
|
||||
|
||||
enum RoomScreenAlertInfoType: Hashable {
|
||||
enum TimelineAlertInfoType: Hashable {
|
||||
case audioRecodingPermissionError
|
||||
case pollEndConfirmation(String)
|
||||
case sendingFailed
|
||||
|
||||
@@ -42,6 +42,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private var currentUserProxy: RoomMemberProxyProtocol?
|
||||
|
||||
private var paginateBackwardsTask: Task<Void, Never>?
|
||||
private var paginateForwardsTask: Task<Void, Never>?
|
||||
|
||||
@@ -173,7 +175,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
case .handleTimelineItemMenuAction(let itemID, let action):
|
||||
timelineInteractionHandler.handleTimelineItemMenuAction(action, itemID: itemID)
|
||||
case .tappedOnSenderDetails(userID: let userID):
|
||||
actionsSubject.send(.tappedOnSenderDetails(userID: userID))
|
||||
handleTappedOnSenderDetails(userID: userID)
|
||||
case .displayEmojiPicker(let itemID):
|
||||
timelineInteractionHandler.displayEmojiPicker(for: itemID)
|
||||
case .displayReactionSummary(let itemID, let key):
|
||||
@@ -258,6 +260,41 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func handleTappedOnSenderDetails(userID: String) {
|
||||
// We also need to make sure the user is in the joined state, otherwise we just show the details.
|
||||
guard let memberProxy = roomProxy.membersPublisher.value.first(where: { $0.userID == userID && $0.membership == .join }),
|
||||
let currentUserProxy,
|
||||
currentUserProxy.powerLevel > memberProxy.powerLevel else {
|
||||
actionsSubject.send(.displaySenderDetails(userID: userID))
|
||||
return
|
||||
}
|
||||
|
||||
if state.canCurrentUserBan || state.canCurrentUserKick {
|
||||
let member = RoomMemberDetails(withProxy: memberProxy)
|
||||
let manageMemeberViewModel = ManageRoomMemberSheetViewModel(member: member,
|
||||
canKick: state.canCurrentUserKick,
|
||||
canBan: state.canCurrentUserBan,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: userIndicatorController,
|
||||
analyticsService: analyticsService,
|
||||
mediaProvider: mediaProvider)
|
||||
manageMemeberViewModel.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss(let shouldShowDetails):
|
||||
state.bindings.manageMemberViewModel = nil
|
||||
if shouldShowDetails {
|
||||
actionsSubject.send(.displaySenderDetails(userID: userID))
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
state.bindings.manageMemberViewModel = manageMemeberViewModel
|
||||
} else {
|
||||
actionsSubject.send(.displaySenderDetails(userID: userID))
|
||||
}
|
||||
}
|
||||
|
||||
private func focusLive() {
|
||||
timelineController.focusLive()
|
||||
}
|
||||
@@ -348,6 +385,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
private func updateMembers(_ members: [RoomMemberProxyProtocol]) {
|
||||
state.members = members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
|
||||
dictionary[member.userID] = RoomMemberState(displayName: member.displayName, avatarURL: member.avatarURL)
|
||||
if member.userID == roomProxy.ownUserID {
|
||||
currentUserProxy = member
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +409,18 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
} else {
|
||||
state.canCurrentUserPin = false
|
||||
}
|
||||
|
||||
if case let .success(value) = await roomProxy.canUserKick(userID: roomProxy.ownUserID) {
|
||||
state.canCurrentUserKick = value
|
||||
} else {
|
||||
state.canCurrentUserKick = false
|
||||
}
|
||||
|
||||
if case let .success(value) = await roomProxy.canUserBan(userID: roomProxy.ownUserID) {
|
||||
state.canCurrentUserBan = value
|
||||
} else {
|
||||
state.canCurrentUserBan = false
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSubscriptions() {
|
||||
@@ -898,7 +950,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
userIndicatorController.retractIndicatorWithId(Constants.focusTimelineToastIndicatorID)
|
||||
}
|
||||
|
||||
private func displayAlert(_ type: RoomScreenAlertInfoType) {
|
||||
private func displayAlert(_ type: TimelineAlertInfoType) {
|
||||
switch type {
|
||||
case .audioRecodingPermissionError:
|
||||
state.bindings.alertInfo = .init(id: type,
|
||||
|
||||
@@ -20,6 +20,7 @@ struct RoomMemberDetails: Identifiable, Hashable {
|
||||
|
||||
enum Role { case administrator, moderator, user }
|
||||
let role: Role
|
||||
let powerLevel: Int
|
||||
|
||||
func matches(searchQuery: String) -> Bool {
|
||||
guard !searchQuery.isEmpty else { return true }
|
||||
@@ -38,6 +39,7 @@ extension RoomMemberDetails {
|
||||
isIgnored = proxy.isIgnored
|
||||
isBanned = proxy.membership == .ban
|
||||
role = .init(proxy.role)
|
||||
powerLevel = proxy.powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user