Merge remote-tracking branch 'origin/develop' into bugfix/eric/landscape-bottom-sheet-peek

# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt
This commit is contained in:
ericdecanini 2022-09-14 09:46:43 -04:00
commit 50b042ee98
94 changed files with 1529 additions and 125 deletions

1
changelog.d/7077.wip Normal file
View File

@ -0,0 +1 @@
[Device management] Session details screen

1
changelog.d/7102.bugfix Normal file
View File

@ -0,0 +1 @@
Fixes crash when quickly double clicking FABs in the new app layout

View File

@ -22,7 +22,7 @@ def markwon = "4.6.2"
def moshi = "1.13.0"
def lifecycle = "2.5.1"
def flowBinding = "1.2.0"
def flipper = "0.163.0"
def flipper = "0.164.0"
def epoxy = "4.6.2"
def mavericks = "2.7.0"
def glide = "4.13.2"

View File

@ -320,7 +320,7 @@
<string name="settings_theme">السمة</string>
<string name="encryption_information_decryption_error">خطأ في فكّ التعمية</string>
<string name="encryption_information_device_name">اسم الجهاز</string>
<string name="encryption_information_device_id">معرّف الجهاز</string>
<string name="device_manager_session_details_session_id">معرّف الجهاز</string>
<string name="encryption_information_device_key">مفتاح الجهاز</string>
<string name="encryption_export_room_keys">صدّر مفاتيح الغرفة</string>
<string name="encryption_export_room_keys_summary">صدّر المفاتيح إلى ملف محلي</string>

View File

@ -396,7 +396,7 @@
<string name="settings_theme">Тема</string>
<string name="encryption_information_decryption_error">Грешка при разшифроване</string>
<string name="encryption_information_device_name">Публично име</string>
<string name="encryption_information_device_id">Сесийно ID</string>
<string name="device_manager_session_details_session_id">Сесийно ID</string>
<string name="encryption_information_device_key">Ключ на устройство</string>
<string name="encryption_export_e2e_room_keys">Експортирай E2E ключове за стая</string>
<string name="encryption_export_room_keys">Експортиране на ключове за стая</string>

View File

@ -789,7 +789,7 @@
<string name="encryption_export_room_keys">রুমের কুঞ্জিগুলি এক্সপোর্ট করুন</string>
<string name="encryption_export_e2e_room_keys">শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন</string>
<string name="encryption_information_device_key">সেশানের কুঞ্জি</string>
<string name="encryption_information_device_id">আইডি</string>
<string name="device_manager_session_details_session_id">আইডি</string>
<string name="encryption_information_device_name">সর্বজনীন নাম</string>
<string name="encryption_information_decryption_error">ডিক্রিপশন সমস্যা</string>
<string name="settings_theme">থিম</string>

View File

@ -693,7 +693,7 @@
<string name="encryption_information_decryption_error">ডিক্রিপশন সমস্যা</string>
<string name="encryption_information_device_name">সর্বজনীন নাম</string>
<string name="encryption_information_device_id">আইডি</string>
<string name="device_manager_session_details_session_id">আইডি</string>
<string name="encryption_information_device_key">সেশানের কুঞ্জি</string>
<string name="encryption_export_e2e_room_keys">শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন</string>

View File

@ -448,7 +448,7 @@
<string name="settings_theme">Tema</string>
<string name="encryption_information_decryption_error">Error al desxifrar</string>
<string name="encryption_information_device_name">Nom públic</string>
<string name="encryption_information_device_id">ID de sessió</string>
<string name="device_manager_session_details_session_id">ID de sessió</string>
<string name="encryption_information_device_key">Clau de sessió</string>
<string name="encryption_export_e2e_room_keys">Exporta les claus de la sala E2E</string>
<string name="encryption_export_room_keys">Exporta les claus de la sala</string>

View File

@ -635,7 +635,7 @@
<string name="settings_theme">Motiv vzhledu</string>
<string name="encryption_information_decryption_error">Chyba dešifrování</string>
<string name="encryption_information_device_name">Veřejné jméno</string>
<string name="encryption_information_device_id">ID relace</string>
<string name="device_manager_session_details_session_id">ID relace</string>
<string name="encryption_information_device_key">Klíč relace</string>
<string name="encryption_export_e2e_room_keys">Export E2E klíčů místností</string>
<string name="encryption_export_room_keys">Export klíčů místností</string>

View File

@ -418,7 +418,7 @@
<string name="room_settings_unset_main_address">Als Hauptadresse aufheben</string>
<string name="encryption_information_decryption_error">Entschlüsselungsfehler</string>
<string name="encryption_information_device_name">Öffentlicher Name</string>
<string name="encryption_information_device_id">Sitzungs-ID</string>
<string name="device_manager_session_details_session_id">Sitzungs-ID</string>
<string name="encryption_information_device_key">Sitzungsschlüssel</string>
<string name="encryption_export_e2e_room_keys">Ende-zu-Ende-Raumschlüssel exportieren</string>
<string name="encryption_export_room_keys">Raumschlüssel exportieren</string>

View File

@ -172,7 +172,7 @@
<string name="settings_theme">Θέμα</string>
<string name="encryption_information_decryption_error">Σφάλμα αποκρυπτογράφησης</string>
<string name="encryption_information_device_name">Όνομα συσκευής</string>
<string name="encryption_information_device_id">Αναγνωριστικό συσκευής</string>
<string name="device_manager_session_details_session_id">Αναγνωριστικό συσκευής</string>
<string name="encryption_export_export">Εξαγωγή</string>
<string name="encryption_import_import">Εισαγωγή</string>
<string name="select_room_directory">Επιλέξτε ένα ευρετήριο δωματίων</string>

View File

@ -1084,7 +1084,7 @@
<string name="encryption_export_room_keys">Elporti ŝlosilojn de ĉambroj</string>
<string name="encryption_export_e2e_room_keys">Elporti tutvoje ĉifrajn ŝlosilojn de ĉambroj</string>
<string name="encryption_information_device_key">Ŝlosilo de salutaĵo</string>
<string name="encryption_information_device_id">Identigilo de salutaĵo</string>
<string name="device_manager_session_details_session_id">Identigilo de salutaĵo</string>
<string name="encryption_information_device_name">Publika nomo</string>
<string name="encryption_information_decryption_error">Eraris malĉifrado</string>
<string name="settings_theme">Haŭto</string>

View File

@ -249,7 +249,7 @@
<string name="room_settings_unset_main_address">Desescojer como Dirección Principal</string>
<string name="encryption_information_decryption_error">Error en descifrar</string>
<string name="encryption_information_device_name">Nombre del dispositivo</string>
<string name="encryption_information_device_id">Identificación del dispositivo</string>
<string name="device_manager_session_details_session_id">Identificación del dispositivo</string>
<string name="encryption_information_device_key">Clave del dispositivo</string>
<string name="encryption_export_e2e_room_keys">Exportar claves de cifrado de extremo-a-extremo de salas</string>
<string name="encryption_export_room_keys">Exportar claves de salas</string>

View File

@ -415,7 +415,7 @@
<string name="room_settings_unset_main_address">Dejar de Establecer como dirección principal</string>
<string name="encryption_information_decryption_error">Error de descifrado</string>
<string name="encryption_information_device_name">Nombre público</string>
<string name="encryption_information_device_id">ID de sesión</string>
<string name="device_manager_session_details_session_id">ID de sesión</string>
<string name="encryption_information_device_key">Clave de sesión</string>
<string name="encryption_export_e2e_room_keys">Exportar claves de salas con cifrado Extremo-a-Extremo</string>
<string name="encryption_export_room_keys">Exportar claves de sala</string>

View File

@ -612,7 +612,7 @@
<string name="room_settings_labs_warning_message">Need on alles katsejärgus olevad funktsionaalsused. Ole kasutamisel ettevaatlik.</string>
<string name="encryption_information_decryption_error">Dekrüptimise viga</string>
<string name="encryption_information_device_name">Avalik nimi</string>
<string name="encryption_information_device_id">Sessiooni tunnus</string>
<string name="device_manager_session_details_session_id">Sessiooni tunnus</string>
<string name="encryption_information_device_key">Sessiooni võti</string>
<string name="encryption_export_e2e_room_keys">Ekspordi jututubade läbiva krüptimise võtmed</string>
<string name="encryption_export_room_keys">Ekspordi jututoa võtmed</string>

