Merge remote-tracking branch 'origin/sc_beta' into sc

Change-Id: I61bf897abe30e5763047852bdc4128f6df3a1660
This commit is contained in:
SpiritCroc 2023-04-07 11:09:14 +02:00
commit 07a0d315f5
87 changed files with 1078 additions and 698 deletions

View File

@ -1,3 +1,26 @@
Changes in Element v1.5.30 (2023-04-05)
=======================================
Features ✨
----------
- Permalinks to a room/space are pillified ([#8219](https://github.com/vector-im/element-android/issues/8219))
- Permalinks to a matrix user are pillified ([#8220](https://github.com/vector-im/element-android/issues/8220))
- Permalinks to messages are pillified ([#8221](https://github.com/vector-im/element-android/issues/8221))
Bugfixes 🐛
----------
- Custom sticker picker loads indefinitely ([#8026](https://github.com/vector-im/element-android/issues/8026))
- Replace hardcoded colors by theming colors on save button. ([#8208](https://github.com/vector-im/element-android/issues/8208))
- Add RTL support to RemoveJitsiWidgetView ([#8210](https://github.com/vector-im/element-android/issues/8210))
- Add user completion for matrix ids ([#8217](https://github.com/vector-im/element-android/issues/8217))
- Long name are truncated in the pills ([#8218](https://github.com/vector-im/element-android/issues/8218))
- The read marker is stuck in the past ([#8268](https://github.com/vector-im/element-android/issues/8268))
Other changes
-------------
- Replace Terms and Conditions with Acceptable Use Policy. ([#8286](https://github.com/vector-im/element-android/issues/8286))
Changes in Element v1.5.28 (2023-03-08)
=======================================

View File

@ -24,13 +24,13 @@ buildscript {
classpath libs.gradle.gradlePlugin
classpath libs.gradle.kotlinPlugin
classpath libs.gradle.hiltPlugin
classpath 'com.google.firebase:firebase-appdistribution-gradle:3.2.0'
classpath 'com.google.firebase:firebase-appdistribution-gradle:4.0.0'
classpath 'com.google.gms:google-services:4.3.15'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
classpath "com.likethesalad.android:stem-plugin:2.3.0"
classpath 'org.owasp:dependency-check-gradle:8.1.2'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
classpath 'org.owasp:dependency-check-gradle:8.2.1'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
classpath libs.squareup.paparazziPlugin

View File

@ -11,23 +11,23 @@ def gradle = "7.4.2"
def kotlin = "1.8.10"
def kotlinCoroutines = "1.6.4"
def dagger = "2.45"
def firebaseBom = "31.2.2"
def firebaseBom = "31.4.0"
def appDistribution = "16.0.0-beta06"
def retrofit = "2.9.0"
def markwon = "4.6.2"
def moshi = "1.14.0"
def lifecycle = "2.5.1"
def flowBinding = "1.2.0"
def flipper = "0.183.0"
def flipper = "0.188.0"
def epoxy = "5.0.0"
def mavericks = "3.0.1"
def mavericks = "3.0.2"
def glide = "4.14.2"
def bigImageViewer = "1.8.1"
def jjwt = "0.11.5"
def vanniktechEmoji = "0.16.0"
def sentry = "6.15.0"
def sentry = "6.17.0"
// Use 1.6.0 alpha to fix issue with test
def fragment = "1.6.0-alpha04"
def fragment = "1.6.0-alpha08"
// Testing
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
def espresso = "3.5.1"
@ -48,11 +48,11 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
],
androidx : [
'activity' : "androidx.activity:activity-ktx:1.6.1",
'activity' : "androidx.activity:activity-ktx:1.7.0",
'appCompat' : "androidx.appcompat:appcompat:1.6.1",
'biometric' : "androidx.biometric:biometric:1.1.0",
'core' : "androidx.core:core-ktx:1.9.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'recyclerview' : "androidx.recyclerview:recyclerview:1.3.0",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6",
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
@ -87,7 +87,7 @@ ext.libs = [
//'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
//'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.7"
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.8"
],
dagger : [
'dagger' : "com.google.dagger:dagger:$dagger",
@ -102,7 +102,7 @@ ext.libs = [
],
element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0",
'wysiwyg' : "io.element.android:wysiwyg:1.1.1"
'wysiwyg' : "io.element.android:wysiwyg:1.2.2"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",

View File

@ -39,6 +39,12 @@ ext.groups = [
'com.google.testing.platform',
]
],
snapshot: [
regex: [
],
group: [
]
],
mavenCentral: [
regex: [
],

View File

@ -880,7 +880,7 @@
<string name="create_room_name_hint">Nom</string>
<string name="create_room_public_title">Públic</string>
<string name="create_room_public_description">Qualsevol podrà unir-se a aquesta sala</string>
<string name="keys_backup_unable_to_get_trust_info">Hi ha hagut un error rebent informació de confança</string>
<string name="keys_backup_unable_to_get_trust_info">S\'ha produït un error en obtenir la informació de confiança</string>
<string name="keys_backup_unable_to_get_keys_backup_data">Hi ha hagut un error rebent dades de la còpia de seguretat de les claus</string>
<string name="import_e2e_keys_from_file">Importa claus E2E des del fitxer \"%1$s\".</string>
<string name="settings_sdk_version">Versió de l\'SDK de Matrix</string>
@ -2848,4 +2848,39 @@
<string name="notice_voice_broadcast_ended_by_you">Has finalitzat una emissió de veu.</string>
<string name="notice_voice_broadcast_ended">%1$s a finalitzat una emissió de veu.</string>
<string name="action_stop">Sí, atura</string>
<string name="ended_poll_indicator">Ha finalitzat l\'enquesta.</string>
<string name="room_polls_active">Enquestes actives</string>
<string name="room_polls_active_no_item">No hi ha enquestes actives a la sala</string>
<string name="room_polls_wait_for_display">Mostrant enquestes</string>
<string name="room_polls_load_more">Carrega més enquestes</string>
<string name="room_polls_loading_error">Error obtenint enquestes.</string>
<string name="device_manager_verification_status_detail_session_encryption_not_supported">Aquesta sessió no admet xifrat i, per tant, no es pot verificar.</string>
<string name="device_manager_other_sessions_multi_signout_selection">Tanca sessió</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Tanca %1$d sessió</item>
<item quantity="other">Tanca %1$d sessions</item>
</plurals>
<string name="device_manager_signout_all_other_sessions">Tanca totes les altres sessions</string>
<string name="device_manager_other_sessions_show_ip_address">Mostra l\'adreça IP</string>
<string name="device_manager_other_sessions_hide_ip_address">Amaga l\'adreça IP</string>
<string name="set_link_text">Text</string>
<string name="set_link_link">Enllaç</string>
<string name="set_link_create">Crea un enllaç</string>
<string name="set_link_edit">Edita l\'enllaç</string>
<string name="settings_access_token">\'Token\' d\'accés</string>
<string name="message_reply_to_ended_poll_preview">Enquesta finalitzada</string>
<string name="message_reply_to_poll_preview">Enquesta</string>
<string name="message_reply_to_sender_ended_poll">ha finalitzat una enquesta.</string>
<string name="message_reply_to_sender_created_poll">ha creat una enquesta.</string>
<string name="message_reply_to_sender_sent_sticker">ha enviat un adhesiu.</string>
<string name="message_reply_to_sender_sent_video">ha enviat un vídeo.</string>
<string name="message_reply_to_sender_sent_image">ha enviat una imatge.</string>
<string name="message_reply_to_sender_sent_voice_message">ha enviat un missatge de veu.</string>
<string name="message_reply_to_sender_sent_audio_file">ha enviat un àudio.</string>
<string name="message_reply_to_sender_sent_file">ha enviat un fitxer.</string>
<string name="message_reply_to_prefix">En resposta a</string>
<string name="unable_to_decrypt_some_events_in_poll">Per culpa d\'errors de desxifrat, és possible que alguns vots no s\'hagin comptat</string>
<string name="error_voice_broadcast_unable_to_play">No es pot reproduir l\'emissió de veu.</string>
<string name="error_voice_broadcast_unable_to_decrypt">No s\'ha pogut desxifrar l\'emissió de veu.</string>
<string name="error_voice_broadcast_no_connection_recording">Error de connexió - Enregistrament pausat</string>
</resources>

View File

@ -2688,7 +2688,7 @@
<string name="onboarding_new_app_layout_welcome_title">Vítejte v novém zobrazení!</string>
<string name="home_empty_no_unreads_message">Zde se zobrazí nepřečtené zprávy, pokud nějaké máte.</string>
<string name="home_empty_no_unreads_title">Nic k nahlášení.</string>
<string name="home_empty_no_rooms_message">Univerzální zabezpečená chatovací aplikace pro týmy, přátele a organizace. Vytvořte si chat nebo se připojte k existující místnosti a začněte.</string>
<string name="home_empty_no_rooms_message">Univerzální zabezpečený komunikátor pro týmy, přátele a organizace. Vytvořte si chat nebo se připojte k existující místnosti a začněte.</string>
<string name="home_empty_no_rooms_title">Vítejte v aplikaci ${app_name},
\n%s.</string>
<string name="home_empty_space_no_rooms_message">Prostory představují nový způsob seskupování místností a osob. Pomocí tlačítka vpravo dole můžete přidat stávající místnost nebo vytvořit novou.</string>
@ -2989,4 +2989,7 @@
<string name="settings_external_account_management">Údaje o vašem účtu jsou spravovány odděleně na adrese %1$s.</string>
<string name="settings_external_account_management_title">Účet</string>
<string name="settings_notification_error_on_update">Při aktualizaci předvoleb oznámení došlo k chybě. Zkuste to prosím znovu.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Jakmile se pozvaní uživatelé připojí do aplikace ${app_name}, budete moci komunikovat a místnost bude koncově šifrovaná</string>
<string name="direct_room_encryption_enabled_waiting_users">Čekání na uživatele, než se připojí do ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">Můžete pozvat pouze jeden e-mail najednou</string>
</resources>

View File

@ -1249,7 +1249,7 @@
<string name="room_member_power_level_moderators">Moderatoren</string>
<string name="room_member_power_level_custom">Benutzerdefiniert</string>
<string name="room_member_power_level_invites">Eingeladen</string>
<string name="room_member_power_level_users">Nutzer</string>
<string name="room_member_power_level_users">Benutzer</string>
<string name="room_member_power_level_admin_in">Administrator in %1$s</string>
<string name="room_member_power_level_moderator_in">Moderator in %1$s</string>
<string name="room_member_jump_to_read_receipt">Springen und als gelesen markieren</string>
@ -1859,7 +1859,7 @@
<string name="re_authentication_activity_title">Erneute Authentifizierung erforderlich</string>
<string name="failed_to_initialize_cross_signing">Quersignierung konnte nicht eingerichtet werden</string>
<string name="error_unauthorized">Nicht autorisierte, fehlende gültige Authentifizierungsdaten</string>
<string name="call_transfer_users_tab_title">Nutzer</string>
<string name="call_transfer_users_tab_title">Benutzer</string>
<string name="call_transfer_failure">Beim Weiterleiten des Anrufs ist ein Fehler aufgetreten</string>
<string name="call_transfer_title">Weiterleiten</string>
<string name="call_transfer_connect_action">Verbinden</string>
@ -2928,4 +2928,7 @@
<string name="settings_external_account_management_title">Konto</string>
<string name="settings_external_account_management">Deine Kontodetails werden separat verwaltet bei %1$s.</string>
<string name="settings_notification_error_on_update">Ein Fehler ist während der Aktualisierung deiner Benachrichtigungseinstellungen aufgetreten. Bitte versuche es erneut.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Sobald eingeladene Benutzer ${app_name} beigetreten sind, werdet ihr euch unterhalten können und der Raum Ende-zu-Ende-verschlüsselt sein</string>
<string name="direct_room_encryption_enabled_waiting_users">Warte darauf, dass Benutzer ${app_name} beitreten</string>
<string name="direct_room_user_list_only_invite_one_email">Du kannst E-Mail-Einladung nur nacheinander verschicken</string>
</resources>

View File

@ -2920,4 +2920,16 @@
<string name="rich_text_editor_quote">Lülita tsiteerimine sisse/välja</string>
<string name="rich_text_editor_code_block">Lülita koodiblokk sisse/välja</string>
<string name="settings_notification_error_on_update">Sinu teavituste seadistuste muutmisel tekkis viga. Palu proovi uuesti.</string>
<string name="settings_troubleshoot_test_current_endpoint_failed">Lõppsõlme ei õnnestu leida.</string>
<string name="settings_troubleshoot_test_current_endpoint_success">Hetkel kasutatav lõppsõlm: %s</string>
<string name="settings_troubleshoot_test_current_endpoint_title">Lõppsõlm</string>
<string name="settings_troubleshoot_test_endpoint_registration_failed">Lõppsõlme tunnusloa registreerimine koduserveris ei õnnestunud:
\n%1$s</string>
<string name="settings_troubleshoot_test_endpoint_registration_success">Lõppsõlme registreerimine koduserveris õnnestus.</string>
<string name="settings_troubleshoot_test_endpoint_registration_title">Lõppsõlme registreerimine</string>
<string name="settings_troubleshoot_test_current_gateway_title">Lüüs</string>
<string name="settings_troubleshoot_test_current_gateway">Praegune lüüs: %s</string>
<string name="direct_room_user_list_only_invite_one_email">E-posti teel saad saata kutseid vaid ükshaaval</string>
<string name="direct_room_encryption_enabled_waiting_users">Kasutajate liitumise ootel ${app_name} või mõnes muud ühilduvas rakenduses</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Kui kutse saanud kasutajad on liitunud jututoaga ${app_name}, siis saad sa nendega suhelda ja jututuba on läbivalt krüptitud</string>
</resources>

View File

@ -1229,7 +1229,7 @@
<string name="settings_troubleshoot_diagnostic_running_status">در حال اجرا (%1$d از %2$d)</string>
<string name="settings_troubleshoot_diagnostic_run_button_title">تست‌ها را اجرا کن</string>
<string name="settings_troubleshoot_diagnostic">در حال تشخیص مشکل</string>
<string name="error_threepid_auth_failed">مطمئن شوید لینک فعال‌سازی‌ای را که به ایمیل شما ارسال شده، باز کرده‌اید.</string>
<string name="error_threepid_auth_failed">مطمئن شوید روی پیوند درون رایانامه‌ای که بهتان فرستاده‌ایم زده‌اید.</string>
<string name="settings_remove_three_pid_confirmation_content">برداشتن %s؟</string>
<string name="settings_phone_numbers">شماره تلفن‌ها</string>
<string name="settings_emails_empty">هیچ نشانی رایانامه‌ای به حسابتان افزوده نشده</string>
@ -1303,12 +1303,12 @@
<string name="bootstrap_dont_reuse_pwd">ترجیحا از گذرواژه حساب خود استفاده نکنید.</string>
<string name="verify_not_me_self_verification">ممکن است یکی از موارد زیر به خطر افتاده‌باشد:
\n
\n- گذرواژه شما
\n- سرور شما
\n- این دستگاه یا دستگاه دیگر
\n- اتصال اینترنت دستگاه
\n- گذرواژه‌تان
\n- کارساز خانکیتان
\n- این افزاره یا افزارهٔ دیگر
\n- اتّصال اینترنتی هر یک از افزاره‌ها
\n
\nما توصیه می‌کنیم گذرواژه و کلید بازیابی خود را بلافاصله در تنظیمات تغییر دهید.</string>
\nتوصیه می‌کنیم فوراً گذرواژه و کلید بازیابیتان را در تنظیمات تغییر دهید.</string>
<string name="verify_cancel_other">اگر اکنون انصراف دهید، %1$s (%2$s) را تأیید نخواهید کرد. دوباره در نمایه‌اش آغاز کنید.</string>
<string name="verify_cancel_self_verification_from_trusted">اگر لغو کنید، نمی توانید پیام های رمزگذاری شده را در دستگاه جدید خود بخوانید و سایر کاربران به آن اعتماد نخواهند کرد</string>
<string name="verify_cancel_self_verification_from_untrusted">در صورت لغو ، نمی توانید پیام های رمزشده را در این دستگاه بخوانید و سایر کاربران به آن اعتماد نخواهند کرد</string>
@ -1341,19 +1341,19 @@
<string name="room_settings_enable_encryption">به کار انداختن رمزنگاری سرتاسری…</string>
<string name="command_description_rainbow_emote">شکلک را با رنگ‌بندی رنگین گمان ارسال می کند</string>
<string name="command_description_rainbow">پیام را با رنگ‌بندی رنگین کمان ارسال می کند</string>
<string name="verify_cannot_cross_sign">این نشست نمی‌تواند تائید را با نشست‌های دیگر شما به اشتراک بگذارد.
\nتائید به صورت محلی ذخیره می‌شود و در نسخه‌ی بعدی برنامه به اشتراک گذاشته می‌شود.</string>
<string name="verify_cannot_cross_sign">این نشست نمی‌تواند این تائید را با دیگر نشست‌هایتان هم‌رسانی کند.
\nتائید به صورت محلی ذخیره شده و در نگارشی جدیدتر از کاره هم‌رسانی خواهد شد.</string>
<string name="rendering_event_error_exception">المنت هنگام ارائه محتوای رویدادی با شناسه \'%1$s\' با مشکل روبرو شد</string>
<string name="rendering_event_error_type_of_event_not_handled">المنت رویداد \'%1$s\' را پشتیبانی نمی‌کند</string>
<string name="verification_conclusion_compromised">ممکن است یکی از موارد زیر به خطر افتاده باشد:
\n
\n- سرور میزبان شما
\n- سرور کاربری که او را تائید می‌کنید
\n- اتصال اینترنت شما یا کاربران دیگر
\n- دستگاه شما یا دستگاه سایر کاربران</string>
\n- کارساز خانگیتان
\n- کارساز خانگی کاربری که دارید تائیدش می‌کنید
\n- اتّصال اینترنتی شما یا دیگر کاربران
\n- افزارهٔ شما یا دیگر کاربران</string>
<string name="login_error_threepid_denied">دامنهٔ رایانامه‌تان مجاز به ثبت‌نام روی این کارساز نیست</string>
<string name="command_description_shrug">¯\\\\_(ツ)_/¯ را به یک پیام متنی ساده تغییر می دهد</string>
<string name="permalink_malformed">لینک مشکل دارد</string>
<string name="permalink_malformed">پیوند matrix.toتان بدریخت است</string>
<string name="soft_logout_sso_not_same_user_error">نشست فعلی مربوط به کاربر %1$s است و شما اطلاعات حساب کاربر %2$s را ارائه داده‌اید. این مورد توسط المنت پشتیبانی نمی‌شود .
\nلطفا ابتدا داده ها را پاک کنید، سپس با یک حساب دیگر وارد برنامه شوید.</string>
<string name="soft_logout_clear_data_dialog_e2e_warning_content">دسترسی به پیام‌های رمزشده را از دست خواهید داد مگر اینکه برای بازیابی کلیدهای رمزگذاری خود، به حساب خود وارد شوید.</string>
@ -1364,13 +1364,13 @@
\nاگر کارتان با این افزاره تمام شده یا می‌خواهید به حساب دیگری وارد شوید، پاکشان کنید.</string>
<string name="soft_logout_signin_e2e_warning_notice">برای بازیابی کلیدهای رمزگذاری ذخیره شده در این دستگاه، وارد حساب خود شوید. شما برای خواندن همه پیام‌های رمزشده‌ی خود در هر دستگاهی به این کلید‌ها نیاز دارید.</string>
<string name="soft_logout_signin_notice">مدیر کارساز خانگیتان (%1$s) شما (%2$s) را از حسابتان (%3$s) خارج کرد.</string>
<string name="signed_out_notice">این می تواند به دلایل مختلف باشد:
<string name="signed_out_notice">می‌تواند به دلایل مختلفی باشد:
\n
\n• شما گذرواژه خود را در نشست دیگری تغییر داده‌اید.
\n• گذرواژه‌تان را در نشستی دیگر تغییر داده‌اید.
\n
\n• این نشست را در یکی از نشست دیگر خود حذف کرده‌اید.
\n• این نشست را از نشستی دیگر حذف کرده‌اید.
\n
\n• ادمین سرور حساب شما را غیرفعال کرده‌است.</string>
\n• مدیر کارسازتان دسترسی حسابتان را به دلایل امنیت نامعتبر کرده.</string>
<string name="autodiscover_well_known_error">ناتوان در یافتن یک کارساز خانگی معتبر. لطفاً شناسه‌تان را بررسی کنید</string>
<string name="login_signin_matrix_id_error_invalid_matrix_id">این یک شناسه کاربری معتبر نیست. قالب صحیح: \\\'@user:homeserver.org\\\'</string>
<string name="login_signin_matrix_id_password_notice">اگر گذرواژه خود را نمی‌دانید، برای تنظیم مجدد آن بازگردید.</string>
@ -1379,19 +1379,19 @@
<item quantity="one">درخواست های زیادی ارسال شده است. می توانید در %1$d ثانیه دوباره امتحان کنید…</item>
<item quantity="other">درخواست های زیادی ارسال شده است. می توانید در %1$d ثانیه دوباره امتحان کنید…</item>
</plurals>
<string name="login_error_outdated_homeserver_warning_content">این نسخه سرور بسیار قدیمی است. از ادمین خود بخواهید که آن را ارتقا دهد. البته شما می‌توانید ادامه دهید ، اما برخی از ویژگی ها ممکن است به درستی کار نکنند.</string>
<string name="login_error_outdated_homeserver_warning_content">این کارساز خانگی نگارشی قدیمی را اجرا می‌کند. از مدیر درخواست ارتقا کنید. می‌توانید ادامه دهید؛ گرچه ممکن است برخی ویژگی‌ها درست کار نکنند.</string>
<string name="login_error_outdated_homeserver_title">کارساز خانگی قدیمی</string>
<string name="login_validation_code_is_not_correct">کد وارد شده صحیح نیست. لطفا بررسی فرمائید.</string>
<string name="login_wait_for_email_notice">ما ایمیلی به %1$s ارسال کردیم.
\nلطفاً برای ادامه فرآیند ایجاد حساب‌کاربری بر روی لینک موجود در آن کلیک کنید.</string>
<string name="login_wait_for_email_title">لطفا ایمیل خود را بررسی کنید</string>
<string name="login_wait_for_email_notice">رایانامه‌ای به %1$s فرستادیم.
\nلطفاً برای ادامهٔ ایجاد حساب، پیوند درونش را بزنید.</string>
<string name="login_wait_for_email_title">لطفاً رایانامه‌تان را بررسی کنید</string>
<string name="login_terms_title">برای ادامه، شرایط را قبول کنید</string>
<string name="login_a11y_captcha_container">لطفا کپچا را حل کنید</string>
<string name="login_a11y_choose_other">گزینش یک کارساز خانگی سفارشی</string>
<string name="login_a11y_choose_modular">گزینش خدمات ماتریکس المنت</string>
<string name="login_a11y_choose_matrix_org">انتخاب matrix.org</string>
<string name="login_signup_cancel_confirmation_content">حسابتان هنوز ایجاد نشده. توقّف فرآیند ثبت‌نام؟</string>
<string name="login_signin_username_hint">نام‌کاربری یا ایمیل</string>
<string name="login_signin_username_hint">نام‌کاربری یا رایانامه</string>
<string name="login_signup_to">ثبت نام در %1$s</string>
<string name="login_msisdn_error_other">شماره تلفن نامعتبر است. لطفا آن را بررسی کنید</string>
<string name="login_msisdn_confirm_notice">ما یک کد فعال‌سازی به %1$s ارسال کردیم. لطفا آن را وارد کنید.</string>
@ -1401,28 +1401,28 @@
\n
\nفرآیند تغییر گذرواژه را متوقف می‌کنید؟</string>
<string name="login_reset_password_success_notice_2">از تمام نشست‌ها خارج شده و دیگر آگاهی‌ای دریافت نخواهید کرد. برای به کار انداختن دوبارهٔ آگاهی‌ها، روی هر دستگاهی، دوباره وارد شوید.</string>
<string name="login_reset_password_mail_confirmation_submit">ایمیل را تایید کردم</string>
<string name="login_reset_password_mail_confirmation_notice_2">برای تایید گذرواژه جدید لینک ارسالی را باز کرده، سپس روی متن زیر کلیک کنید.</string>
<string name="login_reset_password_mail_confirmation_notice">یک ایمیل تائید به %1$s ارسال شد.</string>
<string name="login_reset_password_mail_confirmation_title">صندوق ایمیل خود را بررسی کنید</string>
<string name="login_reset_password_mail_confirmation_submit">نشانی رایانامه‌ام را تأیید کرده‌ام</string>
<string name="login_reset_password_mail_confirmation_notice_2">برای تأیید گذرواژهٔ جدیدتان روی پیوند بزنید. پس از پیروی از پیوند درونش،‌ پایین را بزنید.</string>
<string name="login_reset_password_mail_confirmation_notice">رایانامهٔ تأییدی به %1$s فرستاده شد.</string>
<string name="login_reset_password_mail_confirmation_title">صندوق ورودیتان را بررسی کنید</string>
<string name="login_reset_password_error_not_found">این نشانی رایانامه به هیچ حسابی پیوند داده نشده</string>
<string name="login_reset_password_warning_content">با تغییر گذرواژه ، کلیدهای رمزگذاری سرتاسر در تمام نشست‌های شما تغییر کرده و تاریخچه گفتگوی رمزشده‌ی شما غیرقابل خواندن می‌شود. قبل از تغییر گذرواژه، کلید امنیتی یا کلید پشتیبان را وارد کرده و یا کلیدهای اتاق خود را از نشست دیگری استخراج کنید.</string>
<string name="login_reset_password_submit">بعدی</string>
<string name="login_reset_password_notice">برای تأیید تنظیم گذرواژه‌ی جدید ، یک ایمیل تأیید به آدرس ایمیل شما ارسال خواهد شد.</string>
<string name="login_reset_password_notice">برای تأیید تنظیم گذرواژهٔ جدیدتان رایانامهٔ تأییدی به صندوق وردیتان فرستاده خواهد شد.</string>
<string name="login_reset_password_on">بازنشانی گذرواژه در %1$s</string>
<string name="login_login_with_email_error">این نشانی رایانامه به هیچ حسابی مرتبط نیست.</string>
<string name="login_registration_not_supported">برنامه قادر به ایجاد حساب در این سرور نیست.
<string name="login_registration_not_supported">برنامه قادر به ایجاد حساب روی این کارساز خانگی نیست.
\n
\nآیا می خواهید با استفاده از مرورگر حساب کاربری بسازید؟</string>
\nمی‌خواهید با یک کارخواه وب ثبت نام کنید؟</string>
<string name="login_registration_disabled">متأسفانه این کارساز، حساب جدید نمی‌پذیرد.</string>
<string name="login_mode_not_supported">برنامه نمی‌تواند به این سرور متصل شود. سرور از این مکانیزم‌(ها) برای ورود پشتیبانی می کند: %1$s.
<string name="login_mode_not_supported">برنامه قادر به ورود به این کارساز خانگی نیست. کارساز خانگی از گونه‌های ورود زیر پشتیبانی می کند: %1$s.
\n
\nآیا می خواهید با استفاده از مرورگر وارد شوید؟</string>
\nمی‌خواهید با یک کارخواه وب وارد شوید؟</string>
<string name="login_sso_error_message">هنگام بارگیری صفحه، خطایی روی داد: %1$s (%2$d)</string>
<string name="login_server_url_form_common_notice">نشانی کارسازی که می‌خواهید استفاده کنید را وارد کنید</string>
<string name="login_server_url_form_modular_notice">نشانی کارساز یا المنت ماژولاری که می‌خواهید استفاده کنید را وارد کنید</string>
<string name="login_server_matrix_org_text">به میلیون‌ها نفر در بزرگ‌ترین کارساز عمومی بپیوندید</string>
<string name="login_server_text">درست مانند ایمیل، حساب‌های کاربری یک خانه دارند؛ اگرچه می توانید با هر کسی که دوست دارید، صحبت کنید</string>
<string name="login_server_text">درست مانند رایانامه، حساب‌ها یک خانه دارند؛ گرچه می توانید با هر کسی صحبت کنید</string>
<string name="login_server_title">کارسازی برگزینید</string>
<string name="login_splash_submit">شروع کنید</string>
<string name="login_splash_text3">تجربه‌هایتان را گسترش داده و شخصی‌سازی کنید</string>
@ -1434,8 +1434,8 @@
<string name="direct_room_join_rules_invite">%1$s اتاق را به گونه‌ای تنظیم کرده که تنها افراد با دعوت بتوانند به اتاق بپیوندند.</string>
<string name="room_join_rules_invite_by_you">شما اتاق را به گونه‌ای تنظیم کرده‌اید که تنها افراد با دعوت بتوانند به اتاق بپیوندند.</string>
<string name="room_join_rules_invite">%1$s اتاق را به گونه‌ای تنظیم کرده‌است که تنها اعضا با دعوت بتوانند به اتاق بپیوندند.</string>
<string name="room_join_rules_public_by_you">شما اتاق را برای هر فردی که لینک آن را دارد عمومی کردید.</string>
<string name="room_join_rules_public">%1$s اتاق را برای هر کس که لینک آن را دارد، عمومی کن.</string>
<string name="room_join_rules_public_by_you">اتاق را برای هرکسی که پیوندش را دارد عمومی کردید.</string>
<string name="room_join_rules_public">%1$s اتاق را برای هرکسی که پیوندش را دارد عمومی کرد.</string>
<string name="help_long_click_on_room_for_more_options">برای دیدن گزینه‌های بیشتر، روی یک اتاق کلیک طولانی کنید</string>
<string name="spoiler">تباه‌کننده</string>
<string name="command_description_spoiler">ارسال به عنوان پیام تباه‌کننده</string>
@ -1470,21 +1470,21 @@
<string name="a11y_create_menu_open">گشودن فهرست ایجاد اتاق</string>
<string name="a11y_open_drawer">کشوی پیمایش را باز کنید</string>
<string name="send_attachment">ارسال ضمیمه</string>
<string name="error_network_timeout">به نظر می رسد پاسخگویی سرور بسیار طولانی شده‌است، این امر می‌تواند به دلیل اتصال ضعیف یا خطای موجود در سرور باشد. لطفا پس از مدتی دوباره امتحان کنید.</string>
<string name="error_terms_not_accepted">لطفاً هنگامی که شرایط و ضوابط سرور خود را پذیرفتید، دوباره امتحان کنید.</string>
<string name="settings_agree_to_terms">با شرایط خدمات سرور هویت‌سنجی (%s) موافقت کنید تا بتوانید از طریق آدرس ایمیل یا شماره تلفن قابل پیدا شدن باشید.</string>
<string name="settings_discovery_disconnect_with_bound_pid">شما در حال حاضر آدرس‌های ایمیل یا شماره تلفن‌ها را در سرور هویت‌سنجی %1$s به اشتراک می گذارید. برای توقف اشتراک آنها باید دوباره به %2$s متصل شوید.</string>
<string name="error_network_timeout">به نظر کارساز بیش از حد برای پاسخ وقت می‌گیرد. این امر می‌تواند ناشی از اتّصال ضعیف یا خطایی در کارساز باشد. لطفاً پس از مدتی دوباره تلاش کنید.</string>
<string name="error_terms_not_accepted">لطفاً پس از پذیرش شرایط و ضوابط کارساز خانگیتان دوباره تلاش کنید.</string>
<string name="settings_agree_to_terms">برای قابل کشف بودن با نشانی رایانامه یا شماره تلفن با شرایط خدمت کارساز هویت (%s) موافقت کنید.</string>
<string name="settings_discovery_disconnect_with_bound_pid">دارید نشانی‌های رایانامه یا شماره‌های تلفن را روی کارساز هویت %1$s هم‌رسانی می‌کنید برای توقّف هم‌رسانیشان باید دوباره به %2$s وصل شوید.</string>
<string name="settings_text_message_sent_wrong_code">کد تائید صحیح نیست.</string>
<string name="settings_text_message_sent_hint">کد</string>
<string name="settings_text_message_sent">پیامکی به %s ارسال شده است. لطفاً کد تأیید موجود در آن را وارد کنید.</string>
<string name="settings_discovery_no_terms">سرور هویت‌سنجی‌ای که انتخاب کرده‌اید هیچگونه شرایط و ضوابطی ندارد. فقط اگر به صاحب سرویس اعتماد دارید ادامه دهید</string>
<string name="settings_discovery_no_terms">کارساز هویت گزیده‌تان هیچ شرایط خدمتی ندارد. تنها اگر به صاحب خدمت اعتماد دارید ادامه دهید</string>
<string name="settings_discovery_no_terms_title">کارساز هویت، شرایط و ضوابط استفاده ندارد</string>
<string name="settings_discovery_please_enter_server">لطفاً نشانی کارساز هویت را وارد کنید</string>
<string name="settings_discovery_bad_identity_server">نتوانست به کارساز هویت وصل شود</string>
<string name="settings_discovery_enter_identity_server">یک نشانی کارساز هویت وارد کنید</string>
<string name="settings_discovery_confirm_mail_not_clicked">رایانامه‌ای به %s فرستادیم. لطفاً نخست رایانامه‌تان را بررسی کرده و روی پیوند تأیید کلیک کنید</string>
<string name="settings_discovery_confirm_mail">رایانامه‌ای به %s فرستادیم. رایانامه‌تان را بررسی کرده و روی پیوند تأیید کلیک کنید</string>
<string name="settings_discovery_disconnect_identity_server_info">قطع ارتباط با سرور هویت‌سنجی به این معنی است که توسط کاربران دیگر قابل شناسایی نخواهید بود و نمی توانید دیگران را از طریق ایمیل یا تلفن دعوت کنید.</string>
<string name="settings_discovery_disconnect_identity_server_info">قطع شدن ازکارساز هویت به معنی قابل کشف نبودنتان به دست دیگر کابران است و نخواهید توانست دیگران را با رایانامه یا تلفن دعوت کنید.</string>
<string name="settings_discovery_identity_server_info_none">در حال استفاده از کارساز هویتی نیستید. برای کشق و قابل کشف بودن به دست آشنایان موجودی که می‌شناسید، یک کارساز هویت در زیر پیکربندی کنید.</string>
<string name="settings_discovery_identity_server_info">دارید برای کشف و قابل کشف بودن به دست آشنایان موجودی که می‌شناسید از %1$s استفاده می‌کنید.</string>
<string name="settings_troubleshoot_test_token_registration_quick_fix">ثبت ژتون</string>
@ -1526,7 +1526,7 @@
<string name="keys_backup_banner_in_progress">پشتیبان‌گیری از کلیدهای شما. این ممکن است چند دقیقه طول بکشد…</string>
<string name="keys_backup_banner_update_line2">مدیریت در بخش پشتیبان‌گیری از کلید</string>
<string name="keys_backup_banner_update_line1">کلیدهای رمزگذاری جدید</string>
<string name="keys_backup_settings_delete_confirm_message">کلیدهای رمزگذاری پشتیبان شما از سرور حذف شوند؟ در اینن صورت دیگر نخواهید توانست از کلید بازیابی خود برای خواندن پیام رمزشده‌ی قبلی خود استفاده کنید.</string>
<string name="keys_backup_settings_delete_confirm_message">حذف کلیدهای رمزنگاری پشتیبان گرفته از کارساز؟ دیگر قادر به استفاده از کلید بازیابیتان برای خواندن تاریخچهٔ پیام‌های رمزشده‌‌تان نخواهید بود.</string>
<string name="keys_backup_settings_delete_confirm_title">حذف نسخه‌ی پشتیبان</string>
<string name="keys_backup_settings_checking_backup_state">در حال بررسی وضعیت نسخه‌ی پشتیبان</string>
<string name="keys_backup_settings_deleting_backup">در حال حذف نسخه‌ی پشتیبان…</string>
@ -1571,9 +1571,9 @@
\nکلید بازیابی خود را در جایی بسیار امن نظیر برنامه‌های شناخته‌شده‌ی مدیریت گذرواژه نگه دارید</string>
<string name="keys_backup_setup_step3_text_line1">درحال پشتیبان‌گیری از کلید‌های شما.</string>
<string name="keys_backup_setup_step1_recovery_key_alternative">یا، نسخه‌ی پشتیبان خود را با یک کلید بازیابی ایمن کنید، و آن را در جایی امن ذخیره کنید.</string>
<string name="keys_backup_setup_step2_text_description">ما یک نسخه رمزگذاری شده از کلیدهای شما را در سرور ذخیره خواهیم کرد. با استفاده از کلید امنیتی قوی، از نسخه‌ی پشتیبان خود محافظت کنید.
<string name="keys_backup_setup_step2_text_description">نگارشی رمز شده از کلیدهایتان را روی کارساز خانگیتان ذخیره خواهیم کرد. برای امن نگه داشنتن پشتیبانتان با عبارت عبوری محافظتش کنید.
\n
\nبرای حداکثر امنیت، کلید امنیتی باید با رمز ورود حساب شما متفاوت باشد.</string>
\nبرای امنیت بیشینه باید با گذرواژهٔ حسابتان فرق داشته باشد.</string>
<string name="keys_backup_setup_step1_description">پیام‌‌ها در اتاق‌های رمزشده، با رمزنگاری سرتاسری امن شده‌اند. فقط شما و گیرنده(ها) کلیدهای خواندم این پیام‌ها را دارید.
\n
\nبرای جلوگیری از گم کردن کلیدهایتان، از آن‌ها به صورت امن، پشتیبان بگیرید.</string>
@ -1656,8 +1656,8 @@
<string name="settings_media">رسانه</string>
<string name="settings_select_country">گزینش یک کشور</string>
<string name="account_phone_number_already_used_error">این شماره تلفن قبلا استفاده شده‌است.</string>
<string name="account_email_already_used_error">این آدرس ایمیل قبلا استفاده شده‌است.</string>
<string name="account_email_validation_message">لطفاً ایمیل خود را بررسی کنید و روی لینک ارسال شده، کلیک کنید. پس از انجام این کار، روی ادامه کلیک کنید.</string>
<string name="account_email_already_used_error">این نشانی رایانامه در حال استفاده است.</string>
<string name="account_email_validation_message">لطفاً رایانامه‌تان را بررسی کرده رو روی پیوند درونش بزنید. پس از این کار، ادامه را بزنید.</string>
<string name="disabled_integration_dialog_content">برای انجام این کار اجازه‌ی یکپارچه‌سازی را در تنظیمات فعال کنید.</string>
<string name="disabled_integration_dialog_title">یکپارچه‌سازی‌ها غیر فعال هستند</string>
<string name="devices_details_last_seen_format">%1$s @ %2$s</string>
@ -1736,7 +1736,7 @@
<string name="permissions_denied_qr_code">برای پویش یک رمز QR نیاز است دسترسی به دوربین را مجاز کنید.</string>
<string name="start_chatting">آغاز به گپ</string>
<string name="settings_export_trail">برون‌ریزی بازرسی</string>
<string name="create_room_disable_federation_description">اگر اتاق فقط برای تعامل با افراد داخل سرور خانه شما می‌باشد، این قابلیت را فعال کنید. این تنظیم را بعدا نمی‌توانید تغییر دهید.</string>
<string name="create_room_disable_federation_description">اگر اتاق فقط برای تعامل با گروه‌های داخلی روی کارساز خانگیتان است، این قابلیت را به کار بیندازید. بعداُ قابل تغییر نیست.</string>
<string name="bottom_sheet_setup_secure_backup_security_key_subtitle">یک کلید امنیتی ایجاد کنید تا در مکانی امن مانند سامانه مدیریت رمز عبور یا گاوصندوق آن را ذخیره کنید.</string>
<string name="identity_server_error_no_current_binding_error">ارتباطی با این شناسه وجود ندارد.</string>
<string name="confirm_your_identity_quad_s">هویت خود را تأیید کنید تا به پیام‌های رمز شده دسترسی پیدا کنید.</string>
@ -1877,10 +1877,10 @@
<item quantity="other">%d ورودی</item>
</plurals>
<string name="settings_server_upload_size_unknown">این محدودیت ناشناخته است.</string>
<string name="settings_server_upload_size_content">سرور شما فایل‌های ضمیمه (پرونده‌ها، فایل‌های چندرسانه‌ای و غیره) تا حجم %s را می‌پذیرد.</string>
<string name="settings_server_upload_size_title">محدودیت بارگذاری فایل بر روی سرور</string>
<string name="settings_server_version">ورژن سرور</string>
<string name="settings_server_name">نام سرور</string>
<string name="settings_server_upload_size_content">کارساز خانگیتان پیوست(پرونده‌ها، رسانه و…) را تا %s می‌پذیرد.</string>
<string name="settings_server_upload_size_title">کران بارگذاری پروندهٔ کارساز</string>
<string name="settings_server_version">نگارش کارساز</string>
<string name="settings_server_name">نام کارساز</string>
<string name="room_list_quick_actions_room_settings">تنظیمات اتاق</string>
<string name="jitsi_leave_conf_to_join_another_one_content">آیا مایل به ترک جلسه‌ی فعلی و ورود به جلسه‌ی دیگری هستید؟</string>
<string name="room_settings_room_version_title">ورژن اتاق</string>
@ -1962,8 +1962,8 @@
<string name="invite_just_to_this_room">فقط به این اتاق</string>
<string name="invite_to_space_with_name_desc">آنها قادر به کاوش در %s خواهند بود</string>
<string name="invite_to_space_with_name">دعوت به %s</string>
<string name="invite_by_link">به اشتراک‌گذاری لینک</string>
<string name="invite_by_email">دعوت با ایمیل</string>
<string name="invite_by_link">هم‌رسانی پیوند</string>
<string name="invite_by_email">دعوت با رایانامه</string>
<string name="invite_people_to_your_space_desc">در حال حاضر فقط شما هستید. %s با دیگران حتی بهتر خواهد بود.</string>
<string name="invite_people_menu">دعوت افراد</string>
<string name="invite_people_to_your_space">دعوت افراد به فضایتان</string>
@ -2929,4 +2929,7 @@
<string name="error_voice_broadcast_unable_to_decrypt">ناتوان در رمزگشایی این پخش صوتی.</string>
<string name="settings_external_account_management">جزییات حسابتان جداگانه در %1$s مدیریت می‌شود.</string>
<string name="settings_notification_error_on_update">هنگام به‌روز رسانی ترجیحات آگاهیتان خطایی رخ داد. لطفاً دوباره تلاش کنید.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">به محض پیوستن کاربران دعوت شده، قادر به گپ خواهید بود و اتاق رمزنگاری سرتاسری می‌شود</string>
<string name="direct_room_encryption_enabled_waiting_users">منتظر پیوستن کاربران به ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">در هر زمان تنها می‌توانید یک رایانامه را دعوت کنید</string>
</resources>

View File

@ -2929,4 +2929,7 @@
<string name="settings_external_account_management">Les détails de votre compte sont gérés séparément sur %1$s.</string>
<string name="settings_external_account_management_title">Compte</string>
<string name="settings_notification_error_on_update">Nous avons rencontré une erreur lors de la mise-à-jour de vos préférences de notification. Veuillez réessayer.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Une fois que les utilisateurs invités seront connectés sur ${app_name}, vous pourrez discuter et le salon sera chiffré de bout en bout</string>
<string name="direct_room_encryption_enabled_waiting_users">En attente de la connexion des utilisateurs sur ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">Vous ne pouvez envoyer quune seule invitation par e-mail à la fois</string>
</resources>

View File

@ -2929,4 +2929,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
<string name="settings_external_account_management">A fiókadatok külön vannak kezelve itt: %1$s.</string>
<string name="settings_external_account_management_title">Fiók</string>
<string name="settings_notification_error_on_update">Hiba történt az értesítések beállításának frissítésekor. Próbáld újra.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Miután a meghívott felhasználók csatlakoztak a(z) ${app_name} alkalmazáshoz, beszélhetsz velük és a szoba végpontok között titkosítva lesz</string>
<string name="direct_room_encryption_enabled_waiting_users">${app_name} alkalmazáshoz csatlakozó emberekre várakozás</string>
<string name="direct_room_user_list_only_invite_one_email">E-mail meghívóból egyszerre csak egy küldhető</string>
</resources>

View File

@ -2871,4 +2871,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
<string name="settings_external_account_management">Detail akun Anda dikelola secara terpisah di %1$s.</string>
<string name="settings_external_account_management_title">Akun</string>
<string name="settings_notification_error_on_update">Terjadi kesalahan saat memperbarui preferensi notifikasi Anda. Silakan coba lagi.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Ketika pengguna yang diundang trlah bergabung ${app_name}, Anda akan dapat mengobrol dan ruangannya akan terenkripsi secara ujung ke ujung</string>
<string name="direct_room_encryption_enabled_waiting_users">Menunggu pengguna untuk bergabung ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">Amda hanya dapat mengundang satu surel satu-satu</string>
</resources>

View File

@ -2920,4 +2920,7 @@
<string name="settings_external_account_management">I dettagli del tuo account sono gestiti separatamente su %1$s.</string>
<string name="settings_external_account_management_title">Account</string>
<string name="settings_notification_error_on_update">Si è verificato un errore aggiornando le tue preferenze di notifica. Riprova.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Una volta che gli utenti si saranno uniti a ${app_name}, potrete scrivervi e la stanza sarà crittografata end-to-end</string>
<string name="direct_room_encryption_enabled_waiting_users">In attesa che gli utenti si uniscano a ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">Puoi invitare una sola email alla volta</string>
</resources>

View File

@ -2989,4 +2989,7 @@
<string name="settings_external_account_management">Údaje o vašom účte sú spravované samostatne na %1$s.</string>
<string name="settings_external_account_management_title">Účet</string>
<string name="settings_notification_error_on_update">Pri aktualizácii vašich predvolieb oznámení došlo k chybe. Skúste prosím znova.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Keď sa pozvaní používatelia pripoja k aplikácii ${app_name}, budete môcť konverzovať a miestnosť bude end-to-end šifrovaná</string>
<string name="direct_room_encryption_enabled_waiting_users">Čaká sa na pripojenie používateľov k aplikácii ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">Naraz môžete pozvať len jeden e-mail</string>
</resources>

View File

@ -2915,4 +2915,7 @@
<string name="settings_external_account_management">Hollësitë e llogarisë tuaj administrohen më vete, te %1$s.</string>
<string name="settings_external_account_management_title">Llogari</string>
<string name="settings_notification_error_on_update">Ndodhi një gabim, kur u përditësuan parapëlqimet tuaja për njoftime. Ju lutemi, riprovoni.</string>
<string name="direct_room_encryption_enabled_waiting_users">Po pritet që përdoruesit të bëhen pjesë e ${app_name}</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Pasi përdoruesit e ftuar të jenë bërë pjesë e ${app_name}, do të jeni në gjendje të bisedoni dhe dhoma do të jetë e fshehtëzuar skaj-më-skaj</string>
<string name="direct_room_user_list_only_invite_one_email">Mund të ftoni vetëm një email në herë</string>
</resources>

View File

@ -3049,4 +3049,7 @@
<string name="settings_external_account_management">Керування подробицями вашого облікового запису відбувається окремо на %1$s.</string>
<string name="settings_external_account_management_title">Обліковий запис</string>
<string name="settings_notification_error_on_update">Сталася помилка під час оновлення налаштувань сповіщень. Повторіть спробу.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Після того, як запрошені користувачі приєднаються до ${app_name}, ви зможете спілкуватися з ними, а кімната буде захищена наскрізним шифруванням</string>
<string name="direct_room_encryption_enabled_waiting_users">Очікування на приєднання користувачів до ${app_name}</string>
<string name="direct_room_user_list_only_invite_one_email">Ви можете запросити лише одну адресу електронної пошти за раз</string>
</resources>

File diff suppressed because it is too large Load Diff

View File

@ -1002,6 +1002,7 @@
<string name="settings_version">Version</string>
<string name="settings_olm_version">olm version</string>
<string name="settings_app_term_conditions">Terms &amp; conditions</string>
<string name="settings_acceptable_use_policy">Acceptable Use Policy</string>
<string name="settings_third_party_notices">Third party notices</string>
<string name="settings_copyright">Copyright</string>
<string name="settings_privacy_policy">Privacy policy</string>
@ -3539,4 +3540,11 @@
<string name="settings_access_token">Access Token</string>
<string name="settings_access_token_summary">Your access token gives full access to your account. Do not share it with anyone.</string>
<!-- Pills -->
<string name="pill_message_from_user">Message from %s</string>
<string name="pill_message_from_unknown_user">Message</string>
<string name="pill_message_in_room">Message in %s</string>
<string name="pill_message_in_unknown_room">Message in room</string>
<string name="pill_message_unknown_room_or_space">Room/Space</string>
</resources>

View File

@ -18,8 +18,8 @@
<item name="dialog_width_ratio" format="float" type="dimen">0.75</item>
<dimen name="pill_avatar_size">16dp</dimen>
<dimen name="pill_min_height">20dp</dimen>
<dimen name="pill_avatar_size">20sp</dimen>
<dimen name="pill_min_height">26sp</dimen>
<dimen name="pill_text_padding">4dp</dimen>
<dimen name="call_pip_height">128dp</dimen>

View File

@ -62,7 +62,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.5.28\""
buildConfigField "String", "SDK_VERSION", "\"1.5.30\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""

View File

@ -65,27 +65,14 @@ object MatrixPatterns {
private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/"
const val SEP_REGEX = "/"
private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK = PERMALINK_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE)
private val PATTERN_CONTAIN_APP_PERMALINK = APP_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE)
// ascii characters in the range \x20 (space) to \x7E (~)
val ORDER_STRING_REGEX = "[ -~]+".toRegex()
// list of patterns to find some matrix item.
val MATRIX_PATTERNS = listOf(
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID,
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS,
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID,
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS,
PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER,
PATTERN_CONTAIN_MATRIX_ALIAS,
PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER,
@ -146,6 +133,12 @@ object MatrixPatterns {
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
}
fun isPermalink(str: String?): Boolean {
return str != null &&
(PATTERN_CONTAIN_MATRIX_TO_PERMALINK.containsMatchIn(str) ||
PATTERN_CONTAIN_APP_PERMALINK.containsMatchIn(str))
}
/**
* Extract server name from a matrix id.
*

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.permalinks
import android.text.Spannable
import android.util.Patterns
import org.matrix.android.sdk.api.MatrixPatterns
/**
@ -44,22 +45,26 @@ object MatrixLinkify {
}
val text = spannable.toString()
var hasMatch = false
for (pattern in MatrixPatterns.MATRIX_PATTERNS) {
for (pattern in listOf(Patterns.WEB_URL.toRegex()).plus(MatrixPatterns.MATRIX_PATTERNS)) {
for (match in pattern.findAll(spannable)) {
hasMatch = true
val startPos = match.range.first
if (startPos == 0 || text[startPos - 1] != '/') {
val endPos = match.range.last + 1
var url = text.substring(match.range)
if (MatrixPatterns.isUserId(url) ||
val isPermalink = MatrixPatterns.isPermalink(url)
if (isPermalink ||
MatrixPatterns.isUserId(url) ||
MatrixPatterns.isRoomAlias(url) ||
MatrixPatterns.isRoomId(url) ||
MatrixPatterns.isGroupId(url) ||
MatrixPatterns.isEventId(url)) {
url = PermalinkService.MATRIX_TO_URL_BASE + url
if (!isPermalink) {
url = PermalinkService.MATRIX_TO_URL_BASE + url
}
val span = MatrixPermalinkSpan(url, callback)
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
val span = MatrixPermalinkSpan(url, callback)
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}

View File

@ -30,7 +30,8 @@ data class RoomMemberQueryParams(
val displayName: QueryStringValue,
val memberships: List<Membership>,
val userId: QueryStringValue,
val excludeSelf: Boolean
val excludeSelf: Boolean,
val displayNameOrUserId: QueryStringValue,
) {
class Builder {
@ -39,12 +40,14 @@ data class RoomMemberQueryParams(
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List<Membership> = Membership.all()
var excludeSelf: Boolean = false
var displayNameOrUserId: QueryStringValue = QueryStringValue.NoCondition
fun build() = RoomMemberQueryParams(
displayName = displayName,
memberships = memberships,
userId = userId,
excludeSelf = excludeSelf
excludeSelf = excludeSelf,
displayNameOrUserId = displayNameOrUserId
)
}
}

View File

@ -77,7 +77,8 @@ sealed class MatrixItem(
data class RoomItem(
override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null
override val avatarUrl: String? = null,
val roomDisplayName: String? = null
) :
MatrixItem(id, displayName, avatarUrl) {
init {
@ -103,7 +104,8 @@ sealed class MatrixItem(
data class RoomAliasItem(
override val id: String,
override val displayName: String? = null,
override val avatarUrl: String? = null
override val avatarUrl: String? = null,
val roomDisplayName: String? = null
) :
MatrixItem(id, displayName, avatarUrl) {
init {
@ -147,6 +149,8 @@ sealed class MatrixItem(
val displayName = when (this) {
// use the room display name for the notify everyone item
is EveryoneInRoomItem -> roomDisplayName
is RoomItem -> roomDisplayName ?: displayName
is RoomAliasItem -> roomDisplayName ?: displayName
else -> displayName
}
return (displayName?.takeIf { it.isNotBlank() } ?: id)

View File

@ -17,14 +17,13 @@
package org.matrix.android.sdk.internal.session.room.membership
import androidx.lifecycle.LiveData
import com.otaliastudios.opengl.core.use
import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.realm.Realm
import io.realm.RealmQuery
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.room.members.MembershipService
@ -58,7 +57,6 @@ internal class DefaultMembershipService @AssistedInject constructor(
private val cryptoService: CryptoService,
@UserId
private val userId: String,
private val matrixConfiguration: MatrixConfiguration,
private val queryStringValueProcessor: QueryStringValueProcessor
) : MembershipService {
@ -120,6 +118,13 @@ internal class DefaultMembershipService @AssistedInject constructor(
if (queryParams.excludeSelf) {
notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
}
if (queryParams.displayNameOrUserId != QueryStringValue.NoCondition) {
beginGroup()
process(RoomMemberSummaryEntityFields.USER_ID, queryParams.displayNameOrUserId)
or()
process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayNameOrUserId)
endGroup()
}
}
}
}

View File

@ -13238,6 +13238,9 @@
"oclock",
"twelve",
"twelve_o_clock",
"00:00",
"0000",
"1200",
"time",
"noon",
"midnight",
@ -13258,6 +13261,9 @@
"twelve",
"twelve-thirty",
"twelve_thirty",
"00:30",
"0030",
"1230",
"time",
"late",
"early",
@ -13275,6 +13281,9 @@
"oclock",
"one",
"one_o_clock",
"100",
"13:00",
"1300",
"time",
"late",
"early",
@ -13292,6 +13301,9 @@
"one-thirty",
"thirty",
"one_thirty",
"130",
"13:30",
"1330",
"time",
"late",
"early",
@ -13309,6 +13321,9 @@
"oclock",
"two",
"two_o_clock",
"200",
"14:00",
"1400",
"time",
"late",
"early",
@ -13326,6 +13341,9 @@
"two",
"two-thirty",
"two_thirty",
"230",
"14:30",
"1430",
"time",
"late",
"early",
@ -13343,6 +13361,9 @@
"oclock",
"three",
"three_o_clock",
"300",
"15:00",
"1500",
"time",
"late",
"early",
@ -13360,6 +13381,9 @@
"three",
"three-thirty",
"three_thirty",
"330",
"15:30",
"1530",
"time",
"late",
"early",
@ -13377,6 +13401,9 @@
"four",
"oclock",
"four_o_clock",
"400",
"16:00",
"1600",
"time",
"late",
"early",
@ -13394,6 +13421,9 @@
"four-thirty",
"thirty",
"four_thirty",
"430",
"16:30",
"1630",
"time",
"late",
"early",
@ -13411,6 +13441,9 @@
"five",
"oclock",
"five_o_clock",
"500",
"17:00",
"1700",
"time",
"late",
"early",
@ -13428,6 +13461,9 @@
"five-thirty",
"thirty",
"five_thirty",
"530",
"17:30",
"1730",
"time",
"late",
"early",
@ -13445,6 +13481,9 @@
"oclock",
"six",
"six_o_clock",
"600",
"18:00",
"1800",
"time",
"late",
"early",
@ -13464,6 +13503,9 @@
"six-thirty",
"thirty",
"six_thirty",
"630",
"18:30",
"1830",
"time",
"late",
"early",
@ -13481,6 +13523,9 @@
"oclock",
"seven",
"seven_o_clock",
"700",
"19:00",
"1900",
"time",
"late",
"early",
@ -13498,6 +13543,9 @@
"seven-thirty",
"thirty",
"seven_thirty",
"730",
"19:30",
"1930",
"time",
"late",
"early",
@ -13515,6 +13563,9 @@
"eight",
"oclock",
"eight_o_clock",
"800",
"20:00",
"2000",
"time",
"late",
"early",
@ -13532,6 +13583,9 @@
"eight-thirty",
"thirty",
"eight_thirty",
"830",
"20:30",
"2030",
"time",
"late",
"early",
@ -13549,6 +13603,9 @@
"nine",
"oclock",
"nine_o_clock",
"900",
"21:00",
"2100",
"time",
"late",
"early",
@ -13566,6 +13623,9 @@
"nine-thirty",
"thirty",
"nine_thirty",
"930",
"21:30",
"2130",
"time",
"late",
"early",
@ -13583,6 +13643,9 @@
"oclock",
"ten",
"ten_o_clock",
"1000",
"22:00",
"2200",
"time",
"late",
"early",
@ -13600,6 +13663,9 @@
"ten-thirty",
"thirty",
"ten_thirty",
"1030",
"22:30",
"2230",
"time",
"late",
"early",
@ -13617,6 +13683,9 @@
"eleven",
"oclock",
"eleven_o_clock",
"1100",
"23:00",
"2300",
"time",
"late",
"early",
@ -13634,6 +13703,9 @@
"eleven-thirty",
"thirty",
"eleven_thirty",
"1130",
"23:30",
"2330",
"time",
"late",
"early",
@ -20066,7 +20138,11 @@
"1234",
"input",
"numbers",
"blue-square"
"blue-square",
"1",
"2",
"3",
"4"
]
},
"input-symbols": {

View File

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Hlavně opravy chyb.
Úplný seznam změn: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Die wichtigsten Änderungen in dieser Version: Hauptsächlich Fehlerbehebungen.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Main changes in this version: permalinks to rooms, spaces, users and messages are now displayed as pills in the timeline. We also fixed some issues with custom stickers and the read marker getting stuck in the past.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: põhiliselt veaparandused.
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
تغییرات عمده در این نگارش: عمدتاً رفع اشکال.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Principaux changements pour cette version : Principalement des corrections de bugs.
Intégralité des changements : https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Fő változás ebben a verzióban: hibajavítások.
Teljes változásnapló: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Kebanyakan perbaikan kutu.
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Modifiche principali in questa versione: correzioni di errori.
Cronologia completa: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Hlavne opravy chýb.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Ndryshimet kryesore në këtë version: Kryesisht ndreqje të metash.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Ndryshimet kryesore në këtë version: Kryesisht ndreqje të metash.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
Основні зміни у цій версії: Переважно виправлення помилок.
Повний перелік змін: https://github.com/vector-im/element-android/releases

View File

@ -1,2 +1,2 @@
此版本的主要變動:語音訊息的預設啟用。
此版本的主要變動:預設啟用語音訊息功能
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.2.0

View File

@ -1,2 +1,2 @@
此版本的主要變動:對 VoIP 與空間功能的諸多改善(仍在測試中)。
此版本的主要變動:對 VoIP 與聊天空間功能的諸多改善(仍在測試中)。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.2.1

View File

@ -1,2 +1,2 @@
此版本的主要變動:使用空間來整理您的聊天室!
此版本的主要變動:使用聊天空間來整理您的聊天室!
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.0

View File

@ -1,2 +1,2 @@
此版本的主要變動使用空間來整理您的聊天室v1.3.1 修復了在 v1.3.0 中遇到的當機問題。
此版本的主要變動:使用聊天空間來整理您的聊天室v1.3.1 修復了在 v1.3.0 中遇到的當機問題。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.1

View File

@ -1,2 +1,2 @@
此版本的主要變動:為直接訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。恢復對 Android Auto 的支援。
此版本的主要變動:為私人訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.5

View File

@ -1,2 +1,2 @@
此版本的主要變動:為直接訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
此版本的主要變動:為私人訊息聊天室新增 Presence 支援(請注意:此功能在 matrix.org 上停用)。加回 Android Auto 支援。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.3.6

View File

@ -1,2 +1,2 @@
此版本的主要變動:富文本編輯器全螢幕模式的全新建置與錯誤修復。
此版本的主要變動:全新實作的富文本編輯器全螢幕模式與錯誤修復。
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@ -1,2 +1,2 @@
此版本的主要變動:富文本編輯器全螢幕模式全新建置與錯誤修復。
此版本的主要變動:全新實作的富文本編輯器全螢幕模式與錯誤修復。
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@ -1,2 +1,2 @@
此版本中的主要變動:討論串現在預設啟用
此版本的主要變動:預設開啟對話串功能
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@ -1,2 +1,2 @@
此版本中的主要變動:討論串現在預設啟用
此版本的主要變動:預設開啟對話串功能
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@ -0,0 +1,2 @@
此版本中的主要變動:主要是臭蟲修復。
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@ -37,7 +37,7 @@ ext.versionMinor = 5
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
ext.versionPatch = 28
ext.versionPatch = 30
ext.scVersion = 65

View File

@ -150,7 +150,7 @@ fun initialSyncIdlingResource(session: Session): IdlingResource {
this.callback = callback
}
override fun onChanged(t: SyncState?) {
override fun onChanged(value: SyncState) {
val isIdle = session.syncService().hasAlreadySynced()
if (isIdle) {
callback?.onTransitionToIdle()
@ -241,10 +241,10 @@ fun allSecretsKnownIdling(session: Session): IdlingResource {
this.callback = callback
}
override fun onChanged(t: Optional<PrivateKeysInfo>?) {
println("*** [$name] allSecretsKnownIdling ${t?.getOrNull()}")
privateKeysInfo = t?.getOrNull()
if (t?.getOrNull()?.allKnown() == true) {
override fun onChanged(value: Optional<PrivateKeysInfo>) {
println("*** [$name] allSecretsKnownIdling ${value.getOrNull()}")
privateKeysInfo = value.getOrNull()
if (value.getOrNull()?.allKnown() == true) {
session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().removeObserver(this)
callback?.onTransitionToIdle()
}

View File

@ -122,7 +122,7 @@ abstract class VerificationTestBase {
session.syncService().getSyncStateLive()
}
val syncObserver = object : Observer<SyncState> {
override fun onChanged(t: SyncState?) {
override fun onChanged(value: SyncState) {
if (session.syncService().hasAlreadySynced()) {
lock.countDown()
syncLiveData.removeObserver(this)

View File

@ -133,7 +133,7 @@ dependencies {
implementation libs.androidx.biometric
api "org.threeten:threetenbp:1.4.0:no-tzdb"
api "com.gabrielittner.threetenbp:lazythreetenbp:0.13.0"
api "com.gabrielittner.threetenbp:lazythreetenbp:0.14.0"
implementation libs.squareup.moshi
implementation libs.squareup.moshiKt
@ -244,7 +244,7 @@ dependencies {
// UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
implementation "androidx.emoji2:emoji2:1.2.0"
implementation "androidx.emoji2:emoji2:1.3.0"
// WebRTC
// org.webrtc:google-webrtc is for development purposes only

View File

@ -21,13 +21,16 @@ import android.text.Editable
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.core.platform.SimpleTextWatcher
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import reactivecircus.flowbinding.android.widget.textChanges
fun TextInputLayout.editText() = this.editText!!
@ -85,7 +88,7 @@ fun TextInputLayout.setOnImeDoneListener(action: () -> Unit) {
fun TextInputLayout.setOnFocusLostListener(lifecycleOwner: LifecycleOwner, action: () -> Unit) {
editText().setOnFocusChangeListener { _, hasFocus ->
when (hasFocus) {
false -> lifecycleOwner.lifecycleScope.launchWhenResumed { action() }
false -> lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { action() } }
else -> {
// do nothing
}

View File

@ -291,17 +291,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
}
}
// SC-TODO: this is the pre-v1.5.18 implementation of observeViewEvents, to fix custom widgets. Revert me once upstream implements a fix.
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.oldObserveViewEvents(observer: (T) -> Unit) {
val tag = this@VectorBaseFragment::class.simpleName.toString()
viewEvents
.stream(tag)
.onEach {
observer(it)
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
/* ==========================================================================================
* Views
* ========================================================================================== */

View File

@ -15,6 +15,7 @@ import im.vector.app.features.html.HtmlCodeSpan
import io.noties.markwon.core.spans.EmphasisSpan
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
/**
* TextView that reserves space at the bottom for overlaying it with a footer, e.g. in a FrameLayout or RelativeLayout
@ -73,7 +74,7 @@ interface AbstractFooteredTextView {
val looksLikeRtl = layout.isRtlCharAt(lastVisibleCharacter)
*/
// Get required width for all lines
// Get required width for all lines (not using measuredWidth so wrap_content doesn't go match_parent if long lines enforced some line breaks)
var maxLineWidth = 0f
for (i in 0 until layout.lineCount) {
// For some reasons, the getLineWidth is not working too well with RTL lines when rendering replies.
@ -87,6 +88,9 @@ interface AbstractFooteredTextView {
max(layout.getLineWidth(i), maxLineWidth)
}
}
// Huge PillImageSpans might want to reserve more horizontal space with above approach than what is available,
// so ensure we don't give them more then super would, such that their auto-ellipsize feature works properly.
maxLineWidth = min(maxLineWidth, measuredWidth.toFloat())
// Fix wrap_content in multi-line texts by using maxLineWidth instead of measuredWidth here
// (compare WrapWidthTextView.kt)

View File

@ -48,9 +48,7 @@ open class LiveEvent<out T>(private val content: T) {
* [onEventUnhandledContent] is *only* called if the [LiveEvent]'s contents has not been handled.
*/
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<LiveEvent<T>> {
override fun onChanged(event: LiveEvent<T>?) {
event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value)
}
override fun onChanged(value: LiveEvent<T>) {
value.getContentIfNotHandled()?.let { onEventUnhandledContent(it) }
}
}

View File

@ -41,19 +41,23 @@ private val IGNORED_OPTIONS: Options? = null
@Singleton
class DefaultVectorAnalytics @Inject constructor(
postHogFactory: PostHogFactory,
private val postHogFactory: PostHogFactory,
private val sentryAnalytics: SentryAnalytics,
analyticsConfig: AnalyticsConfig,
private val analyticsConfig: AnalyticsConfig,
private val analyticsStore: AnalyticsStore,
private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory,
@NamedGlobalScope private val globalScope: CoroutineScope
) : VectorAnalytics {
private val posthog: PostHog? = when {
analyticsConfig.isEnabled -> postHogFactory.createPosthog()
else -> {
Timber.tag(analyticsTag.value).w("Analytics is disabled")
null
private var posthog: PostHog? = null
private fun createPosthog(): PostHog? {
return when {
analyticsConfig.isEnabled -> postHogFactory.createPosthog()
else -> {
Timber.tag(analyticsTag.value).w("Analytics is disabled")
null
}
}
}
@ -150,6 +154,7 @@ class DefaultVectorAnalytics @Inject constructor(
userConsent?.let { _userConsent ->
when (_userConsent) {
true -> {
posthog = createPosthog()
posthog?.optOut(false)
identifyPostHog()
pendingUserProperties?.let { doUpdateUserProperties(it) }
@ -159,6 +164,8 @@ class DefaultVectorAnalytics @Inject constructor(
// When opting out, ensure that the queue is flushed first, or it will be flushed later (after user has revoked consent)
posthog?.flush()
posthog?.optOut(true)
posthog?.shutdown()
posthog = null
}
}
}

View File

@ -76,6 +76,7 @@ class AutocompleteMemberController @Inject constructor(private val context: Cont
roomMember.roomMemberSummary.let { user ->
id(user.userId)
matrixItem(user.toMatrixItem())
subName(user.userId)
avatarRenderer(host.avatarRenderer)
clickListener { host.listener?.onItemClick(roomMember) }
}

View File

@ -112,8 +112,8 @@ class AutocompleteMemberPresenter @AssistedInject constructor(
* ========================================================================================== */
private fun createQueryParams(query: CharSequence?) = roomMemberQueryParams {
displayName = if (query.isNullOrBlank()) {
QueryStringValue.IsNotEmpty
displayNameOrUserId = if (query.isNullOrBlank()) {
QueryStringValue.NoCondition
} else {
QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
}

View File

@ -29,6 +29,7 @@ import im.vector.app.R
import im.vector.app.databinding.ViewRemoveJitsiWidgetBinding
import im.vector.app.features.home.room.detail.RoomDetailViewState
import org.matrix.android.sdk.api.session.room.model.Membership
import kotlin.math.absoluteValue
@SuppressLint("ClickableViewAccessibility") class RemoveJitsiWidgetView @JvmOverloads constructor(
context: Context,
@ -55,7 +56,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
return@setOnTouchListener when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (currentState == State.Idle) {
val initialX = views.removeJitsiSlidingContainer.x - event.rawX
val initialX = event.rawX
updateState(State.Sliding(initialX, 0f, false))
}
true
@ -73,8 +74,9 @@ import org.matrix.android.sdk.api.session.room.model.Membership
}
MotionEvent.ACTION_MOVE -> {
if (currentState is State.Sliding) {
val translationX = (currentState.initialX + event.rawX).coerceAtLeast(0f)
val hasReachedActivationThreshold = translationX >= views.root.width / 4
val deltaX = event.rawX - currentState.initialX
val translationX = if (!isRtl) deltaX.coerceAtLeast(0f) else deltaX.coerceAtMost(0f)
val hasReachedActivationThreshold = translationX.absoluteValue >= views.root.width / 4
updateState(State.Sliding(currentState.initialX, translationX, hasReachedActivationThreshold))
}
true

View File

@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.util.MatrixItem
fun MatrixItem.getBestName(): String {
// Note: this code is copied from [DisplayNameResolver] in the SDK
return if (this is MatrixItem.RoomAliasItem) {
return if (this is MatrixItem.RoomAliasItem && displayName.isNullOrBlank()) {
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
id
} else {

View File

@ -30,7 +30,9 @@ import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -417,8 +419,8 @@ class HomeActivity :
private fun handleStartRecoverySetup() {
// To avoid IllegalStateException in case the transaction was executed after onSaveInstanceState
lifecycleScope.launchWhenResumed {
navigator.open4SSetup(this@HomeActivity, SetupMode.NORMAL)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { navigator.open4SSetup(this@HomeActivity, SetupMode.NORMAL) }
}
}

View File

@ -39,7 +39,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class UndoReaction(val targetEventId: String, val reaction: String, val reason: String? = "") : RoomDetailAction()
data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailAction()
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailAction()
data class NavigateToEvent(val eventId: String, val highlight: Boolean) : RoomDetailAction()
data class NavigateToEvent(val eventId: String, val highlight: Boolean, val isFirstUnreadEvent: Boolean = false) : RoomDetailAction()
data class MarkAllAsRead(val forceIfOpenedAnonymously: Boolean = false) : RoomDetailAction()
data class DownloadOrOpen(val eventId: String, val senderId: String?, val messageFileContent: MessageWithAttachmentContent) : RoomDetailAction()
object JoinAndOpenReplacementRoom : RoomDetailAction()

View File

@ -42,7 +42,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailViewEvents()
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
data class NavigateToEvent(val eventId: String, val isFirstUnreadEvent: Boolean) : RoomDetailViewEvents()
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
object LeaveJitsiConference : RoomDetailViewEvents()

View File

@ -51,7 +51,9 @@ import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
@ -179,7 +181,6 @@ import im.vector.app.features.home.room.threads.ThreadsManager
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillsPostProcessor
import im.vector.app.features.invite.VectorInviteView
import im.vector.app.features.location.LocationSharingMode
import im.vector.app.features.location.toLocationData
@ -271,7 +272,6 @@ class TimelineFragment :
@Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider
@Inject lateinit var imageContentRenderer: ImageContentRenderer
@Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore
@Inject lateinit var pillsPostProcessorFactory: PillsPostProcessor.Factory
@Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker
@Inject lateinit var shareIntentHandler: ShareIntentHandler
@ -806,7 +806,9 @@ class TimelineFragment :
private fun navigateToEvent(action: RoomDetailViewEvents.NavigateToEvent) {
setInitialForceScrollEnabled(true)
scrollOnNewMessageCallback.initialForceScrollEventId = action.eventId
val scrollPosition = timelineEventController.searchPositionOfEvent(action.eventId)
val scrollPosition = timelineEventController.getPositionOfReadMarker().takeIf { action.isFirstUnreadEvent }
?: timelineEventController.searchPositionOfEvent(action.eventId)
if (scrollPosition == null) {
scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId)
} else {
@ -1385,29 +1387,31 @@ class TimelineFragment :
private fun updateJumpToReadMarkerViewVisibility() {
if (isThreadTimeLine()) return
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
val state = timelineViewModel.awaitState()
val showJumpToUnreadBanner = when (state.unreadState) {
UnreadState.Unknown,
UnreadState.HasNoUnread -> false
is UnreadState.ReadMarkerNotLoaded -> true
is UnreadState.HasUnread -> {
if (state.canShowJumpToReadMarker) {
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
val positionOfReadMarker = withContext(Dispatchers.Default) {
timelineEventController.getPositionOfReadMarker()
}
if (positionOfReadMarker == null) {
false
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
val state = timelineViewModel.awaitState()
val showJumpToUnreadBanner = when (state.unreadState) {
UnreadState.Unknown,
UnreadState.HasNoUnread -> false
is UnreadState.ReadMarkerNotLoaded -> true
is UnreadState.HasUnread -> {
if (state.canShowJumpToReadMarker) {
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
val positionOfReadMarker = withContext(Dispatchers.Default) {
timelineEventController.getPositionOfReadMarker()
}
if (positionOfReadMarker == null) {
false
} else {
positionOfReadMarker > lastVisibleItem
}
} else {
positionOfReadMarker > lastVisibleItem
false
}
} else {
false
}
}
views.jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
}
views.jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
}
}
@ -1932,14 +1936,16 @@ class TimelineFragment :
}
override fun onRoomCreateLinkClicked(url: String) {
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
permalinkHandler
.launch(requireActivity(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean {
requireActivity().finish()
return false
}
})
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
permalinkHandler
.launch(requireActivity(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean {
requireActivity().finish()
return false
}
})
}
}
}
@ -2337,10 +2343,10 @@ class TimelineFragment :
private fun onJumpToReadMarkerClicked() = withState(timelineViewModel) {
if (it.unreadState is UnreadState.HasUnread) {
timelineViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false))
timelineViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, highlight = false, isFirstUnreadEvent = true))
}
if (it.unreadState is UnreadState.ReadMarkerNotLoaded) {
timelineViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.readMarkerId, false))
timelineViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.readMarkerId, highlight = false))
}
}

View File

@ -1150,7 +1150,7 @@ class TimelineViewModel @AssistedInject constructor(
if (action.highlight) {
setState { copy(highlightedEventId = targetEventId) }
}
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId))
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId, action.isFirstUnreadEvent))
}
private fun handleResendEvent(action: RoomDetailAction.ResendMessage) {

View File

@ -20,21 +20,30 @@ import android.content.Context
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.util.Patterns
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.html.PillImageSpan
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
class EventTextRenderer @AssistedInject constructor(
@Assisted private val roomId: String?,
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val activeSessionHolder: ActiveSessionHolder,
private val sessionHolder: ActiveSessionHolder,
) {
@AssistedFactory
@ -46,7 +55,8 @@ class EventTextRenderer @AssistedInject constructor(
* @param text the text to be rendered
*/
fun render(text: CharSequence): CharSequence {
return renderNotifyEveryone(text)
val formattedText = renderPermalinks(text)
return renderNotifyEveryone(formattedText)
}
private fun renderNotifyEveryone(text: CharSequence): CharSequence {
@ -59,8 +69,18 @@ class EventTextRenderer @AssistedInject constructor(
}
}
private fun renderPermalinks(text: CharSequence): CharSequence {
return if (roomId != null) {
SpannableStringBuilder(text).apply {
addPermalinksSpans(this)
}
} else {
text
}
}
private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
val room: RoomSummary? = activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
val matrixItem = MatrixItem.EveryoneInRoomItem(
id = roomId,
avatarUrl = room?.avatarUrl,
@ -76,6 +96,23 @@ class EventTextRenderer @AssistedInject constructor(
}
}
private fun addPermalinksSpans(text: Spannable) {
for (match in Patterns.WEB_URL.toRegex().findAll(text)) {
val url = text.substring(match.range)
val matrixItem = if (MatrixPatterns.isPermalink(url)) {
when (val permalinkData = PermalinkParser.parse(url)) {
is PermalinkData.UserLink -> permalinkData.toMatrixItem()
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
else -> null
}
} else null
if (matrixItem != null) {
addPillSpan(text, createPillImageSpan(matrixItem), match.range.first, match.range.last + 1)
}
}
}
private fun createPillImageSpan(matrixItem: MatrixItem) =
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
@ -87,4 +124,46 @@ class EventTextRenderer @AssistedInject constructor(
) {
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? =
roomId?.let { sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, it)?.toMatrixItem() }
?: sessionHolder.getSafeActiveSession()?.getUserOrDefault(userId)?.toMatrixItem()
private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem =
if (eventId.isNullOrEmpty()) {
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomIdOrAlias)
when {
isRoomAlias -> MatrixItem.RoomAliasItem(roomIdOrAlias, room?.displayName, room?.avatarUrl)
room == null -> MatrixItem.RoomItem(roomIdOrAlias, context.getString(R.string.pill_message_unknown_room_or_space))
room.roomType == RoomType.SPACE -> MatrixItem.SpaceItem(roomIdOrAlias, room.displayName, room.avatarUrl)
else -> MatrixItem.RoomItem(roomIdOrAlias, room.displayName, room.avatarUrl)
}
} else {
if (roomIdOrAlias == roomId) {
val session = sessionHolder.getSafeActiveSession()
val event = session?.eventService()?.getEventFromCache(roomId, eventId!!)
val user = event?.senderId?.let { session.roomService().getRoomMember(it, roomId) }
val text = user?.let {
context.getString(R.string.pill_message_from_user, user.displayName)
} ?: context.getString(R.string.pill_message_from_unknown_user)
MatrixItem.RoomItem(roomIdOrAlias, text, user?.avatarUrl, user?.displayName)
} else {
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomIdOrAlias)
when {
isRoomAlias -> MatrixItem.RoomAliasItem(
roomIdOrAlias,
context.getString(R.string.pill_message_in_room, room?.displayName ?: roomIdOrAlias),
room?.avatarUrl,
room?.displayName
)
room != null -> MatrixItem.RoomItem(
roomIdOrAlias,
context.getString(R.string.pill_message_in_room, room.displayName),
room.avatarUrl,
room.displayName
)
else -> MatrixItem.RoomItem(roomIdOrAlias, context.getString(R.string.pill_message_in_unknown_room))
}
}
}
}

View File

@ -44,12 +44,11 @@ fun CharSequence.findPillsAndProcess(scope: CoroutineScope, processBlock: (PillI
}
fun CharSequence.linkify(callback: TimelineEventController.UrlClickCallback?): CharSequence {
val text = this.toString()
// SpannableStringBuilder is used to avoid Epoxy throwing ImmutableModelException
val spannable = SpannableStringBuilder(this)
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
override fun onUrlClicked(url: String) {
callback?.onUrlClicked(url, text)
callback?.onUrlClicked(url, this.toString())
}
})
VectorLinkify.addLinks(spannable, true)

View File

@ -22,19 +22,24 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.text.style.ReplacementSpan
import android.widget.TextView
import androidx.annotation.UiThread
import androidx.core.content.ContextCompat
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.chip.ChipDrawable
import im.vector.app.R
import im.vector.app.core.extensions.isMatrixId
import im.vector.app.core.glide.GlideRequests
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
import org.matrix.android.sdk.api.util.MatrixItem
import java.lang.ref.WeakReference
@ -100,6 +105,15 @@ class PillImageSpan(
val transY: Int = y + (fm.descent + fm.ascent - pillDrawable.bounds.bottom) / 2
canvas.save()
canvas.translate(x, transY.toFloat())
val rect = Rect()
canvas.getClipBounds(rect)
val maxWidth = rect.right
if (pillDrawable.intrinsicWidth > maxWidth) {
pillDrawable.setBounds(0, 0, maxWidth, pillDrawable.intrinsicHeight)
pillDrawable.ellipsize = TextUtils.TruncateAt.END
}
pillDrawable.draw(canvas)
canvas.restore()
}
@ -113,10 +127,28 @@ class PillImageSpan(
private fun createChipDrawable(): ChipDrawable {
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
val icon = try {
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
} catch (exception: Exception) {
avatarRenderer.getPlaceholderDrawable(matrixItem)
val icon = when {
matrixItem is MatrixItem.RoomAliasItem && matrixItem.avatarUrl.isNullOrEmpty() &&
matrixItem.displayName == context.getString(R.string.pill_message_in_room, matrixItem.id) -> {
ContextCompat.getDrawable(context, R.drawable.ic_permalink_round)
}
matrixItem is MatrixItem.RoomItem && matrixItem.avatarUrl.isNullOrEmpty() && (
matrixItem.displayName == context.getString(R.string.pill_message_in_unknown_room) ||
matrixItem.displayName == context.getString(R.string.pill_message_unknown_room_or_space) ||
matrixItem.displayName == context.getString(R.string.pill_message_from_unknown_user)
) -> {
ContextCompat.getDrawable(context, R.drawable.ic_permalink_round)
}
matrixItem is MatrixItem.UserItem && matrixItem.avatarUrl.isNullOrEmpty() && matrixItem.displayName?.isMatrixId().orTrue() -> {
ContextCompat.getDrawable(context, R.drawable.ic_user_round)
}
else -> {
try {
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
} catch (exception: Exception) {
avatarRenderer.getPlaceholderDrawable(matrixItem)
}
}
}
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {

View File

@ -28,7 +28,7 @@ import im.vector.app.features.home.AvatarRenderer
import io.noties.markwon.core.spans.LinkSpan
import io.noties.markwon.image.AsyncDrawableSpan
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.getUser
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -57,15 +57,15 @@ class PillsPostProcessor @AssistedInject constructor(
* ========================================================================================== */
override fun afterRender(renderedText: Spannable) {
addPillSpans(renderedText, roomId)
addPillSpans(renderedText)
}
/* ==========================================================================================
* Helper methods
* ========================================================================================== */
private fun addPillSpans(renderedText: Spannable, roomId: String?) {
addLinkSpans(renderedText, roomId)
private fun addPillSpans(renderedText: Spannable) {
addLinkSpans(renderedText)
}
private fun addPillSpan(
@ -77,11 +77,11 @@ class PillsPostProcessor @AssistedInject constructor(
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
private fun addLinkSpans(renderedText: Spannable, roomId: String?) {
private fun addLinkSpans(renderedText: Spannable) {
// We let markdown handle links and then we add PillImageSpan if needed.
val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java)
linkSpans.forEach { linkSpan ->
val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach
val pillSpan = linkSpan.createPillSpan() ?: return@forEach
val startSpan = renderedText.getSpanStart(linkSpan)
val endSpan = renderedText.getSpanEnd(linkSpan)
// GlideImagesPlugin causes duplicated pills if we have a nested image: https://github.com/SchildiChat/SchildiChat-android/issues/148
@ -105,21 +105,18 @@ class PillsPostProcessor @AssistedInject constructor(
private fun createPillImageSpan(matrixItem: MatrixItem) =
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? {
private fun LinkSpan.createPillSpan(): PillImageSpan? {
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
is PermalinkData.UserLink -> permalinkData.toMatrixItem(roomId)
is PermalinkData.UserLink -> permalinkData.toMatrixItem()
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
else -> null
} ?: return null
return createPillImageSpan(matrixItem)
}
private fun PermalinkData.UserLink.toMatrixItem(roomId: String?): MatrixItem? =
if (roomId == null) {
sessionHolder.getSafeActiveSession()?.getUserOrDefault(userId)?.toMatrixItem()
} else {
sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, roomId)?.toMatrixItem()
}
private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? =
roomId?.let { sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, it)?.toMatrixItem() }
?: sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem()
private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem? =
if (eventId == null) {

View File

@ -26,7 +26,9 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -47,6 +49,7 @@ import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomShe
import im.vector.app.features.location.live.tracking.LocationSharingAndroidService
import im.vector.app.features.location.option.LocationSharingOption
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.util.MatrixItem
import java.lang.ref.WeakReference
import javax.inject.Inject
@ -97,11 +100,13 @@ class LocationSharingFragment :
}.also { views.mapView.addOnDidFailLoadingMapListener(it) }
views.mapView.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated {
views.mapView.initialize(
url = urlMapProvider.getMapUrl(),
locationTargetChangeListener = this@LocationSharingFragment
)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
views.mapView.initialize(
url = urlMapProvider.getMapUrl(),
locationTargetChangeListener = this@LocationSharingFragment
)
}
}
initLocateButton()

View File

@ -22,7 +22,9 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@ -42,6 +44,7 @@ import im.vector.app.features.location.LocationSharingArgs
import im.vector.app.features.location.MapState
import im.vector.app.features.location.UrlMapProvider
import im.vector.app.features.location.showUserLocationNotAvailableErrorDialog
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
import javax.inject.Inject
@ -77,8 +80,10 @@ class LocationPreviewFragment :
}.also { views.mapView.addOnDidFailLoadingMapListener(it) }
views.mapView.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated {
views.mapView.initialize(urlMapProvider.getMapUrl())
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
views.mapView.initialize(urlMapProvider.getMapUrl())
}
}
observeViewEvents()

View File

@ -114,13 +114,8 @@ class PermalinkHandler @Inject constructor(
val rootThreadEventId = permalinkData.eventId?.let { eventId ->
val room = roomId?.let { session?.getRoom(it) }
val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) {
eventId
} else {
null
}
val event = room?.getTimelineEvent(eventId)
event?.root?.getRootThreadEventId() ?: eventId.takeIf { event?.isRootThread() == true }
}
openRoom(
navigationInterceptor,

View File

@ -16,7 +16,9 @@
package im.vector.app.features.settings
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import dagger.hilt.android.AndroidEntryPoint
@ -129,34 +131,36 @@ class VectorSettingsPinFragment :
}
private fun refreshPinCodeStatus() {
lifecycleScope.launchWhenResumed {
val hasPinCode = pinCodeStore.hasEncodedPin()
usePinCodePref.isChecked = hasPinCode
usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (hasPinCode) {
lifecycleScope.launch {
pinCodeStore.deletePinCode()
refreshPinCodeStatus()
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
val hasPinCode = pinCodeStore.hasEncodedPin()
usePinCodePref.isChecked = hasPinCode
usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (hasPinCode) {
lifecycleScope.launch {
pinCodeStore.deletePinCode()
refreshPinCodeStatus()
}
} else {
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.CREATE
)
}
} else {
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.CREATE
)
true
}
true
}
changePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (hasPinCode) {
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.MODIFY
)
changePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (hasPinCode) {
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.MODIFY
)
}
true
}
true
}
}
}

View File

@ -27,7 +27,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
@ -185,11 +187,11 @@ class VectorSettingsSecurityPrivacyFragment :
}
.launchIn(viewLifecycleOwner.lifecycleScope)
lifecycleScope.launchWhenResumed {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
rawService
.getElementWellknown(session.sessionParams)
?.isE2EByDefault() == false
viewLifecycleOwner.lifecycleScope.launch {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
rawService
.getElementWellknown(session.sessionParams)
?.isE2EByDefault() == false
}
}
@ -416,16 +418,18 @@ class VectorSettingsSecurityPrivacyFragment :
}
private fun openPinCodePreferenceScreen() {
lifecycleScope.launchWhenResumed {
val hasPinCode = pinCodeStore.hasEncodedPin()
if (hasPinCode) {
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.AUTH
)
} else {
doOpenPinCodePreferenceScreen()
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
val hasPinCode = pinCodeStore.hasEncodedPin()
if (hasPinCode) {
navigator.openPinCode(
requireContext(),
pinActivityResultLauncher,
PinMode.AUTH
)
} else {
doOpenPinCodePreferenceScreen()
}
}
}
}

View File

@ -19,7 +19,7 @@ package im.vector.app.features.settings
object VectorSettingsUrls {
const val HELP = "https://schildi.chat/android/faq"
const val COPYRIGHT = "https://element.io/copyright"
const val TAC = "https://element.io/terms-of-service"
const val ACCEPTABLE_USE_POLICY = "https://element.io/acceptable-use-policy-terms"
const val PRIVACY_POLICY = "https://schildi.chat/android/privacy"
const val DISCLAIMER_URL = "https://element.io/previously-riot"
const val THIRD_PARTY_LICENSES = "file:///android_asset/open_source_licenses.html"

View File

@ -31,7 +31,7 @@ class ElementLegals @Inject constructor(
fun getData(): List<ServerPolicy> {
return listOf(
ServerPolicy(stringProvider.getString(R.string.settings_copyright), VectorSettingsUrls.COPYRIGHT),
ServerPolicy(stringProvider.getString(R.string.settings_app_term_conditions), VectorSettingsUrls.TAC),
ServerPolicy(stringProvider.getString(R.string.settings_acceptable_use_policy), VectorSettingsUrls.ACCEPTABLE_USE_POLICY),
ServerPolicy(stringProvider.getString(R.string.settings_privacy_policy), VectorSettingsUrls.PRIVACY_POLICY)
)
}

View File

@ -24,7 +24,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.args
import com.airbnb.mvrx.withState
@ -45,6 +47,7 @@ import im.vector.app.features.settings.VectorSettingsActivity
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.user.model.User
import reactivecircus.flowbinding.android.widget.textChanges
@ -174,8 +177,10 @@ class UserListFragment :
// Scroll to the bottom when adding chips. When removing chips, do not scroll
if (newNumberOfChips >= currentNumberOfChips) {
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
views.chipGroupScrollView.fullScroll(ScrollView.FOCUS_DOWN)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
views.chipGroupScrollView.fullScroll(ScrollView.FOCUS_DOWN)
}
}
}
}

View File

@ -92,7 +92,7 @@ class WidgetFragment :
if (fragmentArgs.kind.isAdmin()) {
viewModel.getPostAPIMediator().setWebView(views.widgetWebView)
}
viewModel.oldObserveViewEvents {
viewModel.observeViewEvents {
Timber.v("Observed view events: $it")
when (it) {
is WidgetViewEvents.DisplayTerms -> displayTerms(it)
@ -218,7 +218,7 @@ class WidgetFragment :
override fun invalidate() = withState(viewModel) { state ->
Timber.v("Invalidate state: $state")
when (state.formattedURL) {
when (val formattedUrl = state.formattedURL) {
Uninitialized,
is Loading -> {
setStateError(null)
@ -227,6 +227,9 @@ class WidgetFragment :
views.widgetProgressBar.isVisible = true
}
is Success -> {
if (views.widgetWebView.url == null) {
loadFormattedUrl(formattedUrl())
}
setStateError(null)
when (state.webviewLoadedUrl) {
Uninitialized -> {
@ -253,7 +256,7 @@ class WidgetFragment :
// we need to show Error
views.widgetWebView.isInvisible = true
views.widgetProgressBar.isVisible = false
setStateError(state.formattedURL.error.message)
setStateError(formattedUrl.error.message)
}
}
}
@ -323,8 +326,12 @@ class WidgetFragment :
}
private fun loadFormattedUrl(event: WidgetViewEvents.OnURLFormatted) {
loadFormattedUrl(event.formattedURL)
}
private fun loadFormattedUrl(formattedUrl: String) {
views.widgetWebView.clearHistory()
views.widgetWebView.loadUrl(event.formattedURL)
views.widgetWebView.loadUrl(formattedUrl)
}
private fun setStateError(message: String?) {

View File

@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="?colorPrimary"/>
<path
android:pathData="M9.818,18.787L14.705,23.818L26.182,12"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View File

@ -5,12 +5,12 @@
android:viewportHeight="36">
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#8bc34a"/>
android:fillColor="?colorPrimary"/>
<path
android:pathData="M9.818,18.787L14.705,23.818L26.182,12"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeColor="?colorOnPrimary"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
android:fillColor="@color/element_name_01"/>
<path
android:pathData="m12.378,8.101 l0.356,-0.356c0.984,-0.984 2.57,-0.994 3.543,-0.021 0.973,0.972 0.963,2.559 -0.021,3.543l-1.693,1.693c-0.984,0.984 -2.57,0.994 -3.543,0.021m0.603,2.919 l-0.356,0.356c-0.984,0.984 -2.57,0.994 -3.543,0.021 -0.973,-0.973 -0.963,-2.559 0.021,-3.543l1.693,-1.693c0.984,-0.984 2.57,-0.994 3.543,-0.021"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="@color/palette_white"
android:strokeLineCap="round"/>
</vector>

View File

@ -4,15 +4,15 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17.5911,20.2922C15.9951,21.3704 14.0711,22 12,22C9.7488,22 7.6713,21.2561 6,20.0007C3.5711,18.1763 2,15.2716 2,12C2,6.4771 6.4771,2 12,2C17.5228,2 22,6.4771 22,12C22,15.4518 20.2511,18.4951 17.5911,20.2922ZM12,12.5C13.6569,12.5 15,11.0449 15,9.25C15,7.4551 13.6569,6 12,6C10.3431,6 9,7.4551 9,9.25C9,11.0449 10.3431,12.5 12,12.5ZM12,20C14.162,20 16.1236,19.1424 17.5634,17.7488C16.673,15.5506 14.5176,14 12,14C9.4824,14 7.327,15.5506 6.4366,17.7488C7.8763,19.1424 9.838,20 12,20Z"
android:fillColor="#C1C6CD"
android:pathData="M18.709,21.951C16.794,23.244 14.485,24 12,24C9.299,24 6.806,23.107 4.8,21.601C1.885,19.412 0,15.926 0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,16.142 21.901,19.794 18.709,21.951ZM12,12.6C13.988,12.6 15.6,10.854 15.6,8.7C15.6,6.546 13.988,4.8 12,4.8C10.012,4.8 8.4,6.546 8.4,8.7C8.4,10.854 10.012,12.6 12,12.6ZM12,21.6C14.594,21.6 16.948,20.571 18.676,18.899C17.608,16.261 15.021,14.4 12,14.4C8.979,14.4 6.392,16.261 5.324,18.899C7.052,20.571 9.406,21.6 12,21.6Z"
android:fillColor="?vctr_content_secondary"
android:fillType="evenOdd"/>
<group>
<clip-path
android:pathData="M17.5911,20.2922C15.9951,21.3704 14.0711,22 12,22C9.7488,22 7.6713,21.2561 6,20.0007C3.5711,18.1763 2,15.2716 2,12C2,6.4771 6.4771,2 12,2C17.5228,2 22,6.4771 22,12C22,15.4518 20.2511,18.4951 17.5911,20.2922ZM12,12.5C13.6569,12.5 15,11.0449 15,9.25C15,7.4551 13.6569,6 12,6C10.3431,6 9,7.4551 9,9.25C9,11.0449 10.3431,12.5 12,12.5ZM12,20C14.162,20 16.1236,19.1424 17.5634,17.7488C16.673,15.5506 14.5176,14 12,14C9.4824,14 7.327,15.5506 6.4366,17.7488C7.8763,19.1424 9.838,20 12,20Z"
android:pathData="M18.709,21.951C16.794,23.244 14.485,24 12,24C9.299,24 6.806,23.107 4.8,21.601C1.885,19.412 0,15.926 0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,16.142 21.901,19.794 18.709,21.951ZM12,12.6C13.988,12.6 15.6,10.854 15.6,8.7C15.6,6.546 13.988,4.8 12,4.8C10.012,4.8 8.4,6.546 8.4,8.7C8.4,10.854 10.012,12.6 12,12.6ZM12,21.6C14.594,21.6 16.948,20.571 18.676,18.899C17.608,16.261 15.021,14.4 12,14.4C8.979,14.4 6.392,16.261 5.324,18.899C7.052,20.571 9.406,21.6 12,21.6Z"
android:fillType="evenOdd"/>
<path
android:pathData="M17.5911,20.2922L16.4715,18.6349L17.5911,20.2922ZM6,20.0007L4.7989,21.5999L4.7989,21.5999L6,20.0007ZM17.5634,17.7488L18.9544,19.1859L19.9234,18.2479L19.4171,16.998L17.5634,17.7488ZM6.4366,17.7488L4.5829,16.998L4.0766,18.2479L5.0456,19.1859L6.4366,17.7488ZM12,24C14.4825,24 16.7945,23.244 18.7107,21.9494L16.4715,18.6349C15.1957,19.4968 13.6596,20 12,20V24ZM4.7989,21.5999C6.8046,23.1065 9.3008,24 12,24V20C10.1967,20 8.538,19.4058 7.2011,18.4016L4.7989,21.5999ZM0,12C0,15.9273 1.8887,19.414 4.7989,21.5999L7.2011,18.4016C5.2535,16.9387 4,14.616 4,12H0ZM12,0C5.3726,0 0,5.3726 0,12H4C4,7.5817 7.5817,4 12,4V0ZM24,12C24,5.3726 18.6274,0 12,0V4C16.4183,4 20,7.5817 20,12H24ZM18.7107,21.9494C21.8977,19.7963 24,16.144 24,12H20C20,14.7596 18.6045,17.1939 16.4715,18.6349L18.7107,21.9494ZM13,9.25C13,10.0941 12.4046,10.5 12,10.5V14.5C14.9091,14.5 17,11.9958 17,9.25H13ZM12,8C12.4046,8 13,8.4059 13,9.25H17C17,6.5043 14.9091,4 12,4V8ZM11,9.25C11,8.4059 11.5954,8 12,8V4C9.0909,4 7,6.5043 7,9.25H11ZM12,10.5C11.5954,10.5 11,10.0941 11,9.25H7C7,11.9958 9.0909,14.5 12,14.5V10.5ZM16.1724,16.3118C15.0906,17.3588 13.6223,18 12,18V22C14.7017,22 17.1567,20.926 18.9544,19.1859L16.1724,16.3118ZM12,16C13.6752,16 15.1146,17.0305 15.7097,18.4996L19.4171,16.998C18.2314,14.0707 15.3599,12 12,12V16ZM8.2903,18.4996C8.8854,17.0305 10.3248,16 12,16V12C8.6401,12 5.7686,14.0707 4.5829,16.998L8.2903,18.4996ZM12,18C10.3777,18 8.9094,17.3588 7.8276,16.3118L5.0456,19.1859C6.8433,20.926 9.2983,22 12,22V18Z"
android:fillColor="#C1C6CD"/>
android:pathData="M18.709,21.951L19.564,23.216L18.709,21.951ZM4.8,21.601L3.883,22.822H3.883L4.8,21.601ZM18.676,18.899L19.738,19.996L20.478,19.28L20.092,18.325L18.676,18.899ZM5.324,18.899L3.908,18.325L3.522,19.28L4.262,19.996L5.324,18.899ZM12,25.527C14.8,25.527 17.404,24.675 19.564,23.216L17.854,20.685C16.184,21.814 14.171,22.473 12,22.473V25.527ZM3.883,22.822C6.144,24.52 8.956,25.527 12,25.527V22.473C9.641,22.473 7.467,21.694 5.717,20.38L3.883,22.822ZM-1.527,12C-1.527,16.427 0.601,20.357 3.883,22.822L5.717,20.38C3.17,18.466 1.527,15.425 1.527,12H-1.527ZM12,-1.527C4.529,-1.527 -1.527,4.529 -1.527,12H1.527C1.527,6.216 6.216,1.527 12,1.527V-1.527ZM25.527,12C25.527,4.529 19.471,-1.527 12,-1.527V1.527C17.784,1.527 22.473,6.216 22.473,12H25.527ZM19.564,23.216C23.159,20.788 25.527,16.671 25.527,12H22.473C22.473,15.613 20.644,18.8 17.854,20.685L19.564,23.216ZM14.073,8.7C14.073,10.128 13.032,11.073 12,11.073V14.127C14.944,14.127 17.127,11.58 17.127,8.7H14.073ZM12,6.327C13.032,6.327 14.073,7.272 14.073,8.7H17.127C17.127,5.82 14.944,3.273 12,3.273V6.327ZM9.927,8.7C9.927,7.272 10.968,6.327 12,6.327V3.273C9.055,3.273 6.873,5.82 6.873,8.7H9.927ZM12,11.073C10.968,11.073 9.927,10.128 9.927,8.7H6.873C6.873,11.58 9.055,14.127 12,14.127V11.073ZM17.614,17.801C16.16,19.209 14.182,20.073 12,20.073V23.127C15.007,23.127 17.737,21.933 19.738,19.996L17.614,17.801ZM12,15.927C14.378,15.927 16.417,17.391 17.26,19.472L20.092,18.325C18.798,15.131 15.664,12.873 12,12.873V15.927ZM6.74,19.472C7.582,17.391 9.622,15.927 12,15.927V12.873C8.336,12.873 5.202,15.131 3.908,18.325L6.74,19.472ZM12,20.073C9.818,20.073 7.84,19.209 6.386,17.801L4.262,19.996C6.263,21.933 8.993,23.127 12,23.127V20.073Z"
android:fillColor="?vctr_content_secondary"/>
</group>
</vector>

File diff suppressed because one or more lines are too long

View File

@ -80,6 +80,12 @@ class DefaultVectorAnalyticsTest {
@Test
fun `when revoking consent to analytics then updates posthog opt out to true and closes Sentry`() = runTest {
// For opt-out to have effect on Posthog, it has to be used first, so it has to be opt-in first
fakeAnalyticsStore.givenUserContent(consent = true)
fakePostHog.verifyOptOutStatus(optedOut = false)
fakeSentryAnalytics.verifySentryInit()
// Then test opt-out
fakeAnalyticsStore.givenUserContent(consent = false)
fakePostHog.verifyOptOutStatus(optedOut = true)