Merge branch 'develop' into aleksandrs/6963_multi_session_logout

This commit is contained in:
paleksandrs 2022-10-31 11:30:12 +02:00
commit c02f144314
45 changed files with 509 additions and 200 deletions

View file

@ -1,9 +1,6 @@
name: UI Tests CI name: UI Tests CI
on: on:
push:
branches: [ develop ]
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:

View file

@ -17,7 +17,8 @@ jobs:
contains(github.event.issue.labels.*.name, 'Z-IA') || contains(github.event.issue.labels.*.name, 'Z-IA') ||
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
contains(github.event.issue.labels.*.name, 'A-Tags') contains(github.event.issue.labels.*.name, 'A-Tags') ||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor')
steps: steps:
- uses: actions/github-script@v5 - uses: actions/github-script@v5
with: with:
@ -267,7 +268,7 @@ jobs:
name: Add labelled issues to PS features team 3 name: Add labelled issues to PS features team 3
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: > if: >
contains(github.event.issue.labels.*.name, 'A-Composer-WYSIWYG') contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor'')
steps: steps:
- uses: octokit/graphql-action@v2.x - uses: octokit/graphql-action@v2.x
id: add_to_project id: add_to_project

View file

@ -154,5 +154,14 @@ post_install do |installer|
config.build_settings['WARNING_CFLAGS'] ||= ['$(inherited)','-Wno-nullability-completeness'] config.build_settings['WARNING_CFLAGS'] ||= ['$(inherited)','-Wno-nullability-completeness']
config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)', '-Xcc', '-Wno-nullability-completeness'] config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)', '-Xcc', '-Wno-nullability-completeness']
end end
# Fix Xcode 14 resource bundle signing issues
# https://github.com/CocoaPods/CocoaPods/issues/11402#issuecomment-1259231655
if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
target.build_configurations.each do |config|
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
end
end
end end
end end

View file

@ -2639,3 +2639,15 @@
// Send Media Actions // Send Media Actions
"wysiwyg_composer_start_action_media_picker" = "Fotobibliothek"; "wysiwyg_composer_start_action_media_picker" = "Fotobibliothek";
"settings_labs_enable_wysiwyg_composer" = "Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus)"; "settings_labs_enable_wysiwyg_composer" = "Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus)";
"wysiwyg_composer_start_action_voice_broadcast" = "Sprachübertragung";
"voice_broadcast_already_in_progress_message" = "Du zeichnest bereits eine Sprachübertragung auf. Bitte beende die laufende Übertragung, um eine neue zu beginnen.";
"voice_broadcast_blocked_by_someone_else_message" = "Jemand anderes nimmt bereits eine Sprachübertragung auf. Warte auf das Ende der Übertragung, bevor du eine neue startest.";
"voice_broadcast_permission_denied_message" = "Du hast nicht die nötigen Berechtigungen, um eine Sprachübertragung in diesem Raum zu starten. Kontaktiere einen Raumadministrator, um deine Berechtigungen anzupassen.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Sprachübertragung kann nicht gestartet werden";
"settings_labs_enable_voice_broadcast" = "Sprachübertragung (in aktiver Entwicklung)";
"voice_broadcast_playback_loading_error" = "Wiedergabe der Sprachübertragung nicht möglich.";
"deselect_all" = "Alle abwählen";
"user_other_session_menu_select_sessions" = "Sitzungen auswählen";
"user_other_session_selected_count" = "%@ ausgewählt";

View file

@ -2506,3 +2506,83 @@
"authentication_qr_login_start_subtitle" = "Kasuta selle seadme kaamerat ja logi sisse teises seadmes kuvatud QR-koodi alusel:"; "authentication_qr_login_start_subtitle" = "Kasuta selle seadme kaamerat ja logi sisse teises seadmes kuvatud QR-koodi alusel:";
"authentication_qr_login_start_title" = "Loe QR-koodi"; "authentication_qr_login_start_title" = "Loe QR-koodi";
"authentication_login_with_qr" = "Logi sisse QR-koodi abil"; "authentication_login_with_qr" = "Logi sisse QR-koodi abil";
"wysiwyg_composer_format_action_strikethrough" = "Kasuta allajoonitud kirja";
"wysiwyg_composer_format_action_underline" = "Kasuta läbijoonitud kirja";
"wysiwyg_composer_format_action_italic" = "Kasuta kaldkirja";
// Formatting Actions
"wysiwyg_composer_format_action_bold" = "Kasuta paksu kirja";
"wysiwyg_composer_start_action_voice_broadcast" = "Ringhäälingukõne";
"wysiwyg_composer_start_action_text_formatting" = "Tekstivorming";
"wysiwyg_composer_start_action_camera" = "Kaamera";
"wysiwyg_composer_start_action_location" = "Asukoht";
"wysiwyg_composer_start_action_polls" = "Küsitlused";
"wysiwyg_composer_start_action_attachments" = "Manused";
"wysiwyg_composer_start_action_stickers" = "Kleepsud";
// Mark: - WYSIWYG Composer
// Send Media Actions
"wysiwyg_composer_start_action_media_picker" = "Fotode kogu";
"user_session_details_last_activity" = "Viimati kasutusel";
"device_type_name_unknown" = "Tundmatu seadmetüüp";
"device_type_name_mobile" = "Mobiiltelefon";
"device_type_name_web" = "Veebiliides";
"device_type_name_desktop" = "Töölauarakendus";
"user_inactive_session_item_with_date" = "Pole olnud kasutusel üle 90 päeva (%@)";
"user_inactive_session_item" = "Pole olnud kasutusel üle 90 päeva";
"user_session_item_details_last_activity" = "Viimati kasutusel %@";
"user_other_session_clear_filter" = "Eemalda filter";
"user_other_session_no_unverified_sessions" = "Verifitseerimata sessioone ei leidu.";
"user_other_session_no_verified_sessions" = "Verifitseeritud sessioone ei leidu.";
"user_other_session_no_inactive_sessions" = "Ei leidu sessioone, mis pole aktiivses kasutuses.";
"user_other_session_filter_menu_inactive" = "Pole pidevas kasutuses";
"user_other_session_filter_menu_unverified" = "Verifitseerimata";
"user_other_session_filter_menu_verified" = "Verifitseeritud";
"user_other_session_filter_menu_all" = "Kõik sessioonid";
"user_other_session_filter" = "Filtreeri";
"user_other_session_verified_sessions_header_subtitle" = "Parima turvalisuse nimel logi välja neist sessioonidest, mida sa enam ei kasuta või ei tunne ära.";
"user_other_session_current_session_details" = "Sinu praegune sessioon";
"user_other_session_unverified_sessions_header_subtitle" = "Turvalise sõnumvahetuse nimel verifitseeri kõik oma sessioonid ning logi neist välja, mida sa enam ei kasuta või ei tunne enam ära.";
"user_other_session_security_recommendation_title" = "Turvalisusega seotud soovitused";
"user_other_session_verified_additional_info" = "See sessioon on valmis turvaliseks sõnumivahetuseks.";
"user_other_session_unverified_additional_info" = "Parima turvalisuse ja töökindluse nimel verifitseeri see sessioon või logi ta võrgust välja.";
"user_session_verification_unknown_additional_info" = "Selle sessiooni olekut ei saa tuvastada enne kui oled ta verifitseerinud.";
"user_session_verification_unknown_short" = "Teadmata olek";
"user_session_verification_unknown" = "Verifitseerimise olek on määratlemata";
"user_sessions_overview_link_device" = "Seo teise seadmega";
// MARK: User sessions management
// Parameter is the application display name (e.g. "Element")
"user_sessions_default_session_display_name" = "%@ iOS";
"voice_broadcast_playback_loading_error" = "Selle ringhäälingukõne esitamine ei õnnestu.";
"voice_broadcast_already_in_progress_message" = "Sa juba salvestad ringhäälingukõnet. Uue alustamiseks palun lõpeta eelmine salvestus.";
"voice_broadcast_blocked_by_someone_else_message" = "Keegi juba salvestab ringhäälingukõnet. Uue ringhäälingukõne salvestamiseks palun oota, kuni see teine ringhäälingukõne on lõppenud.";
"voice_broadcast_permission_denied_message" = "Sul pole piisavalt õigusi selles jututoas ringhäälingukõne algatamiseks. Õiguste lisamiseks palun võta ühendust jututoa haldajaga.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Uue ringhäälingukõne alustamine pole võimalik";
"sign_out_confirmation_message" = "Kas sa oled kindel et soovid välja logida?";
// MARK: Sign out warning
"sign_out" = "Logi välja";
"manage_session_rename" = "Muuda sessiooni nime";
"manage_session_name_info_link" = "Lisateave";
/* The placeholder will be replaces with manage_session_name_info_link */
"manage_session_name_info" = "Palun arvesta, et sessioonide nimed on näha ka kõikidele osapooltele, kellega sa suhtled. %@";
"manage_session_name_hint" = "Sinu enda kirjutatud sessiooninimede alusel on sul oma seadmeid lihtsam ära tunda.";
"settings_labs_enable_voice_broadcast" = "Ringhäälingukõne (aktiivses arenduses)";
"settings_labs_enable_wysiwyg_composer" = "Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim)";
"authentication_qr_login_failure_retry" = "Proovi uuesti";
"authentication_qr_login_failure_request_timed_out" = "Sidumine ei lõppenud etteantud aja jooksul.";
"authentication_qr_login_failure_request_denied" = "Teine seade lükkas päringu tagasi.";
"authentication_qr_login_failure_invalid_qr" = "QR-kood on vigane.";
"authentication_qr_login_failure_title" = "Seose loomine ei õnenstunud";
"authentication_qr_login_loading_signed_in" = "Sa oled oma teises seadmes sisse loginud Matrix'i võrku.";
"authentication_qr_login_loading_waiting_signin" = "Ootame, et teine seade logiks võrku.";
"authentication_qr_login_loading_connecting_device" = "Loon ühendust seadmega";
"authentication_qr_login_confirm_alert" = "Palun vaata, et sa kindlasti tead, kust see QR-kood kuvatakse. Sellisel viisil seadmete sidumisel sa annad oma kasutajakontole täiemahulise ligipääsu.";
"authentication_qr_login_confirm_subtitle" = "Kontrolli, et järgnev kood klapib teises seadmes kuvatava koodiga:";

View file