View File

@ -406,7 +406,7 @@ Kontuan izan ekintza honek aplikazioa berrabiaraziko duela eta denbora bat behar
<string name="encryption_information_decryption_error">Deszifratze errorea</string>
<string name="encryption_information_device_name">Izen publikoa</string>
<string name="encryption_information_device_id">IDa</string>
<string name="device_manager_session_details_session_id">IDa</string>
<string name="encryption_information_device_key">Saioaren gakoa</string>
<string name="encryption_export_e2e_room_keys">Esportatu E2E geletako gakoak</string>

View File

@ -678,7 +678,7 @@
<string name="room_settings_labs_warning_message">این‌ها ویژگی‌های آزمایشی‌ای هستند که ممکن است به روش‌های نامنتظره‌ای حراب شوندا. با احتیاط استفاده کنید.</string>
<string name="room_settings_set_main_address">تنظیم به عنوان نشانی اصلی</string>
<string name="encryption_information_device_name">نام عمومی</string>
<string name="encryption_information_device_id">شناسهٔ نشست</string>
<string name="device_manager_session_details_session_id">شناسهٔ نشست</string>
<string name="encryption_information_device_key">کلید نشست</string>
<string name="encryption_export_e2e_room_keys">برون‌ریزی کلید‌های اتاق‌های سرتاسری</string>
<string name="encryption_export_room_keys">برون‌ریزی کلید‌های اتاق‌ها</string>

View File

@ -366,7 +366,7 @@
<string name="room_settings_unset_main_address">Kumoa pääosoitteeksi asettaminen</string>
<string name="encryption_information_decryption_error">Salauksenpurkuvirhe</string>
<string name="encryption_information_device_name">Julkinen nimi</string>
<string name="encryption_information_device_id">Istunnon tunnus</string>
<string name="device_manager_session_details_session_id">Istunnon tunnus</string>
<string name="encryption_information_device_key">Istunnon avain</string>
<string name="encryption_export_e2e_room_keys">Vie salatun huoneen avaimet</string>
<string name="encryption_export_room_keys">Vie huoneen avaimet</string>

View File

@ -778,7 +778,7 @@
<string name="encryption_export_room_keys">Exporter les clés des salons</string>
<string name="encryption_export_e2e_room_keys">Exporter les clés E2E des salons</string>
<string name="encryption_information_device_key">Clé de la session</string>
<string name="encryption_information_device_id">Identifiant de session</string>
<string name="device_manager_session_details_session_id">Identifiant de session</string>
<string name="encryption_information_device_name">Nom public</string>
<string name="encryption_information_decryption_error">Erreur de déchiffrement</string>
<string name="settings_theme">Thème</string>

View File

@ -346,7 +346,7 @@
<string name="room_settings_unset_main_address">Désactiver comme adresse principale</string>
<string name="encryption_information_decryption_error">Erreur de déchiffrement</string>
<string name="encryption_information_device_name">Nom public</string>
<string name="encryption_information_device_id">Identifiant de session</string>
<string name="device_manager_session_details_session_id">Identifiant de session</string>
<string name="encryption_information_device_key">Clé de la session</string>
<string name="encryption_export_e2e_room_keys">Exporter les clés E2E des salons</string>
<string name="encryption_export_room_keys">Exporter les clés des salons</string>

View File

@ -380,7 +380,7 @@
<string name="settings_theme">Tema</string>
<string name="encryption_information_decryption_error">Fallo ao descifrar</string>
<string name="encryption_information_device_name">Nome do dispositivo</string>
<string name="encryption_information_device_id">ID de sesión</string>
<string name="device_manager_session_details_session_id">ID de sesión</string>
<string name="encryption_information_device_key">Chave do dispositivo</string>
<string name="encryption_export_e2e_room_keys">Exportar chaves E2E da sala</string>
<string name="encryption_export_room_keys">Exportar chaves da sala</string>

View File

@ -572,7 +572,7 @@
<string name="settings_theme">Tema</string>
<string name="encryption_information_decryption_error">Greška u dešifriranju</string>
<string name="encryption_information_device_name">Javni naziv</string>
<string name="encryption_information_device_id">Identitet</string>
<string name="device_manager_session_details_session_id">Identitet</string>
<string name="encryption_information_device_key">Ključ sesije</string>
<string name="encryption_export_e2e_room_keys">Izvezi sobne ključeve za E2E</string>
<string name="encryption_export_room_keys">Izvezi sobne ključeve</string>

View File

@ -351,7 +351,7 @@
<string name="room_settings_unset_main_address">Kiszedés fő címek közül</string>
<string name="encryption_information_decryption_error">Visszafejtés hiba</string>
<string name="encryption_information_device_name">Nyilvános név</string>
<string name="encryption_information_device_id">Munkamenet-azonosító</string>
<string name="device_manager_session_details_session_id">Munkamenet-azonosító</string>
<string name="encryption_information_device_key">Munkamenet kulcs</string>
<string name="encryption_export_e2e_room_keys">E2E szoba kulcsok exportálása</string>
<string name="encryption_export_room_keys">Szoba kulcsok exportálása</string>

View File

@ -301,7 +301,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
<string name="settings_theme">Tema</string>
<string name="encryption_information_decryption_error">Kesalahan dekripsi</string>
<string name="encryption_information_device_name">Nama perangkat</string>
<string name="encryption_information_device_id">ID Sesi</string>
<string name="device_manager_session_details_session_id">ID Sesi</string>
<string name="encryption_information_device_key">Kunci perangkat</string>
<string name="encryption_export_e2e_room_keys">Ekspor kunci ruangan terenkripsi</string>
<string name="encryption_export_room_keys">Ekspor ruangan kunci</string>

View File

@ -193,7 +193,7 @@
<string name="settings_theme">Þema</string>
<string name="encryption_information_decryption_error">Afkóðunarvilla</string>
<string name="encryption_information_device_name">Heiti tækis</string>
<string name="encryption_information_device_id">Auðkenni setu</string>
<string name="device_manager_session_details_session_id">Auðkenni setu</string>
<string name="encryption_information_device_key">Dulritunarlykill setu</string>
<string name="encryption_export_export">Flytja út</string>
<string name="passphrase_enter_passphrase">Settu inn lykilsetningu</string>

View File

@ -430,7 +430,7 @@
<string name="settings_theme">Tema</string>
<string name="encryption_information_decryption_error">Errore di decriptazione</string>
<string name="encryption_information_device_name">Nome pubblico</string>
<string name="encryption_information_device_id">ID sessione</string>
<string name="device_manager_session_details_session_id">ID sessione</string>
<string name="encryption_information_device_key">Chiave sessione</string>
<string name="encryption_export_e2e_room_keys">Esporta le chiavi di crittografia E2E delle stanze</string>
<string name="encryption_export_room_keys">Esporta le chiavi delle stanze</string>

View File

@ -542,7 +542,7 @@
<string name="encryption_export_room_keys">יצא מפתחות חדר</string>
<string name="encryption_export_e2e_room_keys">ייצא מפתחות חדר E2E</string>
<string name="encryption_information_device_key">מזהה מפתח</string>
<string name="encryption_information_device_id">מזהה מושב</string>
<string name="device_manager_session_details_session_id">מזהה מושב</string>
<string name="encryption_information_device_name">שם ציבורי</string>
<string name="encryption_information_decryption_error">שגיאת פענוח</string>
<string name="settings_theme">ערכת נושא</string>

View File

@ -197,7 +197,7 @@
<string name="room_settings_labs_warning_message">これらは予期しない不具合が生じるかもしれない実験的機能です。慎重に使用してください。</string>
<string name="room_settings_set_main_address">メインアドレスとして設定</string>
<string name="room_settings_unset_main_address">メインアドレスとしての設定を解除</string>
<string name="encryption_information_device_id">セッションID</string>
<string name="device_manager_session_details_session_id">セッションID</string>
<string name="font_size">文字の大きさ</string>
<string name="tiny">とても小さい</string>
<string name="small">小さい</string>

