From ad0b2f114f00edbdfad6ba82454ebe759b6b443d Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Thu, 12 Oct 2023 18:15:47 +0200 Subject: [PATCH] @room in user suggestions (#1880) * for now I am using a local RTE solution, need to fix one test * fixed a test * new rte version * pr suggestions * @room added to the suggestions * removed empty space * revert * code improvement * project updated * package --- ElementX.xcodeproj/project.pbxproj | 8 ++- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../en.lproj/Localizable.strings | 29 ++++----- ElementX/Sources/Generated/Strings.swift | 52 ++++++++-------- .../Mocks/Generated/GeneratedMocks.swift | 21 +++++++ ElementX/Sources/Mocks/RoomProxyMock.swift | 2 + ElementX/Sources/Other/Extensions/Array.swift | 10 ++++ .../Sources/Other/MatrixEntityRegex.swift | 2 +- .../Sources/Other/Pills/PillConstants.swift | 27 +++++++++ .../Sources/Other/Pills/PillContext.swift | 2 +- .../CompletionSuggestionModels.swift | 16 +++++ .../CompletionSuggestionService.swift | 60 ++++++++++++------- .../ComposerToolbarViewModel.swift | 6 +- .../View/CompletionSuggestionView.swift | 2 +- .../Sources/Services/Room/RoomProxy.swift | 9 +++ .../Services/Room/RoomProxyProtocol.swift | 2 + NSE/SupportingFiles/target.yml | 1 + .../CompletionSuggestionServiceTests.swift | 37 ++++++++++++ .../ComposerToolbarViewModelTests.swift | 24 +++++++- changelog.d/1875.feature | 1 + project.yml | 2 +- 21 files changed, 241 insertions(+), 76 deletions(-) create mode 100644 ElementX/Sources/Other/Pills/PillConstants.swift create mode 100644 changelog.d/1875.feature diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index b1752cc44..de20840ed 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -313,6 +313,7 @@ 61941DEE5F3834765770BE01 /* InviteUsersScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */; }; 61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; }; 6213C897001F953E21D3CC16 /* CompletionSuggestionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */; }; + 62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; }; 62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; }; 6298AB0906DDD3525CD78C6B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 1081D3630AAD3ACEDDEC3A98 /* LRUCache */; }; 63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */; }; @@ -455,6 +456,7 @@ 8922219C5C934C4155E8CA50 /* SharedUserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */; }; 8944548A684F1C837CEC47F4 /* RoomMembersListScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0946F77B696176E062D037 /* RoomMembersListScreenModels.swift */; }; 89658A44C9FC19B58FD1C226 /* ServerConfirmationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */; }; + 899359A4D1147601F6C4E364 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; }; 899793EFC63DF93C3E0141E7 /* RoomMemberDetailsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA60F848D1C14F873F9621A /* RoomMemberDetailsScreenCoordinator.swift */; }; 8A0BD60CA4A6004DB06B5403 /* MediaUploadingPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669F35C505ACE1110589F875 /* MediaUploadingPreprocessor.swift */; }; 8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5E9C044BEB7C70B1378E91 /* UserSession.swift */; }; @@ -1551,6 +1553,7 @@ CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenCoordinator.swift; sourceTree = ""; }; CACA846B3E3E9A521D98B178 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; CB26F24164E9461B2054D0B3 /* OnboardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreenModels.swift; sourceTree = ""; }; + CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillConstants.swift; sourceTree = ""; }; CBBCC6E74774E79B599625D0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoinedRoomSize+MemberCount.swift"; sourceTree = ""; }; CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenMemberCell.swift; sourceTree = ""; }; @@ -3356,6 +3359,7 @@ 15748C254911E3654C93B0ED /* MentionBuilder.swift */, E1E0B4A34E69BD2132BEC521 /* MessageText.swift */, 1B53D6C5C0D14B04D3AB3F6E /* PillAttachmentViewProvider.swift */, + CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */, 86376BEE425704AEE197CA54 /* PillContext.swift */, 9CF1EE0AA78470C674554262 /* PillTextAttachment.swift */, 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */, @@ -4580,6 +4584,7 @@ B89990DD875B0B603D4D4332 /* NotificationItemProxyProtocol.swift in Sources */, B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */, 234E2C782981003971ABE96E /* PermalinkBuilder.swift in Sources */, + 62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */, 55CDD3968D95D1A820B5491E /* PlaceholderAvatarImage.swift in Sources */, F12F6BED7B6D7EE4BEE55039 /* PlainMentionBuilder.swift in Sources */, 414F50CFCFEEE2611127DCFB /* RestorationToken.swift in Sources */, @@ -5035,6 +5040,7 @@ 80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */, 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */, EE4E2C1922BBF5169E213555 /* PillAttachmentViewProvider.swift in Sources */, + 899359A4D1147601F6C4E364 /* PillConstants.swift in Sources */, 767D366C40F1311CFA333763 /* PillContext.swift in Sources */, 7708976CEE6AFB5CFAEFBA68 /* PillTextAttachment.swift in Sources */, 8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */, @@ -5984,7 +5990,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-wysiwyg-composer-swift"; requirement = { kind = exactVersion; - version = 2.14.2; + version = 2.14.3; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d9a7b6d78..36026c369 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -129,8 +129,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "6e567f647f34b58f0ff6d48f2bb6e535e58a6593", - "version" : "2.14.2" + "revision" : "a82eece5d3ce6aff87bcd5460f04bedc9e2492cf", + "version" : "2.14.3" } }, { diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index b0d8f65ba..4ed27f3fa 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -1,12 +1,16 @@ "Notification" = "Notification"; +"a11y_delete" = "Delete"; "a11y_hide_password" = "Hide password"; "a11y_notifications_mentions_only" = "Mentions only"; "a11y_notifications_muted" = "Muted"; +"a11y_pause" = "Pause"; +"a11y_play" = "Play"; "a11y_poll" = "Poll"; "a11y_poll_end" = "Ended poll"; "a11y_send_files" = "Send files"; "a11y_show_password" = "Show password"; "a11y_user_menu" = "User menu"; +"a11y_voice_message_record" = "Record voice message. Double tap and hold to record. Release to end recording."; "action_accept" = "Accept"; "action_add_to_timeline" = "Add to timeline"; "action_back" = "Back"; @@ -63,6 +67,7 @@ "action_send_message" = "Send message"; "action_share" = "Share"; "action_share_link" = "Share link"; +"action_sign_in_again" = "Sign in again"; "action_skip" = "Skip"; "action_start" = "Start"; "action_start_chat" = "Start chat"; @@ -88,6 +93,7 @@ "common_emote" = "* %1$@ %2$@"; "common_encryption_enabled" = "Encryption enabled"; "common_error" = "Error"; +"common_everyone" = "Everyone"; "common_file" = "File"; "common_forward_message" = "Forward message"; "common_gif" = "GIF"; @@ -124,7 +130,6 @@ "common_search_results" = "Search results"; "common_secure_backup" = "Secure backup"; "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"; @@ -202,7 +207,7 @@ "notification_unread_notified_messages_in_room" = "%1$@ in %2$@"; "notification_unread_notified_messages_in_room_and_invitation" = "%1$@ in %2$@ and %3$@"; "preference_rageshake" = "Rageshake to report bug"; -"rageshake_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"; +"rageshake_detection_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"; @@ -269,6 +274,7 @@ "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_subtitle" = "What is the address of your server?"; +"screen_change_server_title" = "Select your server"; "screen_create_poll_add_option_btn" = "Add option"; "screen_create_poll_anonymous_desc" = "Show results only after poll ends"; "screen_create_poll_anonymous_headline" = "Hide votes"; @@ -340,7 +346,6 @@ "screen_onboarding_sign_in_manually" = "Sign in manually"; "screen_onboarding_sign_in_with_qr_code" = "Sign in with QR code"; "screen_onboarding_sign_up" = "Create account"; -"screen_onboarding_subtitle" = "Communicate and collaborate securely"; "screen_onboarding_welcome_message" = "Welcome to the fastest Element ever. Supercharged for speed and simplicity."; "screen_onboarding_welcome_subtitle" = "Welcome to %1$@. Supercharged, for speed and simplicity."; "screen_onboarding_welcome_title" = "Be in your element"; @@ -428,13 +433,17 @@ "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_settings_oidc_account" = "Account and devices"; "screen_share_location_title" = "Share location"; "screen_share_my_location_action" = "Share my location"; "screen_share_open_apple_maps" = "Open in Apple Maps"; "screen_share_open_google_maps" = "Open in Google Maps"; "screen_share_open_osm_maps" = "Open in OpenStreetMap"; "screen_share_this_location_action" = "Share this location"; +"screen_signed_out_reason_1" = "You’ve changed your password on another session"; +"screen_signed_out_reason_2" = "You have deleted the session from another session"; +"screen_signed_out_reason_3" = "Your server’s administrator has invalidated your access"; +"screen_signed_out_subtitle" = "You might have been signed out for one of the reasons listed below. Please sign in again to continue using %@."; +"screen_signed_out_title" = "You’re signed out"; "screen_signout_confirmation_dialog_content" = "Are you sure you want to sign out?"; "screen_signout_confirmation_dialog_title" = "Sign out"; "screen_signout_in_progress_dialog_content" = "Signing out…"; @@ -454,7 +463,6 @@ "session_verification_banner_title" = "Verify it’s you"; "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"; @@ -516,14 +524,10 @@ "dialog_title_success" = "Success"; "notification_fallback_content" = "Notification"; "notification_room_action_quick_reply" = "Quick reply"; -"rageshake_detection_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"; -"screen_account_provider_continue" = "Continue"; "screen_analytics_settings_help_us_improve" = "Share anonymous usage data to help us identify issues."; "screen_analytics_settings_read_terms" = "You can read all our terms %1$@."; "screen_analytics_settings_read_terms_content_link" = "here"; "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_submit" = "Continue"; -"screen_change_server_title" = "Select your server"; "screen_create_room_title" = "Create a room"; "screen_dm_details_block_alert_action" = "Block"; "screen_dm_details_block_alert_description" = "Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime."; @@ -531,18 +535,11 @@ "screen_dm_details_unblock_alert_action" = "Unblock"; "screen_dm_details_unblock_alert_description" = "You'll be able to see all messages from them again."; "screen_dm_details_unblock_user" = "Unblock user"; -"screen_login_password_hint" = "Password"; -"screen_login_submit" = "Continue"; "screen_login_subtitle" = "Matrix is an open network for secure, decentralised communication."; -"screen_login_username_hint" = "Username"; "screen_report_content_block_user" = "Block user"; "screen_room_details_leave_room_title" = "Leave room"; -"screen_room_details_people_title" = "People"; "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_retry_send_menu_remove_action" = "Remove"; -"screen_session_verification_cancelled_title" = "Verification cancelled"; -"screen_session_verification_positive_button_ready" = "Start"; "screen_signout_confirmation_dialog_submit" = "Sign out"; "screen_signout_preference_item" = "Sign out"; diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index d11d9c007..4b98f30a5 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -10,12 +10,18 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces public enum L10n { + /// Delete + public static var a11yDelete: String { return L10n.tr("Localizable", "a11y_delete") } /// Hide password public static var a11yHidePassword: String { return L10n.tr("Localizable", "a11y_hide_password") } /// Mentions only public static var a11yNotificationsMentionsOnly: String { return L10n.tr("Localizable", "a11y_notifications_mentions_only") } /// Muted public static var a11yNotificationsMuted: String { return L10n.tr("Localizable", "a11y_notifications_muted") } + /// Pause + public static var a11yPause: String { return L10n.tr("Localizable", "a11y_pause") } + /// Play + public static var a11yPlay: String { return L10n.tr("Localizable", "a11y_play") } /// Poll public static var a11yPoll: String { return L10n.tr("Localizable", "a11y_poll") } /// Ended poll @@ -26,6 +32,8 @@ public enum L10n { public static var a11yShowPassword: String { return L10n.tr("Localizable", "a11y_show_password") } /// User menu public static var a11yUserMenu: String { return L10n.tr("Localizable", "a11y_user_menu") } + /// Record voice message. Double tap and hold to record. Release to end recording. + public static var a11yVoiceMessageRecord: String { return L10n.tr("Localizable", "a11y_voice_message_record") } /// Accept public static var actionAccept: String { return L10n.tr("Localizable", "action_accept") } /// Add to timeline @@ -142,6 +150,8 @@ public enum L10n { public static var actionShare: String { return L10n.tr("Localizable", "action_share") } /// Share link public static var actionShareLink: String { return L10n.tr("Localizable", "action_share_link") } + /// Sign in again + public static var actionSignInAgain: String { return L10n.tr("Localizable", "action_sign_in_again") } /// Skip public static var actionSkip: String { return L10n.tr("Localizable", "action_skip") } /// Start @@ -192,6 +202,8 @@ public enum L10n { public static var commonEncryptionEnabled: String { return L10n.tr("Localizable", "common_encryption_enabled") } /// Error public static var commonError: String { return L10n.tr("Localizable", "common_error") } + /// Everyone + public static var commonEveryone: String { return L10n.tr("Localizable", "common_everyone") } /// File public static var commonFile: String { return L10n.tr("Localizable", "common_file") } /// Forward message @@ -284,8 +296,6 @@ public enum L10n { public static var commonSecureBackup: String { return L10n.tr("Localizable", "common_secure_backup") } /// Security public static var commonSecurity: String { return L10n.tr("Localizable", "common_security") } - /// Select your server - public static var commonSelectYourServer: String { return L10n.tr("Localizable", "common_select_your_server") } /// Sending… public static var commonSending: String { return L10n.tr("Localizable", "common_sending") } /// Server not supported @@ -498,8 +508,6 @@ public enum L10n { public static var preferenceRageshake: String { return L10n.tr("Localizable", "preference_rageshake") } /// You seem to be shaking the phone in frustration. Would you like to open the bug report screen? public static var rageshakeDetectionDialogContent: String { return L10n.tr("Localizable", "rageshake_detection_dialog_content") } - /// You seem to be shaking the phone in frustration. Would you like to open the bug report screen? - public static var rageshakeDialogContent: String { return L10n.tr("Localizable", "rageshake_dialog_content") } /// This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages. public static var reportContentExplanation: String { return L10n.tr("Localizable", "report_content_explanation") } /// Reason for reporting this content @@ -560,8 +568,6 @@ public enum L10n { } /// Change account provider public static var screenAccountProviderChange: String { return L10n.tr("Localizable", "screen_account_provider_change") } - /// Continue - public static var screenAccountProviderContinue: String { return L10n.tr("Localizable", "screen_account_provider_continue") } /// Homeserver address public static var screenAccountProviderFormHint: String { return L10n.tr("Localizable", "screen_account_provider_form_hint") } /// Enter a search term or a domain address. @@ -660,8 +666,6 @@ public enum L10n { public static func screenChangeServerFormNotice(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_change_server_form_notice", String(describing: p1)) } - /// Continue - public static var screenChangeServerSubmit: String { return L10n.tr("Localizable", "screen_change_server_submit") } /// What is the address of your server? public static var screenChangeServerSubtitle: String { return L10n.tr("Localizable", "screen_change_server_subtitle") } /// Select your server @@ -760,10 +764,6 @@ public enum L10n { public static var screenLoginErrorUnsupportedAuthentication: String { return L10n.tr("Localizable", "screen_login_error_unsupported_authentication") } /// Enter your details public static var screenLoginFormHeader: String { return L10n.tr("Localizable", "screen_login_form_header") } - /// Password - public static var screenLoginPasswordHint: String { return L10n.tr("Localizable", "screen_login_password_hint") } - /// Continue - public static var screenLoginSubmit: String { return L10n.tr("Localizable", "screen_login_submit") } /// Matrix is an open network for secure, decentralised communication. public static var screenLoginSubtitle: String { return L10n.tr("Localizable", "screen_login_subtitle") } /// Welcome back! @@ -772,8 +772,6 @@ public enum L10n { public static func screenLoginTitleWithHomeserver(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_login_title_with_homeserver", String(describing: p1)) } - /// Username - public static var screenLoginUsernameHint: String { return L10n.tr("Localizable", "screen_login_username_hint") } /// Failed selecting media, please try again. public static var screenMediaPickerErrorFailedSelection: String { return L10n.tr("Localizable", "screen_media_picker_error_failed_selection") } /// Failed processing media to upload, please try again. @@ -844,8 +842,6 @@ public enum L10n { public static var screenOnboardingSignInWithQrCode: String { return L10n.tr("Localizable", "screen_onboarding_sign_in_with_qr_code") } /// Create account public static var screenOnboardingSignUp: String { return L10n.tr("Localizable", "screen_onboarding_sign_up") } - /// Communicate and collaborate securely - public static var screenOnboardingSubtitle: String { return L10n.tr("Localizable", "screen_onboarding_subtitle") } /// Welcome to the fastest Element ever. Supercharged for speed and simplicity. public static var screenOnboardingWelcomeMessage: String { return L10n.tr("Localizable", "screen_onboarding_welcome_message") } /// Welcome to %1$@. Supercharged, for speed and simplicity. @@ -906,8 +902,6 @@ public enum L10n { public static var screenRoomDetailsNotificationModeDefault: String { return L10n.tr("Localizable", "screen_room_details_notification_mode_default") } /// Notifications public static var screenRoomDetailsNotificationTitle: String { return L10n.tr("Localizable", "screen_room_details_notification_title") } - /// People - public static var screenRoomDetailsPeopleTitle: String { return L10n.tr("Localizable", "screen_room_details_people_title") } /// Room name public static var screenRoomDetailsRoomNameLabel: String { return L10n.tr("Localizable", "screen_room_details_room_name_label") } /// Security @@ -984,8 +978,6 @@ public enum L10n { public static var screenRoomReactionsShowLess: String { return L10n.tr("Localizable", "screen_room_reactions_show_less") } /// Show more public static var screenRoomReactionsShowMore: String { return L10n.tr("Localizable", "screen_room_reactions_show_more") } - /// Remove - public static var screenRoomRetrySendMenuRemoveAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_remove_action") } /// Send again public static var screenRoomRetrySendMenuSendAgainAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_send_again_action") } /// Your message failed to send @@ -1020,8 +1012,6 @@ public enum L10n { } /// Something doesn’t seem right. Either the request timed out or the request was denied. public static var screenSessionVerificationCancelledSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_cancelled_subtitle") } - /// Verification cancelled - public static var screenSessionVerificationCancelledTitle: String { return L10n.tr("Localizable", "screen_session_verification_cancelled_title") } /// Confirm that the emojis below match those shown on your other session. public static var screenSessionVerificationCompareEmojisSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_compare_emojis_subtitle") } /// Compare emojis @@ -1036,8 +1026,6 @@ public enum L10n { public static var screenSessionVerificationPositiveButtonCanceled: String { return L10n.tr("Localizable", "screen_session_verification_positive_button_canceled") } /// I am ready public static var screenSessionVerificationPositiveButtonInitial: String { return L10n.tr("Localizable", "screen_session_verification_positive_button_initial") } - /// Start - public static var screenSessionVerificationPositiveButtonReady: String { return L10n.tr("Localizable", "screen_session_verification_positive_button_ready") } /// Waiting to match public static var screenSessionVerificationPositiveButtonVerifyingOngoing: String { return L10n.tr("Localizable", "screen_session_verification_positive_button_verifying_ongoing") } /// Compare the unique emoji, ensuring they appear in the same order. @@ -1050,8 +1038,6 @@ public enum L10n { public static var screenSessionVerificationWaitingToAcceptSubtitle: String { return L10n.tr("Localizable", "screen_session_verification_waiting_to_accept_subtitle") } /// Waiting to accept request public static var screenSessionVerificationWaitingToAcceptTitle: String { return L10n.tr("Localizable", "screen_session_verification_waiting_to_accept_title") } - /// Account and devices - public static var screenSettingsOidcAccount: String { return L10n.tr("Localizable", "screen_settings_oidc_account") } /// Share location public static var screenShareLocationTitle: String { return L10n.tr("Localizable", "screen_share_location_title") } /// Share my location @@ -1064,6 +1050,18 @@ public enum L10n { public static var screenShareOpenOsmMaps: String { return L10n.tr("Localizable", "screen_share_open_osm_maps") } /// Share this location public static var screenShareThisLocationAction: String { return L10n.tr("Localizable", "screen_share_this_location_action") } + /// You’ve changed your password on another session + public static var screenSignedOutReason1: String { return L10n.tr("Localizable", "screen_signed_out_reason_1") } + /// You have deleted the session from another session + public static var screenSignedOutReason2: String { return L10n.tr("Localizable", "screen_signed_out_reason_2") } + /// Your server’s administrator has invalidated your access + public static var screenSignedOutReason3: String { return L10n.tr("Localizable", "screen_signed_out_reason_3") } + /// You might have been signed out for one of the reasons listed below. Please sign in again to continue using %@. + public static func screenSignedOutSubtitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "screen_signed_out_subtitle", String(describing: p1)) + } + /// You’re signed out + public static var screenSignedOutTitle: String { return L10n.tr("Localizable", "screen_signed_out_title") } /// Are you sure you want to sign out? public static var screenSignoutConfirmationDialogContent: String { return L10n.tr("Localizable", "screen_signout_confirmation_dialog_content") } /// Sign out @@ -1114,8 +1112,6 @@ public enum L10n { public static var settingsRageshake: String { return L10n.tr("Localizable", "settings_rageshake") } /// Detection threshold public static var settingsRageshakeDetectionThreshold: String { return L10n.tr("Localizable", "settings_rageshake_detection_threshold") } - /// General - public static var settingsTitleGeneral: String { return L10n.tr("Localizable", "settings_title_general") } /// Version: %1$@ (%2$@) public static func settingsVersionNumber(_ p1: Any, _ p2: Any) -> String { return L10n.tr("Localizable", "settings_version_number", String(describing: p1), String(describing: p2)) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 715734555..337c01453 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -1749,6 +1749,27 @@ class RoomProxyMock: RoomProxyProtocol { return canUserRedactUserIDReturnValue } } + //MARK: - canUserTriggerRoomNotification + + var canUserTriggerRoomNotificationUserIDCallsCount = 0 + var canUserTriggerRoomNotificationUserIDCalled: Bool { + return canUserTriggerRoomNotificationUserIDCallsCount > 0 + } + var canUserTriggerRoomNotificationUserIDReceivedUserID: String? + var canUserTriggerRoomNotificationUserIDReceivedInvocations: [String] = [] + var canUserTriggerRoomNotificationUserIDReturnValue: Result! + var canUserTriggerRoomNotificationUserIDClosure: ((String) async -> Result)? + + func canUserTriggerRoomNotification(userID: String) async -> Result { + canUserTriggerRoomNotificationUserIDCallsCount += 1 + canUserTriggerRoomNotificationUserIDReceivedUserID = userID + canUserTriggerRoomNotificationUserIDReceivedInvocations.append(userID) + if let canUserTriggerRoomNotificationUserIDClosure = canUserTriggerRoomNotificationUserIDClosure { + return await canUserTriggerRoomNotificationUserIDClosure(userID) + } else { + return canUserTriggerRoomNotificationUserIDReturnValue + } + } //MARK: - createPoll var createPollQuestionAnswersPollKindCallsCount = 0 diff --git a/ElementX/Sources/Mocks/RoomProxyMock.swift b/ElementX/Sources/Mocks/RoomProxyMock.swift index 27c60cb3f..af8e24378 100644 --- a/ElementX/Sources/Mocks/RoomProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomProxyMock.swift @@ -35,6 +35,7 @@ struct RoomProxyMockConfiguration { var inviter: RoomMemberProxyMock? var memberForID: RoomMemberProxyMock = .mockMe var ownUserID = "@alice:somewhere.org" + var canUserTriggerRoomNotification = false var invitedMembersCount = 100 var joinedMembersCount = 50 @@ -81,5 +82,6 @@ extension RoomProxyMock { setTopicClosure = { _ in .success(()) } getMemberUserIDReturnValue = .success(configuration.memberForID) canUserRedactUserIDReturnValue = .success(false) + canUserTriggerRoomNotificationUserIDReturnValue = .success(configuration.canUserTriggerRoomNotification) } } diff --git a/ElementX/Sources/Other/Extensions/Array.swift b/ElementX/Sources/Other/Extensions/Array.swift index 26830cb57..3e519f955 100644 --- a/ElementX/Sources/Other/Extensions/Array.swift +++ b/ElementX/Sources/Other/Extensions/Array.swift @@ -73,3 +73,13 @@ extension Array where Element == RoomTimelineItemProtocol { first { $0.id.timelineID == id.timelineID } } } + +extension Array { + func appending(_ element: Element) -> [Element] { + self + [element] + } + + func appending(_ elements: [Element]) -> [Element] { + self + elements + } +} diff --git a/ElementX/Sources/Other/MatrixEntityRegex.swift b/ElementX/Sources/Other/MatrixEntityRegex.swift index 090316c75..fc56c6f6a 100644 --- a/ElementX/Sources/Other/MatrixEntityRegex.swift +++ b/ElementX/Sources/Other/MatrixEntityRegex.swift @@ -38,7 +38,7 @@ enum MatrixEntityRegex: String { case .eventId: return "\\$[a-z0-9_\\-\\/]+(:[a-z0-9]+\\.[a-z0-9]+)?" case .allUsers: - return "@room" + return PillConstants.atRoom } } diff --git a/ElementX/Sources/Other/Pills/PillConstants.swift b/ElementX/Sources/Other/Pills/PillConstants.swift new file mode 100644 index 000000000..9dbe25520 --- /dev/null +++ b/ElementX/Sources/Other/Pills/PillConstants.swift @@ -0,0 +1,27 @@ +// +// 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 + +enum PillConstants { + static let atRoom = "@room" + static var everyone: String { + L10n.commonEveryone + } + + /// Used by the WYSIWYG as the urlString value to identify @room mentions + static let composerAtRoomURLString = "#" +} diff --git a/ElementX/Sources/Other/Pills/PillContext.swift b/ElementX/Sources/Other/Pills/PillContext.swift index fa8394f17..c66fad64d 100644 --- a/ElementX/Sources/Other/Pills/PillContext.swift +++ b/ElementX/Sources/Other/Pills/PillContext.swift @@ -52,7 +52,7 @@ final class PillContext: ObservableObject { } } case .allUsers: - viewState = PillViewState(contentID: roomContext.viewState.roomID, isOwnMention: true, name: roomContext.viewState.roomTitle, displayText: "@room", avatarURL: roomContext.viewState.roomAvatarURL) + viewState = PillViewState(contentID: roomContext.viewState.roomID, isOwnMention: true, name: roomContext.viewState.roomTitle, displayText: PillConstants.atRoom, avatarURL: roomContext.viewState.roomAvatarURL) } } } diff --git a/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionModels.swift b/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionModels.swift index 3a459349a..02adaecb3 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionModels.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionModels.swift @@ -20,11 +20,23 @@ import WysiwygComposer enum SuggestionItem: Identifiable, Equatable { case user(item: MentionSuggestionItem) + case allUsers(item: MentionSuggestionItem) var id: String { switch self { case .user(let user): return user.id + case .allUsers: + return PillConstants.atRoom + } + } + + var name: String? { + switch self { + case .user(let user): + return user.displayName + case .allUsers: + return PillConstants.everyone } } } @@ -33,6 +45,10 @@ struct MentionSuggestionItem: Identifiable, Equatable { let id: String let displayName: String? let avatarURL: URL? + + static func allUsersMention(roomAvatar: URL?) -> Self { + MentionSuggestionItem(id: PillConstants.atRoom, displayName: PillConstants.everyone, avatarURL: roomAvatar) + } } extension WysiwygComposer.SuggestionPattern { diff --git a/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift b/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift index 2c0c592ff..146ac978c 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift @@ -18,50 +18,70 @@ import Combine import Foundation final class CompletionSuggestionService: CompletionSuggestionServiceProtocol { + private var canMentionAllUsers = false let areSuggestionsEnabled: Bool - let suggestionsPublisher: AnyPublisher<[SuggestionItem], Never> + private(set) var suggestionsPublisher: AnyPublisher<[SuggestionItem], Never> = Empty().eraseToAnyPublisher() private let suggestionTriggerSubject = CurrentValueSubject(nil) init(roomProxy: RoomProxyProtocol, areSuggestionsEnabled: Bool) { self.areSuggestionsEnabled = areSuggestionsEnabled guard areSuggestionsEnabled else { - suggestionsPublisher = Empty().eraseToAnyPublisher() return } suggestionsPublisher = suggestionTriggerSubject .combineLatest(roomProxy.members) - .map { suggestionTrigger, members -> [SuggestionItem] in - guard let suggestionTrigger else { + .map { [weak self] suggestionPattern, members -> [SuggestionItem] in + guard let suggestionPattern else { return [] } - switch suggestionTrigger.type { + switch suggestionPattern.type { case .user: - return members.filter { member in - guard !member.isAccountOwner && member.membership == .join else { - return false + var membersSuggestion = members + .compactMap { member -> SuggestionItem? in + guard !member.isAccountOwner, + member.membership == .join, + Self.isIncluded(searchText: suggestionPattern.text, userID: member.userID, displayName: member.displayName) else { + return nil + } + return SuggestionItem.user(item: .init(id: member.userID, displayName: member.displayName, avatarURL: member.avatarURL)) } - - let containedInUserID = member.userID.localizedStandardContains(suggestionTrigger.text.lowercased()) - - let containedInDisplayName: Bool - if let displayName = member.displayName { - containedInDisplayName = displayName.localizedStandardContains(suggestionTrigger.text.lowercased()) - } else { - containedInDisplayName = false - } - - return containedInUserID || containedInDisplayName + if self?.canMentionAllUsers == true, + Self.isIncluded(searchText: suggestionPattern.text, userID: PillConstants.atRoom, displayName: PillConstants.everyone) { + membersSuggestion + .append(SuggestionItem.allUsers(item: .allUsersMention(roomAvatar: roomProxy.avatarURL))) } - .map { SuggestionItem.user(item: .init(id: $0.userID, displayName: $0.displayName, avatarURL: $0.avatarURL)) } + return membersSuggestion } } .debounce(for: 0.5, scheduler: DispatchQueue.main) .eraseToAnyPublisher() + + Task { + switch await roomProxy.canUserTriggerRoomNotification(userID: roomProxy.ownUserID) { + case .success(let value): + canMentionAllUsers = value + case .failure: + canMentionAllUsers = false + } + } } func setSuggestionTrigger(_ suggestionTrigger: SuggestionPattern?) { suggestionTriggerSubject.value = suggestionTrigger } + + private static func isIncluded(searchText: String, userID: String, displayName: String?) -> Bool { + let containedInUserID = userID.localizedStandardContains(searchText.lowercased()) + + let containedInDisplayName: Bool + if let displayName { + containedInDisplayName = displayName.localizedStandardContains(searchText.lowercased()) + } else { + containedInDisplayName = false + } + + return containedInUserID || containedInDisplayName + } } diff --git a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift index 23eee165b..085d9f332 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/ComposerToolbarViewModel.swift @@ -166,8 +166,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool wysiwygViewModel.mentionReplacer = ComposerMentionReplacer { urlString, string in let attributedString: NSMutableAttributedString - // This is the all room mention case - if urlString == "#" { + // This is the all room mention special case + if urlString == PillConstants.composerAtRoomURLString { attributedString = NSMutableAttributedString(string: string, attributes: [.MatrixAllUsersMention: true]) } else { attributedString = NSMutableAttributedString(string: string, attributes: [.link: URL(string: urlString) as Any]) @@ -188,6 +188,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool return } wysiwygViewModel.setMention(url: url.absoluteString, name: item.displayName ?? item.id, mentionType: .user) + case .allUsers: + wysiwygViewModel.setAtRoomMention() } } diff --git a/ElementX/Sources/Screens/ComposerToolbar/View/CompletionSuggestionView.swift b/ElementX/Sources/Screens/ComposerToolbar/View/CompletionSuggestionView.swift index dd6460244..ca2394c8c 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/View/CompletionSuggestionView.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/View/CompletionSuggestionView.swift @@ -68,7 +68,7 @@ struct CompletionSuggestionView: View { onTap(item) } label: { switch item { - case .user(let mention): + case .user(let mention), .allUsers(let mention): MentionSuggestionItemView(imageProvider: imageProvider, item: mention) } } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index f72110723..13492c76e 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -661,6 +661,15 @@ class RoomProxy: RoomProxyProtocol { return .failure(.failedCheckingPermission) } } + + func canUserTriggerRoomNotification(userID: String) async -> Result { + do { + return try await .success(room.canUserTriggerRoomNotification(userId: userID)) + } catch { + MXLog.error("Failed checking if the user can trigger room notification with error: \(error)") + return .failure(.failedCheckingPermission) + } + } // MARK: - Polls diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index dcb69255b..edf6b8622 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -168,6 +168,8 @@ protocol RoomProxyProtocol { func uploadAvatar(media: MediaInfo) async -> Result func canUserRedact(userID: String) async -> Result + + func canUserTriggerRoomNotification(userID: String) async -> Result func createPoll(question: String, answers: [String], pollKind: Poll.Kind) async -> Result diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index 0d8ac5c81..d77d37757 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -97,6 +97,7 @@ targets: - path: ../../ElementX/Sources/Other/UserPreference.swift - path: ../../ElementX/Sources/Other/TestablePreview.swift - path: ../../ElementX/Sources/Other/Pills/PlainMentionBuilder.swift + - path: ../../ElementX/Sources/Other/Pills/PillConstants.swift - path: ../../ElementX/Sources/Services/Background/BackgroundTaskProtocol.swift - path: ../../ElementX/Sources/Services/Background/BackgroundTaskServiceProtocol.swift - path: ../../ElementX/Sources/Services/Keychain/KeychainController.swift diff --git a/UnitTests/Sources/CompletionSuggestionServiceTests.swift b/UnitTests/Sources/CompletionSuggestionServiceTests.swift index befa46d1f..44656951b 100644 --- a/UnitTests/Sources/CompletionSuggestionServiceTests.swift +++ b/UnitTests/Sources/CompletionSuggestionServiceTests.swift @@ -49,5 +49,42 @@ final class CompletionSuggestionServiceTests: XCTestCase { } service.setSuggestionTrigger(.init(type: .user, text: "me")) try await deferred.fulfill() + + deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in + suggestions == [] + } + service.setSuggestionTrigger(.init(type: .user, text: "room")) + try await deferred.fulfill() + + deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in + suggestions == [] + } + service.setSuggestionTrigger(.init(type: .user, text: "everyon")) + try await deferred.fulfill() + } + + func testUserSuggestonsIncludingAllUsers() async throws { + let alice: RoomMemberProxyMock = .mockAlice + let members: [RoomMemberProxyMock] = [alice, .mockBob, .mockCharlie, .mockMe] + let roomProxyMock = RoomProxyMock(with: .init(displayName: "test", members: members, canUserTriggerRoomNotification: true)) + let service = CompletionSuggestionService(roomProxy: roomProxyMock, areSuggestionsEnabled: true) + + var deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in + suggestions == [] + } + + try await deferred.fulfill() + + deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in + suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil))] + } + service.setSuggestionTrigger(.init(type: .user, text: "ro")) + try await deferred.fulfill() + + deferred = deferFulfillment(service.suggestionsPublisher) { suggestions in + suggestions == [.allUsers(item: .allUsersMention(roomAvatar: nil))] + } + service.setSuggestionTrigger(.init(type: .user, text: "every")) + try await deferred.fulfill() } } diff --git a/UnitTests/Sources/ComposerToolbarViewModelTests.swift b/UnitTests/Sources/ComposerToolbarViewModelTests.swift index a12b52914..bd6c98260 100644 --- a/UnitTests/Sources/ComposerToolbarViewModelTests.swift +++ b/UnitTests/Sources/ComposerToolbarViewModelTests.swift @@ -123,14 +123,24 @@ class ComposerToolbarViewModelTests: XCTestCase { XCTAssertEqual(completionSuggestionServiceMock.setSuggestionTriggerReceivedInvocations, [nil, .init(type: .user, text: "test"), nil]) } - func testSelectedSuggestion() { + func testSelectedUserSuggestion() { let suggestion = SuggestionItem.user(item: .init(id: "@test:matrix.org", displayName: "Test", avatarURL: nil)) viewModel.context.send(viewAction: .selectedSuggestion(suggestion)) - XCTAssertEqual(wysiwygViewModel.content.html, "Test ") + XCTAssertEqual(wysiwygViewModel.content.html, "Test ") } - func testUserMentionInRTE() { + func testAllUsersSuggestion() { + let suggestion = SuggestionItem.allUsers(item: .allUsersMention(roomAvatar: nil)) + viewModel.context.send(viewAction: .selectedSuggestion(suggestion)) + + var string = "@room" + // swiftlint:disable:next force_unwrapping + string.unicodeScalars.append(UnicodeScalar(String.nbsp)!) + XCTAssertEqual(wysiwygViewModel.content.html, string) + } + + func testUserMentionPillInRTE() { let userID = "@test:matrix.org" let suggestion = SuggestionItem.user(item: .init(id: userID, displayName: "Test", avatarURL: nil)) viewModel.context.send(viewAction: .selectedSuggestion(suggestion)) @@ -138,4 +148,12 @@ class ComposerToolbarViewModelTests: XCTestCase { let attachment = wysiwygViewModel.textView.attributedText.attribute(.attachment, at: 0, effectiveRange: nil) as? PillTextAttachment XCTAssertEqual(attachment?.pillData?.type, .user(userID: userID)) } + + func testAllUsersMentionPillInRTE() { + let suggestion = SuggestionItem.allUsers(item: .allUsersMention(roomAvatar: nil)) + viewModel.context.send(viewAction: .selectedSuggestion(suggestion)) + + let attachment = wysiwygViewModel.textView.attributedText.attribute(.attachment, at: 0, effectiveRange: nil) as? PillTextAttachment + XCTAssertEqual(attachment?.pillData?.type, .allUsers) + } } diff --git a/changelog.d/1875.feature b/changelog.d/1875.feature new file mode 100644 index 000000000..3f4555bba --- /dev/null +++ b/changelog.d/1875.feature @@ -0,0 +1 @@ +All users mention @room now appears in the completion suggestion view. \ No newline at end of file diff --git a/project.yml b/project.yml index 7a88f2ede..a416d536b 100644 --- a/project.yml +++ b/project.yml @@ -113,7 +113,7 @@ packages: minorVersion: 2.0.0 WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - exactVersion: 2.14.2 + exactVersion: 2.14.3 # path: ../matrix-wysiwyg/platforms/ios/lib/WysiwygComposer SwiftOGG: url: https://github.com/vector-im/swift-ogg