@ -1270,3 +1270,23 @@
"microphone_access_not_granted_for_voice_message" = "جهت ارسال پیام صوتی نیاز به دسترسی به میکروفون وجود دارد اما %@ دسترسی استفاده از آن را ندارد"; "microphone_access_not_granted_for_voice_message" = "جهت ارسال پیام صوتی نیاز به دسترسی به میکروفون وجود دارد اما %@ دسترسی استفاده از آن را ندارد";
"e2e_passphrase_too_short" = "کلمه عبور بیش از حد کوتاه است (حداقل می‌بایست %d کاراکتر باشد)"; "e2e_passphrase_too_short" = "کلمه عبور بیش از حد کوتاه است (حداقل می‌بایست %d کاراکتر باشد)";
"message_reply_to_sender_sent_a_voice_message" = "یک پیام صوتی ارسال کنید."; "message_reply_to_sender_sent_a_voice_message" = "یک پیام صوتی ارسال کنید.";
"onboarding_splash_page_1_title" = "صاحب گفتگوهای خود شوید.";
"onboarding_splash_login_button_title" = "من از قبل حساب کاربری دارم";
// MARK: Onboarding
"onboarding_splash_register_button_title" = "ساخت حساب کاربری";
"accessibility_button_label" = "دکمه";
"saving" = "در حال ذخیره";
// Activities
"loading" = "در حال بارگزاری";
"invite_to" = "دعوت به %@";
"confirm" = "تأیید";
"edit" = "ویرایش";
"suggest" = "پیشنهاد";
"add" = "افزودن";
"existing" = "خروج";
"new_word" = "جدید";
"stop" = "توقف";
"joining" = "پیوستن";
"enable" = "فعال";

View file

@ -2625,4 +2625,15 @@
"authentication_qr_login_start_subtitle" = "Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására:"; "authentication_qr_login_start_subtitle" = "Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására:";
"authentication_qr_login_start_title" = "QR kód beolvasása"; "authentication_qr_login_start_title" = "QR kód beolvasása";
"authentication_login_with_qr" = "Belépés QR kóddal"; "authentication_login_with_qr" = "Belépés QR kóddal";
"settings_labs_enable_voice_broadcast" = "Hang közvetítés (aktív fejlesztés alatt). Jelenleg a hang közvetítést csak a szoba idővonalán jelezzük, egyenlőre nem lehet hangot sugározni vagy belehallgatni a közvetítésbe"; "settings_labs_enable_voice_broadcast" = "Hang közvetítés (aktív fejlesztés alatt)";
"wysiwyg_composer_start_action_voice_broadcast" = "Hang közvetítés";
"voice_broadcast_playback_loading_error" = "A hang közvetítés nem játszható le.";
"voice_broadcast_already_in_progress_message" = "Egy hang közvetítés már folyamatban van. Először fejezd be a jelenlegi közvetítést egy új indításához.";
"voice_broadcast_blocked_by_someone_else_message" = "Valaki már elindított egy hang közvetítést. Várd meg a közvetítés végét az új indításához.";
"voice_broadcast_permission_denied_message" = "Nincs jogosultságod hang közvetítést indítani ebben a szobában. Vedd fel a kapcsolatot a szoba adminisztrátorával a szükséges jogosultság megszerzéséhez.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Az új hang közvetítés nem indítható el";
"deselect_all" = "Semmit nem jelöl ki";
"user_other_session_menu_select_sessions" = "Munkamenetek kiválasztása";
"user_other_session_selected_count" = "%@ kiválasztva";

View file

@ -2832,3 +2832,15 @@
"manage_session_name_info" = "Harap diketahui bahwa nama sesi juga terlihat ke orang-orang yang Anda berkomunikasi. %@"; "manage_session_name_info" = "Harap diketahui bahwa nama sesi juga terlihat ke orang-orang yang Anda berkomunikasi. %@";
"manage_session_name_hint" = "Nama sesi khusus dapat membantu Anda mengenal perangkat Anda dengan lebih mudah."; "manage_session_name_hint" = "Nama sesi khusus dapat membantu Anda mengenal perangkat Anda dengan lebih mudah.";
"settings_labs_enable_wysiwyg_composer" = "Coba editor teks kaya (mode teks biasa akan datang)"; "settings_labs_enable_wysiwyg_composer" = "Coba editor teks kaya (mode teks biasa akan datang)";
"wysiwyg_composer_start_action_voice_broadcast" = "Siaran suara";
"voice_broadcast_playback_loading_error" = "Tidak dapat memainkan siaran suara ini.";
"voice_broadcast_already_in_progress_message" = "Anda saat ini merekam sebuah siaran suara. Mohon akhiri siaran suara Anda saat ini untuk memulai yang baru.";
"voice_broadcast_blocked_by_someone_else_message" = "Ada orang lain yang saat ini merekam sebuah siaran suara. Tunggu siaran suaranya berakhir untuk memulai yang baru.";
"voice_broadcast_permission_denied_message" = "Anda tidak memiliki izin untuk memulai sebuah siaran suara di ruangan ini. Hubungi sebuah administrator ruangan untuk meningkatkan izin Anda.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Tidak dapat memulai sebuah siaran suara baru";
"settings_labs_enable_voice_broadcast" = "Siaran suara (dalam pengembangan aktif)";
"deselect_all" = "Batalkan Semua Pilihan";
"user_other_session_menu_select_sessions" = "Pilih sesi";
"user_other_session_selected_count" = "%@ dipilih";

View file

@ -2605,4 +2605,15 @@
"manage_session_name_info" = "Ricorda che i nomi di sessione sono anche visibili alle persone con cui comunichi. %@"; "manage_session_name_info" = "Ricorda che i nomi di sessione sono anche visibili alle persone con cui comunichi. %@";
"manage_session_name_hint" = "I nomi di sessione personalizzati possono aiutarti a riconoscere i tuoi dispositivi più facilmente."; "manage_session_name_hint" = "I nomi di sessione personalizzati possono aiutarti a riconoscere i tuoi dispositivi più facilmente.";
"settings_labs_enable_wysiwyg_composer" = "Prova l'editor in rich text (il testo semplice è in arrivo)"; "settings_labs_enable_wysiwyg_composer" = "Prova l'editor in rich text (il testo semplice è in arrivo)";
"settings_labs_enable_voice_broadcast" = "Broadcast voce (in sviluppo attivo). Attualmente rileviamo solo il broadcast vocale nella linea temporale della stanza, non è possibile inviare o ascoltare un vero broadcast vocale"; "settings_labs_enable_voice_broadcast" = "Trasmissione vocale (in sviluppo attivo)";
"wysiwyg_composer_start_action_voice_broadcast" = "Trasmissione vocale";
"voice_broadcast_playback_loading_error" = "Impossibile avviare questa trasmissione vocale.";
"voice_broadcast_already_in_progress_message" = "Stai già registrando una trasmissione vocale. Termina quella in corso per iniziarne una nuova.";
"voice_broadcast_blocked_by_someone_else_message" = "Qualcun altro sta già registrando una trasmissione vocale. Aspetta che finisca prima di iniziarne una nuova.";
"voice_broadcast_permission_denied_message" = "Non hai l'autorizzazione necessaria per iniziare un broadcast vocale in questa stanza. Contatta un amministratore della stanza per aggiornare le tue autorizzazioni.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Impossibile iniziare una nuova trasmissione vocale";
"deselect_all" = "Deseleziona tutti";
"user_other_session_menu_select_sessions" = "Seleziona sessioni";
"user_other_session_selected_count" = "%@ selezionate";

View file

@ -751,7 +751,7 @@
"group_participants_invited_section" = "ZAPROSZONY"; "group_participants_invited_section" = "ZAPROSZONY";
"receipt_status_read" = "Odczytano: "; "receipt_status_read" = "Odczytano: ";
// Media picker // Media picker
"media_picker_title" = "Selektor mediów"; "media_picker_title" = "Biblioteka mediów";
// Image picker // Image picker
"image_picker_action_camera" = "Zrób zdjęcie"; "image_picker_action_camera" = "Zrób zdjęcie";
"image_picker_action_library" = "Wybierz z biblioteki"; "image_picker_action_library" = "Wybierz z biblioteki";
@ -2569,7 +2569,7 @@
// Mark: - All Chats // Mark: - All Chats
"all_chats_title" = "Wszystkie rozmowy"; "all_chats_title" = "Rozmowy";
"spaces_subspace_creation_visibility_message" = "Utworzona przestrzeń zostanie dodana do %@."; "spaces_subspace_creation_visibility_message" = "Utworzona przestrzeń zostanie dodana do %@.";
"spaces_subspace_creation_visibility_title" = "Jakiego rodzaju podprzestrzeń chcesz utworzyć?"; "spaces_subspace_creation_visibility_title" = "Jakiego rodzaju podprzestrzeń chcesz utworzyć?";
"spaces_explore_rooms_format" = "Przeglądaj %@"; "spaces_explore_rooms_format" = "Przeglądaj %@";

View file

@ -2606,3 +2606,12 @@
"manage_session_name_info" = "Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica. %@"; "manage_session_name_info" = "Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica. %@";
"manage_session_name_hint" = "Nomes de sessões personalizados podem ajudar você a reconhecer seus dispositivos mais facilmente."; "manage_session_name_hint" = "Nomes de sessões personalizados podem ajudar você a reconhecer seus dispositivos mais facilmente.";
"settings_labs_enable_wysiwyg_composer" = "Experimente o editor de texto rico (modo de texto puro vindo em breve)"; "settings_labs_enable_wysiwyg_composer" = "Experimente o editor de texto rico (modo de texto puro vindo em breve)";
"wysiwyg_composer_start_action_voice_broadcast" = "Broadcast de voz";
"voice_broadcast_playback_loading_error" = "Incapaz de tocar este broadcast de voz.";
"voice_broadcast_already_in_progress_message" = "Você já está gravando um broadcast de voz. Por favor termine seu broadcast de voz atual para começar um novo.";
"voice_broadcast_blocked_by_someone_else_message" = "Alguma outra pessoa já está gravando um broadcast de voz. Espere que o broadcast de voz dela termine para começar um novo.";
"voice_broadcast_permission_denied_message" = "Você não tem as permissões requeridas para começar um broadcast de voz nesta sala. Contacte um(a) administrador(a) da sala para fazer upgrade de suas permissões.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Não dá para começar um novo broadcast de voz";
"settings_labs_enable_voice_broadcast" = "Broadcast de voz (sob desenvolvimento ativo)";

View file