View File

@ -291,7 +291,7 @@
<string name="room_settings_category_advanced_title">Talqayt</string>
<string name="room_settings_labs_pref_title">Tinarimin</string>
<string name="settings_theme">Asentel</string>
<string name="encryption_information_device_id">Asulay n tqimit</string>
<string name="device_manager_session_details_session_id">Asulay n tqimit</string>
<string name="encryption_information_device_key">Tasarut n tɣimit</string>
<string name="encryption_export_e2e_room_keys">Sifeḍ tisura n texxamt E2E</string>
<string name="encryption_export_room_keys">Sifeḍ tisura n texxamt</string>

View File

@ -431,7 +431,7 @@
<string name="settings_theme">테마</string>
<string name="encryption_information_decryption_error">암호 복호화 오류</string>
<string name="encryption_information_device_name">공개 이름</string>
<string name="encryption_information_device_id">ID</string>
<string name="device_manager_session_details_session_id">ID</string>
<string name="encryption_information_device_key">기기 키</string>
<string name="encryption_export_e2e_room_keys">종단간 암호화 방 키 내보내기</string>
<string name="encryption_export_room_keys">방 키 내보내기</string>

View File

@ -909,7 +909,7 @@
<string name="encryption_export_room_keys">ສົ່ງອອກກະແຈຫ້ອງ</string>
<string name="encryption_export_e2e_room_keys">ສົ່ງອອກກະແຈຫ້ອງ E2E</string>
<string name="encryption_information_device_key">ລະຫັດລະບົບ</string>
<string name="encryption_information_device_id">ID ລະບົບ</string>
<string name="device_manager_session_details_session_id">ID ລະບົບ</string>
<string name="encryption_information_device_name">ຊື່ສາທາລະນະ</string>
<string name="encryption_information_decryption_error">ການຖອດລະຫັດຜິດພາດ</string>
<string name="settings_theme">ຫົວຂໍ້</string>

View File

@ -469,7 +469,7 @@
<string name="settings_theme">Tēma</string>
<string name="encryption_information_decryption_error">Atšifrēšanas kļūda</string>
<string name="encryption_information_device_name">Ierīces nosaukums</string>
<string name="encryption_information_device_id">Sesijas ID</string>
<string name="device_manager_session_details_session_id">Sesijas ID</string>
<string name="encryption_information_device_key">Sesijas atslēga</string>
<string name="encryption_export_e2e_room_keys">Eksportēt istabas šifrēšanas atslēgas</string>
<string name="encryption_export_room_keys">Eksportēt istabas atslēgas</string>

View File

@ -119,7 +119,7 @@
<string name="room_settings_banned_users_title">Bannlyste brukere</string>
<string name="room_settings_category_advanced_title">Avansert</string>
<string name="settings_theme">Tema</string>
<string name="encryption_information_device_id">Økt-ID</string>
<string name="device_manager_session_details_session_id">Økt-ID</string>
<string name="encryption_information_device_key">Øktnøkkel</string>
<string name="encryption_export_export">Eksporter</string>
<string name="encryption_import_import">Importer</string>

View File

@ -275,7 +275,7 @@
<string name="room_settings_unset_main_address">Niet instellen als hoofdadres</string>
<string name="encryption_information_decryption_error">Ontsleutelingsfout</string>
<string name="encryption_information_device_name">Publieke naam</string>
<string name="encryption_information_device_id">Sessie ID</string>
<string name="device_manager_session_details_session_id">Sessie ID</string>
<string name="encryption_information_device_key">Sessiesleutel</string>
<string name="encryption_export_e2e_room_keys">E2E-gesprekssleutels exporteren</string>
<string name="encryption_export_room_keys">Gesprekssleutels exporteren</string>

View File

@ -310,7 +310,7 @@
<string name="settings_theme">Preg</string>
<string name="encryption_information_decryption_error">Noko gjekk gale med dekrypteringa</string>
<string name="encryption_information_device_name">Offentleg namn</string>
<string name="encryption_information_device_id">Økt-ID</string>
<string name="device_manager_session_details_session_id">Økt-ID</string>
<string name="encryption_information_device_key">Sesjonsnøkkel</string>
<string name="encryption_export_e2e_room_keys">Eksporter E2E-romnøkklar</string>
<string name="encryption_export_room_keys">Eksporter romnøkklar</string>

View File

@ -231,7 +231,7 @@
<string name="room_settings_set_main_address">Ustaw jako główny adres</string>
<string name="settings_theme">Motyw</string>
<string name="encryption_information_device_name">Nazwa publiczna</string>
<string name="encryption_information_device_id">ID sesji</string>
<string name="device_manager_session_details_session_id">ID sesji</string>
<string name="encryption_export_export">Eksportuj</string>
<string name="passphrase_enter_passphrase">Wprowadź hasło</string>
<string name="passphrase_confirm_passphrase">Potwierdź hasło</string>

View File

@ -418,7 +418,7 @@
<string name="room_settings_unset_main_address">Des-definir como endereço principal</string>
<string name="encryption_information_decryption_error">Erro de decriptação</string>
<string name="encryption_information_device_name">Nome público</string>
<string name="encryption_information_device_id">ID de sessão</string>
<string name="device_manager_session_details_session_id">ID de sessão</string>
<string name="encryption_information_device_key">Chave de sessão</string>
<string name="encryption_export_e2e_room_keys">Exportar chaves de sala E2E</string>
<string name="encryption_export_room_keys">Exportar chaves de sala</string>

View File

@ -246,7 +246,7 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.<
<string name="encryption_information_decryption_error">Erro de decifragem</string>
<string name="encryption_information_device_name">Nome do dispositivo</string>
<string name="encryption_information_device_id">ID do dispositivo</string>
<string name="device_manager_session_details_session_id">ID do dispositivo</string>
<string name="encryption_information_device_key">Chave do dispositivo</string>
<string name="encryption_export_e2e_room_keys">Exportar chaves E2E da sala</string>
<string name="encryption_export_room_keys">Exportar chaves de sala</string>

View File

@ -432,7 +432,7 @@
<string name="room_settings_unset_main_address">Сбросить основной адрес</string>
<string name="encryption_information_decryption_error">Ошибка дешифровки</string>
<string name="encryption_information_device_name">Публичное имя</string>
<string name="encryption_information_device_id">ID сессии</string>
<string name="device_manager_session_details_session_id">ID сессии</string>
<string name="encryption_information_device_key">Ключ сессии</string>
<string name="encryption_export_e2e_room_keys">Экспорт E2E ключей комнаты</string>
<string name="encryption_export_room_keys">Экспорт ключей комнаты</string>

View File

@ -388,7 +388,7 @@
<string name="settings_theme">Vzhľad</string>
<string name="encryption_information_decryption_error">Chyba dešifrovania</string>
<string name="encryption_information_device_name">Verejné meno</string>
<string name="encryption_information_device_id">ID relácie</string>
<string name="device_manager_session_details_session_id">ID relácie</string>
<string name="encryption_information_device_key">Kľúč relácie</string>
<string name="encryption_export_e2e_room_keys">Exportovať šifrovacie kľúče miestnosti</string>
<string name="encryption_export_room_keys">Exportovať kľúče miestnosti</string>

View File

@ -431,7 +431,7 @@
<string name="settings_theme">Temë</string>
<string name="encryption_information_decryption_error">Gabim shfshehtëzimi</string>
<string name="encryption_information_device_name">Emër publik</string>
<string name="encryption_information_device_id">ID Sesioni</string>
<string name="device_manager_session_details_session_id">ID Sesioni</string>
<string name="encryption_information_device_key">Kyç sesioni</string>
<string name="encryption_export_e2e_room_keys">Eksporto kyçe dhome E2E</string>
<string name="encryption_export_room_keys">Eksporto kyçe dhome</string>

View File

