diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index b3c8f6bac..1f667ead5 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -56,6 +56,9 @@ jobs: run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 + + - name: Xcodegen + run: mint run yonaskolb/XcodeGen@2.39.0 # Main step - name: Unit tests diff --git a/.github/workflows/ci-ui-tests.yml b/.github/workflows/ci-ui-tests.yml index 06e117687..1e9ccf56a 100644 --- a/.github/workflows/ci-ui-tests.yml +++ b/.github/workflows/ci-ui-tests.yml @@ -51,6 +51,9 @@ jobs: run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 + + - name: Xcodegen + run: mint run yonaskolb/XcodeGen@2.39.0 # Main step - name: UI tests diff --git a/Brewfile b/Brewfile index e7b7707b9..c1b2289ee 100644 --- a/Brewfile +++ b/Brewfile @@ -1,3 +1,2 @@ -brew "xcodegen" brew "mint" brew "getsentry/tools/sentry-cli" diff --git a/CHANGES.md b/CHANGES.md index 6967d43cc..f2b0bf418 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,36 @@ +## Changes in 1.11.31 (2025-07-28) + +✨ Features + +- Support for MSC 4289. ([#7950](https://github.com/element-hq/element-ios/pull/7950)) + +🙌 Improvements + +- PL 150 users will be displayed as Owners. ([#7951](https://github.com/element-hq/element-ios/issues/7951)) +- Owners can't leave the room if they are the last owners while also not being the last member. ([#7952](https://github.com/element-hq/element-ios/issues/7952)) + +🐛 Bugfixes + +- Room continuity fix for room version 12. ([#7953](https://github.com/element-hq/element-ios/pull/7953)) + + +## Changes in 1.11.30 (2025-06-10) + +No significant changes. + + +## Changes in 1.11.29 (2025-05-29) + +No significant changes. + + +## Changes in 1.11.28 (2025-05-28) + +🙌 Improvements + +- Support for experimental MSC4286 during event rendering. ([#7927](https://github.com/element-hq/element-ios/pull/7927)) + + ## Changes in 1.11.27 (2025-03-28) No significant changes. diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index d05ab8199..7255b5612 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -16,6 +16,6 @@ // // Version -MARKETING_VERSION = 2.25.0 -CURRENT_PROJECT_VERSION = 20220714163152 +MARKETING_VERSION = 2.25.1 +CURRENT_PROJECT_VERSION = 20220714163152 diff --git a/Gemfile.lock b/Gemfile.lock index bd64301a1..6226095b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,17 +26,18 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.2) - aws-partitions (1.1063.0) - aws-sdk-core (3.220.1) + aws-partitions (1.1107.0) + aws-sdk-core (3.224.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) base64 jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.99.0) + logger + aws-sdk-kms (1.101.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.182.0) + aws-sdk-s3 (1.186.1) aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -90,13 +91,13 @@ GEM commander (4.6.0) highline (~> 2.0.0) concurrent-ruby (1.3.5) - connection_pool (2.5.0) + connection_pool (2.5.3) declarative (0.0.20) digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) domain_name (0.6.20240107) dotenv (2.8.1) - drb (2.2.1) + drb (2.2.3) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) @@ -131,7 +132,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.4.0) - fastlane (2.226.0) + fastlane (2.227.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -171,17 +172,17 @@ GEM tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.4.0) + xcpretty (~> 0.4.1) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-brew (0.1.1) - fastlane-plugin-sentry (1.28.0) + fastlane-plugin-sentry (1.29.0) os (~> 1.1, >= 1.1.4) fastlane-plugin-versioning (0.7.1) fastlane-plugin-xcodegen (1.1.0) fastlane-plugin-brew (~> 0.1.1) fastlane-sirp (1.0.0) sysrandom (~> 1.0) - ffi (1.17.1) + ffi (1.17.2) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -229,14 +230,14 @@ GEM i18n (1.14.7) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.10.1) + json (2.12.2) jwt (2.10.1) base64 - logger (1.6.6) + logger (1.7.0) mini_magick (4.13.2) mini_mime (1.1.5) - mini_portile2 (2.8.8) - minitest (5.25.4) + mini_portile2 (2.8.9) + minitest (5.25.5) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.4.1) @@ -307,7 +308,7 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.4.0) rexml (>= 3.3.6, < 4.0) - xcpretty (0.4.0) + xcpretty (0.4.1) rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) diff --git a/Podfile b/Podfile index f87e16533..ac281a9b9 100644 --- a/Podfile +++ b/Podfile @@ -69,7 +69,6 @@ abstract_target 'RiotPods' do # Piwik for analytics pod 'MatomoTracker', '~> 7.5.2' - pod 'zxcvbn-ios' # Tools diff --git a/Podfile.lock b/Podfile.lock index 9f7e01b33..f9e91e62f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -14,49 +14,43 @@ PODS: - AFNetworking/Serialization (4.0.1) - AFNetworking/UIKit (4.0.1): - AFNetworking/NSURLSession + - BlueCryptor (1.0.32) + - BlueECC (1.2.5) + - BlueRSA (1.0.200) - Down (0.11.0) - DSWaveformImage (6.1.1) - - DTCoreText (1.6.26): - - DTCoreText/Core (= 1.6.26) - - DTFoundation/Core (~> 1.7.5) - - DTFoundation/DTAnimatedGIF (~> 1.7.5) - - DTFoundation/DTHTMLParser (~> 1.7.5) - - DTFoundation/UIKit (~> 1.7.5) - - DTCoreText/Core (1.6.26): - - DTFoundation/Core (~> 1.7.5) - - DTFoundation/DTAnimatedGIF (~> 1.7.5) - - DTFoundation/DTHTMLParser (~> 1.7.5) - - DTFoundation/UIKit (~> 1.7.5) - - DTFoundation/Core (1.7.19) - - DTFoundation/DTAnimatedGIF (1.7.19) - - DTFoundation/DTHTMLParser (1.7.19): - - DTFoundation/Core - - DTFoundation/UIKit (1.7.19): - - DTFoundation/Core - - DTTJailbreakDetection (0.4.0) + - FLEX (5.22.10) - FlowCommoniOS (1.12.2) - GBDeviceInfo (7.1.0): - GBDeviceInfo/Core (= 7.1.0) - GBDeviceInfo/Core (7.1.0) - GZIP (1.3.2) - Introspect (0.12.0) + - JitsiMeetSDKLite (8.1.2-lite): + - JitsiWebRTC (~> 111.0) + - JitsiWebRTC (111.0.2) - KeychainAccess (4.2.2) + - KituraContracts (1.2.1): + - LoggerAPI (~> 1.7) - KTCenterFlowLayout (1.3.1) - libbase58 (0.1.4) - libPhoneNumber-iOS (0.9.15) - - MatomoTracker (7.5.2): - - MatomoTracker/Core (= 7.5.2) - - MatomoTracker/Core (7.5.2) + - LoggerAPI (1.9.200): + - Logging (~> 1.1) + - Logging (1.4.0) - MatrixSDK (0.27.17): - MatrixSDK/Core (= 0.27.17) - MatrixSDK/Core (0.27.17): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - - MatrixSDKCrypto (= 0.4.3) + - MatrixSDKCrypto (= 0.11.1) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDKCrypto (0.4.3) + - MatrixSDK/JingleCallStack (0.27.17): + - JitsiMeetSDKLite (= 8.1.2-lite) + - MatrixSDK/Core + - MatrixSDKCrypto (0.11.1) - ReadMoreTextView (3.0.1) - Realm (10.27.0): - Realm/Headers (= 10.27.0) @@ -66,9 +60,19 @@ PODS: - Reusable/View (= 4.1.2) - Reusable/Storyboard (4.1.2) - Reusable/View (4.1.2) + - Sentry (8.46.0): + - Sentry/Core (= 8.46.0) + - Sentry/Core (8.46.0) - SideMenu (6.5.0) + - SwiftBase32 (0.9.0) - SwiftFormat/CLI (0.54.5) - SwiftGen (6.6.3) + - SwiftJWT (3.6.200): + - BlueCryptor (~> 1.0) + - BlueECC (~> 1.1) + - BlueRSA (~> 1.0) + - KituraContracts (~> 1.2) + - LoggerAPI (~> 1.7) - SwiftLint (0.57.0) - SwiftyBeaver (1.9.5) - UICollectionViewLeftAlignedLayout (1.0.2) @@ -82,21 +86,23 @@ PODS: DEPENDENCIES: - Down (~> 0.11.0) - DSWaveformImage (~> 6.1.1) - - DTCoreText (= 1.6.26) - - DTTJailbreakDetection (~> 0.4.0) + - FLEX (~> 5.22.10) - FlowCommoniOS (~> 1.12.0) - GBDeviceInfo (~> 7.1.0) - Introspect (~> 0.1) - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatomoTracker (~> 7.5.2) - - MatrixSDK (from `https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk`, tag `v2.25.0-RC01`) + - MatrixSDK (from `matrix-ios-sdk/MatrixSDK.podspec`) + - MatrixSDK/JingleCallStack (from `matrix-ios-sdk/MatrixSDK.podspec`) - ReadMoreTextView (~> 3.0.1) - Reusable (~> 4.1) + - Sentry (~> 8.46.0) - SideMenu (~> 6.5) + - SwiftBase32 (~> 0.9.0) - SwiftFormat/CLI - SwiftGen + - SwiftJWT (~> 3.6.200) - SwiftLint - UICollectionViewLeftAlignedLayout (~> 1.0.2) - UICollectionViewRightAlignedLayout (~> 0.0.3) @@ -106,28 +112,37 @@ DEPENDENCIES: SPEC REPOS: https://github.com/CocoaPods/Specs.git: + - MatrixSDKCrypto + - Sentry + trunk: - AFNetworking + - BlueCryptor + - BlueECC + - BlueRSA - Down - DSWaveformImage - - DTCoreText - - DTFoundation - - DTTJailbreakDetection + - FLEX - FlowCommoniOS - GBDeviceInfo - GZIP - Introspect + - JitsiMeetSDKLite + - JitsiWebRTC - KeychainAccess + - KituraContracts - KTCenterFlowLayout - libbase58 - libPhoneNumber-iOS - - MatomoTracker - - MatrixSDKCrypto + - LoggerAPI + - Logging - ReadMoreTextView - Realm - Reusable - SideMenu + - SwiftBase32 - SwiftFormat - SwiftGen + - SwiftJWT - SwiftLint - SwiftyBeaver - UICollectionViewLeftAlignedLayout @@ -138,38 +153,40 @@ SPEC REPOS: EXTERNAL SOURCES: MatrixSDK: - :git: https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk - :tag: v2.25.0-RC01 - -CHECKOUT OPTIONS: - MatrixSDK: - :git: https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk - :tag: v2.25.0-RC01 + :path: matrix-ios-sdk/MatrixSDK.podspec SPEC CHECKSUMS: AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58 + BlueCryptor: b0aee3d9b8f367b49b30de11cda90e1735571c24 + BlueECC: 0d18e93347d3ec6d41416de21c1ffa4d4cd3c2cc + BlueRSA: dfeef51db96bcc4edec654956c1581adbda4e6a3 Down: b6ba1bc985c9d2f4e15e3b293d2207766fa12612 DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce - DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce - DTFoundation: 76b624967cf5bcaae6bb057d622c536c36ef36d0 - DTTJailbreakDetection: 5e356c5badc17995f65a83ed9483f787a0057b71 + FLEX: f21ee4f498eed3f8a1eded66b21939fd3b7a22ce FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: 5d62fa85bdcce3ed288d83c28789adf1173e4376 GZIP: 3c0abf794bfce8c7cb34ea05a1837752416c8868 Introspect: b66b675de8a85d9ef832f3a710d8e3c7db186884 + JitsiMeetSDKLite: 895213158cf62342069a10634a41d2f1c00057f7 + JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 + KituraContracts: e845e60dc8627ad0a76fa55ef20a45451d8f830b KTCenterFlowLayout: 6e02b50ab2bd865025ae82fe266ed13b6d9eaf97 - libbase58: 8abc2a53ac38cd37720c0acbc53ef3660e9016c2 + libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 - MatomoTracker: 1d98ddc58322fd9d65e1a6886b8e41363047bd13 - MatrixSDK: 33d348122df228efa234d7b353c33620fc420a59 - MatrixSDKCrypto: 27bee960e0e8b3a3039f3f3e93dd2ec88299c77e + LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d + Logging: beeb016c9c80cf77042d62e83495816847ef108b + MatrixSDK: 45f9f97e7424e5d8731bf6b207c728a71caa8eb1 + MatrixSDKCrypto: e44608012cae9befc52f13cd8e56c6f51ac83702 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2 Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136 + Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 SideMenu: f583187d21c5b1dd04c72002be544b555a2627a2 + SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17 SwiftFormat: 543a7b1ab4a6ce2d88bd5616a17903446ca3dc5c SwiftGen: 4993cbf71cbc4886f775e26f8d5c3a1188ec9f99 + SwiftJWT: 88c412708f58c169d431d344c87bc79a87c830ae SwiftLint: eb47480d47c982481592c195c221d11013a679cc SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 UICollectionViewLeftAlignedLayout: 830bf6fa5bab9f9b464f62e3384f9d2e00b3c0f6 @@ -178,6 +195,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: b5e41c832b239f0b12152d3c9e66baf098d3d57d +PODFILE CHECKSUM: 60f4fe3695f2304b9bba734acf38d59c79678ac1 COCOAPODS: 1.16.2 diff --git a/Riot/Assets/ar.lproj/Localizable.strings b/Riot/Assets/ar.lproj/Localizable.strings index c5e8f6fef..978cd9f3b 100644 --- a/Riot/Assets/ar.lproj/Localizable.strings +++ b/Riot/Assets/ar.lproj/Localizable.strings @@ -167,3 +167,5 @@ /** General **/ "Notification" = "إشعار"; +"VOICE_BROADCAST_FROM_USER" = "%@ بدأ بثًا صوتيًا"; +"UNSUPPORTED_CALL" = "مكالمة غير مدعومة"; diff --git a/Riot/Assets/ar.lproj/Vector.strings b/Riot/Assets/ar.lproj/Vector.strings index d14eab146..838be3117 100644 --- a/Riot/Assets/ar.lproj/Vector.strings +++ b/Riot/Assets/ar.lproj/Vector.strings @@ -2080,3 +2080,556 @@ "poll_timeline_not_closed_subtitle" = "الرجاء المحاولة مرة أخرى"; "poll_edit_form_post_failure_title" = "فشل في نشر الاستطلاع"; "location_sharing_title" = "موقع"; +"room_details_fail_to_update_history_visibility" = "فشل في تحديث رؤية التاريخ"; +"room_details_copy_room_address" = "عنوان غرفة النسخ"; +"room_details_copy_room_url" = "نسخ عنوان URL للغرفة"; +"widget_integration_missing_room_id" = "room_id مفقود في الطلب."; +"room_access_settings_screen_message" = "قرر من يمكنه العثور على %@ والانضمام إليه."; +"room_access_settings_screen_upgrade_alert_message_no_param" = "سيتمكن أي شخص في مساحة الوالدين من العثور على هذه الغرفة والانضمام إليها - دون الحاجة إلى دعوة الجميع يدويًا. يمكنك تغيير ذلك في إعدادات الغرفة في أي وقت."; +"room_access_settings_screen_upgrade_alert_note" = "يرجى ملاحظة أن الترقية ستؤدي إلى إنشاء نسخة جديدة من الغرفة. ستبقى جميع الرسائل الحالية في هذه الغرفة المؤرشفة."; +"room_access_settings_screen_upgrade_alert_auto_invite_switch" = "دعوة الأعضاء تلقائيًا إلى غرفة جديدة"; +"room_notifs_settings_notify_me_for" = "أعلمني بذلك"; +"e2e_room_key_request_message" = "تطلب جلستك غير الموثقة '%@' مفاتيح التشفير."; +"secure_key_backup_setup_intro_use_security_key_title" = "استخدم مفتاح الأمان"; +"secure_key_backup_setup_existing_backup_error_info" = "قم بإلغاء قفله لإعادة استخدامه في النسخ الاحتياطي الآمن أو احذفه لإنشاء نسخة احتياطية جديدة للرسائل في النسخ الاحتياطي الآمن."; +"room_details_fail_to_add_room_aliases" = "فشل في إضافة عناوين الغرف الجديدة"; +"room_details_fail_to_update_room_direct" = "فشل في تحديث العلم المباشر لهذه الغرفة"; +"room_access_settings_screen_restricted_message" = "اسمح لأي شخص في مساحة بالبحث والانضمام.\nسيُطلب منك تأكيد المساحات."; +"room_suggestion_settings_screen_title" = "إنشاء غرفة مقترحة في مساحة"; +"group_details_title" = "تفاصيل المجتمع"; +"group_invitation_format" = "لقد دعاك %@ للانضمام إلى هذا المجتمع"; +"directory_server_type_homeserver" = "اكتب خادمًا رئيسيًا لإدراج الغرف العامة منه"; +"bug_report_send_logs" = "إرسال تسجيلات الدخول"; +"e2e_room_key_request_start_verification" = "بدء التحقق…"; +"key_backup_setup_intro_manual_export_info" = "(متقدم)"; +"room_access_settings_screen_upgrade_alert_upgrading" = "ترقية الغرفة"; +"room_notifs_settings_done_action" = "منتهي"; +"e2e_room_key_request_share_without_verifying" = "شارك دون التحقق"; +"gdpr_consent_not_given_alert_review_now_action" = "قم بالمراجعة الآن"; +"service_terms_modal_description_integration_manager" = "سيسمح لك هذا باستخدام الروبوتات والجسور والأدوات وحزم الملصقات."; +"key_backup_setup_title" = "النسخ الاحتياطي للمفتاح"; +"group_details_home" = "الصفحة الرئيسية"; +"room_details_set_main_address" = "تعيين كعنوان رئيسي"; +"room_notifs_settings_manage_notifications" = "يمكنك إدارة الإشعارات في %@"; +"room_notifs_settings_encrypted_room_notice" = "يرجى ملاحظة أن الإشارات وإشعارات الكلمات الرئيسية غير متوفرة في الغرف المشفرة على الهاتف المحمول."; +"group_details_rooms" = "الغرف"; +"widget_integration_room_not_visible" = "الغرفة %@ غير مرئية."; +"room_widget_permission_room_id_permission" = "معرف الغرفة"; +"service_terms_modal_information_title_integration_manager" = "مدير التكامل"; +"service_terms_modal_information_description_integration_manager" = "يتيح لك مدير التكامل إضافة ميزات من جهات خارجية."; +"secure_key_backup_setup_intro_use_security_key_info" = "‌إنشاء مفتاح أمان لتخزينه في مكان آمن مثل مدير كلمات المرور أو الخزنة."; +"secure_key_backup_setup_cancel_alert_message" = "إذا ألغيت الاشتراك الآن، فقد تفقد رسائلك وبياناتك المشفرة إذا فقدت الوصول إلى بيانات تسجيل الدخول الخاصة بك.\n\nيمكنك أيضًا إعداد النسخ الاحتياطي الآمن وإدارة مفاتيحك في الإعدادات."; +"key_backup_setup_skip_alert_message" = "قد تفقد الرسائل الآمنة إذا قمت بتسجيل الخروج أو فقدت جهازك."; +"widget_integration_missing_user_id" = "معرف المستخدم مفقود في الطلب."; +"widget_picker_manage_integrations" = "إدارة التكاملات…"; +"room_widget_permission_creator_info_title" = "تمت إضافة هذه الودجت بواسطة:"; +"share_extension_auth_prompt" = "قم بتسجيل الدخول إلى التطبيق الرئيسي لمشاركة المحتوى"; +"e2e_room_key_request_message_new_device" = "لقد قمت بإضافة جلسة جديدة '%@'، والتي تطلب مفاتيح التشفير."; +"room_access_settings_screen_setting_room_access" = "الوصول إلى غرفة الإعداد"; +"group_home_multi_rooms_format" = "%tu غرف"; +"group_participants_add_participant" = "إضافة مشارك"; +"group_participants_leave_prompt_msg" = "هل أنت متأكد أنك تريد مغادرة المجموعة؟"; +"room_widget_permission_widget_id_permission" = "معرف الودجت"; +"share_extension_low_quality_video_title" = "سيتم إرسال الفيديو بجودة منخفضة"; +"share_extension_low_quality_video_message" = "أرسل %@ للحصول على جودة أفضل، أو أرسل بجودة منخفضة أدناه."; +"e2e_room_key_request_title" = "طلب مفتاح التشفير"; +"service_terms_modal_decline_button" = "رفض"; +"service_terms_modal_description_identity_server" = "سيسمح هذا لأي شخص بالعثور عليك إذا كان لديه رقم هاتفك أو بريدك الإلكتروني محفوظًا في جهات اتصال هاتفه."; +"room_access_space_chooser_other_spaces_section_info" = "من المحتمل أن تكون هذه أشياء يشارك فيها مسؤولون آخرون في %@."; +"room_access_settings_screen_nav_title" = "الوصول إلى الغرفة"; +"service_terms_modal_table_header_integration_manager" = "شروط مدير التكامل"; +"room_access_settings_screen_public_message" = "يمكن لأي شخص العثور والانضمام."; +"room_notifs_settings_none" = "لا أحد"; +"room_details_fail_to_enable_encryption" = "فشل في تمكين التشفير في هذه الغرفة"; +"room_details_copy_room_id" = "معرف غرفة النسخ"; +"room_access_settings_screen_upgrade_alert_upgrade_button" = "ترقية"; +"room_suggestion_settings_screen_message" = "يتم الترويج للغرف المقترحة لأعضاء الفضاء باعتبارها غرفًا جيدة للانضمام إليها."; +"room_notifs_settings_account_settings" = "إعدادات الحساب"; +"group_participants_remove_prompt_title" = "تأكيد"; +"service_terms_modal_footer" = "يمكن تعطيل هذه الميزة في أي وقت من خلال الإعدادات."; +"deactivate_account_forget_messages_information_part1" = "يرجى نسيان جميع الرسائل التي أرسلتها عندما تم إلغاء تنشيط حسابي ("; +"secure_key_backup_setup_intro_info" = "قم بحماية نفسك من فقدان الوصول إلى الرسائل والبيانات المشفرة عن طريق عمل نسخة احتياطية لمفاتيح التشفير على الخادم الخاص بك."; +"key_backup_setup_intro_manual_export_action" = "تصدير المفاتيح يدويًا"; +"room_widget_permission_avatar_url_permission" = "عنوان URL للصورة الرمزية الخاصة بك"; +"share_extension_failed_to_encrypt" = "فشل الإرسال. تحقق من إعدادات التشفير لهذه الغرفة في التطبيق الرئيسي"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "أدخل عبارة سرية تعرفها أنت فقط، ثم قم بإنشاء مفتاح للنسخ الاحتياطي."; +"room_details_fail_to_remove_room_aliases" = "فشل في إزالة عناوين الغرف"; +"room_access_settings_screen_private_message" = "يمكن فقط للأشخاص المدعوين العثور على الصفحة والانضمام إليها."; +"room_access_settings_screen_edit_spaces" = "تحرير المساحات"; +"group_details_people" = "الناس"; +"widget_integration_must_be_in_room" = "أنت لست في هذه الغرفة."; +"widget_integration_no_permission_in_room" = "ليس لديك الإذن للقيام بذلك في هذه الغرفة."; +"deactivate_account_informations_part2_emphasize" = "هذا الإجراء لا رجعة فيه."; +"deactivate_account_validate_action" = "إلغاء تنشيط الحساب"; +"deactivate_account_password_alert_message" = "للمتابعة، الرجاء إدخال كلمة مرور حساب Matrix الخاص بك"; +"secure_key_backup_setup_existing_backup_error_unlock_it" = "افتحه"; +"room_notifs_settings_cancel_action" = "إلغاء"; +"share_extension_send_now" = "أرسل الآن"; +"service_terms_modal_title_message" = "للمتابعة، قم بقبول الشروط والأحكام أدناه"; +"room_details_promote_room_suggest_title" = "اقترح على أعضاء المساحة"; +"room_details_fail_to_update_room_canonical_alias" = "فشل في تحديث العنوان الرئيسي"; +"room_details_unset_main_address" = "غير مُعيَّن كعنوان رئيسي"; +"e2e_room_key_request_ignore_request" = "تجاهل الطلب"; +"deactivate_account_forget_messages_information_part3" = ": سيؤدي هذا إلى جعل المستخدمين في المستقبل يرون عرضًا غير كامل للمحادثات)"; +"rerequest_keys_alert_title" = "تم إرسال الطلب"; +"room_details_fail_to_update_room_communities" = "فشل في تحديث المجتمعات ذات الصلة"; +"group_home_one_room_format" = "غرفة واحدة"; +"widget_creation_failure" = "فشل إنشاء الودجت"; +"service_terms_modal_information_title_identity_server" = "خادم الهوية"; +"deactivate_account_forget_messages_information_part2_emphasize" = "تحذير"; +"key_backup_setup_skip_alert_title" = "هل أنت متأكد؟"; +"key_backup_setup_intro_title" = "لا تفقد الرسائل المشفرة أبدًا"; +"widget_picker_title" = "التكاملات"; +"service_terms_modal_accept_button" = "قبول"; +"key_backup_setup_intro_info" = "الرسائل في الغرف المشفرة مؤمنة بتشفير شامل. أنت والمستلم فقط لديهما مفاتيح قراءة هذه الرسائل.\n\nاحتفظ بنسخة احتياطية آمنة من مفاتيحك لتجنب فقدانها."; +"room_access_settings_screen_upgrade_alert_title" = "ترقية الغرفة"; +"room_notifs_settings_all_messages" = "كل الرسائل"; +"deactivate_account_title" = "إلغاء تنشيط الحساب"; +"deactivate_account_informations_part4_emphasize" = "لا يؤدي افتراضيًا إلى نسيان الرسائل التي أرسلتها. "; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "استخدم عبارة أمنية"; +"key_backup_setup_passphrase_title" = "تأمين النسخة الاحتياطية الخاصة بك باستخدام عبارة أمان"; +"bug_report_progress_zipping" = "جمع تسجيلات الدخول"; +"room_details_save_changes_prompt" = "هل تريد حفظ التغييرات؟"; +"room_access_settings_screen_title" = "من يمكنه الوصول إلى هذه الغرفة؟"; +"room_access_settings_screen_upgrade_required" = "الترقية مطلوبة"; +"room_access_settings_screen_upgrade_alert_message" = "سيتمكن أي شخص في %@ من العثور على هذه الغرفة والانضمام إليها - دون الحاجة إلى دعوة الجميع يدويًا. يمكنك تغيير ذلك في أي وقت من إعدادات الغرفة."; +"room_access_space_chooser_known_spaces_section" = "المساحات التي تعرف أنها تحتوي على %@"; +"room_access_space_chooser_other_spaces_section" = "مساحات أو غرف أخرى"; +"room_suggestion_settings_screen_nav_title" = "اقتراح غرفة"; +"room_notifs_settings_mentions_and_keywords" = "الإشارات والكلمات الرئيسية فقط"; +"group_home_one_member_format" = "عضو واحد"; +"group_home_multi_members_format" = "%tu أعضاء"; +"group_participants_leave_prompt_title" = "مغادرة المجموعة"; +"event_formatter_widget_removed" = "تمت إزالة الودجت %@ بواسطة %@"; +"widget_integration_room_not_recognised" = "لم يتم التعرف على هذه الغرفة."; +"widget_integration_positive_power_level" = "يجب أن يكون مستوى الطاقة عددًا صحيحًا موجبًا."; +"widget_integration_manager_disabled" = "يجب عليك تمكين مدير التكامل في الإعدادات"; +"room_widget_permission_webview_information_title" = "قد يؤدي استخدامه إلى تعيين ملفات تعريف الارتباط ومشاركة البيانات مع %@:\n"; +"room_widget_permission_display_name_permission" = "اسم العرض الخاص بك"; +"room_widget_permission_user_id_permission" = "معرف المستخدم الخاص بك"; +"gdpr_consent_not_given_alert_message" = "لمواصلة استخدام %@ homeserver، يجب عليك مراجعة الشروط والأحكام والموافقة عليها."; +"service_terms_modal_table_header_identity_server" = "شروط خادم الهوية"; +"deactivate_account_informations_part1" = "سيؤدي هذا إلى تعطيل حسابك نهائيًا. لن تتمكن من تسجيل الدخول، ولن يتمكن أي شخص من إعادة تسجيل اسم المستخدم نفسه. سيؤدي هذا إلى مغادرة حسابك جميع الغرف التي يشارك فيها، وإزالة تفاصيل حسابك من خادم الهوية الخاص بك. "; +"deactivate_account_informations_part5" = "إذا كنت ترغب في حذف رسائلك، يُرجى وضع علامة في المربع أدناه.\n\nتشبه ميزة رؤية الرسائل في Matrix ميزة البريد الإلكتروني. تعني ميزة \"حذف رسائلك\" أن الرسائل التي أرسلتها لن تُشارك مع أي مستخدمين جدد أو غير مسجلين، بينما سيظل بإمكان المستخدمين المسجلين الذين لديهم حق الوصول إلى هذه الرسائل الوصول إلى نسخهم."; +"deactivate_account_password_alert_title" = "إلغاء تنشيط الحساب"; +"secure_key_backup_setup_intro_title" = "النسخ الاحتياطي الآمن"; +"secure_key_backup_setup_existing_backup_error_title" = "توجد نسخة احتياطية للرسائل بالفعل"; +"secure_key_backup_setup_existing_backup_error_delete_it" = "احذفه"; +"secure_key_backup_setup_cancel_alert_title" = "هل انت متأكد؟"; +"secure_backup_setup_banner_title" = "النسخ الاحتياطي الآمن"; +"secure_backup_setup_banner_subtitle" = "الحماية من فقدان الوصول إلى الرسائل والبيانات المشفرة"; +"key_backup_setup_skip_alert_skip_action" = "تخطي"; +"key_backup_setup_intro_setup_action_without_existing_backup" = "ابدأ باستخدام النسخ الاحتياطي للمفتاح"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "قم بتوصيل هذا الجهاز بـ Key Backup"; +"room_widget_permission_title" = "ودجت تحميل"; +"room_widget_permission_information_title" = "قد يؤدي استخدامه إلى مشاركة البيانات مع %@:\n"; +"service_terms_modal_information_description_identity_server" = "يساعدك خادم الهوية في العثور على جهات الاتصال الخاصة بك، من خلال البحث عن رقم الهاتف أو عنوان البريد الإلكتروني، لمعرفة ما إذا كان لديهم حساب بالفعل."; +"rerequest_keys_alert_message" = "يرجى تشغيل %@ على جهاز آخر يمكنه فك تشفير الرسالة حتى يتمكن من إرسال المفاتيح إلى هذه الجلسة."; +"key_backup_setup_passphrase_info" = "سنخزن نسخة مشفرة من مفاتيحك على خادمنا. احمِ نسختك الاحتياطية بعبارة للحفاظ عليها آمنة.\n\nلأقصى درجات الأمان، يجب أن تكون كلمة المرور مختلفة عن كلمة مرور حساب Matrix الخاص بك."; +"device_verification_emoji_trophy" = "غنيمة"; +"key_backup_recover_invalid_passphrase_title" = "عبارة أمنية غير صحيحة"; +"key_backup_recover_invalid_recovery_key_title" = "عدم تطابق مفتاح الأمان"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "أو قم بتأمين النسخة الاحتياطية الخاصة بك باستخدام مفتاح أمان، وحفظها في مكان آمن."; +"key_backup_recover_title" = "رسائل آمنة"; +"key_backup_recover_from_private_key_info" = "جاري استعادة النسخة الاحتياطية…"; +"device_verification_emoji_cloud" = "سحاب"; +"device_verification_emoji_clock" = "ساعة"; +"device_verification_emoji_pencil" = "قلم رصاص"; +"key_backup_setup_passphrase_passphrase_invalid" = "حاول إضافة كلمة"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "العبارة لا تتطابق"; +"key_backup_setup_success_from_recovery_key_info" = "يتم الآن نسخ مفاتيحك احتياطيًا.\n\nانسخ مفتاح الأمان هذا واحتفظ به في مكان آمن."; +"device_verification_emoji_panda" = "باندا"; +"device_verification_emoji_telephone" = "الهاتف"; +"device_verification_emoji_pig" = "خنزير"; +"device_verification_emoji_paperclip" = "مشبك"; +"device_verification_emoji_ball" = "كرة"; +"device_verification_emoji_guitar" = "غيتار"; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "تأكيد العبارة"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "أدخل مفتاح الأمان"; +"device_verification_emoji_glasses" = "نظارات"; +"device_verification_emoji_unicorn" = "وحيد القرن"; +"device_verification_emoji_flower" = "ورد"; +"device_verification_emoji_tree" = "شجرة"; +"device_verification_emoji_cactus" = "صبار"; +"device_verification_emoji_smiley" = "ابتسامة"; +"device_verification_emoji_scissors" = "مقص"; +"device_verification_emoji_hammer" = "مطرقة"; +"device_verification_emoji_banana" = "موز"; +"device_verification_emoji_turtle" = "سلحفاة"; +"key_backup_setup_passphrase_passphrase_valid" = "عظيم!"; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "حفظ مفتاح الأمان"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "لا تعرف عبارة الأمان الخاصة بك؟ يمكنك "; +"device_verification_emoji_octopus" = "الأخطبوط"; +"device_verification_emoji_strawberry" = "الفراولة"; +"device_verification_emoji_heart" = "قلب"; +"device_verification_emoji_hourglass" = "الساعة الرملية"; +"device_verification_emoji_key" = "مفتاح"; +"device_verification_emoji_flag" = "علَم"; +"device_verification_emoji_bicycle" = "دراجة"; +"device_verification_emoji_rocket" = "صاروخ"; +"device_verification_emoji_aeroplane" = "طائرة"; +"device_verification_emoji_horse" = "حصان"; +"device_verification_emoji_elephant" = "فيل"; +"device_verification_emoji_rabbit" = "أرنب"; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "مفتاح الأمان"; +"device_verification_emoji_rooster" = "ديك"; +"device_verification_emoji_light bulb" = "مصباح كهربائي"; +"key_backup_setup_passphrase_confirm_passphrase_valid" = "عظيم!"; +"key_backup_setup_success_title" = "نجاح!"; +"key_backup_recover_from_passphrase_info" = "استخدم عبارة الأمان الخاصة بك لفتح سجل رسائلك الآمنة"; +"device_verification_emoji_penguin" = "البطريق"; +"device_verification_emoji_apple" = "تفاحة"; +"device_verification_emoji_book" = "كتاب"; +"key_backup_setup_success_from_recovery_key_make_copy_action" = "اصنع نسخة"; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(متقدم) الإعداد باستخدام مفتاح الأمان"; +"device_verification_emoji_fish" = "سمكة"; +"device_verification_emoji_corn" = "حبوب ذرة"; +"device_verification_emoji_robot" = "روبوت"; +"key_backup_setup_passphrase_passphrase_title" = "دخول"; +"device_verification_emoji_umbrella" = "مظلة"; +"device_verification_emoji_cake" = "كعكة"; +"device_verification_emoji_lock" = "قفل"; +"security_settings_crosssigning_bootstrap" = "إعداد"; +"event_formatter_call_decline" = "رفض"; +"key_backup_setup_passphrase_passphrase_placeholder" = "أدخل العبارة"; +"key_backup_setup_passphrase_confirm_passphrase_title" = "تأكيد"; +"key_backup_setup_success_from_passphrase_info" = "يتم نسخ مفاتيحك احتياطيًا.\n\nمفتاح الأمان الخاص بك هو بمثابة شبكة أمان، يمكنك استخدامه لاستعادة الوصول إلى رسائلك المشفرة في حال نسيت كلمة المرور.\n\nاحتفظ بمفتاح الأمان الخاص بك في مكان آمن للغاية، مثل مدير كلمات المرور (أو خزنة)."; +"key_backup_setup_success_from_passphrase_done_action" = "منتهي"; +"key_backup_setup_success_from_recovery_key_made_copy_action" = "لقد قمت بعمل نسخة"; +"key_backup_setup_success_from_secure_backup_info" = "يتم إجراء نسخة احتياطية لمفاتيحك."; +"key_backup_recover_invalid_passphrase" = "لم يتم فك تشفير النسخ الاحتياطي باستخدام هذه العبارة: يرجى التأكد من إدخال عبارة الأمان الصحيحة."; +"key_backup_recover_invalid_recovery_key" = "لم يتمكن من فك تشفير النسخة الاحتياطية باستخدام هذا المفتاح: يرجى التأكد من إدخال مفتاح الأمان الصحيح."; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "استخدم مفتاح الأمان الخاص بك"; +"key_backup_recover_from_recovery_key_info" = "استخدم مفتاح الأمان الخاص بك لفتح سجل رسائلك الآمنة"; +"device_verification_emoji_lion" = "الأسد"; +"device_verification_emoji_butterfly" = "فراشة"; +"device_verification_emoji_globe" = "الكرة الأرضية"; +"device_verification_emoji_fire" = "نار"; +"device_verification_emoji_pizza" = "بيتزا"; +"device_verification_emoji_spanner" = "مفتاح البراغي"; +"device_verification_emoji_gift" = "هدية"; +"key_backup_recover_from_passphrase_recover_action" = "فتح التاريخ"; +"device_verification_emoji_mushroom" = "فطر"; +"device_verification_emoji_moon" = "القمر"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "أدخل العبارة"; +"deactivate_account_informations_part3" = "\n\nإلغاء تنشيط حسابك "; +"user_verification_sessions_list_user_trust_level_unknown_title" = "غير معروف"; +"spaces_coming_soon_detail" = "لم تُطبّق هذه الميزة هنا، ولكنها في طريقها. حاليًا، يمكنك القيام بذلك باستخدام %@ على جهاز الكمبيوتر."; +"space_feature_unavailable_subtitle" = "لم يتم إطلاق Spaces على نظام التشغيل iOS بعد، ولكن يمكنك استخدامها الآن على الويب وسطح المكتب"; +"space_feature_unavailable_information" = "المساحات وسيلة جديدة لتجميع الغرف والأشخاص.\n\nستتوفر قريبًا. حاليًا، إذا انضممت إلى واحدة عبر منصة أخرى، ستتمكن من الوصول إلى أي غرف تنضم إليها هنا."; +"space_tag" = "مساحة"; +"spaces_empty_space_detail" = "قد تكون بعض الغرف مخفية لأنها خاصة وتحتاج إلى دعوة."; +"spaces_no_member_found_detail" = "هل تبحث عن شخص ليس في %@؟ حاليًا، يمكنك دعوته عبر الويب أو سطح المكتب."; +"spaces_no_room_found_detail" = "قد تكون بعض النتائج مخفية لأنها خاصة وتحتاج إلى دعوة للانضمام إليها."; +"space_settings_update_failed_message" = "فشل تحديث إعدادات المساحة. هل تريد إعادة المحاولة؟"; +"space_private_join_rule" = "مساحة خاصة"; +"major_update_learn_more_action" = "معرفة المزيد"; +"emoji_picker_title" = "ردود الفعل"; +"emoji_picker_symbols_category" = "الرموز"; +"user_verification_start_information_part1" = "لمزيد من الأمان، قم بالتحقق "; +"user_verification_sessions_list_user_trust_level_trusted_title" = "موثوق به"; +"user_verification_session_details_information_trusted_current_user" = "تمت الوثوق بهذه الجلسة للمراسلة الآمنة لأنك قمت بالتحقق منها:"; +"user_verification_session_details_information_trusted_other_user_part1" = "هذه الجلسة موثوقة للمراسلة الآمنة لأنها "; +"user_verification_session_details_information_trusted_other_user_part2" = " تم التحقق من ذلك:"; +"home_context_menu_mute" = "كتم"; +"room_intro_cell_information_dm_sentence1_part1" = "هذه هي بداية رسالتك المباشرة مع "; +"room_intro_cell_information_dm_sentence2" = "أنتما الاثنان فقط في هذه المحادثة، ولا يمكن لأي شخص آخر الانضمام."; +"room_invite_not_enough_permission" = "ليس لديك إذن لدعوة الأشخاص إلى هذه الغرفة"; +"leave_space_message" = "هل أنت متأكد أنك تريد مغادرة %@؟ هل تريد أيضًا مغادرة جميع الغرف والمساحات في هذه المساحة؟"; +"spaces_suggested_room" = "مقترح"; +"spaces_empty_space_title" = "لا يوجد غرف في هذه المساحة (حتى الآن)"; +"spaces_add_rooms_coming_soon_title" = "سيتم إضافة الغرف قريبًا"; +"space_participants_action_remove" = "إزالة من هذه المساحة"; +"space_participants_action_ban" = "حظر من هذه المساحة"; +"space_home_show_all_rooms" = "عرض جميع الغرف"; +"space_private_join_rule_detail" = "للدعوة فقط، الأفضل لك أو لفريقك"; +"spaces_invite_people" = "دعوة الناس"; +"spaces_add_space" = "إضافة مساحة"; +"spaces_feature_not_available" = "هذه الميزة غير متوفرة هنا. حاليًا، يمكنك القيام بذلك باستخدام %@ على جهاز الكمبيوتر الخاص بك."; +"spaces_creation_footer" = "يمكنك تغيير هذا لاحقًا"; +"device_verification_emoji_headphones" = "سماعات الرأس"; +"user_verification_session_details_information_untrusted_other_user" = " تم تسجيل الدخول باستخدام جلسة جديدة:"; +"user_verification_session_details_additional_information_untrusted_other_user" = "حتى يثق هذا المستخدم بهذه الجلسة، تُعلَّم الرسائل المرسلة إليها ومنها بتحذيرات. يمكنك أيضًا التحقق منها يدويًا."; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "أدخل عبارة الأمان"; +"create_room_section_footer_type_restricted" = "يمكن لأي شخص في اسم المساحة البحث والانضمام."; +"space_beta_announce_information" = "المساحات هي طريقة جديدة لتجميع الغرف والأشخاص. لم تتوفر بعد على نظام iOS، ولكن يمكنك استخدامها الآن على الويب وسطح المكتب."; +"leave_space_message_admin_warning" = "أنت مسؤول عن هذه المساحة، تأكد من أنك قمت بنقل حقوق المسؤول إلى عضو آخر قبل المغادرة."; +"spaces_creation_settings_message" = "أضف بعض التفاصيل لإبرازها. يمكنك تغييرها في أي وقت."; +"device_verification_emoji_anchor" = "مِرسَاة"; +"error_not_supported_on_mobile" = "لا يمكنك القيام بذلك من %@الجوال."; +"user_verification_sessions_list_user_trust_level_warning_title" = "تحذير"; +"user_verification_session_details_verify_action_current_user" = "التحقق بشكل تفاعلي"; +"create_room_placeholder_topic" = "ما هي هذه الغرفة؟"; +"room_intro_cell_information_room_without_topic_sentence2_part1" = "أضف موضوعًا"; +"space_invite_not_enough_permission" = "ليس لديك إذن لدعوة الأشخاص إلى هذه المساحة"; +"space_public_join_rule_detail" = "مفتوح للجميع، الأفضل للمجتمعات"; +"spaces_creation_visibility_message" = "للانضمام إلى مساحة موجودة، تحتاج إلى دعوة."; +"space_topic" = "وصف"; +"reaction_history_title" = "ردود الفعل"; +"home_context_menu_leave" = "يغادر"; +"spaces_add_space_title" = "إنشاء مساحة"; +"spaces_explore_rooms" = "استكشاف الغرف"; +"device_verification_emoji_bell" = "جرس"; +"device_verification_emoji_pin" = "دبوس"; +"space_feature_unavailable_title" = "المساحات ليست هنا بعد"; +"key_verification_bootstrap_not_setup_message" = "يجب عليك أولاً تمهيد التوقيع المتبادل."; +"user_verification_session_details_verify_action_other_user" = "التحقق يدويًا"; +"secrets_recovery_with_passphrase_information_verify_device" = "استخدم عبارة الأمان الخاصة بك للتحقق من هذا الجهاز."; +"key_verification_alert_title" = "لديك جلسات غير موثقة"; +"device_verification_emoji_folder" = "مجلد"; +"file_upload_error_unsupported_file_type_message" = "نوع الملف غير مدعوم."; +"emoji_picker_places_category" = "السفر والأماكن"; +"error_invite_3pid_with_no_identity_server" = "أضف خادم هوية في إعداداتك لدعوتك عبر البريد الإلكتروني."; +"user_verification_start_waiting_partner" = "في انتظار %@…"; +"user_verification_session_details_trusted_title" = "موثوق به"; +"user_verification_session_details_untrusted_title" = "غير موثوق به"; +"secrets_recovery_reset_action_part_2" = "إعادة تعيين كل شيء"; +"secrets_recovery_with_passphrase_title" = "عبارة أمنية"; +"room_invite_to_space_option_detail" = "بإمكانهم استكشاف %@، ولكنهم لن يكونوا أعضاءً في %@."; +"spaces_home_space_title" = "الصفحة الرئيسية"; +"spaces_left_panel_title" = "المساحات"; +"leave_space_and_all_rooms_action" = "اترك جميع الغرف والمساحات"; +"spaces_no_result_found_title" = "لم يتم العثور على نتائج"; +"spaces_invites_coming_soon_title" = "الدعوات قادمة قريبا"; +"spaces_add_room" = "إضافة غرفة"; +"spaces_subspace_creation_visibility_message" = "سيتم إضافة المساحة التي تم إنشاؤها إلى %@."; +"space_public_join_rule" = "مساحة عامة"; +"secrets_recovery_with_passphrase_information_default" = "قم بالوصول إلى سجل رسائلك الآمنة وهوية التوقيع المتبادل الخاصة بك للتحقق من الجلسات الأخرى عن طريق إدخال عبارة الأمان الخاصة بك."; +"emoji_picker_foods_category" = "الطعام والشراب"; +"emoji_picker_activity_category" = "أنشطة"; +"user_verification_sessions_list_table_title" = "الجلسات"; +"create_room_type_restricted" = "أعضاء المساحة"; +"room_invite_to_room_option_detail" = "لن يكونوا جزءًا من %@."; +"spaces_add_subspace_title" = "إنشاء مساحة داخل %@"; +"space_settings_current_address_message" = "مساحتك متاحة للعرض على\n%@"; +"leave_space_only_action" = "لا تغادر أي غرف"; +"spaces_explore_rooms_room_number" = "%@ غرف"; +"user_verification_sessions_list_session_trusted" = "موثوق به"; +"create_room_show_in_directory_footer" = "سيساعد هذا الأشخاص في العثور على أعضاء جدد والانضمام إليهم."; +"room_intro_cell_information_room_without_topic_sentence2_part2" = " لإعلام الناس بما تحتويه هذه الغرفة."; +"spaces_create_space_title" = "إنشاء مساحة"; +"spaces_create_subspace_title" = "إنشاء مساحة فرعية"; +"file_upload_error_title" = "تحميل الملف"; +"emoji_picker_people_category" = "الوجوه الضاحكة والأشخاص"; +"emoji_picker_nature_category" = "الحيوانات والطبيعة"; +"emoji_picker_objects_category" = "أشياء"; +"emoji_picker_flags_category" = "الأعلام"; +"key_verification_bootstrap_not_setup_title" = "خطأ"; +"key_verification_tile_request_incoming_title" = "طلب التحقق"; +"key_verification_tile_request_outgoing_title" = "تم إرسال التحقق"; +"user_verification_start_verify_action" = "بدء التحقق"; +"user_verification_start_information_part2" = " عن طريق التحقق من رمز لمرة واحدة على كلا الجهازين."; +"user_verification_start_additional_information" = "لضمان الأمان، قم بذلك شخصيًا أو استخدم طريقة أخرى للتواصل."; +"user_verification_sessions_list_information" = "الرسائل التي يتبادلها هذا المستخدم في هذه الغرفة تكون مشفرة من البداية إلى النهاية ولا يمكن لأطراف ثالثة قراءتها."; +"user_verification_session_details_information_untrusted_current_user" = "قم بالتحقق من هذه الجلسة لوضع علامة عليها كموثوقة ومنحها حق الوصول إلى الرسائل المشفرة:"; +"user_verification_session_details_additional_information_untrusted_current_user" = "إذا لم تقم بتسجيل الدخول إلى هذه الجلسة، فقد يكون حسابك معرضًا للخطر."; +"user_verification_session_details_verify_action_current_user_manually" = "التحقق يدويًا عن طريق النص"; +"secrets_recovery_reset_action_part_1" = "هل نسيت أو فقدت كافة خيارات الاسترداد؟ "; +"secrets_recovery_with_passphrase_passphrase_title" = "دخول"; +"room_intro_cell_information_dm_sentence1_part3" = ". "; +"room_intro_cell_information_multiple_dm_sentence2" = "أنت وحدك من يشارك في هذه المحادثة، ما لم يقم أي منكم بدعوة شخص ما للانضمام."; +"room_invite_to_room_option_title" = "إلى هذه الغرفة فقط"; +"spaces_explore_rooms_one_room" = "غرفة واحدة"; +"space_settings_access_section" = "من يمكنه الوصول إلى هذه المساحة؟"; +"spaces_creation_hint" = "المساحات هي طريقة جديدة لتجميع الغرف والأشخاص."; +"spaces_creation_visibility_title" = "ما نوع المساحة التي تريد إنشاءها؟"; +"spaces_subspace_creation_visibility_title" = "ما نوع المساحة الفرعية التي تريد إنشاءها؟"; +"spaces_creation_address" = "عنوان"; +"space_beta_announce_title" = "المساحات قادمة قريبا"; +"user_verification_sessions_list_session_untrusted" = "غير موثوق به"; +"secrets_recovery_with_passphrase_recover_action" = "استخدم العبارة"; +"biometrics_cant_unlocked_alert_title" = "لا يمكن فتح التطبيق"; +"create_room_section_footer_type_public" = "يمكن فقط للأشخاص المدعوين العثور على المساحة والانضمام إليها، وليس فقط الأشخاص الموجودين في اسم المساحة."; +"space_beta_announce_subtitle" = "النسخة الجديدة من المجتمعات"; +"user_session_verification_unknown" = "حالة التحقق غير معروفة"; +"user_session_overview_session_title" = "جلسة"; +"wysiwyg_composer_start_action_location" = "موقع"; +"user_session_details_last_activity" = "آخر نشاط"; +"spaces_creation_sharing_type_me_and_teammates_detail" = "مساحة خاصة لك ولزملائك في الفريق"; +"user_session_details_device_os" = "نظام التشغيل"; +"spaces_creation_private_space_title" = "مساحتك الخاصة"; +"wysiwyg_composer_start_action_polls" = "استطلاعات الرأي"; +"wysiwyg_composer_format_action_unordered_list" = "تبديل القائمة النقطية"; +"wysiwyg_composer_format_action_indent" = "زيادة المسافة البادئة"; +"wysiwyg_composer_format_action_un_indent" = "تقليل المسافة البادئة"; +"spaces_creation_address_default_message" = "ستكون مساحتك متاحة للعرض في\n%@"; +"spaces_creation_new_rooms_general" = "عام"; +"notice_display_name_changed_to" = "%@ غيّر اسم العرض الخاص به إلى %@"; +"network_offline_prompt" = "يبدو أنه غير متصل بالانترنت."; +"side_menu_action_invite_friends" = "دعوة الأصدقاء"; +"spaces_creation_post_process_creating_space" = "إنشاء مساحة"; +"user_session_unverified_additional_info" = "قم بالتحقق من جلستك الحالية للحصول على مراسلة آمنة معززة."; +"user_session_unverified_session_description" = "الجلسات غير المُتحققة هي الجلسات التي تم تسجيل الدخول إليها باستخدام بيانات اعتمادك ولكن لم يتم التحقق منها.\n\nيجب عليك التأكد بشكل خاص من التعرف على هذه الجلسات، فقد تُمثل استخدامًا غير مُصرّح به لحسابك."; +"user_other_session_menu_select_sessions" = "اختيار الجلسات"; +"poll_timeline_total_votes_not_voted" = "تم الإدلاء بـ %lu من الأصوات. صوّت لرؤية النتائج"; +"user_session_permanently_unverified_session_description" = "هذه الجلسة لا تدعم التشفير، لذا لا يمكن التحقق منها.\n\nلن تتمكن من المشاركة في الغرف التي يُفعّل فيها التشفير عند استخدام هذه الجلسة.\n\nلأعلى مستوى من الأمان والخصوصية، يُنصح باستخدام عملاء Matrix الذين يدعمون التشفير."; +"user_session_verified_session_description" = "الجلسات المُتحققة موجودة في أي مكان تستخدم فيه Element بعد إدخال كلمة المرور أو تأكيد هويتك بجلسة مُتحققة أخرى.\n\nهذا يعني أن لديك جميع المفاتيح اللازمة لفتح رسائلك المُشفرة وتأكيد ثقتك بهذه الجلسة للمستخدمين الآخرين."; +"device_name_desktop" = "%@ سطح المكتب"; +"user_sessions_overview_current_session_section_title" = "الجلسة الحالية"; +"user_sessions_view_all_action" = "عرض الكل (%d)"; +"user_session_verify_action" = "التحقق من الجلسة"; +"user_session_view_details" = "عرض التفاصيل"; +"user_session_details_title" = "تفاصيل الجلسة"; +"user_session_inactive_session_title" = "الجلسات غير النشطة"; +"device_type_name_web" = "الويب"; +"wysiwyg_composer_start_action_camera" = "كاميرا"; +"wysiwyg_composer_start_action_voice_broadcast" = "البث الصوتي"; +"wysiwyg_composer_format_action_code_block" = "مكون كود التبديل"; +"user_other_session_no_verified_sessions" = "لم يتم العثور على جلسات تم التحقق منها."; +"user_inactive_session_item" = "غير نشط لمدة 90 يومًا أو أكثر"; +"wysiwyg_composer_format_action_quote" = "اقتباس التبديل"; +"wysiwyg_composer_link_action_edit_title" = "تعديل الرابط"; +"key_backup_setup_passphrase_set_passphrase_action" = "تعيين عبارة"; +"user_session_unverified_session_title" = "جلسة غير مُتحققة"; +"device_name_web" = "%@ ويب"; +"user_session_verification_unknown_short" = "غير معروف"; +"message_reply_to_sender_sent_their_live_location" = "الموقع المباشر"; +"spaces_creation_add_rooms_message" = "بما أن هذه المساحة مخصصة لك فقط، فلن يتم إعلام أحد. يمكنك إضافة المزيد لاحقًا."; +"spaces_creation_post_process_creating_space_task" = "إنشاء %@"; +"spaces_creation_post_process_uploading_avatar" = "تحميل الصورة الرمزية"; +"spaces_creation_in_spacename_plus_many" = "في مساحات %@ + %@"; +"voice_message_stop_locked_mode_recording" = "اضغط على التسجيل الخاص بك لإيقافه أو الاستماع إليه"; +"poll_edit_form_option_number" = "الخيار %lu"; +"poll_timeline_total_votes" = "%lu الأصوات المدلى بها"; +"location_sharing_live_timer_incoming" = "مباشر حتى %@"; +"user_sessions_overview_link_device" = "ربط جهاز"; +"user_other_session_permanently_unverified_additional_info" = "هذه الجلسة لا تدعم التشفير وبالتالي لا يمكن التحقق منها."; +"user_other_session_verified_additional_info" = "هذه الجلسة جاهزة للمراسلة الآمنة."; +"user_session_push_notifications_message" = "عند تشغيلها، ستتلقى هذه الجلسة إشعارات فورية."; +"user_session_details_device_browser" = "المتصفح"; +"user_session_details_application_url" = "عنوان URL"; +"wysiwyg_composer_action_maximise_action" = "توسيع الملحن"; +"wysiwyg_composer_format_action_bold" = "تطبيق تنسيق غامق"; +"wysiwyg_composer_format_action_italic" = "تطبيق التنسيق المائل"; +"wysiwyg_composer_format_action_link" = "تطبيق تنسيق الرابط"; +"wysiwyg_composer_link_action_link" = "وصلة"; +"device_type_name_unknown" = "غير معروف"; +"user_other_session_verified_sessions_header_subtitle" = "للحصول على أفضل مستوى من الأمان، قم بتسجيل الخروج من أي جلسة لا تعرفها أو لا تستخدمها بعد الآن."; +"device_type_name_desktop" = "سطح المكتب"; +"wysiwyg_composer_link_action_create_title" = "إنشاء رابط"; +"deselect_all" = "إلغاء تحديد الكل"; +"ignore_user" = "تجاهل المستخدم"; +"device_verification_emoji_train" = "يدرب"; +"spaces_creation_address_invalid_characters" = "%@\nيحتوي على أحرف غير صالحة"; +"spaces_creation_new_rooms_message" = "سننشئ غرفة لكل واحد منهم."; +"spaces_creation_new_rooms_random" = "عشوائي"; +"spaces_add_room_missing_permission_message" = "ليس لديك الأذونات اللازمة لإضافة غرف إلى هذه المساحة."; +"side_menu_reveal_action_accessibility_label" = "اللوحة اليسرى"; +"voice_message_release_to_send" = "اضغط باستمرار للتسجيل، ثم حرر للإرسال"; +"leave_space_and_more_rooms" = "اترك مساحة و %@غرف"; +"user_session_learn_more" = "معرفة المزيد"; +"wysiwyg_composer_action_minimise_action" = "ملحن الانكماش"; +"user_session_rename_session_description" = "يستطيع المستخدمون الآخرون في الرسائل المباشرة والغرف التي تنضم إليها عرض قائمة كاملة بجلساتك.\n\nهذا يمنحهم الثقة بأنهم يتحدثون إليك حقًا، ويعني أيضًا أنهم يستطيعون رؤية اسم الجلسة التي تُدخلها هنا."; +"user_other_session_filter_menu_verified" = "تم التحقق"; +"user_other_session_no_inactive_sessions" = "لم يتم العثور على جلسات غير نشطة."; +"device_verification_emoji_hat" = "قبعة"; +"space_beta_announce_badge" = "بيتا (تجريبي)"; +"spaces_creation_new_rooms_title" = "ما هي بعض المناقشات التي ستجريها؟"; +"spaces_creation_invite_by_username_title" = "ادعُ فريقك"; +"spaces_creation_post_process_inviting_users" = "دعوة %@ المستخدمين"; +"leave_space_action" = "غادر المساحة"; +"leave_space_selection_all_rooms" = "حدد جميع الغرف"; +"space_avatar_view_accessibility_hint" = "تغيير الصورة الرمزية للمساحة"; +"leave_space_selection_title" = "تحديد الغرف"; +"side_menu_action_settings" = "إعدادات"; +"side_menu_coach_message" = "مرر لليمين أو انقر لرؤية جميع الغرف"; +"voice_message_broadcast_in_progress_title" = "لا يمكن بدء الرسالة الصوتية"; +"voice_message_broadcast_in_progress_message" = "لا يمكنك بدء رسالة صوتية لأنك تُسجِّل بثًا مباشرًا. يُرجى إنهاء البث المباشر لبدء تسجيل رسالة صوتية"; +"voice_broadcast_unauthorized_title" = "لا يمكن بدء بث صوتي جديد"; +"user_session_inactive_session_description" = "الجلسات غير النشطة هي جلسات لم تستخدمها منذ فترة، ولكنها تستمر في تلقي مفاتيح التشفير.\n\nيؤدي حذف الجلسات غير النشطة إلى تحسين الأمان والأداء، ويسهّل عليك تحديد ما إذا كانت الجلسة الجديدة مشبوهة."; +"user_other_session_unverified_sessions_header_subtitle" = "قم بالتحقق من جلساتك للحصول على رسائل آمنة معززة أو قم بتسجيل الخروج من الجلسات التي لا تعرفها أو لا تستخدمها بعد الآن."; +"user_session_name" = "%@: %@"; +"user_other_session_filter_menu_unverified" = "غير مُتحقق"; +"user_session_details_session_section_footer" = "انسخ أي بيانات عن طريق النقر عليها مع الاستمرار في الضغط عليها."; +"user_session_details_device_ip_location" = "موقع IP"; +"user_inactive_session_item_with_date" = "غير نشط لمدة 90 يومًا أو أكثر (%@)"; +"user_session_overview_session_details_button_title" = "تفاصيل الجلسة"; +"wysiwyg_composer_start_action_media_picker" = "مكتبة الصور"; +"user_session_unverified_short" = "غير مُتحقق"; +"wysiwyg_composer_format_action_underline" = "تطبيق تنسيق التسطير"; +"spaces_creation_empty_room_name_error" = "الاسم مطلوب"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "لا يمكن ضمان صحة هذه الرسالة المشفرة على هذا الجهاز."; +"spaces_creation_sharing_type_me_and_teammates_title" = "أنا وزملائي في الفريق"; +"spaces_creation_invite_by_username" = "دعوة عن طريق اسم المستخدم"; +"spaces_creation_in_spacename_plus_one" = "في %@ + 1 مساحة"; +"side_menu_action_help" = "مساعدة"; +"user_session_push_notifications" = "إشعارات الدفع"; +"user_other_session_filter_menu_all" = "جميع الجلسات"; +"user_other_session_filter_menu_inactive" = "غير نشط"; +"user_session_details_application_section_header" = "تطبيق"; +"user_session_details_session_id" = "معرف الجلسة"; +"wysiwyg_composer_start_action_attachments" = "المرفقات"; +"wysiwyg_composer_format_action_ordered_list" = "تبديل القائمة المرقمة"; +"device_verification_emoji_trumpet" = "بوق"; +"key_verification_tile_request_incoming_approval_decline" = "رفض"; +"spaces_creation_address_already_exists" = "%@\nموجود بالفعل"; +"spaces_creation_email_invites_email_title" = "بريد إلكتروني"; +"spaces_creation_sharing_type_just_me_title" = "أنا فقط"; +"spaces_creation_sharing_type_just_me_detail" = "مساحة خاصة لتنظيم غرفك"; +"spaces_creation_in_many_spaces" = "في %@ مساحات"; +"key_backup_recover_from_recovery_key_recovery_key_title" = "دخول"; +"device_verification_emoji_thumbs up" = "ممتاز"; +"spaces_creation_cancel_title" = "توقف عن إنشاء مساحة؟"; +"spaces_creation_cancel_message" = "سيتم فقدان تقدمك."; +"spaces_creation_new_rooms_support" = "دعم"; +"spaces_creation_email_invites_title" = "ادعُ فريقك"; +"leave_space_selection_no_rooms" = "عدم تحديد الغرف"; +"space_avatar_view_accessibility_label" = "الصورة الرمزية"; +"user_session_verified" = "جلسة تم التحقق منها"; +"wysiwyg_composer_start_action_text_formatting" = "تنسيق النص"; +"wysiwyg_composer_format_action_inline_code" = "تطبيق تنسيق الكود المضمن"; +"wysiwyg_composer_link_action_text" = "نص"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "حدد لقبول %@"; +"user_avatar_view_accessibility_label" = "الصورة الرمزية"; +"spaces_creation_public_space_title" = "مساحتك العامة"; +"spaces_creation_email_invites_message" = "ويمكنك دعوتهم لاحقًا أيضًا."; +"poll_timeline_reply_ended_poll" = "انتهى الاستطلاع"; +"user_session_verified_additional_info" = "جلستك الحالية جاهزة للمراسلة الآمنة."; +"user_session_verification_unknown_additional_info" = "قم بالتحقق من جلستك الحالية للكشف عن حالة التحقق الخاصة بهذه الجلسة."; +"user_session_rename_session_title" = "إعادة تسمية الجلسات"; +"user_session_item_details" = "%1$@ · %2$@"; +"user_session_details_session_name" = "اسم الجلسة"; +"user_session_details_application_name" = "اسم"; +"user_other_session_selected_count" = "%@ تم اختياره"; +"wysiwyg_composer_format_action_strikethrough" = "تطبيق تنسيق الشطب"; +"spaces_creation_invite_by_username_message" = "ويمكنك دعوتهم لاحقًا أيضًا."; +"voice_message_remaining_recording_time" = "%@s متبقية"; +"spaces_creation_sharing_type_title" = "مع من تعمل؟"; +"side_menu_action_feedback" = "تعليق"; +"user_session_unverified" = "جلسة غير مُتحققة"; +"user_other_session_security_recommendation_title" = "جلسات أخرى"; +"user_session_details_application_version" = "إصدار"; +"key_backup_recover_from_passphrase_passphrase_title" = "دخول"; +"device_verification_emoji_santa" = "سانتا"; +"major_update_title" = "الشغب الآن %@"; +"home_context_menu_unmute" = "إزالة الكتم"; +"spaces_creation_new_rooms_room_name_title" = "اسم الغرفة"; +"spaces_creation_sharing_type_message" = "تأكد من أن الأشخاص المناسبين لديهم حق الوصول %@. يمكنك تغيير هذا لاحقًا."; +"spaces_creation_add_rooms_title" = "ماذا تريد أن تضيف؟"; +"spaces_creation_post_process_creating_room" = "إنشاء %@"; +"spaces_creation_post_process_adding_rooms" = "إضافة %@ غرف"; +"spaces_creation_in_one_space" = "في مساحة واحدة"; +"voice_message_lock_screen_placeholder" = "رسالة صوتية"; +"user_session_verified_short" = "تم التحقق"; +"user_other_session_unverified_additional_info" = "قم بالتحقق من هذه الجلسة أو تسجيل الخروج منها للحصول على أفضل مستوى من الأمان والموثوقية."; +"user_other_session_current_session_details" = "جلستك الحالية"; +"user_other_session_filter" = "عامل التصفية"; +"user_other_session_no_unverified_sessions" = "لم يتم العثور على جلسات غير موثقة."; +"user_other_session_clear_filter" = "مسح عامل التصفية"; +"user_other_session_menu_sign_out_sessions" = "تسجيل الخروج من جلسات %@"; +"user_session_item_details_last_activity" = "آخر نشاط %@"; +"user_session_details_device_section_header" = "جهاز"; +"user_session_details_device_ip_address" = "عنوان IP"; +"spaces_coming_soon_title" = "قادم قريباً"; +"leave_space_and_one_room" = "اترك مساحة وغرفة واحدة"; +"room_widget_permission_theme_permission" = "موضوعك"; +"user_avatar_view_accessibility_hint" = "تغيير صورة المستخدم الرمزية"; +"user_session_details_session_section_header" = "جلسة"; +"user_session_overview_current_session_title" = "الجلسة الحالية"; +"wysiwyg_composer_start_action_stickers" = "ملصقات"; +"user_session_got_it" = "فهمتها"; +"user_session_verified_session_title" = "الجلسات التي تم التحقق منها"; +"user_session_details_device_model" = "نموذج"; +"device_name_unknown" = "عميل غير معروف"; +"device_type_name_mobile" = "جوال"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 354a27b63..374e01fb4 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -479,6 +479,7 @@ Tap the + to start adding people."; "room_participants_invite_malformed_id" = "Malformed ID. Should be an email address or a Matrix ID like '@localpart:domain'"; "room_participants_invited_section" = "INVITED"; "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "No identity server is configured so you cannot start a chat with a contact using an email."; +"room_participants_leave_not_allowed_for_last_owner_msg" = "You can't leave the room since you're the only owner of it."; "room_participants_online" = "Online"; "room_participants_offline" = "Offline"; @@ -519,10 +520,12 @@ Tap the + to start adding people."; "room_participants_security_information_room_encrypted" = "Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them."; "room_participants_security_information_room_encrypted_for_dm" = "Messages here are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them."; +"room_member_power_level_owner_in" = "Owner in %@"; "room_member_power_level_admin_in" = "Admin in %@"; "room_member_power_level_moderator_in" = "Moderator in %@"; "room_member_power_level_custom_in" = "Custom (%@) in %@"; +"room_member_power_level_short_owner" = "Owner"; "room_member_power_level_short_admin" = "Admin"; "room_member_power_level_short_moderator" = "Mod"; "room_member_power_level_short_custom" = "Custom"; diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index f7c79fe3d..a562f4543 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -2735,3 +2735,5 @@ // MARK: Sunset "sunset_delegated_oidc_registration_not_supported_title" = "Selle rakendusega enam ei saa luua kasutajakontot teenuses %1$@"; +"room_member_power_level_owner_in" = "„%@“ jututoa omanik"; +"room_member_power_level_short_owner" = "Omanik"; diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index 5dae0ef11..fd4f779f5 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -2990,3 +2990,5 @@ // MARK: Sunset "sunset_delegated_oidc_registration_not_supported_title" = "Anda sudah tidak bisa lagi membuat akun dengan %1$@ menggunakan aplikasi ini"; "sunset_delegated_oidc_registration_not_supported_generic_error" = "Anda sudah tidak bisa lagi membuat akun dengan homeserver yang ditentukan menggunakan aplikasi ini"; +"room_member_power_level_owner_in" = "Pemilik dalam %@"; +"room_member_power_level_short_owner" = "Pemilik"; diff --git a/Riot/Assets/it.lproj/Localizable.strings b/Riot/Assets/it.lproj/Localizable.strings index 3232ff2a4..94ba80bd4 100644 --- a/Riot/Assets/it.lproj/Localizable.strings +++ b/Riot/Assets/it.lproj/Localizable.strings @@ -121,3 +121,4 @@ /* New voice broadcast from a specific person, not referencing a room. */ "VOICE_BROADCAST_FROM_USER" = "%@ ha iniziato una trasmissione vocale"; +"UNSUPPORTED_CALL" = "Chiamata non supportata"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 55551cc09..24c1f159c 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -2754,3 +2754,10 @@ "wysiwyg_composer_action_maximise_action" = "Espandi il compositore"; "room_action_report" = "Segnala stanza"; "room_action_report_prompt_reason" = "Motivo della segnalazione della stanza"; +"call_unsupported_matrix_rtc_call" = "Chiamata non supportata. Per unirsi a questa chiamata è necessaria la nuova app Element X."; +"sunset_delegated_oidc_registration_not_supported_title" = "Non puoi più creare un account con %1$@ usando quest'app"; +"sunset_delegated_oidc_registration_not_supported_message" = "Scarica %1$@ per utilizzare %2$@ per il tuo account o scegli un homeserver differente."; +"sunset_download_banner_title" = "Scarica %1$@"; +"sunset_download_banner_message" = "Più veloce, più sicura, e piena di potenti strumenti di collaborazione."; +"sunset_download_banner_learn_more" = "Per saperne di più"; +"sunset_delegated_oidc_registration_not_supported_generic_error" = "Non puoi più creare un account con l'homeserver indicato usando quest'app"; diff --git a/Riot/Assets/lv.lproj/InfoPlist.strings b/Riot/Assets/lv.lproj/InfoPlist.strings index a1cc84b8d..d84040979 100644 --- a/Riot/Assets/lv.lproj/InfoPlist.strings +++ b/Riot/Assets/lv.lproj/InfoPlist.strings @@ -3,3 +3,4 @@ "NSPhotoLibraryUsageDescription" = "Foto bibliotēka tiek izmantota, lai nosūtītu fotoattēlus un video."; "NSMicrophoneUsageDescription" = "Mikrofons tiek izmantots, lai uzņemtu video un veiktu zvanus."; "NSContactsUsageDescription" = "Kontaktu grāmata tiek izmantota, lai meklētu lietotājus pēc epasta adreses vai telefona numura Riot aplikācijā."; +"NSFaceIDUsageDescription" = "Face ID tiek izmantots, lai piekļūtu lietotnei."; diff --git a/Riot/Assets/pt_BR.lproj/Localizable.strings b/Riot/Assets/pt_BR.lproj/Localizable.strings index 49f919d5d..62f30dc0d 100644 --- a/Riot/Assets/pt_BR.lproj/Localizable.strings +++ b/Riot/Assets/pt_BR.lproj/Localizable.strings @@ -118,3 +118,5 @@ /* New file message from a specific person, not referencing a room. */ "LOCATION_FROM_USER" = "%@ compartilhou a localização dela(e)"; +"VOICE_BROADCAST_FROM_USER" = "%@ iniciou uma transmissão de voz"; +"UNSUPPORTED_CALL" = "Chamada não suportada"; diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index edd2cf70e..52d254d8b 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -2556,8 +2556,8 @@ "authentication_qr_login_start_subtitle" = "Use a câmera neste dispositivo para scannar o QR code mostrado em seu outro dispositivo:"; "authentication_qr_login_start_title" = "Scannar QR code"; "authentication_login_with_qr" = "Fazer signin com QR code"; -"wysiwyg_composer_format_action_underline" = "Aplicar formato sublinhar"; -"wysiwyg_composer_format_action_strikethrough" = "Aplicar formato tachar"; +"wysiwyg_composer_format_action_underline" = "Aplicar formato de sublinhado"; +"wysiwyg_composer_format_action_strikethrough" = "Aplicar formato tachado"; "wysiwyg_composer_format_action_italic" = "Aplicar formato itálico"; // Formatting Actions @@ -2671,3 +2671,77 @@ "room_command_join_room_description" = "Entra na sala com o endereço fornecido"; "room_command_invite_user_description" = "Convida o usuário com o ID fornecido para a sala atual"; "authentication_qr_login_failure_device_not_supported" = "Vincular com este dispositivo não é suportado."; +"key_verification_scan_qr_code_information_other_device" = "Aponte sua câmera para o código QR exibido em seu outro dispositivo para verificar esta sessão"; +"wysiwyg_composer_action_minimise_action" = "Compositor psiquiatra"; +"pill_message" = "Mensagem"; +"sunset_delegated_oidc_registration_not_supported_title" = "Você não pode mais criar uma conta com %1$@ usando este aplicativo"; +"sunset_delegated_oidc_registration_not_supported_message" = "Baixe %1$@ para usar %2$@ na sua conta ou escolha um servidor doméstico diferente."; +"sunset_delegated_oidc_registration_not_supported_generic_error" = "Você não pode mais criar uma conta com o servidor doméstico inserido usando este aplicativo"; +"sunset_download_banner_title" = "Baixar %1$@"; +"sunset_download_banner_message" = "Mais rápido, mais seguro e repleto de poderosas ferramentas de colaboração."; +"sunset_download_banner_learn_more" = "Saber mais"; +"call_unsupported_matrix_rtc_call" = "Chamada não suportada. O novo aplicativo Element X é necessário para participar desta chamada."; +"voice_broadcast_voip_cannot_start_description" = "Você não pode iniciar uma chamada porque está gravando uma transmissão ao vivo. Encerre a transmissão ao vivo para iniciar a chamada."; +"poll_timeline_decryption_error" = "Devido a erros de descriptografia, alguns votos podem não ser contados"; +"wysiwyg_composer_action_maximise_action" = "Expandir compositor"; +"room_command_change_room_topic_description" = "Define o tema da sala"; +"room_action_report" = "Sala de relatórios"; +"room_action_report_prompt_reason" = "Motivo da denúncia desta sala"; +"room_command_discard_session_description" = "Força o descarte da sessão de grupo de saída atual em uma sala criptografada"; +"room_command_error_unknown_command" = "Comando inválido ou não tratado"; +"pill_room_fallback_display_name" = "Espaço/Sala"; +"device_verification_self_verify_wait_recover_secrets_additional_help" = "Não consegue acessar uma sessão %@ existente?"; +"room_waiting_other_participants_title" = "Aguardando a adesão dos usuários %@"; +"voice_broadcast_playback_lock_screen_placeholder" = "Transmissão de voz"; +"device_verification_self_verify_open_on_other_device_title" = "Abra %@ no seu outro dispositivo"; +"key_verification_scan_qr_code_information_other_session" = "Aponte sua câmera para o código QR exibido em seu outro dispositivo para verificar sua sessão"; +"voice_message_broadcast_in_progress_title" = "Não é possível iniciar a mensagem de voz"; +"voice_message_broadcast_in_progress_message" = "Você não pode iniciar uma mensagem de voz, pois está gravando uma transmissão ao vivo. Encerre a transmissão ao vivo para iniciar a gravação da mensagem de voz."; +"notice_display_name_changed_to" = "%@ mudou seu nome de exibição para %@"; +"pill_message_from" = "Mensagem de %@"; +"poll_timeline_loading" = "Carregando..."; +"wysiwyg_composer_format_action_un_indent" = "Diminuir recuo"; +"wysiwyg_composer_format_action_indent" = "Aumentar o recuo"; +"room_details_polls" = "Histórico de pesquisas"; +"poll_history_no_active_poll_text" = "Não há enquetes ativas nesta sala"; +"poll_history_fetching_error" = "Erro ao buscar enquetes."; +"wysiwyg_composer_format_action_code_block" = "Alternar bloco de código"; +"poll_history_detail_view_in_timeline" = "Ver enquete na linha do tempo"; +"settings_acceptable_use" = "Política de Uso Aceitável"; +"launch_loading_generic" = "Sincronizando suas conversas"; +"launch_loading_delay_warning" = "Isso pode demorar um pouco mais.\nObrigado pela sua paciência."; +"poll_history_no_past_poll_text" = "Não há pesquisas anteriores nesta sala"; +"poll_timeline_ended_text" = "Terminou a enquete"; +"home_context_menu_mark_as_unread" = "Marcar como não lido"; +"poll_history_title" = "Histórico de pesquisas"; +"poll_history_active_segment_title" = "Pesquisas ativas"; +"wysiwyg_composer_format_action_unordered_list" = "Alternar lista com marcadores"; +"voice_broadcast_recorder_connection_error" = "Erro de conexão - Gravação pausada"; +"wysiwyg_composer_format_action_quote" = "Alternar citação"; +"manage_session_redirect" = "Você será redirecionado ao provedor de autenticação do seu servidor para concluir o logout."; +"manage_session_redirect_error" = "Funcionalidade indisponível no momento. Entre em contato com o administrador do seu servidor doméstico."; +"room_command_reset_user_power_level_description" = "Usuário Deops com ID fornecido"; +"settings_push_rules_error" = "Ocorreu um erro ao atualizar suas preferências de notificação. Tente alternar sua opção novamente."; +"key_verification_scan_qr_code_title" = "Escaneie o código QR"; +"key_verification_scan_qr_code_information_other_user" = "Aponte sua câmera para o código QR exibido no dispositivo para verificar a sessão"; +"key_verification_scan_qr_code_information_new_session" = "Aponte sua câmera para o código QR exibido em seu outro dispositivo para verificar sua nova sessão"; +"voice_broadcast_connection_error_title" = "Erro de conexão"; +"voice_broadcast_connection_error_message" = "Infelizmente, não podemos iniciar a gravação no momento. Tente novamente mais tarde."; +"voice_broadcast_playback_unable_to_decrypt" = "Não é possível decifrar esta transmissão de voz."; +"poll_history_past_segment_title" = "Pesquisas anteriores"; +"poll_history_load_more" = "Carregar mais enquetes"; +"poll_timeline_reply_ended_poll" = "Enquete encerrada"; +"wysiwyg_composer_format_action_ordered_list" = "Alternar lista numerada"; +"key_backup_recover_from_private_key_progress" = "%@%% Concluído"; +"pill_message_in" = "Mensagem em %@"; +"poll_history_loading_text" = "Exibindo enquetes"; +"poll_history_no_active_poll_period_text" = "Não há enquetes ativas nos últimos %@ dias. Carregue mais enquetes para ver as enquetes dos meses anteriores."; +"poll_history_no_past_poll_period_text" = "Não há enquetes anteriores nos últimos %@ dias. Carregue mais enquetes para ver as enquetes dos meses anteriores."; +"settings_manage_account_title" = "Conta"; +"settings_manage_account_action" = "Gerenciar conta"; +"settings_manage_account_description" = "Gerencie sua conta em %@"; +"key_verification_self_verify_security_upgrade_alert_title" = "Aplicativo atualizado"; +"key_verification_self_verify_security_upgrade_alert_message" = "A segurança das mensagens foi aprimorada com a atualização mais recente. Verifique novamente o seu dispositivo."; +"device_verification_self_verify_open_on_other_device_information" = "Você precisa verificar esta sessão para ler seu histórico de mensagens seguras.\n\nAbra o Element em um dos seus outros dispositivos e siga as instruções."; +"voice_broadcast_voip_cannot_start_title" = "Não é possível iniciar uma chamada"; +"room_waiting_other_participants_message" = "Depois que os usuários convidados se juntarem a %@, você poderá bater papo e a sala será criptografada de ponta a ponta"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index d8ff3cf4b..d9ca5af43 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -2124,7 +2124,7 @@ "onboarding_use_case_existing_server_message" = "Хотите присоединиться к существующему серверу?"; "onboarding_use_case_skip_button" = "Пропустить вопрос"; /* The placeholder string contains onboarding_use_case_skip_button as a tappable action */ -"onboarding_use_case_not_sure_yet" = "Ещё не уверенны? %@"; +"onboarding_use_case_not_sure_yet" = "Ещё не уверены? %@"; "onboarding_use_case_community_messaging" = "Сообщества"; "onboarding_use_case_work_messaging" = "Команды"; "onboarding_use_case_personal_messaging" = "Друзья и семья"; diff --git a/Riot/Assets/uz.lproj/Localizable.strings b/Riot/Assets/uz.lproj/Localizable.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/uz.lproj/Localizable.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/uz.lproj/Vector.strings b/Riot/Assets/uz.lproj/Vector.strings new file mode 100644 index 000000000..bf8bb59b9 --- /dev/null +++ b/Riot/Assets/uz.lproj/Vector.strings @@ -0,0 +1 @@ +"store_short_description" = "Xavfsiz markazlashmagan chat/VoIP"; diff --git a/Riot/Assets/vi.lproj/InfoPlist.strings b/Riot/Assets/vi.lproj/InfoPlist.strings index 41eb2f4aa..6ee4855d4 100644 --- a/Riot/Assets/vi.lproj/InfoPlist.strings +++ b/Riot/Assets/vi.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ // Permissions usage explanations -"NSCameraUsageDescription" = "Máy ảnh được sử dụng để chụp ảnh và quay phim, thực hiện các cuộc gọi video."; +"NSCameraUsageDescription" = "Máy ảnh được dùng để gọi truyền hình hoặc quay phim, chụp ảnh."; "NSPhotoLibraryUsageDescription" = "Thư viện ảnh được dùng để gửi hình ảnh và videos."; "NSMicrophoneUsageDescription" = "Element cần quyền truy cập vào mi-crô của bạn để nhận và thực hiện cuộc gọi, quay video, và ghi âm các tin nhắn thoại."; "NSContactsUsageDescription" = "Element sẽ hiển thị danh bạ của bạn để bạn có thể mời họ trò chuyện."; diff --git a/Riot/Assets/vi.lproj/Localizable.strings b/Riot/Assets/vi.lproj/Localizable.strings index bbad48963..01b38d1bb 100644 --- a/Riot/Assets/vi.lproj/Localizable.strings +++ b/Riot/Assets/vi.lproj/Localizable.strings @@ -38,17 +38,17 @@ /* Incoming one-to-one video call */ "VIDEO_CALL_FROM_USER" = "Gọi video từ %@"; /* Incoming unnamed voice conference invite from a specific person */ -"VOICE_CONF_FROM_USER" = "Gọi nhóm từ %@"; +"VOICE_CONF_FROM_USER" = "Cuộc gọi nhóm từ %@"; /* Incoming unnamed video conference invite from a specific person */ -"VIDEO_CONF_FROM_USER" = "Gọi video nhóm từ %@"; +"VIDEO_CONF_FROM_USER" = "Cuộc gọi truyền hình nhóm từ %@"; /* Incoming named voice conference invite from a specific person */ -"VOICE_CONF_NAMED_FROM_USER" = "Gọi nhóm từ %@: '%@'"; +"VOICE_CONF_NAMED_FROM_USER" = "Cuộc gọi nhóm từ %@: '%@'"; /* Incoming named video conference invite from a specific person */ -"VIDEO_CONF_NAMED_FROM_USER" = "Gọi video nhóm từ %@: '%@'"; +"VIDEO_CONF_NAMED_FROM_USER" = "Cuộc gọi video nhóm từ %@: '%@'"; /** Key verification **/ -"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ muốn xác minh"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ muốn xác thực"; /* Group call from user, CallKit caller name */ "GROUP_CALL_FROM_USER" = "%@ (cuộc gọi nhóm)"; @@ -60,7 +60,7 @@ "USER_MEMBERSHIP_UPDATED" = "%@ đã cập nhật hồ sơ"; /* A user has change their avatar */ -"USER_UPDATED_AVATAR" = "%@ đã đổi avatar"; +"USER_UPDATED_AVATAR" = "%@ đã đổi ảnh đại diện"; /* A user has change their name to a new name which we don't know */ "GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ đã đổi tên"; @@ -71,7 +71,7 @@ "USER_UPDATED_DISPLAYNAME" = "%@ đã đổi tên sang %@"; /* A user has reacted to a message, but the reaction content is unknown */ -"GENERIC_REACTION_FROM_USER" = "%@ đã gửi một tương tác"; +"GENERIC_REACTION_FROM_USER" = "%@ đã bày tỏ cảm xúc"; /** Reactions **/ @@ -96,10 +96,10 @@ "STICKER_FROM_USER" = "%@ đã gửi một sticker"; /* A single unread message */ -"SINGLE_UNREAD" = "Bạn đã nhận một tin nhắn"; +"SINGLE_UNREAD" = "Bạn nhận được một tin nhắn"; /* A single unread message in a room */ -"SINGLE_UNREAD_IN_ROOM" = "Bạn đã nhận một tin nhắn trong %@"; +"SINGLE_UNREAD_IN_ROOM" = "Bạn nhận được một tin nhắn trong %@"; /* New file message from a specific person, not referencing a room. */ "FILE_FROM_USER" = "%@ đã gửi một tệp %@"; @@ -131,3 +131,6 @@ /** General **/ "Notification" = "Thông báo"; +"VOICE_BROADCAST_FROM_USER" = "%@ bắt đầu phát thanh"; +"UNSUPPORTED_CALL" = "Cuộc gọi không được hỗ trợ"; +"LOCATION_FROM_USER" = "%@ đã chia sẻ vị trí của họ"; diff --git a/Riot/Assets/vi.lproj/Vector.strings b/Riot/Assets/vi.lproj/Vector.strings index dd731de4d..52cbc29e2 100644 --- a/Riot/Assets/vi.lproj/Vector.strings +++ b/Riot/Assets/vi.lproj/Vector.strings @@ -37,14 +37,14 @@ "auth_register" = "Đăng kí"; "auth_submit" = "Gửi đi"; "auth_skip" = "Bỏ qua"; -"auth_send_reset_email" = "Gửi email đặt lại"; +"auth_send_reset_email" = "Gửi thư đặt lại"; "auth_return_to_login" = "Trở về trang đăng kí"; -"auth_user_id_placeholder" = "Email hoặc tên đăng nhập"; +"auth_user_id_placeholder" = "Địa chỉ thư điện tử hoặc tên đăng nhập"; "auth_password_placeholder" = "Mật khẩu"; "auth_new_password_placeholder" = "Mật khẩu mới"; "auth_user_name_placeholder" = "Tên đăng nhập"; -"auth_optional_email_placeholder" = "Địa chỉ email (không bắt buộc)"; -"auth_email_placeholder" = "Địa chỉ email"; +"auth_optional_email_placeholder" = "Địa chỉ thư điện tử (không bắt buộc)"; +"auth_email_placeholder" = "Địa chỉ thư điện tử"; "auth_optional_phone_placeholder" = "Số điện thoại (không bắt buộc)"; "auth_phone_placeholder" = "Số điện thoại"; "auth_repeat_password_placeholder" = "Nhật lại mật khẩu"; @@ -55,38 +55,38 @@ "warning" = "Cảnh báo"; "auth_invalid_user_name" = "Tên đăng nhập chỉ được chứa các chữ cái, dấu chấm, dấu gạch ngang và dấu gạch dưới"; "auth_invalid_password" = "Mật khẩu quá ngắn (tối thiểu 6 kí tự)"; -"auth_invalid_email" = "Địa chỉ Email không hợp lệ"; +"auth_invalid_email" = "Địa chỉ Địa thư điện tử không hợp lệ"; "auth_invalid_phone" = "Số điện thoại không hợp lệ"; "auth_missing_password" = "Thiếu mật khẩu"; "auth_add_email_message" = "Thêm địa chỉ email vào tài khoản để người dùng khác có thể tìm thấy bạn và giúp bạn thay đổi mật khẩu về sau."; "auth_add_phone_message" = "Thêm số điện thoại vào tài khoản của bạn để giúp người dùng khác có thể tìm thấy bạn."; "auth_add_email_phone_message" = "Thêm địa chỉ email hoặc số điện thoại vào tài khoản để người dùng khác có thể tìm thấy bạn. Địa chỉ email cũng sẽ giúp bạn thay đổi mật khẩu về sau."; "auth_add_email_and_phone_message" = "Thêm địa chỉ email hoặc số điện thoại vào tài khoản để người dùng khác có thể tìm thấy bạn. Địa chỉ email cũng sẽ giúp bạn thay đổi mật khẩu về sau."; -"auth_missing_email" = "Thiếu địa chỉ email"; +"auth_missing_email" = "Thiếu địa chỉ thư điện tử"; "auth_missing_phone" = "Thiếu số điện thoại"; -"auth_missing_email_or_phone" = "Thiếu địa chỉ email hoặc số điện thoại"; -"auth_email_in_use" = "Địa chỉ email này đã được sử dụng"; +"auth_missing_email_or_phone" = "Thiếu địa chỉ thư điện tử hoặc số điện thoại"; +"auth_email_in_use" = "Địa chỉ thư điện tử này đã được sử dụng"; "auth_phone_in_use" = "Số điện thoại này đã được sử dụng"; "auth_untrusted_id_server" = "Máy chủ xác thực không được tin cậy"; "auth_password_dont_match" = "Mật khẩu không trùng khớp"; "auth_username_in_use" = "Tên đăng nhập đang được sử dụng"; "auth_forgot_password" = "Quên mật khẩu danh khoản Matrix?"; -"auth_email_not_found" = "Gửi email thất bại: Địa chỉ email này không thể tìm thấy"; +"auth_email_not_found" = "Gửi thư thất bại: Địa chỉ thư điện tử này không thể tìm thấy"; "auth_use_server_options" = "Sử dụng tùy chọn máy chủ tuỳ chỉnh (nâng cao)"; -"auth_email_validation_message" = "Vui lòng kiểm tra email của bẹn để tiếp tục đăng kí"; +"auth_email_validation_message" = "Vui lòng kiểm tra hòm thư của bẹn để tiếp tục đăng ký"; "auth_msisdn_validation_title" = "Xác minh đang chờ xử lí"; "auth_msisdn_validation_message" = "Chúng tôi đã gửi mã kích hoạt qua SMS. Vui lòng nhập mã kích hoạt bên dưới."; "auth_msisdn_validation_error" = "Không thể xác thực số điện thoại."; "auth_recaptcha_message" = "Home Server này muốn đảm bảo rằng bạn không phải là Robot"; -"auth_reset_password_message" = "Để thay đổi mật khẩu, nhập địa chỉ email được kết nối với tài khoản của bạn:"; -"auth_reset_password_missing_email" = "Bạn phải nhập địa chỉ email đã được kết nối với tài khoản của bạn."; +"auth_reset_password_message" = "Để thay đổi mật khẩu, nhập địa chỉ thư điện tử được kết nối với tài khoản của bạn:"; +"auth_reset_password_missing_email" = "Bạn phải nhập địa chỉ thư điện tử đã được kết nối với tài khoản của bạn."; "auth_reset_password_missing_password" = "Bạn phải nhập mật khẩu mới."; -"auth_reset_password_email_validation_message" = "Email đã được gửi tới %@. Khi bạn đã theo liên kết trong đó, bấm vào dưới đây."; -"auth_reset_password_next_step_button" = "Tôi đã xác thực địa chỉ email của tôi"; -"auth_reset_password_error_unauthorized" = "Xác thực địa chỉ email thất bại: hãy đảm bảo rằng bạn đã bấm vào địa chỉ đính kèm trong email"; -"auth_reset_password_error_not_found" = "Địa chỉ email có vẻ chưa được liên kết với Matrix ID trên homeserver này."; +"auth_reset_password_email_validation_message" = "Thư đã được gửi tới %@. Khi bạn đã theo liên kết trong đó, bấm vào dưới đây."; +"auth_reset_password_next_step_button" = "Tôi đã xác thực địa chỉ thư điện tử của tôi"; +"auth_reset_password_error_unauthorized" = "Xác thực địa chỉ thư điện tử thất bại: hãy đảm bảo rằng bạn đã bấm vào địa chỉ đính kèm trong thư"; +"auth_reset_password_error_not_found" = "Địa chỉ thư điện tử của bạn không có tài khoản trên máy chủ nhà này."; "auth_reset_password_success_message" = "Mật khẩu của bạn đã được thiết lập lại.\n\nBạn đã được đăng xuất khỏi tất cả các thiết bị và sẽ không còn nhận được thông báo. Để bật lại thông báo, đăng nhập lại trên mỗi thiết bị."; -"auth_add_email_and_phone_warning" = "Đăng kí với mật khẩu và số điện thoại cùng lúc chưa được hỗ trợ cho tới khi api được thiết lập. Duy nhất số điện thoại sẽ được liên kết với với tài khoản. Bạn sẽ phải thêm email vào hồ sơ trong mục cài đặt."; +"auth_add_email_and_phone_warning" = "Đăng ký bằng cả địa chỉ thư điện tử và số điện thoại chưa được hỗ trợ vì chưa có API. Chỉ có số điện thoại được thêm vào tài khoản của bạn. Bạn có thể thêm địa chỉ thư điện tử trong phần cài đặt hồ sơ."; // Chat creation "room_creation_title" = "Cuộc trò chuyện mới"; "room_creation_account" = "Tài khoản"; @@ -1594,10 +1594,10 @@ "auth_reset_password_error_is_required" = "Không có máy chủ xác thực nào được cấu hình: thêm một trong các tùy chọn máy chủ để đặt lại mật khẩu của bạn."; "auth_forgot_password_error_no_configured_identity_server" = "Không có máy chủ xác thực nào được cấu hình: thêm một để đặt lại mật khẩu của bạn."; "auth_phone_is_required" = "Không có máy chủ xác thực nào được cấu hình để bạn không thể thêm số điện thoại để đặt lại mật khẩu của mình trong tương lai."; -"auth_email_is_required" = "Không có máy chủ xác thực nào được cấu hình để bạn không thể thêm địa chỉ email để đặt lại mật khẩu của mình trong tương lai."; -"auth_add_email_phone_message_2" = "Đặt email để khôi phục tài khoản. Sử dụng email hoặc điện thoại sau này để được tùy chọn phát hiện bởi những người biết bạn."; +"auth_email_is_required" = "Không có máy chủ xác thực nào được cấu hình nên bạn không thể thêm địa chỉ thư điện tử để đặt lại mật khẩu của mình trong tương lai."; +"auth_add_email_phone_message_2" = "Đặt địa chỉ thư điện tử để khôi phục tài khoản. Sử dụng địa chỉ thư điện tử hoặc điện thoại sau này để được tùy chọn phát hiện bởi những người biết bạn."; "auth_add_phone_message_2" = "Đặt điện thoại và sau đó có thể tùy chọn được tìm kiếm bởi những người biết bạn."; -"auth_add_email_message_2" = "Đặt email để khôi phục tài khoản và sau đó có thể tùy chọn được tìm kiếm bởi những người biết bạn."; +"auth_add_email_message_2" = "Đặt địa chỉ thư điện tử để khôi phục tài khoản và sau đó có thể tùy chọn được tìm kiếm bởi những người biết bạn."; "auth_login_single_sign_on" = "Đăng nhập"; // Accessibility @@ -1994,3 +1994,16 @@ "onboarding_splash_register_button_title" = "Tạo danh khoản"; "accessibility_button_label" = "nút"; "enable" = "Bật"; +"authentication_choose_password_not_verified_title" = "Địa chỉ thư điện tử chưa được xác nhận"; +"authentication_verify_email_text_field_placeholder" = "Địa chỉ thư điện tử"; +"authentication_verify_email_waiting_title" = "Xác nhận địa chỉ thư điện tử của bạn."; +"authentication_forgot_password_input_title" = "Nhập địa chỉ thư điện tử"; +"authentication_forgot_password_text_field_placeholder" = "Địa chỉ thư điện tử"; +"authentication_forgot_password_waiting_button" = "Gửi lại"; +"authentication_login_username" = "Tên đăng nhập / Địa chỉ thư điện tử / Số điện thoại"; +"authentication_verify_email_input_title" = "Nhập địa chỉ thư điện tử"; +"authentication_verify_email_waiting_hint" = "Không nhận được thư?"; +"authentication_verify_email_waiting_button" = "Gửi lại"; +"authentication_verify_email_input_message" = "%@ cần xác thực tài khoản của bạn"; +"authentication_verify_email_waiting_message" = "Làm theo chỉ dẫn được gửi đến %@"; +"authentication_forgot_password_waiting_title" = "Kiểm tra hòm thư của bạn."; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index ee805714c..ac013a687 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -1598,7 +1598,7 @@ "accessibility_button_label" = "按钮"; "enable" = "启用"; "onboarding_splash_page_1_message" = "独立安全的通信,为您提供与家中面谈相同的私密性。"; -"onboarding_splash_page_1_title" = "掌控你的对话。"; +"onboarding_splash_page_1_title" = "掌控您的对话。"; // Onboarding "onboarding_splash_register_button_title" = "注册"; @@ -2086,7 +2086,7 @@ "room_many_users_are_typing" = "%@、%@和其他人正在输入……"; /* The placeholder %1$tu will be replaced with a number and %2$@ with the user's search terms. */ "directory_search_results" = "为%2$@找到%1$tu个结果"; -"onboarding_splash_page_2_title" = "一切都在你的掌控中。"; +"onboarding_splash_page_2_title" = "一切都在您的掌控中。"; "onboarding_congratulations_personalize_button" = "个性化用户资料"; /* The placeholder string contains the user's matrix ID */ "onboarding_congratulations_message" = "你的账户%@已创建"; @@ -2101,7 +2101,7 @@ "onboarding_use_case_title" = "你会和谁聊得最多?"; "onboarding_splash_page_4_message" = "Element也很适合工作场所。受世界最安全的组织的信任。"; "onboarding_splash_page_3_message" = "端到端加密且不要求电话号码。无广告或数据挖掘。"; -"onboarding_splash_page_2_message" = "选择在哪里保存你的对话,给你控制和独立。通过Matrix连接。"; +"onboarding_splash_page_2_message" = "选择在哪里保存您的对话,给您控制和独立。通过Matrix连接。"; // MARK: Reactions @@ -2433,5 +2433,10 @@ "room_command_discard_session_description" = "强制当前已在加密房间中的外部群组会话失效"; "call_jitsi_unable_to_start" = "无法开始会议通话"; "room_suggestion_settings_screen_title" = "将房间设置为该空间中的建议房间"; -"room_suggestion_settings_screen_message" = "向空间成员推荐建议房间"; +"room_suggestion_settings_screen_message" = "向空间成员推荐建议房间。"; "key_backup_recover_from_private_key_progress" = "完成 %@%%"; +"sunset_download_banner_learn_more" = "了解更多"; +"sunset_delegated_oidc_registration_not_supported_message" = "下载 %1$@ 后才能在您的账户中使用 %2$@,或选择另一个家服务器。"; +"sunset_delegated_oidc_registration_not_supported_generic_error" = "您已无法用该家服务器在本应用中创建账户"; +"sunset_download_banner_title" = "下载 %1$@"; +"sunset_delegated_oidc_registration_not_supported_title" = "您已无法再用 %1$@ 在本应用中创建账户"; diff --git a/Riot/Categories/MXRoom.swift b/Riot/Categories/MXRoom.swift new file mode 100644 index 000000000..1ff03e03a --- /dev/null +++ b/Riot/Categories/MXRoom.swift @@ -0,0 +1,36 @@ +// +// 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. +// + +@objc +extension MXRoom { + /// Returns true if the user is the last owner of the room, but not the last member. + func isLastOwner() async throws -> Bool { + let userID = mxSession.myUserId + let state = try await state() + + let requiredPowerLevel: RoomPowerLevel = state.isMSC4289Supported() ? .owner : .admin + + guard state.powerLevelOfUser(withUserID: userID) >= requiredPowerLevel.rawValue else { + return false + } + + guard let joinedMembers = try await members()?.members(with: .join) else { + return false + } + + var areOtherMembers = false + for member in joinedMembers where member.userId != userID { + // User is not the last member in the whole room. + areOtherMembers = true + // If there are other owners/admins the user can leave + if state.powerLevelOfUser(withUserID: member.userId) >= requiredPowerLevel.rawValue { + return false + } + } + return areOtherMembers + } +} diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 49f68d01d..8fc08b49b 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -6155,6 +6155,10 @@ public class VectorL10n: NSObject { public static func roomMemberPowerLevelModeratorIn(_ p1: String) -> String { return VectorL10n.tr("Vector", "room_member_power_level_moderator_in", p1) } + /// Owner in %@ + public static func roomMemberPowerLevelOwnerIn(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_member_power_level_owner_in", p1) + } /// You will not be able to undo this change as you are promoting the user to have the same power level as yourself.\nAre you sure? public static var roomMemberPowerLevelPrompt: String { return VectorL10n.tr("Vector", "room_member_power_level_prompt") @@ -6171,6 +6175,10 @@ public class VectorL10n: NSObject { public static var roomMemberPowerLevelShortModerator: String { return VectorL10n.tr("Vector", "room_member_power_level_short_moderator") } + /// Owner + public static var roomMemberPowerLevelShortOwner: String { + return VectorL10n.tr("Vector", "room_member_power_level_short_owner") + } /// Editing public static var roomMessageEditing: String { return VectorL10n.tr("Vector", "room_message_editing") @@ -6439,6 +6447,10 @@ public class VectorL10n: NSObject { public static var roomParticipantsInvitedSection: String { return VectorL10n.tr("Vector", "room_participants_invited_section") } + /// You can't leave the room since you're the only owner of it. + public static var roomParticipantsLeaveNotAllowedForLastOwnerMsg: String { + return VectorL10n.tr("Vector", "room_participants_leave_not_allowed_for_last_owner_msg") + } /// Leaving public static var roomParticipantsLeaveProcessing: String { return VectorL10n.tr("Vector", "room_participants_leave_processing") diff --git a/Riot/Managers/Widgets/WidgetManager.m b/Riot/Managers/Widgets/WidgetManager.m index 4a3fb8995..430b86119 100644 --- a/Riot/Managers/Widgets/WidgetManager.m +++ b/Riot/Managers/Widgets/WidgetManager.m @@ -348,7 +348,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Check user's power in the room MXRoomPowerLevels *powerLevels = roomState.powerLevels; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:room.mxSession.myUser.userId]; + NSInteger oneSelfPowerLevel = [roomState powerLevelOfUserWithUserID:room.mxSession.myUser.userId]; // The user must be able to send state events to manage widgets if (oneSelfPowerLevel < powerLevels.stateDefault) diff --git a/Riot/Model/Room/RoomPowerLevel.swift b/Riot/Model/Room/RoomPowerLevel.swift index b008801b2..9463f5586 100644 --- a/Riot/Model/Room/RoomPowerLevel.swift +++ b/Riot/Model/Room/RoomPowerLevel.swift @@ -10,13 +10,16 @@ import Foundation /// Riot Standard Room Member Power Level @objc public enum RoomPowerLevel: Int { + case owner = 150 case admin = 100 case moderator = 50 case user = 0 public init?(rawValue: Int) { switch rawValue { - case 100...: + case 150...: + self = .owner + case 100...149: self = .admin case 50...99: self = .moderator diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 81650a760..c13eb9432 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -125,7 +125,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [tableSearchBar setImage:AssetImages.filterOff.image forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal]; - + tableSearchBar.delegate = self; displayedSectionHeaders = [NSMutableArray array]; @@ -133,7 +133,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro _contextMenuProvider = [RecentCellContextMenuProvider new]; self.contextMenuProvider.serviceDelegate = self; self.contextMenuProvider.menuProviderDelegate = self; - + // Set itself as delegate by default. self.delegate = self; } @@ -151,10 +151,10 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Register key backup banner cells [self.recentsTableView registerNib:SecureBackupBannerCell.nib forCellReuseIdentifier:SecureBackupBannerCell.defaultReuseIdentifier]; - + // Register key verification banner cells [self.recentsTableView registerNib:CrossSigningSetupBannerCell.nib forCellReuseIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier]; - + [self.recentsTableView registerClass:SectionHeaderView.class forHeaderFooterViewReuseIdentifier:SectionHeaderView.defaultReuseIdentifier]; @@ -181,7 +181,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [self.recentsSearchBar setImage:AssetImages.filterOff.image forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal]; - + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -196,7 +196,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro - (void)userInterfaceThemeDidChange { [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; - + self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; // Use the primary bg color for the recents table view in plain style. @@ -204,15 +204,15 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro self.recentsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor; topview.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; - + [ThemeService.shared.theme applyStyleOnSearchBar:tableSearchBar]; [ThemeService.shared.theme applyStyleOnSearchBar:self.recentsSearchBar]; - + // Force table refresh [self.recentsTableView reloadData]; [self.emptyView updateWithTheme:ThemeService.shared.theme]; - + [self setNeedsStatusBarAppearanceUpdate]; } @@ -270,7 +270,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [super viewWillAppear:animated]; isViewVisible = YES; [self.screenTracker trackScreen]; - + // Reset back user interactions self.userInteractionEnabled = YES; @@ -341,7 +341,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // the selected room (if any) is highlighted. [self refreshCurrentSelectedCell:YES]; } - + if (self.recentsDataSource) { [self refreshRecentsTable]; @@ -913,10 +913,10 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro { Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerInvite; } - + // Avoid multiple openings of rooms self.userInteractionEnabled = NO; - + // Do not stack views when showing room ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO]; @@ -935,7 +935,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro - (void)showRoomPreviewWithData:(RoomPreviewData*)roomPreviewData { Analytics.shared.joinedRoomTrigger = AnalyticsJoinedRoomTriggerRoomDirectory; - + // Do not stack views when showing room ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO sender:nil sourceView:nil]; @@ -1008,7 +1008,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro { // Retrieve the invited room MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; - + if (invitedRoom.summary.roomType == MXRoomTypeSpace) { // Indicates that spaces are not supported @@ -1023,7 +1023,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro { // Retrieve the invited room MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; - + if (invitedRoom.summary.roomType == MXRoomTypeSpace) { // Indicates that spaces are not supported @@ -1062,7 +1062,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [super dataSource:dataSource didCellChange:changes]; return; } - + if ([changes isKindOfClass:NSIndexPath.class]) { NSIndexPath *indexPath = (NSIndexPath *)changes; @@ -1073,7 +1073,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro TableViewCellWithCollectionView *collectionViewCell = (TableViewCellWithCollectionView *)cell; [collectionViewCell.collectionView reloadData]; - + CGRect headerFrame = [self.recentsTableView rectForHeaderInSection:indexPath.section]; UIView *headerView = [self.recentsTableView headerViewForSection:indexPath.section]; UIView *updatedHeaderView = [self.dataSource viewForHeaderInSection:indexPath.section withFrame:headerFrame inTableView:self.recentsTableView]; @@ -1111,7 +1111,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro } [self showEmptyViewIfNeeded]; - + if (dataSource.state == MXKDataSourceStateReady) { [[NSNotificationCenter defaultCenter] postNotificationName:RecentsViewControllerDataReadyNotification @@ -1120,109 +1120,120 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro } #pragma mark - Swipe actions - - (void)leaveEditedRoom { if (editedRoomId) { NSString *currentRoomId = editedRoomId; - - __weak typeof(self) weakSelf = self; - - NSString *title, *message; - if ([self.mainSession roomWithRoomId:currentRoomId].isDirect) - { - title = [VectorL10n roomParticipantsLeavePromptTitleForDm]; - message = [VectorL10n roomParticipantsLeavePromptMsgForDm]; - } - else - { - title = [VectorL10n roomParticipantsLeavePromptTitle]; - message = [VectorL10n roomParticipantsLeavePromptMsg]; - } - - // confirm leave - UIAlertController *leavePrompt = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [leavePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [leavePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] - style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Check whether the user didn't leave the room yet - // TODO: Handle multi-account - MXRoom *room = [self.mainSession roomWithRoomId:currentRoomId]; - if (room) - { - [self startActivityIndicatorWithLabel:[VectorL10n roomParticipantsLeaveProcessing]]; - // cancel pending uploads/downloads - // they are useless by now - [MXMediaManager cancelDownloadsInCacheFolder:room.roomId]; - - // TODO GFO cancel pending uploads related to this room - - MXLogDebug(@"[RecentsViewController] Leave room (%@)", room.roomId); - - [room leave:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - [self.userIndicatorStore presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]]; - // Force table refresh - [self cancelEditionMode:YES]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[RecentsViewController] Failed to leave room"); - if (weakSelf) - { - typeof(self) self = weakSelf; - // Notify the end user - NSString *userId = room.mxSession.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification - object:error - userInfo:userId ? @{kMXKErrorUserIdKey: userId} : nil]; - - [self stopActivityIndicator]; - - // Leave editing mode - [self cancelEditionMode:self->isRefreshPending]; - } - - }]; - } - else - { - // Leave editing mode - [self cancelEditionMode:self->isRefreshPending]; - } - } - - }]]; - - [leavePrompt mxk_setAccessibilityIdentifier:@"LeaveEditedRoomAlert"]; - [self presentViewController:leavePrompt animated:YES completion:nil]; - currentAlert = leavePrompt; + MXWeakify(self); + MXRoom *room = [self.mainSession roomWithRoomId:currentRoomId]; + __weak typeof(room) weakRoom = room; + [room isLastOwnerWithCompletionHandler:^(BOOL isLastOwner, NSError* error){ + if (isLastOwner) + { + UIAlertController *isLastOwnerPrompt = [UIAlertController alertControllerWithTitle:[VectorL10n error] + message:[VectorL10n roomParticipantsLeaveNotAllowedForLastOwnerMsg] + preferredStyle:UIAlertControllerStyleAlert]; + + [isLastOwnerPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + MXStrongifyAndReturnIfNil(self); + dispatch_async(dispatch_get_main_queue(), ^{ + [self presentViewController:isLastOwnerPrompt animated:YES completion:nil]; + self->currentAlert = isLastOwnerPrompt; + }); + } + else + { + NSString *title, *message; + MXStrongifyAndReturnIfNil(self); + if ([self.mainSession roomWithRoomId:currentRoomId].isDirect) + { + title = [VectorL10n roomParticipantsLeavePromptTitleForDm]; + message = [VectorL10n roomParticipantsLeavePromptMsgForDm]; + } + else + { + title = [VectorL10n roomParticipantsLeavePromptTitle]; + message = [VectorL10n roomParticipantsLeavePromptMsg]; + } + + // confirm leave + UIAlertController *leavePrompt = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + [leavePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [leavePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] + style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + // Check whether the user didn't leave the room yet + // TODO: Handle multi-account + if (weakRoom) + { + [self startActivityIndicatorWithLabel:[VectorL10n roomParticipantsLeaveProcessing]]; + // cancel pending uploads/downloads + // they are useless by now + [MXMediaManager cancelDownloadsInCacheFolder:weakRoom.roomId]; + + // TODO GFO cancel pending uploads related to this room + + MXLogDebug(@"[RecentsViewController] Leave room (%@)", weakRoom.roomId); + + MXWeakify(self); + [weakRoom leave:^{ + MXStrongifyAndReturnIfNil(self); + [self stopActivityIndicator]; + [self.userIndicatorStore presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]]; + // Force table refresh + [self cancelEditionMode:YES]; + + } failure:^(NSError *error) { + + MXLogDebug(@"[RecentsViewController] Failed to leave room"); + MXStrongifyAndReturnIfNil(self); + // Notify the end user + NSString *userId = room.mxSession.myUser.userId; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification + object:error + userInfo:userId ? @{kMXKErrorUserIdKey: userId} : nil]; + + [self stopActivityIndicator]; + + // Leave editing mode + [self cancelEditionMode:self->isRefreshPending]; + + }]; + } + else + { + // Leave editing mode + [self cancelEditionMode:self->isRefreshPending]; + } + + }]]; + [leavePrompt mxk_setAccessibilityIdentifier:@"LeaveEditedRoomAlert"]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self presentViewController:leavePrompt animated:YES completion:nil]; + self->currentAlert = leavePrompt; + }); + } + }]; } } diff --git a/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift index 5eb1bf12f..1c1bb1a1f 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift @@ -134,10 +134,12 @@ class AllChatsEditActionProvider { spaceRoom.state { [weak self] roomState in guard let self = self else { return } - guard let powerLevels = roomState?.powerLevels, let userId = session.myUserId else { + guard let roomState, + let powerLevels = roomState.powerLevels, + let userId = session.myUserId else { return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + let userPowerLevel = roomState.powerLevelOfUser(withUserID: userId) self.isInviteAvailable = userPowerLevel >= powerLevels.invite self.isAddRoomAvailable = userPowerLevel >= parentSpace.minimumPowerLevelForAddingRoom(with: powerLevels) diff --git a/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift index 501f730a9..5f90a33ea 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift @@ -77,10 +77,12 @@ class AllChatsSpaceActionProvider { spaceRoom.state { [weak self] roomState in guard let self = self else { return } - guard let powerLevels = roomState?.powerLevels, let userId = session.myUserId else { + guard let roomState, + let powerLevels = roomState.powerLevels, + let userId = session.myUserId else { return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + let userPowerLevel = roomState.powerLevelOfUser(withUserID: userId) self.isInviteAvailable = userPowerLevel >= powerLevels.invite diff --git a/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift b/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift index b11290a29..e68e1cd86 100644 --- a/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift +++ b/Riot/Modules/ContextMenu/Services/RoomContextActionService.swift @@ -132,6 +132,7 @@ class RoomContextActionService: NSObject, RoomContextActionServiceProtocol { func leaveRoom(promptUser: Bool) { guard promptUser else { + // Only used for declining an invite self.leaveRoom() return } @@ -141,6 +142,26 @@ class RoomContextActionService: NSObject, RoomContextActionServiceProtocol { self.delegate?.roomContextActionService(self, presentAlert: self.getLeaveAlertController()) } else { self.delegate?.roomContextActionService(self, presentAlert: self.leaveAlertController) + + Task { + if try await room.isLastOwner() { + await MainActor.run { + let alertController = UIAlertController(title: VectorL10n.error, message: VectorL10n.roomParticipantsLeaveNotAllowedForLastOwnerMsg, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: VectorL10n.ok, style: .cancel, handler: nil)) + self.delegate?.roomContextActionService(self, presentAlert: alertController) + } + } else { + let title = room.isDirect ? VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle + let message = room.isDirect ? VectorL10n.roomParticipantsLeavePromptMsgForDm : VectorL10n.roomParticipantsLeavePromptMsg + await MainActor.run { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil)) + alertController.addAction(UIAlertAction(title: VectorL10n.leave, style: .default, handler: { [weak self] action in + self?.leaveRoom() + })) + self.delegate?.roomContextActionService(self, presentAlert: alertController) + } + } } } diff --git a/Riot/Modules/Integrations/IntegrationManagerViewController.m b/Riot/Modules/Integrations/IntegrationManagerViewController.m index d97a0d43b..2b815479e 100644 --- a/Riot/Modules/Integrations/IntegrationManagerViewController.m +++ b/Riot/Modules/Integrations/IntegrationManagerViewController.m @@ -489,7 +489,7 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; MXJSONModelSetBoolean(isState, requestData[@"is_state"]); MXRoomPowerLevels *powerLevels = roomState.powerLevels; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self->mxSession.myUser.userId]; + NSInteger userPowerLevel = [roomState powerLevelOfUserWithUserID:self->mxSession.myUser.userId]; BOOL canSend = NO; diff --git a/Riot/Modules/Integrations/Widgets/WidgetViewController.m b/Riot/Modules/Integrations/Widgets/WidgetViewController.m index 5de1215ce..4e7849181 100644 --- a/Riot/Modules/Integrations/Widgets/WidgetViewController.m +++ b/Riot/Modules/Integrations/Widgets/WidgetViewController.m @@ -94,7 +94,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse { // Check user's power in the room MXRoomPowerLevels *powerLevels = roomState.powerLevels; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:session.myUser.userId]; + NSInteger oneSelfPowerLevel = [roomState powerLevelOfUserWithUserID:session.myUser.userId]; // The user must be able to send state events to manage widgets if (oneSelfPowerLevel >= powerLevels.stateDefault) diff --git a/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift b/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift index 012c4eace..6dc0f9cfb 100644 --- a/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift +++ b/Riot/Modules/MatrixKit/Categories/DTHTMLElement+MatrixKit.swift @@ -23,6 +23,16 @@ public extension DTHTMLElement { // Remove any attachments to fix rendering. textAttachment = nil + // Handle special case for span with data-mx-external-payment-details + // This could be based on Storefront.current.countryCode to show the link + // content in unrestricted countries. e.g. currently USA + if name == "span", + let attributes = attributes as? [String: String], + attributes["data-msc4286-external-payment-details"] != nil { + parent.removeChildNode(self) + return + } + // If the element has plain text content show that, // otherwise prevent the tag from displaying. if let stringContent = attributedString()?.string, diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m index 6e154a5ad..968db509a 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m +++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m @@ -220,20 +220,47 @@ Please see LICENSE in the repository root for full details. } case MXKRoomMemberDetailsActionLeave: { - [self addPendingActionMask]; - [self.mxRoom leave:^{ - - [self removePendingActionMask]; - [self withdrawViewControllerAnimated:YES completion:nil]; - - } failure:^(NSError *error) { - - [self removePendingActionMask]; - MXLogDebug(@"[MXKRoomMemberDetailsVC] Leave room %@ failed", self->mxRoom.roomId); - // Notify MatrixKit user - NSString *myUserId = self.mainSession.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - + MXWeakify(self); + [self.mxRoom isLastOwnerWithCompletionHandler:^(BOOL isLastOwner, NSError* error){ + if (isLastOwner) + { + UIAlertController *isLastOwnerPrompt = [UIAlertController alertControllerWithTitle:[VectorL10n error] + message:[VectorL10n roomParticipantsLeaveNotAllowedForLastOwnerMsg] + preferredStyle:UIAlertControllerStyleAlert]; + + [isLastOwnerPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + MXStrongifyAndReturnIfNil(self); + dispatch_async(dispatch_get_main_queue(), ^{ + [self presentViewController:isLastOwnerPrompt animated:YES completion:nil]; + self->currentAlert = isLastOwnerPrompt; + }); + } + else + { + MXStrongifyAndReturnIfNil(self); + [self addPendingActionMask]; + MXWeakify(self); + [self.mxRoom leave:^{ + MXStrongifyAndReturnIfNil(self); + [self removePendingActionMask]; + [self withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self removePendingActionMask]; + MXLogDebug(@"[MXKRoomMemberDetailsVC] Leave room %@ failed", self->mxRoom.roomId); + // Notify MatrixKit user + NSString *myUserId = self.mainSession.myUser.userId; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + + }]; + } }]; break; } @@ -639,9 +666,10 @@ Please see LICENSE in the repository root for full details. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Check user's power level before allowing an action (kick, ban, ...) + MXRoomState *roomState = self.mxRoomLiveTimeline.state; MXRoomPowerLevels *powerLevels = [self.mxRoomLiveTimeline.state powerLevels]; - NSInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:_mxRoomMember.userId]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger memberPowerLevel = [roomState powerLevelOfUserWithUserID:_mxRoomMember.userId]; + NSInteger oneSelfPowerLevel = [roomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; [actionsArray removeAllObjects]; @@ -894,14 +922,14 @@ Please see LICENSE in the repository root for full details. - (void)setPowerLevel:(NSInteger)value promptUser:(BOOL)promptUser { - NSInteger currentPowerLevel = [self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:_mxRoomMember.userId]; + NSInteger currentPowerLevel = [self.mxRoomLiveTimeline.state powerLevelOfUserWithUserID:_mxRoomMember.userId]; // check if the power level has not yet been set to 0 if (value != currentPowerLevel) { __weak typeof(self) weakSelf = self; - if (promptUser && value == [self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]) + if (promptUser && value == [self.mxRoomLiveTimeline.state powerLevelOfUserWithUserID:self.mainSession.myUser.userId]) { // If the user is setting the same power level as his to another user, ask him for a confirmation if (currentAlert) @@ -999,7 +1027,7 @@ Please see LICENSE in the repository root for full details. typeof(self) self = weakSelf; textField.secureTextEntry = NO; - textField.text = [NSString stringWithFormat:@"%ld", (long)[self.mxRoomLiveTimeline.state.powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]]; + textField.text = [NSString stringWithFormat:@"%ld", (long)[self.mxRoomLiveTimeline.state powerLevelOfUserWithUserID:self.mxRoomMember.userId]]; textField.placeholder = nil; textField.keyboardType = UIKeyboardTypeDecimalPad; }]; diff --git a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m index 9ab243448..e1ca0233b 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m +++ b/Riot/Modules/MatrixKit/Controllers/MXKRoomMemberListViewController.m @@ -307,7 +307,7 @@ Please see LICENSE in the repository root for full details. if (showInvitationOption && self->dataSource) { // Check conditions to be able to invite someone - NSInteger oneSelfPowerLevel = [roomState.powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger oneSelfPowerLevel = [roomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel < [roomState.powerLevels invite]) { showInvitationOption = NO; diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m index f14476569..edf9635d0 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m @@ -690,7 +690,7 @@ Please see LICENSE in the repository root for full details. - (BOOL)canInvitePeople { NSInteger requiredLevel = roomDataSource.roomState.powerLevels.invite; - NSInteger myLevel = [roomDataSource.roomState.powerLevels powerLevelOfUserWithUserID:roomDataSource.mxSession.myUserId]; + NSInteger myLevel = [roomDataSource.roomState powerLevelOfUserWithUserID:roomDataSource.mxSession.myUserId]; return myLevel >= requiredLevel; } diff --git a/Riot/Modules/MatrixKit/Views/MXKEventDetailsView.m b/Riot/Modules/MatrixKit/Views/MXKEventDetailsView.m index e8473e0c3..fd549a9f3 100644 --- a/Riot/Modules/MatrixKit/Views/MXKEventDetailsView.m +++ b/Riot/Modules/MatrixKit/Views/MXKEventDetailsView.m @@ -123,7 +123,7 @@ Please see LICENSE in the repository root for full details. MXStrongifyAndReturnIfNil(self); MXRoomPowerLevels *powerLevels = [roomState powerLevels]; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self->mxSession.myUser.userId]; + NSInteger userPowerLevel = [roomState powerLevelOfUserWithUserID:self->mxSession.myUser.userId]; if (powerLevels.redact) { if (userPowerLevel >= powerLevels.redact) diff --git a/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleView.m b/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleView.m index 8365f8c7a..107342d22 100644 --- a/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleView.m +++ b/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleView.m @@ -176,7 +176,7 @@ Please see LICENSE in the repository root for full details. // Check whether the user has enough power to rename the room MXRoomPowerLevels *powerLevels = _mxRoom.dangerousSyncState.powerLevels; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:_mxRoom.mxSession.myUser.userId]; + NSInteger userPowerLevel = [_mxRoom.dangerousSyncState powerLevelOfUserWithUserID:_mxRoom.mxSession.myUser.userId]; if (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomName]) { // Only the room name is edited here, update the text field with the room name diff --git a/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleViewWithTopic.m b/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleViewWithTopic.m index c5d1c2050..c5cb82688 100644 --- a/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleViewWithTopic.m +++ b/Riot/Modules/MatrixKit/Views/RoomTitle/MXKRoomTitleViewWithTopic.m @@ -354,7 +354,7 @@ Please see LICENSE in the repository root for full details. { // Check whether the user has enough power to rename the room MXRoomPowerLevels *powerLevels = self.mxRoom.dangerousSyncState.powerLevels; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoom.mxSession.myUser.userId]; + NSInteger userPowerLevel = [self.mxRoom.dangerousSyncState powerLevelOfUserWithUserID:self.mxRoom.mxSession.myUser.userId]; if (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomName]) { // Only the room name is edited here, update the text field with the room name @@ -384,7 +384,7 @@ Please see LICENSE in the repository root for full details. { // Check whether the user has enough power to edit room topic MXRoomPowerLevels *powerLevels = self.mxRoom.dangerousSyncState.powerLevels; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoom.mxSession.myUser.userId]; + NSInteger userPowerLevel = [self.mxRoom.dangerousSyncState powerLevelOfUserWithUserID:self.mxRoom.mxSession.myUser.userId]; if (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomTopic]) { textField.backgroundColor = [UIColor whiteColor]; diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index eab24d9e5..3f9699028 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -420,11 +420,15 @@ Please see LICENSE in the repository root for full details. MXStrongifyAndReturnIfNil(self); MXRoomPowerLevels *powerLevels = [roomState powerLevels]; - NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; + NSInteger powerLevel = [roomState powerLevelOfUserWithUserID:self.mxRoomMember.userId]; RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel]; switch (roomPowerLevel) { + case RoomPowerLevelOwner: + self.roomMemberPowerLevelLabel.text = [VectorL10n roomMemberPowerLevelOwnerIn:self.mxRoom.summary.displayName]; + self.roomMemberPowerLevelContainerView.hidden = NO; + break; case RoomPowerLevelAdmin: self.roomMemberPowerLevelLabel.text = [BWIL10n roomMemberPowerLevelAdminIn:self.mxRoom.summary.displayName]; self.roomMemberPowerLevelContainerView.hidden = NO; @@ -594,8 +598,8 @@ Please see LICENSE in the repository root for full details. // Check user's power level before allowing an action (kick, ban, ...) MXRoomPowerLevels *powerLevels = [self.mxRoom.dangerousSyncState powerLevels]; - NSInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger memberPowerLevel = [self.mxRoom.dangerousSyncState powerLevelOfUserWithUserID:self.mxRoomMember.userId]; + NSInteger oneSelfPowerLevel = [self.mxRoom.dangerousSyncState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; [adminActionsArray removeAllObjects]; [otherActionsArray removeAllObjects]; diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index 0f6a5829f..4e7a4beb8 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -796,8 +796,8 @@ Please see LICENSE in the repository root for full details. { // Order first by power levels (admins then moderators then others) MXRoomPowerLevels *powerLevels = [roomState powerLevels]; - NSInteger powerLevelA = [powerLevels powerLevelOfUserWithUserID:contactA.mxMember.userId]; - NSInteger powerLevelB = [powerLevels powerLevelOfUserWithUserID:contactB.mxMember.userId]; + NSInteger powerLevelA = [roomState powerLevelOfUserWithUserID:contactA.mxMember.userId]; + NSInteger powerLevelB = [roomState powerLevelOfUserWithUserID:contactB.mxMember.userId]; if (powerLevelA == powerLevelB) { @@ -1099,6 +1099,9 @@ Please see LICENSE in the repository root for full details. NSString *powerLevelText; switch (roomPowerLevel) { + case RoomPowerLevelOwner: + powerLevelText = [VectorL10n roomMemberPowerLevelShortOwner]; + break; case RoomPowerLevelAdmin: powerLevelText = [VectorL10n roomMemberPowerLevelShortAdmin]; break; @@ -1309,62 +1312,7 @@ Please see LICENSE in the repository root for full details. if (section == participantsSection && userParticipant && (0 == row) && !currentSearchText.length) { - // Leave ? - MXWeakify(self); - - NSString *title, *message; - if (self.mxRoom.isDirect) - { - title = [VectorL10n roomParticipantsLeavePromptTitleForDm]; - message = [VectorL10n roomParticipantsLeavePromptMsgForDm]; - } - else - { - title = [VectorL10n roomParticipantsLeavePromptTitle]; - message = [VectorL10n roomParticipantsLeavePromptMsg]; - } - - currentAlert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - - [self addPendingActionMask]; - MXWeakify(self); - [self.mxRoom leave:^{ - - MXStrongifyAndReturnIfNil(self); - [self withdrawViewControllerAnimated:YES completion:nil]; - - } failure:^(NSError *error) { - - MXStrongifyAndReturnIfNil(self); - [self removePendingActionMask]; - MXLogDebug(@"[RoomParticipantsVC] Leave room %@ failed", self.mxRoom.roomId); - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"RoomParticipantsVCLeaveAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; + [self leaveRoom]; } else { @@ -1511,6 +1459,90 @@ Please see LICENSE in the repository root for full details. } } +- (void)leaveRoom { + MXWeakify(self); + + [self.mxRoom isLastOwnerWithCompletionHandler:^(BOOL isLastOwner, NSError* error) { + if (isLastOwner) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n error] + message:[VectorL10n roomParticipantsLeaveNotAllowedForLastOwnerMsg] + preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self presentViewController:self->currentAlert animated:YES completion:nil]; + }); + } + else + { + // Leave ? + MXStrongifyAndReturnIfNil(self); + NSString *title, *message; + if (self.mxRoom.isDirect) + { + title = [VectorL10n roomParticipantsLeavePromptTitleForDm]; + message = [VectorL10n roomParticipantsLeavePromptMsgForDm]; + } + else + { + title = [VectorL10n roomParticipantsLeavePromptTitle]; + message = [VectorL10n roomParticipantsLeavePromptMsg]; + } + + self->currentAlert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + }]]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + [self addPendingActionMask]; + MXWeakify(self); + [self.mxRoom leave:^{ + + MXStrongifyAndReturnIfNil(self); + [self withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + MXStrongifyAndReturnIfNil(self); + [self removePendingActionMask]; + MXLogDebug(@"[RoomParticipantsVC] Leave room %@ failed", self.mxRoom.roomId); + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + + }]]; + + [self->currentAlert mxk_setAccessibilityIdentifier:@"RoomParticipantsVCLeaveAlert"]; + [self presentViewController:self->currentAlert animated:YES completion:nil]; + } + }]; +} + - (void)onCancel:(id)sender { [self withdrawViewControllerAnimated:YES completion:nil]; diff --git a/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift b/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift index 444a0a090..fb56c8ad5 100644 --- a/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift @@ -163,12 +163,13 @@ final class RoomParticipantsInviteCoordinatorBridgePresenter: NSObject { } room.state { roomState in - guard let powerLevels = roomState?.powerLevels else { + guard let roomState, + let powerLevels = roomState.powerLevels else { MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] canInvite: room powerLevels not found") completion(false) return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + let userPowerLevel = roomState.powerLevelOfUser(withUserID: userId) completion(userPowerLevel >= powerLevels.invite) } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 5095599c9..a24bcb773 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -102,6 +102,18 @@ final class RoomInfoListViewController: UIViewController { return self.doNotLeaveUIAlertController() } } + + private lazy var isLastOwnerAlertController: UIAlertController = { + let title = VectorL10n.error + let message = VectorL10n.roomParticipantsLeaveNotAllowedForLastOwnerMsg + let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) + + controller.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil)) + controller.mxk_setAccessibilityIdentifier("RoomSettingsVCLastOwnerAlert") + + return controller + }() + private enum RowType { case `default` @@ -267,8 +279,11 @@ final class RoomInfoListViewController: UIViewController { VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle let rowLeave = Row(type: .destructive, icon: Asset.Images.roomActionLeave.image, text: leaveTitle, accessoryType: .none) { + if BWIBuildSettings.shared.lastAdminIsNotAllowedToLeaveRoom { self.present(self.getLeaveAlertController(), animated: true, completion: nil) + } else if viewData.isLastOwner { + self.present(self.isLastOwnerAlertController, animated: true, completion: nil) } else { self.present(self.leaveAlertController, animated: true, completion: nil) } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewData.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewData.swift index c217514b8..b8be0b49e 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewData.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewData.swift @@ -24,4 +24,5 @@ struct RoomInfoListViewData { let isEncrypted: Bool let isDirect: Bool let basicInfoViewData: RoomInfoBasicViewData + let isLastOwner: Bool } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift index 83b134917..bc2b9b46f 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift @@ -27,6 +27,7 @@ final class RoomInfoListViewModel: NSObject, RoomInfoListViewModelType { private let session: MXSession private let room: MXRoom + private var isLastOwner = false // MARK: Public @@ -70,7 +71,8 @@ final class RoomInfoListViewModel: NSObject, RoomInfoListViewModelType { return RoomInfoListViewData(numberOfMembers: Int(room.summary.membersCount.joined), isEncrypted: room.summary.isEncrypted, isDirect: room.isDirect, - basicInfoViewData: basicInfoViewData) + basicInfoViewData: basicInfoViewData, + isLastOwner: isLastOwner) } // bwi: 5216 - federation @@ -163,6 +165,9 @@ final class RoomInfoListViewModel: NSObject, RoomInfoListViewModelType { @objc private func roomSummaryUpdated(_ notification: Notification) { // force update view self.update(viewState: .loaded(viewData: viewData)) + Task { + isLastOwner = (try? await room.isLastOwner()) == true + } } private func loadData() { diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index a286e268e..71dadb1eb 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1235,7 +1235,7 @@ static CGSize kThreadListBarButtonItemImageSize; if (self.roomDataSource.roomState) { MXRoomPowerLevels *powerLevels = self.roomDataSource.roomState.powerLevels; - NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger userPowerLevel = [self.roomDataSource.roomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; BOOL canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsMessage:kMXEventTypeStringRoomMessage]); BOOL isRoomObsolete = self.roomDataSource.roomState.isObsolete; @@ -1906,7 +1906,7 @@ static CGSize kThreadListBarButtonItemImageSize; { MXRoomPowerLevels *powerLevels = [self.roomDataSource.roomState powerLevels]; NSInteger requiredPower = [powerLevels minimumPowerLevelForSendingEventAsStateEvent:eventTypeString]; - NSInteger myPower = [powerLevels powerLevelOfUserWithUserID:self.roomDataSource.mxSession.myUserId]; + NSInteger myPower = [self.roomDataSource.roomState powerLevelOfUserWithUserID:self.roomDataSource.mxSession.myUserId]; return myPower >= requiredPower; } @@ -2724,23 +2724,6 @@ static CGSize kThreadListBarButtonItemImageSize; } } -- (void)leaveRoom -{ - [self startActivityIndicator]; - - [self.roomDataSource.room leave:^{ - - [self stopActivityIndicator]; - [self notifyDelegateOnLeaveRoomIfNecessary]; - - } failure:^(NSError *error) { - - [self stopActivityIndicator]; - MXLogDebug(@"[RoomVC] Failed to reject an invited room (%@) failed", self.roomDataSource.room.roomId); - - }]; -} - - (void)notifyDelegateOnLeaveRoomIfNecessary { if (isRoomLeft) { return; diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index f7ed0ea7b..ea245c289 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -678,7 +678,7 @@ BOOL reloadToggleCell = false; { // Check user's power level to know whether the user is allowed to turn on the encryption mode MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger oneSelfPowerLevel = [mxRoomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomEncryption]) { @@ -721,7 +721,7 @@ BOOL reloadToggleCell = false; return; MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger oneSelfPowerLevel = [mxRoomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel < [powerLevels minimumPowerLevelForSendingEventAsStateEvent:eventTypeForSelectedField]) return; @@ -2249,7 +2249,7 @@ BOOL reloadToggleCell = false; // Check user's power level to know which settings are editable. MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger oneSelfPowerLevel = [mxRoomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; // general settings if (section == SECTION_TAG_MAIN) @@ -3398,7 +3398,7 @@ BOOL reloadToggleCell = false; { // Check user's power level to know whether the user is allowed to set the main address MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + NSInteger oneSelfPowerLevel = [mxRoomState powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomAliases]) { diff --git a/Riot/Modules/SideMenu/SideMenuCoordinator.swift b/Riot/Modules/SideMenu/SideMenuCoordinator.swift index 86e902f3d..8f3a77f19 100644 --- a/Riot/Modules/SideMenu/SideMenuCoordinator.swift +++ b/Riot/Modules/SideMenu/SideMenuCoordinator.swift @@ -329,11 +329,12 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { spaceRoom.state { [weak self] roomState in guard let self = self else { return } - guard let powerLevels = roomState?.powerLevels, let userId = session.myUserId else { + guard let roomState, + let powerLevels = roomState.powerLevels, let userId = session.myUserId else { MXLog.error("[SpaceMembersCoordinator] spaceMemberListCoordinatorShowInvite: failed to find powerLevels for room") return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + let userPowerLevel = roomState.powerLevelOfUser(withUserID: userId) guard userPowerLevel >= powerLevels.invite else { let alert = UIAlertController(title: VectorL10n.spacesInvitePeople, message: VectorL10n.spaceInviteNotEnoughPermission, preferredStyle: .alert) diff --git a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift index e5d8252f0..4d29ad958 100644 --- a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift @@ -165,11 +165,12 @@ extension SpaceMembersCoordinator: SpaceMemberListCoordinatorDelegate { spaceRoom.state { [weak self] roomState in guard let self = self else { return } - guard let powerLevels = roomState?.powerLevels, let userId = self.parameters.session.myUserId else { + guard let roomState, + let powerLevels = roomState.powerLevels, let userId = self.parameters.session.myUserId else { MXLog.error("[SpaceMembersCoordinator] spaceMemberListCoordinatorShowInvite: failed to find powerLevels for room") return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + let userPowerLevel = roomState.powerLevelOfUser(withUserID: userId) guard userPowerLevel >= powerLevels.invite else { let alert = UIAlertController(title: VectorL10n.spacesInvitePeople, message: VectorL10n.spaceInviteNotEnoughPermission, preferredStyle: .alert) diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift index fb81fa71d..931757f32 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift @@ -166,7 +166,7 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { if let spaceRoom = self.spaceRoom { spaceRoom.state { roomState in self.powerLevels = roomState?.powerLevels - self.powerLevelOfCurrentUser = self.powerLevels?.powerLevelOfUser(withUserID: self.session.myUserId) + self.powerLevelOfCurrentUser = roomState?.powerLevelOfUser(withUserID: self.session.myUserId) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift index aebbe6333..3b3bf700c 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StartLocationSharing/Coordinator/LocationSharingCoordinator.swift @@ -159,8 +159,9 @@ final class LocationSharingCoordinator: Coordinator, Presentable { // Check if user can send beacon info state event private func canShareLiveLocation() -> Bool { guard let myUserId = parameters.roomDataSource.mxSession.myUserId, - let roomPowerLevels = parameters.roomDataSource.roomState.powerLevels, - let userPowerLevel = RoomPowerLevel(rawValue: roomPowerLevels.powerLevelOfUser(withUserID: myUserId)) else { + let roomState = parameters.roomDataSource.roomState, + let roomPowerLevels = roomState.powerLevels, + let userPowerLevel = RoomPowerLevel(rawValue: roomState.powerLevelOfUser(withUserID: myUserId)) else { return false } diff --git a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift index 00663c0b3..c8383d8e2 100644 --- a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift @@ -162,8 +162,10 @@ private class CompletionSuggestionCoordinatorRoomMemberProvider: RoomMembersProv /// Gets the power levels for the room to update suggestions accordingly. func updateWithPowerLevels() { room.state { [weak self] state in - guard let self, let powerLevels = state?.powerLevels else { return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: self.userID) + guard let self, + let state, + let powerLevels = state.powerLevels else { return } + let userPowerLevel = state.powerLevelOfUser(withUserID: self.userID) let mentionRoomPowerLevel = powerLevels.minimumPowerLevel(forNotifications: kMXRoomPowerLevelNotificationsRoomKey, defaultPower: kMXRoomPowerLevelNotificationsRoomDefault) self.canMentionRoom = userPowerLevel >= mentionRoomPowerLevel @@ -208,9 +210,11 @@ private class CompletionSuggestionCoordinatorCommandProvider: CommandsProviderPr func updateWithPowerLevels() { room.state { [weak self] state in - guard let self, let powerLevels = state?.powerLevels else { return } + guard let self, + let state, + let powerLevels = state.powerLevels else { return } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: self.userID) + let userPowerLevel = state.powerLevelOfUser(withUserID: self.userID) self.isRoomAdmin = RoomPowerLevel(rawValue: userPowerLevel) == .admin } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift index 0b09739f8..e496efac5 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift @@ -150,12 +150,12 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { return allowedParentIds } - private func isField(ofType notification: String, editableWith powerLevels: MXRoomPowerLevels?) -> Bool { - guard let powerLevels = powerLevels else { + private func isField(ofType notification: String, editableWith roomState: MXRoomState) -> Bool { + guard let powerLevels = roomState.powerLevels else { return false } - let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: session.myUserId) + let userPowerLevel = roomState.powerLevelOfUser(withUserID: session.myUserId) return userPowerLevel >= powerLevels.minimumPowerLevel(forNotifications: notification, defaultPower: powerLevels.stateDefault) } @@ -226,11 +226,11 @@ class SpaceSettingsService: SpaceSettingsServiceProtocol { avatarUrl: roomState.avatar, visibility: visibility(with: roomState), allowedParentIds: allowedParentIds(with: roomState), - isAvatarEditable: isField(ofType: kMXEventTypeStringRoomAvatar, editableWith: roomState.powerLevels), - isNameEditable: isField(ofType: kMXEventTypeStringRoomName, editableWith: roomState.powerLevels), - isTopicEditable: isField(ofType: kMXEventTypeStringRoomTopic, editableWith: roomState.powerLevels), - isAddressEditable: isField(ofType: kMXEventTypeStringRoomAliases, editableWith: roomState.powerLevels), - isAccessEditable: isField(ofType: kMXEventTypeStringRoomJoinRules, editableWith: roomState.powerLevels) + isAvatarEditable: isField(ofType: kMXEventTypeStringRoomAvatar, editableWith: roomState), + isNameEditable: isField(ofType: kMXEventTypeStringRoomName, editableWith: roomState), + isTopicEditable: isField(ofType: kMXEventTypeStringRoomTopic, editableWith: roomState), + isAddressEditable: isField(ofType: kMXEventTypeStringRoomAliases, editableWith: roomState), + isAccessEditable: isField(ofType: kMXEventTypeStringRoomJoinRules, editableWith: roomState) ) } diff --git a/RiotSwiftUI/targetUnitTests.yml b/RiotSwiftUI/targetUnitTests.yml index ec16e078a..5debda940 100644 --- a/RiotSwiftUI/targetUnitTests.yml +++ b/RiotSwiftUI/targetUnitTests.yml @@ -49,7 +49,6 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: org.matrix.$(PRODUCT_NAME:rfc1034identifier) PRODUCT_NAME: RiotSwiftUnitTests configs: - Debug: Release: PROVISIONING_PROFILE: $(RIOT_PROVISIONING_PROFILE) PROVISIONING_PROFILE_SPECIFIER: $(RIOT_PROVISIONING_PROFILE_SPECIFIER) diff --git a/RiotTests/MatrixKitTests/MXKEventFormatterTests.m b/RiotTests/MatrixKitTests/MXKEventFormatterTests.m index f16f786eb..24457b990 100644 --- a/RiotTests/MatrixKitTests/MXKEventFormatterTests.m +++ b/RiotTests/MatrixKitTests/MXKEventFormatterTests.m @@ -239,6 +239,24 @@ Please see LICENSE in the repository root for full details. XCTAssertFalse(hasAttachment, @"iFrame attachments should be removed as they're not included in the allowedHTMLTags array."); } +- (void)testMxExternalPaymentDetailsRemoved +{ + // Given an HTML string containing a with data-mx-external-payment-details. + NSString *html = @"This is visible. But text is hidden and this link too"; + + // When rendering this string as an attributed string. + NSAttributedString *attributedString = [eventFormatter renderHTMLString:html + forEvent:anEvent + withRoomState:nil + andLatestRoomState:nil]; + + // Then the attributed string should have the stripped and not include any attachments. + XCTAssertEqualObjects(attributedString.string, @"This is visible", @"The tag content should be removed."); + + BOOL hasAttachment = [attributedString containsAttachmentsInRange:NSMakeRange(0, attributedString.length)]; + XCTAssertFalse(hasAttachment, @"span attachments should be removed as they're not included in the allowedHTMLTags array."); +} + - (void)testRenderHTMLStringWithMXReply { // Given an HTML string representing a matrix reply. diff --git a/RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift b/RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift index 1632da47f..ec87c1c29 100644 --- a/RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift +++ b/RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift @@ -25,7 +25,8 @@ class EncryptionTrustLevelTests: XCTestCase { identity: .other( userId: "Bob", masterKey: "MSK", - selfSigningKey: "SSK" + selfSigningKey: "SSK", + hasVerificationViolation: false ), isVerified: isVerified ) diff --git a/RiotTests/target.yml b/RiotTests/target.yml index 4d26d25d0..5bf89d435 100644 --- a/RiotTests/target.yml +++ b/RiotTests/target.yml @@ -52,7 +52,6 @@ targets: SWIFT_OBJC_BRIDGING_HEADER: RiotTests/RiotTests-Bridging-Header.h TEST_HOST: $(BUILT_PRODUCTS_DIR)/Element.app/Element configs: - Debug: Release: PROVISIONING_PROFILE: $(RIOT_PROVISIONING_PROFILE) PROVISIONING_PROFILE_SPECIFIER: $(RIOT_PROVISIONING_PROFILE_SPECIFIER) diff --git a/matrix-ios-sdk b/matrix-ios-sdk index 6a49ee70a..b0f967c9c 160000 --- a/matrix-ios-sdk +++ b/matrix-ios-sdk @@ -1 +1 @@ -Subproject commit 6a49ee70a54aa2d4cade316b536f2ca35ce93e73 +Subproject commit b0f967c9c884e1a24d037f516f40a5ffb6c29586