@ -603,7 +603,7 @@
"key_backup_recover_from_passphrase_passphrase_title" = "Ввод"; "key_backup_recover_from_passphrase_passphrase_title" = "Ввод";
"key_backup_recover_from_passphrase_passphrase_placeholder" = "Введите секретную фразу"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "Введите секретную фразу";
"key_backup_recover_from_passphrase_recover_action" = "Разблокировать историю"; "key_backup_recover_from_passphrase_recover_action" = "Разблокировать историю";
"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Не знаете вашу секретную фразу для восстановления? Вы можете "; "key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Не помните свою мнемоническую фразу? Вы можете ";
"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "использовать ключ безопасности"; "key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "использовать ключ безопасности";
"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = ".";
"key_backup_recover_from_recovery_key_info" = "Используйте ключ безопасности для разблокировки истории безопасных сообщений"; "key_backup_recover_from_recovery_key_info" = "Используйте ключ безопасности для разблокировки истории безопасных сообщений";
@ -624,7 +624,7 @@
"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Ключ безопасности"; "key_backup_setup_success_from_recovery_key_recovery_key_title" = "Ключ безопасности";
"key_backup_setup_success_from_recovery_key_make_copy_action" = "Сделать копию"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Сделать копию";
"key_backup_setup_success_from_recovery_key_made_copy_action" = "Я сделал копию"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Я сделал копию";
"key_backup_recover_invalid_passphrase_title" = "Неверная секретная фраза для восстановления"; "key_backup_recover_invalid_passphrase_title" = "Неверная мнемоническая фраза";
"key_backup_recover_invalid_recovery_key_title" = "Несоответствующий ключ безопасности"; "key_backup_recover_invalid_recovery_key_title" = "Несоответствующий ключ безопасности";
"key_backup_setup_banner_title" = "Не теряйте зашифрованные сообщения"; "key_backup_setup_banner_title" = "Не теряйте зашифрованные сообщения";
"key_backup_setup_banner_subtitle" = "Начать использовать ключ восстановления"; "key_backup_setup_banner_subtitle" = "Начать использовать ключ восстановления";
@ -641,7 +641,7 @@
"key_backup_setup_intro_setup_action_with_existing_backup" = "Использовать ключ восстановления"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Использовать ключ восстановления";
"settings_key_backup_info" = "Зашифрованные сообщения защищены сквозным шифрованием. Только вы и получатель(и) имеют ключи для чтения этих сообщений."; "settings_key_backup_info" = "Зашифрованные сообщения защищены сквозным шифрованием. Только вы и получатель(и) имеют ключи для чтения этих сообщений.";
"settings_key_backup_info_signout_warning" = "Сделайте резервную копию ключей перед выходом, чтобы не потерять их."; "settings_key_backup_info_signout_warning" = "Сделайте резервную копию ключей перед выходом, чтобы не потерять их.";
"key_backup_setup_passphrase_title" = "Защитите резервную копию секретной фразой"; "key_backup_setup_passphrase_title" = "Защитите резервную копию мнемонической фразой";
"key_backup_setup_passphrase_setup_recovery_key_info" = "Или защитите свою резервную копию с помощью ключа безопасности, сохранив ее в безопасном месте."; "key_backup_setup_passphrase_setup_recovery_key_info" = "Или защитите свою резервную копию с помощью ключа безопасности, сохранив ее в безопасном месте.";
"key_backup_setup_passphrase_setup_recovery_key_action" = "(Расширенный) Настройка с ключом безопасности"; "key_backup_setup_passphrase_setup_recovery_key_action" = "(Расширенный) Настройка с ключом безопасности";
// Success from passphrase // Success from passphrase
@ -654,7 +654,7 @@
"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Зашифрованные сообщения будут утеряны"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Зашифрованные сообщения будут утеряны";
"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Мне не нужны мои зашифрованные сообщения"; "sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Мне не нужны мои зашифрованные сообщения";
"sign_out_non_existing_key_backup_alert_title" = "Вы потеряете доступ к зашифрованным сообщениям если выйдете сейчас"; "sign_out_non_existing_key_backup_alert_title" = "Вы потеряете доступ к зашифрованным сообщениям если выйдете сейчас";
"key_backup_recover_invalid_passphrase" = "Невозможно расшифровать резервную копию с помощью этой секретной фразы: убедитесь, что вы ввели верную секретную фразу для восстановления."; "key_backup_recover_invalid_passphrase" = "Невозможно расшифровать резервную копию с помощью этой фразы: убедитесь, что вы ввели верную мнемоническую фразу.";
"key_backup_recover_invalid_recovery_key" = "Невозможно расшифровать резервную копию с помощью этого ключа: убедитесь, что вы ввели верный ключ безопасности."; "key_backup_recover_invalid_recovery_key" = "Невозможно расшифровать резервную копию с помощью этого ключа: убедитесь, что вы ввели верный ключ безопасности.";
"e2e_key_backup_wrong_version_button_settings" = "Настройки"; "e2e_key_backup_wrong_version_button_settings" = "Настройки";
"key_backup_setup_intro_manual_export_info" = "(Расширенный)"; "key_backup_setup_intro_manual_export_info" = "(Расширенный)";
@ -986,7 +986,7 @@
"secure_key_backup_setup_intro_info" = "Защитите себя от потери доступа к зашифрованным сообщениям и данным, создав резервную копию ключей шифрования на своём сервере."; "secure_key_backup_setup_intro_info" = "Защитите себя от потери доступа к зашифрованным сообщениям и данным, создав резервную копию ключей шифрования на своём сервере.";
"secure_key_backup_setup_intro_use_security_key_title" = "Используйте ключ безопасности"; "secure_key_backup_setup_intro_use_security_key_title" = "Используйте ключ безопасности";
"secure_key_backup_setup_intro_use_security_key_info" = "Создайте ключ безопасности для хранения в надежном месте, например в менеджере паролей или сейфе."; "secure_key_backup_setup_intro_use_security_key_info" = "Создайте ключ безопасности для хранения в надежном месте, например в менеджере паролей или сейфе.";
"secure_key_backup_setup_intro_use_security_passphrase_title" = "Использовать секретную фразу"; "secure_key_backup_setup_intro_use_security_passphrase_title" = "Использовать мнемоническую фразу";
"secure_key_backup_setup_intro_use_security_passphrase_info" = "Введите секретную фразу, известную только вам, и создайте ключ для резервного копирования."; "secure_key_backup_setup_intro_use_security_passphrase_info" = "Введите секретную фразу, известную только вам, и создайте ключ для резервного копирования.";
"secure_key_backup_setup_existing_backup_error_title" = "Резервная копия сообщений уже существует"; "secure_key_backup_setup_existing_backup_error_title" = "Резервная копия сообщений уже существует";
"secure_key_backup_setup_existing_backup_error_info" = "Разблокируйте его для повторного использования в защищенной резервной копии или удалите для создания новой резервной копии сообщений в защищенной резервной копии."; "secure_key_backup_setup_existing_backup_error_info" = "Разблокируйте его для повторного использования в защищенной резервной копии или удалите для создания новой резервной копии сообщений в защищенной резервной копии.";
@ -1024,7 +1024,7 @@
"device_verification_self_verify_wait_information" = "Подтвердите этот сеанс на одном из других ваших сеансов, предоставив ему доступ к зашифрованным сообщениям.\n\nИспользуйте последнюю версию %@ на других ваших устройствах:"; "device_verification_self_verify_wait_information" = "Подтвердите этот сеанс на одном из других ваших сеансов, предоставив ему доступ к зашифрованным сообщениям.\n\nИспользуйте последнюю версию %@ на других ваших устройствах:";
"device_verification_self_verify_wait_additional_information" = "Это работает с %@ и другими клиентами Matrix с поддержкой кросс-подписи."; "device_verification_self_verify_wait_additional_information" = "Это работает с %@ и другими клиентами Matrix с поддержкой кросс-подписи.";
"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Используйте ключ безопасности"; "device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Используйте ключ безопасности";
"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Используйте секретную фразу или ключ безопасности"; "device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Используйте мнемоническую фразу или бумажный ключ";
"device_verification_self_verify_wait_recover_secrets_additional_information" = "Если вы не можете получить доступ к существующему сеансу"; "device_verification_self_verify_wait_recover_secrets_additional_information" = "Если вы не можете получить доступ к существующему сеансу";
"key_verification_verify_sas_title_emoji" = "Сравните смайлы"; "key_verification_verify_sas_title_emoji" = "Сравните смайлы";
"key_verification_verify_sas_title_number" = "Сравните числа"; "key_verification_verify_sas_title_number" = "Сравните числа";
@ -1102,17 +1102,17 @@
"user_verification_session_details_verify_action_current_user" = "Интерактивная проверка"; "user_verification_session_details_verify_action_current_user" = "Интерактивная проверка";
"user_verification_session_details_verify_action_current_user_manually" = "Ручная проверка с помощью текста"; "user_verification_session_details_verify_action_current_user_manually" = "Ручная проверка с помощью текста";
"user_verification_session_details_verify_action_other_user" = "Подтверждение вручную"; "user_verification_session_details_verify_action_other_user" = "Подтверждение вручную";
"secrets_recovery_with_passphrase_title" = "Секретная фраза"; "secrets_recovery_with_passphrase_title" = "Мнемоническая фраза";
"secrets_recovery_with_passphrase_information_default" = "Получите доступ к своей защищённой истории сообщений и вашей личности с кросс-подписью для проверки других сеансов, введя секретную фразу."; "secrets_recovery_with_passphrase_information_default" = "Получите доступ к своей защищённой истории сообщений и вашей личности с кросс-подписью для проверки других сеансов, введя секретную фразу.";
"secrets_recovery_with_passphrase_information_verify_device" = "Используйте секретную фразу, чтобы проверить это устройство."; "secrets_recovery_with_passphrase_information_verify_device" = "Используйте свою мнемоническую фразу, чтобы заверить эту сессию.";
"secrets_recovery_with_passphrase_passphrase_title" = "Ввод"; "secrets_recovery_with_passphrase_passphrase_title" = "Ввод";
"secrets_recovery_with_passphrase_passphrase_placeholder" = "Введите секретную фразу"; "secrets_recovery_with_passphrase_passphrase_placeholder" = "Введите мнемоническую фразу";
"secrets_recovery_with_passphrase_recover_action" = "Использовать секретную фразу"; "secrets_recovery_with_passphrase_recover_action" = "Использовать секретную фразу";
"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Не знаете вашу секретную фразу? Вы можете "; "secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Не помните свою мнемоническую фразу? Вы можете ";
"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "использовать ключ безопасности"; "secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "использовать бумажный ключ";
"secrets_recovery_with_passphrase_lost_passphrase_action_part3" = "."; "secrets_recovery_with_passphrase_lost_passphrase_action_part3" = ".";
"secrets_recovery_with_passphrase_invalid_passphrase_title" = "Невозможно получить доступ к секретному хранилищу"; "secrets_recovery_with_passphrase_invalid_passphrase_title" = "Невозможно получить доступ к секретному хранилищу";
"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Убедитесь, что вы ввели правильную секретную фразу."; "secrets_recovery_with_passphrase_invalid_passphrase_message" = "Убедитесь, что вы ввели верную мнемоническую фразу.";
"secrets_recovery_with_key_title" = "Ключ безопасности"; "secrets_recovery_with_key_title" = "Ключ безопасности";
"secrets_recovery_with_key_information_default" = "Получите доступ к своей защищённой истории сообщений и вашей личности с кросс-подписью для проверки других сеансов, введя ключ безопасности."; "secrets_recovery_with_key_information_default" = "Получите доступ к своей защищённой истории сообщений и вашей личности с кросс-подписью для проверки других сеансов, введя ключ безопасности.";
"secrets_recovery_with_key_information_verify_device" = "Используйте ключ безопасности, чтобы проверить это устройство."; "secrets_recovery_with_key_information_verify_device" = "Используйте ключ безопасности, чтобы проверить это устройство.";
@ -1128,11 +1128,11 @@
"secrets_setup_recovery_key_done_action" = "Готово"; "secrets_setup_recovery_key_done_action" = "Готово";
"secrets_setup_recovery_key_storage_alert_title" = "Храните его в безопасности"; "secrets_setup_recovery_key_storage_alert_title" = "Храните его в безопасности";
"secrets_setup_recovery_key_storage_alert_message" = "✓ Распечатайте и храните в безопасном месте\n✓ Сохраните его на USB-носителе или резервном носителе\n✓ Скопируйте его в свое личное облачное хранилище"; "secrets_setup_recovery_key_storage_alert_message" = "✓ Распечатайте и храните в безопасном месте\n✓ Сохраните его на USB-носителе или резервном носителе\n✓ Скопируйте его в свое личное облачное хранилище";
"secrets_setup_recovery_passphrase_title" = "Задайте секретную фразу"; "secrets_setup_recovery_passphrase_title" = "Задайте мнемоническую фразу";
"secrets_setup_recovery_passphrase_information" = "Введите секретную фразу, известную только вам, для защиты данных на вашем сервере."; "secrets_setup_recovery_passphrase_information" = "Введите секретную фразу, известную только вам, для защиты данных на вашем сервере.";
"secrets_setup_recovery_passphrase_additional_information" = "Не используйте пароль своей учетной записи."; "secrets_setup_recovery_passphrase_additional_information" = "Не используйте пароль своей учетной записи.";
"secrets_setup_recovery_passphrase_validate_action" = "Готово"; "secrets_setup_recovery_passphrase_validate_action" = "Готово";
"secrets_setup_recovery_passphrase_confirm_information" = "Для подтверждения введите вашу секретную фразу ещё раз."; "secrets_setup_recovery_passphrase_confirm_information" = "Введите мнемоническую фразу ещё раз, чтобы подтвердить её.";
"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Подтвердить"; "secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Подтвердить";
"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Подтвердить секретную фразу"; "secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Подтвердить секретную фразу";
"cross_signing_setup_banner_title" = "Настройка шифрования"; "cross_signing_setup_banner_title" = "Настройка шифрования";
@ -1238,8 +1238,8 @@
// MARK: - Home // MARK: - Home
"home_empty_view_title" = "Добро пожаловать в %@,\n%@"; "home_empty_view_title" = "Добро пожаловать в %@,\n%@";
"secrets_setup_recovery_passphrase_summary_information" = "Запомните свою секретную фразу. Её можно использовать для разблокировки ваших зашифрованных сообщений и данных."; "secrets_setup_recovery_passphrase_summary_information" = "Запомните свою мнемоническую фразу. Её можно использовать для разблокировки ваших зашифрованных сообщений и данных.";
"secrets_setup_recovery_passphrase_summary_title" = "Сохраните вашу секретную фразу"; "secrets_setup_recovery_passphrase_summary_title" = "Сохраните свою мнемоническую фразу";
"favourites_empty_view_information" = "Вы можете добавить в избранное несколькими способами - самый быстрый - просто нажать и удерживать. Нажмите на звёздочку, и они автоматически появятся здесь, и вы их навсегда сохраните."; "favourites_empty_view_information" = "Вы можете добавить в избранное несколькими способами - самый быстрый - просто нажать и удерживать. Нажмите на звёздочку, и они автоматически появятся здесь, и вы их навсегда сохраните.";
// MARK: - Favourites // MARK: - Favourites
@ -1355,7 +1355,7 @@
"space_feature_unavailable_title" = "Пространств ещё нет"; "space_feature_unavailable_title" = "Пространств ещё нет";
"secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Введите свой ключ безопасности, чтобы продолжить."; "secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Введите свой ключ безопасности, чтобы продолжить.";
"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Введите секретную фразу, чтобы продолжить."; "secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Введите мнемоническую фразу, чтобы продолжить.";
"key_verification_verify_qr_code_scan_code_other_device_action" = "Сканирование с помощью этого устройства"; "key_verification_verify_qr_code_scan_code_other_device_action" = "Сканирование с помощью этого устройства";
// Success from secure backup // Success from secure backup