@ -918,7 +918,7 @@
<string name="settings_secure_backup_enter_to_setup">Sätt upp på den här enheten</string>
<string name="reset_secure_backup_title">Generera en ny säkerhetskopia eller sätt en ny lösenfras för din existerande säkerhetskopia.</string>
<string name="room_settings_labs_warning_message">Detta är experimentella funktioner som kan gå sönder på oväntade sätt. Använd varsamt.</string>
<string name="encryption_information_device_id">Sessions-ID</string>
<string name="device_manager_session_details_session_id">Sessions-ID</string>
<string name="encryption_information_device_key">Sessionsnyckel</string>
<string name="encryption_export_e2e_room_keys">Exportera krypteringsnycklar</string>
<string name="encryption_export_room_keys">Exportera rumsnycklar</string>

View File

@ -260,7 +260,7 @@
<string name="room_settings_set_main_address">ప్రధాన చిరునామాగా సెట్ చేయండి</string>
<string name="encryption_information_device_name">పరికరం పేరు</string>
<string name="encryption_information_device_id">పరికరం ID</string>
<string name="device_manager_session_details_session_id">పరికరం ID</string>
<string name="encryption_information_device_key">పరికరం కీ</string>
<string name="encryption_export_e2e_room_keys">E2E గది కీలను ఎగుమతి చేయండి</string>

View File

@ -376,7 +376,7 @@
<string name="settings_theme">Tema</string>
<string name="encryption_information_decryption_error">Çözme hatası</string>
<string name="encryption_information_device_name">Görünür Ad</string>
<string name="encryption_information_device_id">Oturum kimliği</string>
<string name="device_manager_session_details_session_id">Oturum kimliği</string>
<string name="encryption_information_device_key">Oturum anahtarı</string>
<string name="encryption_export_e2e_room_keys">E2E Oda anahtarlarını dışa aktar</string>
<string name="encryption_export_room_keys">Oda anahtarlarını dışa aktar</string>

View File

@ -354,7 +354,7 @@
<string name="room_settings_unset_main_address">Зробити не основною адресою</string>
<string name="encryption_information_decryption_error">Помилка розшифрування</string>
<string name="encryption_information_device_name">Загальнодоступна назва</string>
<string name="encryption_information_device_id">ID сеансу</string>
<string name="device_manager_session_details_session_id">ID сеансу</string>
<string name="encryption_information_device_key">Ключ сеансу</string>
<string name="encryption_export_e2e_room_keys">Експортувати E2E ключі кімнати</string>
<string name="encryption_export_room_keys">Експортувати ключі кімнати</string>

View File

@ -594,7 +594,7 @@
<string name="deactivate_account_title">Hủy tài khoản</string>
<string name="dialog_user_consent_submit">Xem lại ngay</string>
<string name="encryption_information_device_key">Chìa khóa phiên</string>
<string name="encryption_information_device_id">Mã phiên</string>
<string name="device_manager_session_details_session_id">Mã phiên</string>
<string name="encryption_information_device_name">Tên công khai</string>
<string name="encryption_information_decryption_error">Lỗi giải mã</string>
<string name="room_settings_labs_warning_message">Những chức năng này mang tính thí nghiệm có thể còn nhiều lỗi. Lưu ý khi dùng.</string>

View File

@ -242,7 +242,7 @@
<string name="settings_password_updated">你的密码已更新</string>
<string name="encryption_information_decryption_error">解密错误</string>
<string name="encryption_information_device_name">公开名称</string>
<string name="encryption_information_device_id">会话 ID</string>
<string name="device_manager_session_details_session_id">会话 ID</string>
<string name="encryption_information_device_key">会话密钥</string>
<string name="encryption_import_import">导入</string>
<string name="encryption_information_verified">已验证</string>

View File

@ -469,7 +469,7 @@
<string name="settings_theme">主題</string>
<string name="encryption_information_decryption_error">解密錯誤</string>
<string name="encryption_information_device_name">公開名稱</string>
<string name="encryption_information_device_id">工作階段 ID</string>
<string name="device_manager_session_details_session_id">工作階段 ID</string>
<string name="encryption_information_device_key">工作階段金鑰</string>
<string name="encryption_export_e2e_room_keys">匯出聊天室的端到端加密金鑰</string>
<string name="encryption_export_room_keys">匯出聊天室的加密金鑰</string>

View File

@ -1212,7 +1212,6 @@
<string name="encryption_information_decryption_error">Decryption error</string>
<string name="encryption_information_device_name">Public name</string>
<string name="encryption_information_device_id">Session ID</string>
<string name="encryption_information_device_key">Session key</string>
<string name="encryption_export_e2e_room_keys">Export E2E room keys</string>
@ -3263,8 +3262,15 @@
</plurals>
<string name="device_manager_current_session_title">Current Session</string>
<string name="device_manager_session_title">Session</string>
<string name="device_manager_device_title">Device</string>
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
<string name="device_manager_session_last_activity">Last activity %1$s</string>
<string name="device_manager_session_details_title">Session details</string>
<string name="device_manager_session_details_description">Application, device, and activity information.</string>
<string name="device_manager_session_details_session_name">Session name</string>
<string name="device_manager_session_details_session_id">Session ID</string>
<string name="device_manager_session_details_session_last_activity">Last activity</string>
<string name="device_manager_session_details_device_ip_address">IP address</string>
<!-- Note to translators: %s will be replaces with selected space name -->
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SessionOverviewEntryView">
<attr name="sessionOverviewEntryTitle" format="string" />
<attr name="sessionOverviewEntryDescription" format="string" />
</declare-styleable>
</resources>

View File

@ -2,8 +2,8 @@
<resources>
<declare-styleable name="SessionsListHeaderView">
<attr name="devicesListHeaderTitle" format="string" />
<attr name="devicesListHeaderDescription" format="string" />
<attr name="sessionsListHeaderTitle" format="string" />
<attr name="sessionsListHeaderDescription" format="string" />
</declare-styleable>
</resources>

View File

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextAppearance.Vector.Subtitle.DevicesManagement">
<item name="android:textColor">?vctr_content_primary</item>
</style>
<style name="TextAppearance.Vector.Subtitle.Medium.DevicesManagement">
<item name="android:textColor">?vctr_content_primary</item>
</style>

View File

@ -340,6 +340,7 @@
<activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
<activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity"/>
<!-- Services -->

View File