View file

@ -2828,3 +2828,15 @@
"manage_session_name_info" = "Uvedomte si, že názvy relácií sú viditeľné aj pre ľudí, s ktorými komunikujete. %@"; "manage_session_name_info" = "Uvedomte si, že názvy relácií sú viditeľné aj pre ľudí, s ktorými komunikujete. %@";
"manage_session_name_hint" = "Vlastné názvy relácií vám pomôžu ľahšie rozpoznať vaše zariadenia."; "manage_session_name_hint" = "Vlastné názvy relácií vám pomôžu ľahšie rozpoznať vaše zariadenia.";
"settings_labs_enable_wysiwyg_composer" = "Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro)"; "settings_labs_enable_wysiwyg_composer" = "Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro)";
"wysiwyg_composer_start_action_voice_broadcast" = "Hlasové vysielanie";
"voice_broadcast_already_in_progress_message" = "Už nahrávate hlasové vysielanie. Ukončite aktuálne hlasové vysielanie a spustite nové.";
"voice_broadcast_blocked_by_someone_else_message" = "Niekto iný už nahráva hlasové vysielanie. Počkajte, kým sa skončí jeho hlasové vysielanie, a potom spustite nové.";
"voice_broadcast_permission_denied_message" = "Nemáte požadované oprávnenia na spustenie hlasového vysielania v tejto miestnosti. Obráťte sa na správcu miestnosti, aby vám rozšíril oprávnenia.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Nie je možné spustiť nové hlasové vysielanie";
"settings_labs_enable_voice_broadcast" = "Hlasové vysielanie (v štádiu aktívneho vývoja)";
"voice_broadcast_playback_loading_error" = "Toto hlasové vysielanie nie je možné prehrať.";
"deselect_all" = "Zrušiť výber všetkých";
"user_other_session_selected_count" = "%@ vybratých";
"user_other_session_menu_select_sessions" = "Vyberte relácie";

View file

@ -2830,3 +2830,15 @@
"manage_session_name_info" = "Зауважте, що назви сеансів також видно людям, з якими ви спілкуєтесь. %@"; "manage_session_name_info" = "Зауважте, що назви сеансів також видно людям, з якими ви спілкуєтесь. %@";
"manage_session_name_hint" = "Власні назви сеансів допоможуть вам легше розпізнавати ваші пристрої."; "manage_session_name_hint" = "Власні назви сеансів допоможуть вам легше розпізнавати ваші пристрої.";
"settings_labs_enable_wysiwyg_composer" = "Спробуйте розширений текстовий редактор (незабаром з'явиться режим звичайного тексту)"; "settings_labs_enable_wysiwyg_composer" = "Спробуйте розширений текстовий редактор (незабаром з'явиться режим звичайного тексту)";
"wysiwyg_composer_start_action_voice_broadcast" = "Голосові повідомлення";
"voice_broadcast_playback_loading_error" = "Неможливо відтворити це голосове повідомлення.";
"voice_broadcast_already_in_progress_message" = "Ви вже записуєте голосове повідомлення. Завершіть поточну трансляцію, щоб розпочати нову.";
"voice_broadcast_blocked_by_someone_else_message" = "Хтось інший вже записує голосове повідомлення. Зачекайте, поки закінчиться трансляція, щоб розпочати нову.";
"voice_broadcast_permission_denied_message" = "Ви не маєте необхідних дозволів для початку трансляції голосового повідомлення в цій кімнаті. Зверніться до адміністратора кімнати, щоб оновити ваші дозволи.";
// Mark: - Voice broadcast
"voice_broadcast_unauthorized_title" = "Не вдалося розпочати трансляцію нового голосового повідомлення";
"settings_labs_enable_voice_broadcast" = "Голосові повідомлення (в активній розробці)";
"deselect_all" = "Скасувати вибір усіх";
"user_other_session_menu_select_sessions" = "Вибрати сеанси";
"user_other_session_selected_count" = "Вибрано %@";

View file