@ -88,6 +88,7 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie
import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
import im.vector.app.features.settings.devices.DevicesViewModel
import im.vector.app.features.settings.devices.v2.details.SessionDetailsViewModel
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
import im.vector.app.features.settings.devtools.AccountDataViewModel
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
@ -641,4 +642,9 @@ interface MavericksViewModelModule {
@IntoMap
@MavericksViewModelKey(SessionOverviewViewModel::class)
fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(SessionDetailsViewModel::class)
fun sessionDetailsViewModelFactory(factory: SessionDetailsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.utils
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.core.content.getSystemService
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
class CopyToClipboardUseCase @Inject constructor(
@ApplicationContext private val context: Context,
) {
fun execute(text: CharSequence) {
context.getSystemService<ClipboardManager>()
?.setPrimaryClip(ClipData.newPlainText("", text))
}
}

View File

@ -19,8 +19,6 @@ package im.vector.app.core.utils
import android.annotation.TargetApi
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -100,8 +98,7 @@ fun requestDisablingBatteryOptimization(activity: Activity, activityResultLaunch
* @param toastMessage content of the toast message as a String resource
*/
fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = true, @StringRes toastMessage: Int = R.string.copied_to_clipboard) {
val clipboard = context.getSystemService<ClipboardManager>()!!
clipboard.setPrimaryClip(ClipData.newPlainText("", text))
CopyToClipboardUseCase(context).execute(text)
if (showToast) {
context.toast(toastMessage)
}

View File

@ -201,13 +201,12 @@ class NewHomeDetailFragment :
private fun setupFabs() {
showFABs()
views.newLayoutCreateChatButton.setOnClickListener {
newChatBottomSheet.show(requireActivity().supportFragmentManager, NewChatBottomSheet.TAG)
views.newLayoutCreateChatButton.debouncedClicks {
newChatBottomSheet.takeIf { !it.isAdded }?.show(requireActivity().supportFragmentManager, NewChatBottomSheet.TAG)
}
views.newLayoutOpenSpacesButton.setOnClickListener {
// Click action for open spaces modal goes here
spaceListBottomSheet.show(requireActivity().supportFragmentManager, SpaceListBottomSheet.TAG)
views.newLayoutOpenSpacesButton.debouncedClicks {
spaceListBottomSheet.takeIf { !it.isAdded }?.show(requireActivity().supportFragmentManager, SpaceListBottomSheet.TAG)
}
}

View File

@ -28,7 +28,7 @@ import im.vector.app.features.navigation.Navigator
import javax.inject.Inject
@AndroidEntryPoint
class NewChatBottomSheet @Inject constructor() : VectorBaseBottomSheetDialogFragment<FragmentNewChatBottomSheetBinding>() {
class NewChatBottomSheet : VectorBaseBottomSheetDialogFragment<FragmentNewChatBottomSheetBinding>() {
@Inject lateinit var navigator: Navigator

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
class CheckIfSectionDeviceIsVisibleUseCase @Inject constructor() {
fun execute(deviceInfo: DeviceInfo): Boolean {
return deviceInfo.lastSeenIp?.isNotEmpty().orFalse()
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
class CheckIfSectionSessionIsVisibleUseCase @Inject constructor() {
fun execute(deviceInfo: DeviceInfo): Boolean {
return deviceInfo.displayName?.isNotEmpty().orFalse() ||
deviceInfo.deviceId?.isNotEmpty().orFalse() ||
(deviceInfo.lastSeenTs ?: 0) > 0
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import im.vector.app.core.platform.VectorViewModelAction
sealed class SessionDetailsAction : VectorViewModelAction {
data class CopyToClipboard(val content: String) : SessionDetailsAction()
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.airbnb.mvrx.Mavericks
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
/**
* Display the details info about a Session.
*/
@AndroidEntryPoint
class SessionDetailsActivity : SimpleFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isFirstCreation()) {
addFragment(
container = views.container,
fragmentClass = SessionDetailsFragment::class.java,
params = intent.getParcelableExtra(Mavericks.KEY_ARG)
)
}
}
companion object {
fun newIntent(context: Context, deviceId: String): Intent {
return Intent(context, SessionDetailsActivity::class.java).apply {
putExtra(Mavericks.KEY_ARG, SessionDetailsArgs(deviceId))
}
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class SessionDetailsArgs(
val deviceId: String
) : Parcelable

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import android.view.View
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass
abstract class SessionDetailsContentItem : VectorEpoxyModel<SessionDetailsContentItem.Holder>(R.layout.item_session_details_content) {
@EpoxyAttribute
var title: String? = null
@EpoxyAttribute
var description: String? = null
@EpoxyAttribute
var hasDivider: Boolean = true
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var onLongClickListener: View.OnLongClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.sessionDetailsContentTitle.text = title
holder.sessionDetailsContentDescription.text = description
holder.view.isClickable = onLongClickListener != null
holder.view.setOnLongClickListener(onLongClickListener)
holder.sessionDetailsContentDivider.isVisible = hasDivider
}
class Holder : VectorEpoxyHolder() {
val sessionDetailsContentTitle by bind<TextView>(R.id.sessionDetailsContentTitle)
val sessionDetailsContentDescription by bind<TextView>(R.id.sessionDetailsContentDescription)
val sessionDetailsContentDivider by bind<View>(R.id.sessionDetailsContentDivider)
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import android.view.View
import androidx.annotation.StringRes
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.DimensionConverter
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
class SessionDetailsController @Inject constructor(
private val checkIfSectionSessionIsVisibleUseCase: CheckIfSectionSessionIsVisibleUseCase,
private val checkIfSectionDeviceIsVisibleUseCase: CheckIfSectionDeviceIsVisibleUseCase,
private val stringProvider: StringProvider,
private val dateFormatter: VectorDateFormatter,
private val dimensionConverter: DimensionConverter,
) : TypedEpoxyController<DeviceInfo>() {
var callback: Callback? = null
interface Callback {
fun onItemLongClicked(content: String)
}
override fun buildModels(data: DeviceInfo?) {
data?.let { info ->
val hasSectionSession = hasSectionSession(data)
if (hasSectionSession) {
buildSectionSession(info)
}
if (hasSectionDevice(data)) {
buildSectionDevice(info, addExtraTopMargin = hasSectionSession)
}
}
}
private fun buildHeaderItem(@StringRes titleResId: Int, addExtraTopMargin: Boolean = false) {
val host = this
sessionDetailsHeaderItem {
id(titleResId)
title(host.stringProvider.getString(titleResId))
addExtraTopMargin(addExtraTopMargin)
dimensionConverter(host.dimensionConverter)
}
}
private fun buildContentItem(@StringRes titleResId: Int, value: String, hasDivider: Boolean) {
val host = this
sessionDetailsContentItem {
id(titleResId)
title(host.stringProvider.getString(titleResId))
description(value)
hasDivider(hasDivider)
onLongClickListener(View.OnLongClickListener {
host.callback?.onItemLongClicked(value)
true
})
}
}
private fun hasSectionSession(data: DeviceInfo): Boolean {
return checkIfSectionSessionIsVisibleUseCase.execute(data)
}
private fun buildSectionSession(data: DeviceInfo) {
val sessionName = data.displayName
val sessionId = data.deviceId
val sessionLastSeenTs = data.lastSeenTs
buildHeaderItem(R.string.device_manager_session_title)
sessionName?.let {
val hasDivider = sessionId != null || sessionLastSeenTs != null
buildContentItem(R.string.device_manager_session_details_session_name, it, hasDivider)
}
sessionId?.let {
val hasDivider = sessionLastSeenTs != null
buildContentItem(R.string.device_manager_session_details_session_id, it, hasDivider)
}
sessionLastSeenTs?.let {
val formattedDate = dateFormatter.format(it, DateFormatKind.MESSAGE_DETAIL)
val hasDivider = false
buildContentItem(R.string.device_manager_session_details_session_last_activity, formattedDate, hasDivider)
}
}
private fun hasSectionDevice(data: DeviceInfo): Boolean {
return checkIfSectionDeviceIsVisibleUseCase.execute(data)
}
private fun buildSectionDevice(data: DeviceInfo, addExtraTopMargin: Boolean) {
val lastSeenIp = data.lastSeenIp
buildHeaderItem(R.string.device_manager_device_title, addExtraTopMargin)
lastSeenIp?.let {
val hasDivider = false
buildContentItem(R.string.device_manager_session_details_device_ip_address, it, hasDivider)
}
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.showOptimizedSnackbar
import im.vector.app.databinding.FragmentSessionDetailsBinding
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
/**
* Display the details info about a Session.
*/
@AndroidEntryPoint
class SessionDetailsFragment :
VectorBaseFragment<FragmentSessionDetailsBinding>() {
@Inject lateinit var sessionDetailsController: SessionDetailsController
private val viewModel: SessionDetailsViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionDetailsBinding {
return FragmentSessionDetailsBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initToolbar()
initSessionDetails()
observeViewEvents()
}
private fun initToolbar() {
(activity as? AppCompatActivity)
?.supportActionBar
?.setTitle(R.string.device_manager_session_details_title)
}
private fun initSessionDetails() {
sessionDetailsController.callback = object : SessionDetailsController.Callback {
override fun onItemLongClicked(content: String) {
viewModel.handle(SessionDetailsAction.CopyToClipboard(content))
}
}
views.sessionDetails.configureWith(sessionDetailsController)
}
private fun observeViewEvents() {
viewModel.observeViewEvents { viewEvent ->
when (viewEvent) {
SessionDetailsViewEvent.ContentCopiedToClipboard -> view?.showOptimizedSnackbar(getString(R.string.copied_to_clipboard))
}
}
}
override fun onDestroyView() {
cleanUpSessionDetails()
super.onDestroyView()
}
private fun cleanUpSessionDetails() {
sessionDetailsController.callback = null
views.sessionDetails.cleanup()
}
override fun invalidate() = withState(viewModel) { state ->
if (state.deviceInfo is Success) {
renderSessionDetails(state.deviceInfo.invoke())
} else {
hideSessionDetails()
}
}
private fun renderSessionDetails(deviceInfo: DeviceInfo) {
views.sessionDetails.isVisible = true
sessionDetailsController.setData(deviceInfo)
}
private fun hideSessionDetails() {
views.sessionDetails.isGone = true
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.utils.DimensionConverter
private const val EXTRA_TOP_MARGIN_DP = 48
@EpoxyModelClass
abstract class SessionDetailsHeaderItem : VectorEpoxyModel<SessionDetailsHeaderItem.Holder>(R.layout.item_session_details_header) {
@EpoxyAttribute
var title: String? = null
@EpoxyAttribute
var addExtraTopMargin: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var dimensionConverter: DimensionConverter? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.sessionDetailsHeaderTitle.text = title
val topMargin = if (addExtraTopMargin) {
dimensionConverter?.dpToPx(EXTRA_TOP_MARGIN_DP) ?: 0
} else {
0
}
holder.sessionDetailsHeaderTitle.updateLayoutParams<ViewGroup.MarginLayoutParams> {
updateMargins(top = topMargin)
}
}
class Holder : VectorEpoxyHolder() {
val sessionDetailsHeaderTitle by bind<TextView>(R.id.sessionDetailsHeaderTitle)
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import im.vector.app.core.platform.VectorViewEvents
sealed class SessionDetailsViewEvent : VectorViewEvents {
object ContentCopiedToClipboard : SessionDetailsViewEvent()
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.utils.CopyToClipboardUseCase
import im.vector.app.features.settings.devices.v2.overview.GetDeviceFullInfoUseCase
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class SessionDetailsViewModel @AssistedInject constructor(
@Assisted val initialState: SessionDetailsViewState,
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
private val copyToClipboardUseCase: CopyToClipboardUseCase,
) : VectorViewModel<SessionDetailsViewState, SessionDetailsAction, SessionDetailsViewEvent>(initialState) {
companion object : MavericksViewModelFactory<SessionDetailsViewModel, SessionDetailsViewState> by hiltMavericksViewModelFactory()
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<SessionDetailsViewModel, SessionDetailsViewState> {
override fun create(initialState: SessionDetailsViewState): SessionDetailsViewModel
}
init {
observeSessionInfo(initialState.deviceId)
}
private fun observeSessionInfo(deviceId: String) {
getDeviceFullInfoUseCase.execute(deviceId)
.onEach { setState { copy(deviceInfo = Success(it.deviceInfo)) } }
.launchIn(viewModelScope)
}
override fun handle(action: SessionDetailsAction) {
return when (action) {
is SessionDetailsAction.CopyToClipboard -> handleCopyToClipboard(action)
}
}
private fun handleCopyToClipboard(copyToClipboard: SessionDetailsAction.CopyToClipboard) {
copyToClipboardUseCase.execute(copyToClipboard.content)
_viewEvents.post(SessionDetailsViewEvent.ContentCopiedToClipboard)
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
data class SessionDetailsViewState(
val deviceId: String,
val deviceInfo: Async<DeviceInfo> = Uninitialized,
) : MavericksState {
constructor(args: SessionDetailsArgs) : this(
deviceId = args.deviceId
)
}

View File

@ -24,6 +24,7 @@ import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.setTextWithColoredPart
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
@ -91,13 +92,14 @@ class SessionInfoView @JvmOverloads constructor(
private fun appendLearnMoreToVerificationStatus() {
val status = views.sessionInfoVerificationStatusDetailTextView.text
val learnMore = context.getString(R.string.action_learn_more)
val stringBuilder = StringBuilder()
stringBuilder.append(status)
stringBuilder.append(" ")
stringBuilder.append(learnMore)
val statusText = buildString {
append(status)
append(" ")
append(learnMore)
}
views.sessionInfoVerificationStatusDetailTextView.setTextWithColoredPart(
fullText = stringBuilder.toString(),
fullText = statusText,
coloredPart = learnMore,
underline = false
) {
@ -172,15 +174,7 @@ class SessionInfoView @JvmOverloads constructor(
views.sessionInfoLastActivityTextView.isGone = true
}
deviceInfo.lastSeenIp
?.takeIf { isLastSeenDetailsVisible }
?.let { ipAddress ->
views.sessionInfoLastIPAddressTextView.isVisible = true
views.sessionInfoLastIPAddressTextView.text = ipAddress
}
?: run {
views.sessionInfoLastIPAddressTextView.isGone = true
}
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible })
}
private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {

View File

@ -53,26 +53,27 @@ class SessionsListHeaderView @JvmOverloads constructor(
}
private fun setTitle(typedArray: TypedArray) {
val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle)
val title = typedArray.getString(R.styleable.SessionsListHeaderView_sessionsListHeaderTitle)
binding.sessionsListHeaderTitle.text = title
}
private fun setDescription(typedArray: TypedArray) {
val description = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderDescription)
val description = typedArray.getString(R.styleable.SessionsListHeaderView_sessionsListHeaderDescription)
if (description.isNullOrEmpty()) {
binding.sessionsListHeaderDescription.isVisible = false
return
}
val learnMore = context.getString(R.string.action_learn_more)
val stringBuilder = StringBuilder()
stringBuilder.append(description)
stringBuilder.append(" ")
stringBuilder.append(learnMore)
val fullDescription = buildString {
append(description)
append(" ")
append(learnMore)
}
binding.sessionsListHeaderDescription.isVisible = true
binding.sessionsListHeaderDescription.setTextWithColoredPart(
fullText = stringBuilder.toString(),
fullText = fullDescription,
coloredPart = learnMore,
underline = false
) {

View File

@ -25,8 +25,8 @@ import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveU
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.flow.unwrap
import javax.inject.Inject
class GetDeviceFullInfoUseCase @Inject constructor(
@ -36,7 +36,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
) {
fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
fun execute(deviceId: String): Flow<DeviceFullInfo> {
return activeSessionHolder.getSafeActiveSession()?.let { session ->
combine(
getCurrentSessionCrossSigningInfoUseCase.execute(),
@ -58,7 +58,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
null
}
fullInfo.toOptional()
}
}.unwrap()
} ?: emptyFlow()
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.overview
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.use
import im.vector.app.R
import im.vector.app.core.extensions.setAttributeBackground
import im.vector.app.databinding.ViewSessionOverviewEntryBinding
class SessionOverviewEntryView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val binding = ViewSessionOverviewEntryBinding.inflate(
LayoutInflater.from(context),
this
)
init {
initBackground()
context.obtainStyledAttributes(
attrs,
R.styleable.SessionOverviewEntryView,
0,
0
).use {
setTitle(it)
setDescription(it)
}
}
private fun initBackground() {
binding.root.setAttributeBackground(android.R.attr.selectableItemBackground)
}
private fun setTitle(typedArray: TypedArray) {
val title = typedArray.getString(R.styleable.SessionOverviewEntryView_sessionOverviewEntryTitle)
binding.sessionsOverviewEntryTitle.text = title
}
private fun setDescription(typedArray: TypedArray) {
val description = typedArray.getString(R.styleable.SessionOverviewEntryView_sessionOverviewEntryDescription)
binding.sessionsOverviewEntryDescription.text = description
}
}

View File

@ -45,6 +45,8 @@ import javax.inject.Inject
class SessionOverviewFragment :
VectorBaseFragment<FragmentSessionOverviewBinding>() {
@Inject lateinit var viewNavigator: SessionOverviewViewNavigator
@Inject lateinit var dateFormatter: VectorDateFormatter
@Inject lateinit var drawableProvider: DrawableProvider
@ -79,6 +81,7 @@ class SessionOverviewFragment :
override fun invalidate() = withState(viewModel) { state ->
updateToolbar(state.isCurrentSession)
updateEntryDetails(state.deviceId)
if (state.deviceInfo is Success) {
renderSessionInfo(state.isCurrentSession, state.deviceInfo.invoke())
} else {
@ -93,6 +96,12 @@ class SessionOverviewFragment :
?.setTitle(titleResId)
}
private fun updateEntryDetails(deviceId: String) {
views.sessionOverviewEntryDetails.setOnClickListener {
viewNavigator.navigateToSessionDetails(requireContext(), deviceId)
}
}
private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) {
views.sessionOverviewInfo.isVisible = true
val viewState = SessionInfoViewState(

View File

@ -26,7 +26,6 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.Session
@ -54,7 +53,6 @@ class SessionOverviewViewModel @AssistedInject constructor(
private fun observeSessionInfo(deviceId: String) {
getDeviceFullInfoUseCase.execute(deviceId)
.mapNotNull { it.getOrNull() }
.onEach { setState { copy(deviceInfo = Success(it)) } }
.launchIn(viewModelScope)
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.overview
import android.content.Context
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
import javax.inject.Inject
class SessionOverviewViewNavigator @Inject constructor() {
fun navigateToSessionDetails(context: Context, deviceId: String) {
context.startActivity(SessionDetailsActivity.newIntent(context, deviceId))
}
}

View File

@ -39,7 +39,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/encryption_information_device_id"
android:text="@string/device_manager_session_details_session_id"
android:textStyle="bold" />
<TextView

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sessionDetails"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:listitem="@layout/item_session_details_content" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<im.vector.app.features.settings.devices.v2.list.SessionInfoView
android:id="@+id/sessionOverviewInfo"
android:layout_width="0dp"
@ -17,4 +21,16 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<im.vector.app.features.settings.devices.v2.overview.SessionOverviewEntryView
android:id="@+id/sessionOverviewEntryDetails"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sessionOverviewInfo"
app:sessionOverviewEntryDescription="@string/device_manager_session_details_description"
app:sessionOverviewEntryTitle="@string/device_manager_session_details_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -12,8 +12,8 @@
android:id="@+id/deviceListHeaderSectionSecurityRecommendations"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription="@string/device_manager_header_section_security_recommendations_description"
app:devicesListHeaderTitle="@string/device_manager_header_section_security_recommendations_title"
app:sessionsListHeaderDescription="@string/device_manager_header_section_security_recommendations_description"
app:sessionsListHeaderTitle="@string/device_manager_header_section_security_recommendations_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -60,8 +60,8 @@
android:id="@+id/deviceListHeaderCurrentSession"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription=""
app:devicesListHeaderTitle="@string/device_manager_current_session_title"
app:sessionsListHeaderDescription=""
app:sessionsListHeaderTitle="@string/device_manager_current_session_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" />
@ -90,8 +90,8 @@
android:id="@+id/deviceListHeaderOtherSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:devicesListHeaderDescription="@string/settings_sessions_other_description"
app:devicesListHeaderTitle="@string/settings_sessions_other_title"
app:sessionsListHeaderDescription="@string/settings_sessions_other_description"
app:sessionsListHeaderTitle="@string/settings_sessions_other_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" />

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="14dp">
<TextView
android:id="@+id/sessionDetailsContentTitle"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
app:layout_constraintEnd_toStartOf="@id/sessionDetailsContentDescription"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Session name" />
<TextView
android:id="@+id/sessionDetailsContentDescription"
style="@style/TextAppearance.Vector.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:gravity="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/sessionDetailsContentTitle"
app:layout_constraintTop_toTopOf="parent"
tools:text="Element Web: Firefox on macOS" />
<View
android:id="@+id/sessionDetailsContentDivider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="14dp"
android:background="@drawable/divider_horizontal"
app:layout_constraintEnd_toEndOf="@id/sessionDetailsContentDescription"
app:layout_constraintStart_toStartOf="@id/sessionDetailsContentTitle"
app:layout_constraintTop_toBottomOf="@id/sessionDetailsContentBarrier" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/sessionDetailsContentBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="sessionDetailsContentTitle, sessionDetailsContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sessionDetailsHeaderTitle"
style="@style/TextAppearance.Vector.Body.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:lines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Session" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<TextView
android:id="@+id/sessionsOverviewEntryTitle"
style="@style/TextAppearance.Vector.Subtitle.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Session details" />
<TextView
android:id="@+id/sessionsOverviewEntryDescription"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle"
app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle"
app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryTitle"
tools:text="Application, device, and activity information." />
<View
android:id="@+id/sessionsOverviewEntryDivider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="20dp"
android:background="@drawable/divider_horizontal"
app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle"
app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle"
app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryDescription" />
</merge>

View File

@ -35,7 +35,7 @@
<im.vector.app.core.preference.VectorPreference
android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"
android:persistent="false"
android:title="@string/encryption_information_device_id"
android:title="@string/device_manager_session_details_session_id"
tools:summary="VZRHETBEER" />
<im.vector.app.core.preference.VectorPreference

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.utils
import android.content.ClipData
import im.vector.app.test.fakes.FakeContext
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
private const val A_TEXT = "text"
class CopyToClipboardUseCaseTest {
private val fakeContext = FakeContext()
private val copyToClipboardUseCase = CopyToClipboardUseCase(
context = fakeContext.instance
)
@Before
fun setup() {
mockkStatic(ClipData::class)
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `given a text when executing the use case then the text is copied into the clipboard`() {
// Given
val clipboardManager = fakeContext.givenClipboardManager()
clipboardManager.givenSetPrimaryClip()
val clipData = mockk<ClipData>()
every { ClipData.newPlainText(any(), any()) } returns clipData
// When
copyToClipboardUseCase.execute(A_TEXT)
// Then
clipboardManager.verifySetPrimaryClip(clipData)
verify { ClipData.newPlainText("", A_TEXT) }
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
private const val AN_IP_ADDRESS = "ip-address"
class CheckIfSectionDeviceIsVisibleUseCaseTest {
private val checkIfSectionDeviceIsVisibleUseCase = CheckIfSectionDeviceIsVisibleUseCase()
@Test
fun `given device info with Ip address when checking is device section is visible then it returns true`() = runTest {
// Given
val deviceInfo = givenADeviceInfo(AN_IP_ADDRESS)
// When
val result = checkIfSectionDeviceIsVisibleUseCase.execute(deviceInfo)
// Then
result shouldBeEqualTo true
}
@Test
fun `given device info with empty or null Ip address when checking is device section is visible then it returns false`() = runTest {
// Given
val deviceInfo1 = givenADeviceInfo("")
val deviceInfo2 = givenADeviceInfo(null)
// When
val result1 = checkIfSectionDeviceIsVisibleUseCase.execute(deviceInfo1)
val result2 = checkIfSectionDeviceIsVisibleUseCase.execute(deviceInfo2)
// Then
result1 shouldBeEqualTo false
result2 shouldBeEqualTo false
}
private fun givenADeviceInfo(ipAddress: String?): DeviceInfo {
val info = mockk<DeviceInfo>()
every { info.lastSeenIp } returns ipAddress
return info
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
private const val A_SESSION_NAME = "session-name"
private const val A_SESSION_ID = "session-id"
private const val A_LAST_SEEN_TS = 123L
class CheckIfSectionSessionIsVisibleUseCaseTest {
private val checkIfSectionSessionIsVisibleUseCase = CheckIfSectionSessionIsVisibleUseCase()
@Test
fun `given device info with name, id or lastSeenTs when checking is session section is visible then it returns true`() = runTest {
// Given
val deviceInfoList = listOf(
givenADeviceInfo(
sessionName = A_SESSION_NAME,
sessionId = null,
lastSeenTs = null,
),
givenADeviceInfo(
sessionName = null,
sessionId = A_SESSION_ID,
lastSeenTs = null,
),
givenADeviceInfo(
sessionName = null,
sessionId = null,
lastSeenTs = A_LAST_SEEN_TS,
),
givenADeviceInfo(
sessionName = A_SESSION_NAME,
sessionId = A_SESSION_ID,
lastSeenTs = null,
),
givenADeviceInfo(
sessionName = A_SESSION_NAME,
sessionId = null,
lastSeenTs = A_LAST_SEEN_TS,
),
givenADeviceInfo(
sessionName = null,
sessionId = A_SESSION_ID,
lastSeenTs = A_LAST_SEEN_TS,
),
givenADeviceInfo(
sessionName = A_SESSION_NAME,
sessionId = A_SESSION_ID,
lastSeenTs = A_LAST_SEEN_TS,
),
)
deviceInfoList.forEach { deviceInfo ->
// When
val result = checkIfSectionSessionIsVisibleUseCase.execute(deviceInfo)
// Then
result shouldBeEqualTo true
}
}
@Test
fun `given device info with missing session info when checking is session section is visible then it returns true`() = runTest {
// Given
val deviceInfoList = listOf(
givenADeviceInfo(
sessionName = null,
sessionId = null,
lastSeenTs = null,
),
givenADeviceInfo(
sessionName = "",
sessionId = "",
lastSeenTs = null,
),
givenADeviceInfo(
sessionName = "",
sessionId = "",
lastSeenTs = -1,
),
)
deviceInfoList.forEach { deviceInfo ->
// When
val result = checkIfSectionSessionIsVisibleUseCase.execute(deviceInfo)
// Then
result shouldBeEqualTo false
}
}
private fun givenADeviceInfo(
sessionName: String?,
sessionId: String?,
lastSeenTs: Long?,
): DeviceInfo {
val info = mockk<DeviceInfo>()
every { info.displayName } returns sessionName
every { info.deviceId } returns sessionId
every { info.lastSeenTs } returns lastSeenTs
return info
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.details
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.core.utils.CopyToClipboardUseCase
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.overview.GetDeviceFullInfoUseCase
import im.vector.app.test.test
import im.vector.app.test.testDispatcher
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
private const val A_SESSION_ID = "session-id"
private const val A_TEXT = "text"
class SessionDetailsViewModelTest {
@get:Rule
val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher)
private val args = SessionDetailsArgs(
deviceId = A_SESSION_ID
)
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
private val copyToClipboardUseCase = mockk<CopyToClipboardUseCase>()
private fun createViewModel() = SessionDetailsViewModel(
initialState = SessionDetailsViewState(args),
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase,
copyToClipboardUseCase = copyToClipboardUseCase,
)
@Test
fun `given the viewModel has been initialized then viewState is updated with session info`() {
// Given
val deviceFullInfo = mockk<DeviceFullInfo>()
val deviceInfo = mockk<DeviceInfo>()
every { deviceFullInfo.deviceInfo } returns deviceInfo
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
val expectedState = SessionDetailsViewState(
deviceId = A_SESSION_ID,
deviceInfo = Success(deviceInfo)
)
// When
val viewModel = createViewModel()
// Then
viewModel.test()
.assertLatestState { state -> state == expectedState }
.finish()
verify { getDeviceFullInfoUseCase.execute(A_SESSION_ID) }
}
@Test
fun `given copyToClipboard action when viewModel handle it then related use case is executed and viewEvent is updated`() {
// Given
val deviceFullInfo = mockk<DeviceFullInfo>()
val deviceInfo = mockk<DeviceInfo>()
every { deviceFullInfo.deviceInfo } returns deviceInfo
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
val action = SessionDetailsAction.CopyToClipboard(A_TEXT)
every { copyToClipboardUseCase.execute(any()) } just runs
// When
val viewModel = createViewModel()
val viewModelTest = viewModel.test()
viewModel.handle(action)
// Then
viewModelTest
.assertEvent { it is SessionDetailsViewEvent.ContentCopiedToClipboard }
.finish()
verify { copyToClipboardUseCase.execute(A_TEXT) }
}
}

View File

@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeNull
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -72,6 +73,7 @@ class GetDeviceFullInfoUseCaseTest {
@Test
fun `given current session and info for device when getting device info then the result is correct`() = runTest {
// Given
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
val deviceInfo = DeviceInfo(
lastSeenTs = A_TIMESTAMP
@ -85,16 +87,16 @@ class GetDeviceFullInfoUseCaseTest {
val isInactive = false
every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive
// When
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
deviceFullInfo shouldBeEqualTo Optional(
DeviceFullInfo(
// Then
deviceFullInfo shouldBeEqualTo DeviceFullInfo(
deviceInfo = deviceInfo,
cryptoDeviceInfo = cryptoDeviceInfo,
roomEncryptionTrustLevel = trustLevel,
isInactive = isInactive,
)
)
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
verify { getCurrentSessionCrossSigningInfoUseCase.execute() }
verify { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) }
@ -104,16 +106,19 @@ class GetDeviceFullInfoUseCaseTest {
}
@Test
fun `given current session and no info for device when getting device info then the result is null`() = runTest {
fun `given current session and no info for device when getting device info then the result is empty`() = runTest {
// Given
givenCurrentSessionCrossSigningInfo()
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(null))
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(null))
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
// When
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
deviceFullInfo shouldBeEqualTo Optional(null)
// Then
deviceFullInfo.shouldBeNull()
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
@ -121,11 +126,14 @@ class GetDeviceFullInfoUseCaseTest {
@Test
fun `given no current session when getting device info then the result is empty`() = runTest {
// Given
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
// When
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
deviceFullInfo shouldBeEqualTo null
// Then
deviceFullInfo.shouldBeNull()
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
}

View File

@ -21,22 +21,21 @@ import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.test
import im.vector.app.test.testDispatcher
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.util.Optional
private const val A_SESSION_ID = "session-id"
class SessionOverviewViewModelTest {
@get:Rule
val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher())
val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher)
private val args = SessionOverviewArgs(
deviceId = A_SESSION_ID
@ -52,17 +51,20 @@ class SessionOverviewViewModelTest {
@Test
fun `given the viewModel has been initialized then viewState is updated with session info`() {
// Given
val sessionParams = givenIdForSession(A_SESSION_ID)
val deviceFullInfo = mockk<DeviceFullInfo>()
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(Optional(deviceFullInfo))
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
val expectedState = SessionOverviewViewState(
deviceId = A_SESSION_ID,
isCurrentSession = true,
deviceInfo = Success(deviceFullInfo)
)
// When
val viewModel = createViewModel()
// Then
viewModel.test()
.assertLatestState { state -> state == expectedState }
.finish()

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2.overview
import android.content.Intent
import im.vector.app.features.settings.devices.v2.details.SessionDetailsActivity
import im.vector.app.test.fakes.FakeContext
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
private const val A_SESSION_ID = "session_id"
class SessionOverviewViewNavigatorTest {
private val context = FakeContext()
private val sessionOverviewViewNavigator = SessionOverviewViewNavigator()
@Before
fun setUp() {
mockkObject(SessionDetailsActivity)
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `given a session id when navigating to details then it starts the correct activity`() {
// Given
val intent = givenIntentForSessionDetails(A_SESSION_ID)
context.givenStartActivity(intent)
// When
sessionOverviewViewNavigator.navigateToSessionDetails(context.instance, A_SESSION_ID)
// Then
verify {
context.instance.startActivity(intent)
}
}
private fun givenIntentForSessionDetails(sessionId: String): Intent {
val intent = mockk<Intent>()
every { SessionDetailsActivity.newIntent(context.instance, sessionId) } returns intent
return intent
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import android.content.ClipData
import android.content.ClipboardManager
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
class FakeClipboardManager {
val instance = mockk<ClipboardManager>()
fun givenSetPrimaryClip() {
every { instance.setPrimaryClip(any()) } just runs
}
fun verifySetPrimaryClip(clipData: ClipData) {
verify { instance.setPrimaryClip(clipData) }
}
}

View File

@ -16,6 +16,7 @@
package im.vector.app.test.fakes
import android.content.ClipboardManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@ -74,4 +75,10 @@ class FakeContext(
fun givenStartActivity(intent: Intent) {
every { instance.startActivity(intent) } just runs
}
fun givenClipboardManager(): FakeClipboardManager {
val fakeClipboardManager = FakeClipboardManager()
givenService(Context.CLIPBOARD_SERVICE, ClipboardManager::class.java, fakeClipboardManager.instance)
return fakeClipboardManager
}
}