@ -329,7 +329,7 @@
{ {
if (self.mxSession.crypto) if (self.mxSession.crypto)
{ {
[self.mxSession.crypto trustLevelSummaryForUserIds:@[userId] onComplete:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { [self.mxSession.crypto trustLevelSummaryForUserIds:@[userId] forceDownload:NO success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) {
UserEncryptionTrustLevel userEncryptionTrustLevel; UserEncryptionTrustLevel userEncryptionTrustLevel;
double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted;
@ -341,7 +341,7 @@
else if (trustedDevicesPercentage == 0.0) else if (trustedDevicesPercentage == 0.0)
{ {
// Verify if the user has the user has cross-signing enabled // Verify if the user has the user has cross-signing enabled
if ([self.mxSession.crypto crossSigningKeysForUser:userId]) if ([self.mxSession.crypto.crossSigning crossSigningKeysForUser:userId])
{ {
userEncryptionTrustLevel = UserEncryptionTrustLevelNotVerified; userEncryptionTrustLevel = UserEncryptionTrustLevelNotVerified;
} }
@ -357,6 +357,9 @@
onComplete(userEncryptionTrustLevel); onComplete(userEncryptionTrustLevel);
} failure:^(NSError *error) {
MXLogErrorDetails(@"[MXRoom+Riot] Error fetching trust level summary", error);
onComplete(UserEncryptionTrustLevelUnknown);
}]; }];
} }
else else

View file

@ -195,7 +195,9 @@ UINavigationControllerDelegate
- (BOOL)presentIncomingKeyVerificationRequest:(id<MXKeyVerificationRequest>)incomingKeyVerificationRequest - (BOOL)presentIncomingKeyVerificationRequest:(id<MXKeyVerificationRequest>)incomingKeyVerificationRequest
inSession:(MXSession*)session; inSession:(MXSession*)session;
- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession; - (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember
session:(MXSession*)mxSession
completion:(void (^)(void))completion;
- (BOOL)presentCompleteSecurityForSession:(MXSession*)mxSession; - (BOOL)presentCompleteSecurityForSession:(MXSession*)mxSession;

View file

@ -129,6 +129,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
*/ */
KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter; KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter;
/**
Completion block for the requester of key verification
*/
void (^keyVerificationCompletionBlock)(void);
/** /**
Currently displayed secure backup setup Currently displayed secure backup setup
*/ */
@ -3700,7 +3705,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
return presented; return presented;
} }
- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession - (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember
session:(MXSession*)mxSession
completion:(void (^)(void))completion;
{ {
MXLogDebug(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: %@", roomMember); MXLogDebug(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: %@", roomMember);
@ -3713,6 +3720,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[keyVerificationCoordinatorBridgePresenter presentFrom:self.presentedViewController roomMember:roomMember animated:YES]; [keyVerificationCoordinatorBridgePresenter presentFrom:self.presentedViewController roomMember:roomMember animated:YES];
presented = YES; presented = YES;
keyVerificationCompletionBlock = completion;
} }
else else
{ {
@ -3744,7 +3753,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId
{ {
MXCrypto *crypto = coordinatorBridgePresenter.session.crypto; id<MXCrypto> crypto = coordinatorBridgePresenter.session.crypto;
if (!crypto.backup.hasPrivateKeyInCryptoStore || !crypto.backup.enabled) if (!crypto.backup.hasPrivateKeyInCryptoStore || !crypto.backup.enabled)
{ {
MXLogDebug(@"[AppDelegate][MXKeyVerification] requestAllPrivateKeys: Request key backup private keys"); MXLogDebug(@"[AppDelegate][MXKeyVerification] requestAllPrivateKeys: Request key backup private keys");
@ -3765,6 +3774,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
}]; }];
keyVerificationCoordinatorBridgePresenter = nil; keyVerificationCoordinatorBridgePresenter = nil;
if (keyVerificationCompletionBlock) {
keyVerificationCompletionBlock();
}
keyVerificationCompletionBlock = nil;
} }
#pragma mark - New request #pragma mark - New request
@ -3984,7 +3998,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
- (void)registerUserDidSignInOnNewDeviceNotificationForSession:(MXSession*)session - (void)registerUserDidSignInOnNewDeviceNotificationForSession:(MXSession*)session
{ {
MXCrossSigning *crossSigning = session.crypto.crossSigning; id<MXCrossSigning> crossSigning = session.crypto.crossSigning;
if (!crossSigning) if (!crossSigning)
{ {
@ -4075,7 +4089,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
- (void)registerDidChangeCrossSigningKeysNotificationForSession:(MXSession*)session - (void)registerDidChangeCrossSigningKeysNotificationForSession:(MXSession*)session
{ {
MXCrossSigning *crossSigning = session.crypto.crossSigning; id<MXCrossSigning> crossSigning = session.crypto.crossSigning;
if (!crossSigning) if (!crossSigning)
{ {

View file

@ -324,12 +324,8 @@ extension KeyVerificationCoordinator: KeyVerificationDataLoadingCoordinatorDeleg
// MARK: - DeviceVerificationStartCoordinatorDelegate // MARK: - DeviceVerificationStartCoordinatorDelegate
extension KeyVerificationCoordinator: DeviceVerificationStartCoordinatorDelegate { extension KeyVerificationCoordinator: DeviceVerificationStartCoordinatorDelegate {
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) {
self.showVerifyBySAS(transaction: transaction, animated: true) self.showVerifyByScanning(keyVerificationRequest: request, animated: true)
}
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) {
self.didCancel()
} }
func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) { func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) {

View file

@ -64,12 +64,8 @@ extension DeviceVerificationStartCoordinator: DeviceVerificationStartViewModelCo
self.delegate?.deviceVerificationStartCoordinatorDidCancel(self) self.delegate?.deviceVerificationStartCoordinatorDidCancel(self)
} }
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) {
self.delegate?.deviceVerificationStartCoordinator(self, didCompleteWithOutgoingTransaction: transaction) self.delegate?.deviceVerificationStartCoordinator(self, otherDidAcceptRequest: request)
}
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) {
self.delegate?.deviceVerificationStartCoordinator(self, didTransactionCancelled: transaction)
} }
func deviceVerificationStartViewModelDidCancel(_ viewModel: DeviceVerificationStartViewModelType) { func deviceVerificationStartViewModelDidCancel(_ viewModel: DeviceVerificationStartViewModelType) {

View file

@ -19,8 +19,7 @@
import Foundation import Foundation
protocol DeviceVerificationStartCoordinatorDelegate: AnyObject { protocol DeviceVerificationStartCoordinatorDelegate: AnyObject {
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest)
func deviceVerificationStartCoordinator(_ coordinator: DeviceVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction)
func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType) func deviceVerificationStartCoordinatorDidCancel(_ coordinator: DeviceVerificationStartCoordinatorType)
} }

View file

@ -29,7 +29,7 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy
private let otherUser: MXUser private let otherUser: MXUser
private let otherDevice: MXDeviceInfo private let otherDevice: MXDeviceInfo
private var transaction: MXSASTransaction! private var request: MXKeyVerificationRequest?
// MARK: Public // MARK: Public
@ -52,12 +52,12 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy
case .beginVerifying: case .beginVerifying:
self.beginVerifying() self.beginVerifying()
case .verifyUsingLegacy: case .verifyUsingLegacy:
self.cancelTransaction() self.cancelRequest()
self.update(viewState: .verifyUsingLegacy(self.session, self.otherDevice)) self.update(viewState: .verifyUsingLegacy(self.session, self.otherDevice))
case .verifiedUsingLegacy: case .verifiedUsingLegacy:
self.coordinatorDelegate?.deviceVerificationStartViewModelDidUseLegacyVerification(self) self.coordinatorDelegate?.deviceVerificationStartViewModelDidUseLegacyVerification(self)
case .cancel: case .cancel:
self.cancelTransaction() self.cancelRequest()
self.coordinatorDelegate?.deviceVerificationStartViewModelDidCancel(self) self.coordinatorDelegate?.deviceVerificationStartViewModelDidCancel(self)
} }
} }
@ -67,30 +67,22 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy
private func beginVerifying() { private func beginVerifying() {
self.update(viewState: .loading) self.update(viewState: .loading)
self.verificationManager.beginKeyVerification(withUserId: self.otherUser.userId, andDeviceId: self.otherDevice.deviceId, method: MXKeyVerificationMethodSAS, success: { [weak self] (transaction) in self.verificationManager.requestVerificationByToDevice(withUserId: otherUser.userId, deviceIds: [otherDevice.deviceId], methods: [MXKeyVerificationMethodSAS], success: { [weak self] request in
guard let self = self else {
guard let sself = self else {
return
}
guard let sasTransaction = transaction as? MXSASTransaction, !sasTransaction.isIncoming else {
return return
} }
sself.transaction = sasTransaction self.request = request
sself.update(viewState: .loaded) self.update(viewState: .loaded)
sself.registerTransactionDidStateChangeNotification(transaction: sasTransaction) self.registerKeyVerificationRequestDidChangeNotification(for: request)
}, failure: {[weak self] error in }, failure: {[weak self] error in
self?.update(viewState: .error(error)) self?.update(viewState: .error(error))
}) })
} }
private func cancelTransaction() { private func cancelRequest() {
guard let transaction = self.transaction else { request?.cancel(with: MXTransactionCancelCode.user(), success: nil)
return
}
transaction.cancel(with: MXTransactionCancelCode.user())
} }
private func update(viewState: DeviceVerificationStartViewState) { private func update(viewState: DeviceVerificationStartViewState) {
@ -98,37 +90,41 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy
} }
// MARK: - MXKeyVerificationTransactionDidChange // MARK: - MXKeyVerificationRequestDidChange
private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) { private func registerKeyVerificationRequestDidChangeNotification(for request: MXKeyVerificationRequest) {
NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) NotificationCenter.default.addObserver(self, selector: #selector(requestDidStateChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: request)
} }
private func unregisterTransactionDidStateChangeNotification() { private func unregisterKeyVerificationRequestDidChangeNotification() {
NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil)
} }
@objc private func transactionDidStateChange(notification: Notification) { @objc private func requestDidStateChange(notification: Notification) {
guard let transaction = notification.object as? MXSASTransaction, !transaction.isIncoming else { guard let request = notification.object as? MXKeyVerificationRequest, request.requestId == self.request?.requestId else {
return return
} }
switch transaction.state { switch request.state {
case MXSASTransactionStateShowSAS: case MXKeyVerificationRequestStateAccepted, MXKeyVerificationRequestStateReady:
self.unregisterTransactionDidStateChangeNotification() self.unregisterKeyVerificationRequestDidChangeNotification()
self.coordinatorDelegate?.deviceVerificationStartViewModel(self, didCompleteWithOutgoingTransaction: transaction) self.coordinatorDelegate?.deviceVerificationStartViewModel(self, otherDidAcceptRequest: request)
case MXSASTransactionStateCancelled:
guard let reason = transaction.reasonCancelCode else { case MXKeyVerificationRequestStateCancelled:
guard let reason = request.reasonCancelCode else {
return return
} }
self.unregisterTransactionDidStateChangeNotification() self.unregisterKeyVerificationRequestDidChangeNotification()
self.update(viewState: .cancelled(reason)) self.update(viewState: .cancelled(reason))
case MXSASTransactionStateCancelledByMe: case MXKeyVerificationRequestStateCancelledByMe:
guard let reason = transaction.reasonCancelCode else { guard let reason = request.reasonCancelCode else {
return return
} }
self.unregisterTransactionDidStateChangeNotification() self.unregisterKeyVerificationRequestDidChangeNotification()
self.update(viewState: .cancelledByMe(reason)) self.update(viewState: .cancelledByMe(reason))
case MXKeyVerificationRequestStateExpired:
self.unregisterKeyVerificationRequestDidChangeNotification()
self.update(viewState: .error(UserVerificationStartViewModelError.keyVerificationRequestExpired))
default: default:
break break
} }

View file

@ -25,8 +25,7 @@ protocol DeviceVerificationStartViewModelViewDelegate: AnyObject {
protocol DeviceVerificationStartViewModelCoordinatorDelegate: AnyObject { protocol DeviceVerificationStartViewModelCoordinatorDelegate: AnyObject {
func deviceVerificationStartViewModelDidUseLegacyVerification(_ viewModel: DeviceVerificationStartViewModelType) func deviceVerificationStartViewModelDidUseLegacyVerification(_ viewModel: DeviceVerificationStartViewModelType)
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest)
func deviceVerificationStartViewModel(_ viewModel: DeviceVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction)
func deviceVerificationStartViewModelDidCancel(_ viewModel: DeviceVerificationStartViewModelType) func deviceVerificationStartViewModelDidCancel(_ viewModel: DeviceVerificationStartViewModelType)
} }

View file

@ -189,6 +189,7 @@ extension UserVerificationCoordinator: KeyVerificationCoordinatorDelegate {
func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) {
dismissPresenter(coordinator: coordinator) dismissPresenter(coordinator: coordinator)
delegate?.userVerificationCoordinatorDidComplete(self)
} }
func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) { func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) {

View file

@ -1743,8 +1743,18 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
return; return;
} }
if (![mxSession.crypto.crossSigning isKindOfClass:[MXLegacyCrossSigning class]]) {
MXLogFailure(@"Device dehydratation is currently only supported by legacy cross signing, add support to all implementations");
if (failure)
{
failure(nil);
}
return;
}
MXLegacyCrossSigning *crossSigning = (MXLegacyCrossSigning *)mxSession.crypto.crossSigning;;
MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: starting device dehydration"); MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: starting device dehydration");
[[MXKAccountManager sharedManager].dehydrationService dehydrateDeviceWithMatrixRestClient:mxRestClient crypto:mxSession.crypto dehydrationKey:keyData success:^(NSString *deviceId) { [[MXKAccountManager sharedManager].dehydrationService dehydrateDeviceWithMatrixRestClient:mxRestClient crossSigning:crossSigning dehydrationKey:keyData success:^(NSString *deviceId) {
MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device successfully dehydrated"); MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device successfully dehydrated");
if (success) if (success)

View file

@ -65,9 +65,12 @@
_event = event; _event = event;
_displayFix = MXKRoomBubbleComponentDisplayFixNone; _displayFix = MXKRoomBubbleComponentDisplayFixNone;
if ([event.content[@"format"] isEqualToString:kMXRoomMessageFormatHTML])
NSString *format = event.content[@"format"];
if ([format isKindOfClass:[NSString class]] && [format isEqualToString:kMXRoomMessageFormatHTML])
{ {
if ([((NSString*)event.content[@"formatted_body"]) containsString:@"<blockquote"]) NSString *formattedBody = (NSString*)event.content[@"formatted_body"];
if ([formattedBody isKindOfClass:[NSString class]] && [formattedBody containsString:@"<blockquote"])
{ {
_displayFix |= MXKRoomBubbleComponentDisplayFixHtmlBlockquote; _displayFix |= MXKRoomBubbleComponentDisplayFixHtmlBlockquote;
} }

View file

@ -440,7 +440,9 @@
- (void)startUserVerification - (void)startUserVerification
{ {
[[AppDelegate theDelegate] presentUserVerificationForRoomMember:self.mxRoomMember session:self.mainSession]; [[AppDelegate theDelegate] presentUserVerificationForRoomMember:self.mxRoomMember session:self.mainSession completion:^{
[self refreshUserEncryptionTrustLevel];
}];
} }
- (void)presentUserVerification - (void)presentUserVerification
@ -1332,6 +1334,7 @@
- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId
{ {
[self refreshUserEncryptionTrustLevel];
[self dismissKeyVerificationCoordinatorBridgePresenter]; [self dismissKeyVerificationCoordinatorBridgePresenter];
} }

View file

@ -806,7 +806,7 @@ static BOOL _disableLongPressGestureOnEvent;
mimetype = bubbleData.attachment.contentInfo[@"mimetype"]; mimetype = bubbleData.attachment.contentInfo[@"mimetype"];
} }
if ([mimetype isEqualToString:@"image/gif"]) if ([mimetype isKindOfClass:[NSString class]] && [mimetype isEqualToString:@"image/gif"])
{ {
if (_isAutoAnimatedGif) if (_isAutoAnimatedGif)
{ {

View file

@ -88,7 +88,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
let subView: UIView = hostingViewController.view let subView: UIView = hostingViewController.view
self.addSubview(subView) self.addSubview(subView)
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false self.translatesAutoresizingMaskIntoConstraints = false
subView.translatesAutoresizingMaskIntoConstraints = false subView.translatesAutoresizingMaskIntoConstraints = false
heightConstraint = subView.heightAnchor.constraint(equalToConstant: height) heightConstraint = subView.heightAnchor.constraint(equalToConstant: height)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@ -103,7 +103,13 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
.sink(receiveValue: { [weak self] idealHeight in .sink(receiveValue: { [weak self] idealHeight in
guard let self = self else { return } guard let self = self else { return }
self.updateToolbarHeight(wysiwygHeight: idealHeight) self.updateToolbarHeight(wysiwygHeight: idealHeight)
}) }),
// Required to update the view constraints after minimise/maximise is tapped
wysiwygViewModel.$idealHeight
.removeDuplicates()
.sink { [weak hostingViewController] _ in
hostingViewController?.view.setNeedsLayout()
}
] ]
update(theme: ThemeService.shared().theme) update(theme: ThemeService.shared().theme)

View file

@ -324,7 +324,7 @@ TableViewSectionsDelegate>
// Crypto sessions section // Crypto sessions section
if (RiotSettings.shared.settingsSecurityScreenShowSessions) if (RiotSettings.shared.settingsSecurityScreenShowSessions && !RiotSettings.shared.enableNewSessionManager)
{ {
Section *sessionsSection = [Section sectionWithTag:SECTION_CRYPTO_SESSIONS]; Section *sessionsSection = [Section sectionWithTag:SECTION_CRYPTO_SESSIONS];
@ -627,7 +627,7 @@ TableViewSectionsDelegate>
- (void)loadCrossSigning - (void)loadCrossSigning
{ {
MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
[crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { [crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) {
if (stateUpdated) if (stateUpdated)
@ -643,7 +643,7 @@ TableViewSectionsDelegate>
{ {
NSInteger numberOfRowsInCrossSigningSection; NSInteger numberOfRowsInCrossSigningSection;
MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
switch (crossSigning.state) switch (crossSigning.state)
{ {
case MXCrossSigningStateNotBootstrapped: // Action: Bootstrap case MXCrossSigningStateNotBootstrapped: // Action: Bootstrap
@ -661,7 +661,7 @@ TableViewSectionsDelegate>
- (NSAttributedString*)crossSigningInformation - (NSAttributedString*)crossSigningInformation
{ {
MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
NSString *crossSigningInformation; NSString *crossSigningInformation;
switch (crossSigning.state) switch (crossSigning.state)
@ -708,7 +708,7 @@ TableViewSectionsDelegate>
buttonCell.mxkButton.accessibilityIdentifier = nil; buttonCell.mxkButton.accessibilityIdentifier = nil;
// And customise it // And customise it
MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; id<MXCrossSigning> crossSigning = self.mainSession.crypto.crossSigning;
switch (crossSigning.state) switch (crossSigning.state)
{ {
case MXCrossSigningStateNotBootstrapped: // Action: Bootstrap case MXCrossSigningStateNotBootstrapped: // Action: Bootstrap

View file

@ -106,7 +106,11 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
} }
func stopScanning(destroy: Bool) { func stopScanning(destroy: Bool) {
if (zxCapture.delegate != nil) {
// Setting the zxCapture to nil without checking makes it start
// scanning and implicitly requesting camera access
zxCapture.delegate = nil zxCapture.delegate = nil
}
guard zxCapture.running else { guard zxCapture.running else {
return return
@ -292,7 +296,7 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
MXLog.debug("[QRLoginService] Received cross-signing details \(responsePayload)") MXLog.debug("[QRLoginService] Received cross-signing details \(responsePayload)")
if let masterKeyFromVerifyingDevice = responsePayload.masterKey, if let masterKeyFromVerifyingDevice = responsePayload.masterKey,
let localMasterKey = session.crypto.crossSigningKeys(forUser: session.myUserId).masterKeys?.keys { let localMasterKey = session.crypto.crossSigning.crossSigningKeys(forUser: session.myUserId)?.masterKeys?.keys {
guard masterKeyFromVerifyingDevice == localMasterKey else { guard masterKeyFromVerifyingDevice == localMasterKey else {
MXLog.error("[QRLoginService] Received invalid master key from verifying device") MXLog.error("[QRLoginService] Received invalid master key from verifying device")
await teardownRendezvous(state: .failed(error: .rendezvousFailed)) await teardownRendezvous(state: .failed(error: .rendezvousFailed))
@ -348,6 +352,7 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
await teardownRendezvous() await teardownRendezvous()
} }
@MainActor
private func teardownRendezvous(state: QRLoginServiceState? = nil) async { private func teardownRendezvous(state: QRLoginServiceState? = nil) async {
// Stop listening for changes, try deleting the resource // Stop listening for changes, try deleting the resource
_ = await rendezvousService?.tearDown() _ = await rendezvousService?.tearDown()

View file

@ -36,6 +36,7 @@ struct ScreenList: View {
VStack { VStack {
TextField("Search", text: $searchQuery) TextField("Search", text: $searchQuery)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.autocorrectionDisabled()
.padding(.horizontal) .padding(.horizontal)
.accessibilityIdentifier("searchQueryTextField") .accessibilityIdentifier("searchQueryTextField")
.onChange(of: searchQuery, perform: search) .onChange(of: searchQuery, perform: search)

View file

@ -20,16 +20,33 @@ import XCTest
extension XCUIApplication { extension XCUIApplication {
func goToScreenWithIdentifier(_ identifier: String) { func goToScreenWithIdentifier(_ identifier: String) {
// Search for the screen identifier // Search for the screen identifier
textFields["searchQueryTextField"].tap() let textField = textFields["searchQueryTextField"]
typeText(identifier)
let button = buttons[identifier] let button = buttons[identifier]
let footer = staticTexts["footerText"]
while !button.isHittable, !footer.isHittable { // Sometimes the search gets stuck without showing any results. Try to nudge it along
tables.firstMatch.swipeUp() for _ in 0...10 {
textField.clearAndTypeText(identifier)
if button.exists {
break
}
} }
button.tap() button.tap()
} }
} }
private extension XCUIElement {
func clearAndTypeText(_ text: String) {
guard let stringValue = value as? String else {
XCTFail("Tried to clear and type text into a non string value")
return
}
tap()
let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
typeText(deleteString)
typeText(text)
}
}

View file

@ -30,6 +30,19 @@ final class ComposerUITests: MockScreenTestCase {
wysiwygTextView.typeText("test") wysiwygTextView.typeText("test")
XCTAssertTrue(sendButton.exists) XCTAssertTrue(sendButton.exists)
XCTAssertFalse(app.buttons["editButton"].exists) XCTAssertFalse(app.buttons["editButton"].exists)
let maximiseButton = app.buttons["maximiseButton"]
let minimiseButton = app.buttons["minimiseButton"]
XCTAssertFalse(minimiseButton.exists)
XCTAssertTrue(maximiseButton.exists)
maximiseButton.tap()
XCTAssertTrue(minimiseButton.exists)
XCTAssertFalse(maximiseButton.exists)
minimiseButton.tap()
XCTAssertFalse(minimiseButton.exists)
XCTAssertTrue(maximiseButton.exists)
} }
func testReplyMode() throws { func testReplyMode() throws {
@ -56,6 +69,19 @@ final class ComposerUITests: MockScreenTestCase {
let textViewContent = wysiwygTextView.value as! String let textViewContent = wysiwygTextView.value as! String
XCTAssertFalse(textViewContent.isEmpty) XCTAssertFalse(textViewContent.isEmpty)
XCTAssertFalse(cancelButton.exists) XCTAssertFalse(cancelButton.exists)
let maximiseButton = app.buttons["maximiseButton"]
let minimiseButton = app.buttons["minimiseButton"]
XCTAssertFalse(minimiseButton.exists)
XCTAssertTrue(maximiseButton.exists)
maximiseButton.tap()
XCTAssertTrue(minimiseButton.exists)
XCTAssertFalse(maximiseButton.exists)
minimiseButton.tap()
XCTAssertFalse(minimiseButton.exists)
XCTAssertTrue(maximiseButton.exists)
} }
func testEditMode() throws { func testEditMode() throws {
@ -82,5 +108,18 @@ final class ComposerUITests: MockScreenTestCase {
let textViewContent = wysiwygTextView.value as! String let textViewContent = wysiwygTextView.value as! String
XCTAssertTrue(textViewContent.isEmpty) XCTAssertTrue(textViewContent.isEmpty)
XCTAssertFalse(cancelButton.exists) XCTAssertFalse(cancelButton.exists)
let maximiseButton = app.buttons["maximiseButton"]
let minimiseButton = app.buttons["minimiseButton"]
XCTAssertFalse(minimiseButton.exists)
XCTAssertTrue(maximiseButton.exists)
maximiseButton.tap()
XCTAssertTrue(minimiseButton.exists)
XCTAssertFalse(maximiseButton.exists)
minimiseButton.tap()
XCTAssertFalse(minimiseButton.exists)
XCTAssertTrue(maximiseButton.exists)
} }
} }

View file

@ -51,6 +51,14 @@ struct Composer: View {
viewModel.viewState.sendMode == .edit ? "editButton" : "sendButton" viewModel.viewState.sendMode == .edit ? "editButton" : "sendButton"
} }
private var toggleButtonAcccessibilityIdentifier: String {
wysiwygViewModel.maximised ? "minimiseButton" : "maximiseButton"
}
private var toggleButtonImageName: String {
wysiwygViewModel.maximised ? Asset.Images.minimiseComposer.name : Asset.Images.maximiseComposer.name
}
private var borderColor: Color { private var borderColor: Color {
focused ? theme.colors.quarterlyContent : theme.colors.quinaryContent focused ? theme.colors.quarterlyContent : theme.colors.quinaryContent
} }
@ -76,8 +84,6 @@ struct Composer: View {
var body: some View { var body: some View {
VStack(spacing: 8) { VStack(spacing: 8) {
let rect = RoundedRectangle(cornerRadius: cornerRadius) let rect = RoundedRectangle(cornerRadius: cornerRadius)
// TODO: Fix maximise animation bugs before re-enabling
// ZStack(alignment: .topTrailing) {
VStack(spacing: 12) { VStack(spacing: 12) {
if viewModel.viewState.shouldDisplayContext { if viewModel.viewState.shouldDisplayContext {
HStack { HStack {
@ -103,6 +109,7 @@ struct Composer: View {
.padding(.top, 8) .padding(.top, 8)
.padding(.horizontal, horizontalPadding) .padding(.horizontal, horizontalPadding)
} }
HStack(alignment: .top, spacing: 0) {
WysiwygComposerView( WysiwygComposerView(
focused: $focused, focused: $focused,
content: wysiwygViewModel.content, content: wysiwygViewModel.content,
@ -113,26 +120,28 @@ struct Composer: View {
.tintColor(theme.colors.accent) .tintColor(theme.colors.accent)
.placeholder(viewModel.viewState.placeholder, color: theme.colors.tertiaryContent) .placeholder(viewModel.viewState.placeholder, color: theme.colors.tertiaryContent)
.frame(height: wysiwygViewModel.idealHeight) .frame(height: wysiwygViewModel.idealHeight)
.padding(.horizontal, horizontalPadding)
.onAppear { .onAppear {
wysiwygViewModel.setup() wysiwygViewModel.setup()
} }
// Button { Button {
// withAnimation(.easeInOut(duration: 0.25)) { wysiwygViewModel.maximised.toggle()
// viewModel.maximised.toggle() } label: {
// } Image(toggleButtonImageName)
// } label: { .resizable()
// Image(viewModel.maximised ? Asset.Images.minimiseComposer.name : Asset.Images.maximiseComposer.name) .foregroundColor(theme.colors.tertiaryContent)
// .foregroundColor(theme.colors.tertiaryContent) .frame(width: 16, height: 16)
// } }
// .padding(.top, 4) .accessibilityIdentifier(toggleButtonAcccessibilityIdentifier)
// .padding(.trailing, 12) .padding(.leading, 12)
// } .padding(.trailing, 4)
}
.padding(.horizontal, horizontalPadding)
.padding(.top, topPadding) .padding(.top, topPadding)
.padding(.bottom, verticalPadding) .padding(.bottom, verticalPadding)
} }
.clipShape(rect) .clipShape(rect)
.overlay(rect.stroke(borderColor, lineWidth: 1)) .overlay(rect.stroke(borderColor, lineWidth: 1))
.animation(.easeInOut(duration: 0.1), value: wysiwygViewModel.idealHeight)
.padding(.horizontal, horizontalPadding) .padding(.horizontal, horizontalPadding)
.padding(.top, 8) .padding(.top, 8)
.onTapGesture { .onTapGesture {
@ -148,7 +157,6 @@ struct Composer: View {
.resizable() .resizable()
.foregroundColor(theme.colors.tertiaryContent) .foregroundColor(theme.colors.tertiaryContent)
.frame(width: 14, height: 14) .frame(width: 14, height: 14)
} }
.frame(width: 36, height: 36) .frame(width: 36, height: 36)
.background(Circle().fill(theme.colors.system)) .background(Circle().fill(theme.colors.system))

View file

@ -24,36 +24,45 @@ class TimelinePollUITests: MockScreenTestCase {
XCTAssert(app.staticTexts["Question"].exists) XCTAssert(app.staticTexts["Question"].exists)
XCTAssert(app.staticTexts["20 votes cast"].exists) XCTAssert(app.staticTexts["20 votes cast"].exists)
XCTAssert(app.buttons["First, 10 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%") XCTAssertEqual(app.staticTexts["PollAnswerOption0Count"].label, "10 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption0Progress"].value as? String, "50%")
XCTAssert(app.buttons["Second, 5 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssertEqual(app.buttons["Second, 5 votes"].value as! String, "25%") XCTAssertEqual(app.staticTexts["PollAnswerOption1Count"].label, "5 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption1Progress"].value as? String, "25%")
XCTAssert(app.buttons["Third, 15 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%") XCTAssertEqual(app.staticTexts["PollAnswerOption2Count"].label, "15 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption2Progress"].value as? String, "75%")
app.buttons["First, 10 votes"].tap() app.buttons["PollAnswerOption0"].tap()
XCTAssert(app.buttons["First, 11 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssertEqual(app.buttons["First, 11 votes"].value as! String, "55%") XCTAssertEqual(app.staticTexts["PollAnswerOption0Count"].label, "11 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption0Progress"].value as? String, "55%")
XCTAssert(app.buttons["Second, 4 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssertEqual(app.buttons["Second, 4 votes"].value as! String, "20%") XCTAssertEqual(app.staticTexts["PollAnswerOption1Count"].label, "4 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption1Progress"].value as? String, "20%")
XCTAssert(app.buttons["Third, 15 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%") XCTAssertEqual(app.staticTexts["PollAnswerOption2Count"].label, "15 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption2Progress"].value as? String, "75%")
app.buttons["Third, 15 votes"].tap() app.buttons["PollAnswerOption2"].tap()
XCTAssert(app.buttons["First, 10 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%") XCTAssertEqual(app.staticTexts["PollAnswerOption0Count"].label, "10 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption0Progress"].value as? String, "50%")
XCTAssert(app.buttons["Second, 4 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssertEqual(app.buttons["Second, 4 votes"].value as! String, "20%") XCTAssertEqual(app.staticTexts["PollAnswerOption1Count"].label, "4 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption1Progress"].value as? String, "20%")
XCTAssert(app.buttons["Third, 16 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
XCTAssertEqual(app.buttons["Third, 16 votes"].value as! String, "80%") XCTAssertEqual(app.staticTexts["PollAnswerOption2Count"].label, "16 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption2Progress"].value as? String, "80%")
} }
func testOpenUndisclosedPoll() { func testOpenUndisclosedPoll() {
@ -62,29 +71,29 @@ class TimelinePollUITests: MockScreenTestCase {
XCTAssert(app.staticTexts["Question"].exists) XCTAssert(app.staticTexts["Question"].exists)
XCTAssert(app.staticTexts["20 votes cast"].exists) XCTAssert(app.staticTexts["20 votes cast"].exists)
XCTAssert(!app.buttons["First, 10 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssert(app.buttons["First"].exists) XCTAssert(!app.staticTexts["PollAnswerOption0Count"].exists)
XCTAssertTrue((app.buttons["First"].value as! String).isEmpty) XCTAssert(!app.progressIndicators["PollAnswerOption0Progress"].exists)
XCTAssert(!app.buttons["Second, 5 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssert(app.buttons["Second"].exists) XCTAssert(!app.staticTexts["PollAnswerOption1Count"].exists)
XCTAssertTrue((app.buttons["Second"].value as! String).isEmpty) XCTAssert(!app.progressIndicators["PollAnswerOption1Progress"].exists)
XCTAssert(!app.buttons["Third, 15 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
XCTAssert(app.buttons["Third"].exists) XCTAssert(!app.staticTexts["PollAnswerOption2Count"].exists)
XCTAssertTrue((app.buttons["Third"].value as! String).isEmpty) XCTAssert(!app.progressIndicators["PollAnswerOption2Progress"].exists)
app.buttons["First"].tap() app.buttons["PollAnswerOption0"].tap()
XCTAssert(app.buttons["First"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssert(app.buttons["Second"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssert(app.buttons["Third"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
app.buttons["Third"].tap() app.buttons["PollAnswerOption2"].tap()
XCTAssert(app.buttons["First"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssert(app.buttons["Second"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssert(app.buttons["Third"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
} }
func testClosedDisclosedPoll() { func testClosedDisclosedPoll() {
@ -101,24 +110,30 @@ class TimelinePollUITests: MockScreenTestCase {
XCTAssert(app.staticTexts["Question"].exists) XCTAssert(app.staticTexts["Question"].exists)
XCTAssert(app.staticTexts["Final results based on 20 votes"].exists) XCTAssert(app.staticTexts["Final results based on 20 votes"].exists)
XCTAssert(app.buttons["First, 10 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%") XCTAssertEqual(app.staticTexts["PollAnswerOption0Count"].label, "10 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption0Progress"].value as? String, "50%")
XCTAssert(app.buttons["Second, 5 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssertEqual(app.buttons["Second, 5 votes"].value as! String, "25%") XCTAssertEqual(app.staticTexts["PollAnswerOption1Count"].label, "5 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption1Progress"].value as? String, "25%")
XCTAssert(app.buttons["Third, 15 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%") XCTAssertEqual(app.staticTexts["PollAnswerOption2Count"].label, "15 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption2Progress"].value as? String, "75%")
app.buttons["First, 10 votes"].tap() app.buttons["PollAnswerOption0"].tap()
XCTAssert(app.buttons["First, 10 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption0Label"].label, "First")
XCTAssertEqual(app.buttons["First, 10 votes"].value as! String, "50%") XCTAssertEqual(app.staticTexts["PollAnswerOption0Count"].label, "10 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption0Progress"].value as? String, "50%")
XCTAssert(app.buttons["Second, 5 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption1Label"].label, "Second")
XCTAssertEqual(app.buttons["Second, 5 votes"].value as! String, "25%") XCTAssertEqual(app.staticTexts["PollAnswerOption1Count"].label, "5 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption1Progress"].value as? String, "25%")
XCTAssert(app.buttons["Third, 15 votes"].exists) XCTAssertEqual(app.staticTexts["PollAnswerOption2Label"].label, "Third")
XCTAssertEqual(app.buttons["Third, 15 votes"].value as! String, "75%") XCTAssertEqual(app.staticTexts["PollAnswerOption2Count"].label, "15 votes")
XCTAssertEqual(app.progressIndicators["PollAnswerOption2Progress"].value as? String, "75%")
} }
} }

View file

@ -41,6 +41,7 @@ struct TimelinePollAnswerOptionButton: View {
.overlay(rect.stroke(borderAccentColor, lineWidth: 1.0)) .overlay(rect.stroke(borderAccentColor, lineWidth: 1.0))
.accentColor(progressViewAccentColor) .accentColor(progressViewAccentColor)
} }
.accessibilityIdentifier("PollAnswerOption\(optionIndex)")
} }
var answerOptionLabel: some View { var answerOptionLabel: some View {
@ -53,6 +54,7 @@ struct TimelinePollAnswerOptionButton: View {
Text(answerOption.text) Text(answerOption.text)
.font(theme.fonts.body) .font(theme.fonts.body)
.foregroundColor(theme.colors.primaryContent) .foregroundColor(theme.colors.primaryContent)
.accessibilityIdentifier("PollAnswerOption\(optionIndex)Label")
if poll.closed, answerOption.winner { if poll.closed, answerOption.winner {
Spacer() Spacer()
@ -66,11 +68,13 @@ struct TimelinePollAnswerOptionButton: View {
total: Double(poll.totalAnswerCount)) total: Double(poll.totalAnswerCount))
.progressViewStyle(LinearProgressViewStyle()) .progressViewStyle(LinearProgressViewStyle())
.scaleEffect(x: 1.0, y: 1.2, anchor: .center) .scaleEffect(x: 1.0, y: 1.2, anchor: .center)
.accessibilityIdentifier("PollAnswerOption\(optionIndex)Progress")
if poll.shouldDiscloseResults { if poll.shouldDiscloseResults {
Text(answerOption.count == 1 ? VectorL10n.pollTimelineOneVote : VectorL10n.pollTimelineVotesCount(Int(answerOption.count))) Text(answerOption.count == 1 ? VectorL10n.pollTimelineOneVote : VectorL10n.pollTimelineVotesCount(Int(answerOption.count)))
.font(theme.fonts.footnote) .font(theme.fonts.footnote)
.foregroundColor(poll.closed && answerOption.winner ? theme.colors.accent : theme.colors.secondaryContent) .foregroundColor(poll.closed && answerOption.winner ? theme.colors.accent : theme.colors.secondaryContent)
.accessibilityIdentifier("PollAnswerOption\(optionIndex)Count")
} }
} }
} }
@ -92,6 +96,10 @@ struct TimelinePollAnswerOptionButton: View {
return answerOption.selected ? theme.colors.accent : theme.colors.quarterlyContent return answerOption.selected ? theme.colors.accent : theme.colors.quarterlyContent
} }
var optionIndex: Int {
poll.answerOptions.firstIndex { $0.id == answerOption.id } ?? Int.max
}
} }
struct TimelinePollAnswerOptionButton_Previews: PreviewProvider { struct TimelinePollAnswerOptionButton_Previews: PreviewProvider {

View file

@ -21,10 +21,7 @@ class UserSuggestionUITests: MockScreenTestCase {
func testUserSuggestionScreen() throws { func testUserSuggestionScreen() throws {
app.goToScreenWithIdentifier(MockUserSuggestionScreenState.multipleResults.title) app.goToScreenWithIdentifier(MockUserSuggestionScreenState.multipleResults.title)
XCTAssert(app.tables.firstMatch.waitForExistence(timeout: 1)) let firstButton = app.buttons["displayNameText-userIdText"].firstMatch
XCTAssert(firstButton.waitForExistence(timeout: 10))
let firstButton = app.tables.firstMatch.buttons.firstMatch
_ = firstButton.waitForExistence(timeout: 10)
XCTAssert(firstButton.identifier == "displayNameText-userIdText")
} }
} }

View file

@ -18,7 +18,7 @@ import RiotSwiftUI
import XCTest import XCTest
class UserSessionDetailsUITests: MockScreenTestCase { class UserSessionDetailsUITests: MockScreenTestCase {
func test_longPressDetailsCell_CopiesValueToClipboard() throws { func disabled_broken_xcode14_test_longPressDetailsCell_CopiesValueToClipboard() throws {
app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title) app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title)
UIPasteboard.general.string = "" UIPasteboard.general.string = ""

View file

@ -111,7 +111,7 @@ private class MockSession: MXSession {
} }
/// A mock `MXCrypto` that can override the `canCrossSign` state. /// A mock `MXCrypto` that can override the `canCrossSign` state.
private class MockCrypto: MXCrypto { private class MockCrypto: MXLegacyCrypto {
let canCrossSign: Bool let canCrossSign: Bool
override var crossSigning: MXCrossSigning! { MockCrossSigning(canCrossSign: canCrossSign) } override var crossSigning: MXCrossSigning! { MockCrossSigning(canCrossSign: canCrossSign) }
@ -123,7 +123,7 @@ private class MockCrypto: MXCrypto {
} }
/// A mock `MXCrossSigning` with an overridden `canCrossSign` property. /// A mock `MXCrossSigning` with an overridden `canCrossSign` property.
private class MockCrossSigning: MXCrossSigning { private class MockCrossSigning: MXLegacyCrossSigning {
let canCrossSignMock: Bool let canCrossSignMock: Bool
override var canCrossSign: Bool { canCrossSignMock } override var canCrossSign: Bool { canCrossSignMock }

1
changelog.d/6954.change Normal file
View file

@ -0,0 +1 @@
Added the maximise/minimise toggle button to the Rich Text Composer

View file

@ -0,0 +1 @@
Verification: Deprecate legacy device-to-device verification

View file

@ -0,0 +1 @@
Crypto: Define MXCrypto and MXCrossSigning as protocols

View file

@ -0,0 +1 @@
Add Z-Labs tag for rich text editor and update to the new label naming.

View file

@ -0,0 +1 @@
Hide the old session list when the new device manager is enabled.

View file

@ -21,7 +21,7 @@ platform :ios do
before_all do before_all do
# Ensure used Xcode version # Ensure used Xcode version
xcversion(version: "~> 13.4") xcversion(version: "~> 14.0.1")
end end
#### Public #### #### Public ####
@ -196,7 +196,7 @@ platform :ios do
run_tests( run_tests(
workspace: "Riot.xcworkspace", workspace: "Riot.xcworkspace",
scheme: "RiotSwiftUITests", scheme: "RiotSwiftUITests",
device: "iPhone 13", device: "iPhone 14",
code_coverage: true, code_coverage: true,
# Test result configuration # Test result configuration
result_bundle: true, result_bundle: true,