diff --git a/changelog.d/7077.wip b/changelog.d/7077.wip new file mode 100644 index 0000000000..907993c76f --- /dev/null +++ b/changelog.d/7077.wip @@ -0,0 +1 @@ +[Device management] Session details screen diff --git a/changelog.d/7102.bugfix b/changelog.d/7102.bugfix new file mode 100644 index 0000000000..73d1bbc7d6 --- /dev/null +++ b/changelog.d/7102.bugfix @@ -0,0 +1 @@ +Fixes crash when quickly double clicking FABs in the new app layout diff --git a/dependencies.gradle b/dependencies.gradle index 1e82b61280..c7b441738b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -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" diff --git a/library/ui-strings/src/main/res/values-ar/strings.xml b/library/ui-strings/src/main/res/values-ar/strings.xml index 073f961cb6..70b9a33ab5 100644 --- a/library/ui-strings/src/main/res/values-ar/strings.xml +++ b/library/ui-strings/src/main/res/values-ar/strings.xml @@ -320,7 +320,7 @@ السمة خطأ في فكّ التعمية اسم الجهاز - معرّف الجهاز + معرّف الجهاز مفتاح الجهاز صدّر مفاتيح الغرفة صدّر المفاتيح إلى ملف محلي diff --git a/library/ui-strings/src/main/res/values-bg/strings.xml b/library/ui-strings/src/main/res/values-bg/strings.xml index b29823040f..d3e9e599bc 100644 --- a/library/ui-strings/src/main/res/values-bg/strings.xml +++ b/library/ui-strings/src/main/res/values-bg/strings.xml @@ -396,7 +396,7 @@ Тема Грешка при разшифроване Публично име - Сесийно ID + Сесийно ID Ключ на устройство Експортирай E2E ключове за стая Експортиране на ключове за стая diff --git a/library/ui-strings/src/main/res/values-bn-rBD/strings.xml b/library/ui-strings/src/main/res/values-bn-rBD/strings.xml index 2f068f1bf8..7897da934e 100644 --- a/library/ui-strings/src/main/res/values-bn-rBD/strings.xml +++ b/library/ui-strings/src/main/res/values-bn-rBD/strings.xml @@ -789,7 +789,7 @@ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন সেশানের কুঞ্জি - আইডি + আইডি সর্বজনীন নাম ডিক্রিপশন সমস্যা থিম diff --git a/library/ui-strings/src/main/res/values-bn-rIN/strings.xml b/library/ui-strings/src/main/res/values-bn-rIN/strings.xml index 828bc3bd34..56bde36977 100644 --- a/library/ui-strings/src/main/res/values-bn-rIN/strings.xml +++ b/library/ui-strings/src/main/res/values-bn-rIN/strings.xml @@ -693,7 +693,7 @@ ডিক্রিপশন সমস্যা সর্বজনীন নাম - আইডি + আইডি সেশানের কুঞ্জি শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index 13a5b6c119..77d9fc10d5 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -448,7 +448,7 @@ Tema Error al desxifrar Nom públic - ID de sessió + ID de sessió Clau de sessió Exporta les claus de la sala E2E Exporta les claus de la sala diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index b7bfeac444..ed5a05f38a 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -635,7 +635,7 @@ Motiv vzhledu Chyba dešifrování Veřejné jméno - ID relace + ID relace Klíč relace Export E2E klíčů místností Export klíčů místností diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 8e502a6392..05ae25c5f7 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -418,7 +418,7 @@ Als Hauptadresse aufheben Entschlüsselungsfehler Öffentlicher Name - Sitzungs-ID + Sitzungs-ID Sitzungsschlüssel Ende-zu-Ende-Raumschlüssel exportieren Raumschlüssel exportieren @@ -2622,4 +2622,4 @@ Nicht verifiziert · Letzte Aktivität %1$s Verifiziere deine aktuelle Sitzung für besonders sichere Nachrichtenübertragung. Nicht verifizierte Sitzung - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-el/strings.xml b/library/ui-strings/src/main/res/values-el/strings.xml index 092a01bff4..f4973f4b95 100644 --- a/library/ui-strings/src/main/res/values-el/strings.xml +++ b/library/ui-strings/src/main/res/values-el/strings.xml @@ -172,7 +172,7 @@ Θέμα Σφάλμα αποκρυπτογράφησης Όνομα συσκευής - Αναγνωριστικό συσκευής + Αναγνωριστικό συσκευής Εξαγωγή Εισαγωγή Επιλέξτε ένα ευρετήριο δωματίων diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml index 7e1925f708..f536ca00f9 100644 --- a/library/ui-strings/src/main/res/values-eo/strings.xml +++ b/library/ui-strings/src/main/res/values-eo/strings.xml @@ -1084,7 +1084,7 @@ Elporti ŝlosilojn de ĉambroj Elporti tutvoje ĉifrajn ŝlosilojn de ĉambroj Ŝlosilo de salutaĵo - Identigilo de salutaĵo + Identigilo de salutaĵo Publika nomo Eraris malĉifrado Haŭto diff --git a/library/ui-strings/src/main/res/values-es-rMX/strings.xml b/library/ui-strings/src/main/res/values-es-rMX/strings.xml index 0b38fa6a19..c82f9aff61 100644 --- a/library/ui-strings/src/main/res/values-es-rMX/strings.xml +++ b/library/ui-strings/src/main/res/values-es-rMX/strings.xml @@ -249,7 +249,7 @@ Desescojer como Dirección Principal Error en descifrar Nombre del dispositivo - Identificación del dispositivo + Identificación del dispositivo Clave del dispositivo Exportar claves de cifrado de extremo-a-extremo de salas Exportar claves de salas diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml index 4eec90fbd6..fcdd3f90a0 100644 --- a/library/ui-strings/src/main/res/values-es/strings.xml +++ b/library/ui-strings/src/main/res/values-es/strings.xml @@ -415,7 +415,7 @@ Dejar de Establecer como dirección principal Error de descifrado Nombre público - ID de sesión + ID de sesión Clave de sesión Exportar claves de salas con cifrado Extremo-a-Extremo Exportar claves de sala diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 55fb9dfef0..495f32415f 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -612,7 +612,7 @@ Need on alles katsejärgus olevad funktsionaalsused. Ole kasutamisel ettevaatlik. Dekrüptimise viga Avalik nimi - Sessiooni tunnus + Sessiooni tunnus Sessiooni võti Ekspordi jututubade läbiva krüptimise võtmed Ekspordi jututoa võtmed diff --git a/library/ui-strings/src/main/res/values-eu/strings.xml b/library/ui-strings/src/main/res/values-eu/strings.xml index 7b27d1cc1d..f1f834ee04 100644 --- a/library/ui-strings/src/main/res/values-eu/strings.xml +++ b/library/ui-strings/src/main/res/values-eu/strings.xml @@ -406,7 +406,7 @@ Kontuan izan ekintza honek aplikazioa berrabiaraziko duela eta denbora bat behar Deszifratze errorea Izen publikoa - IDa + IDa Saioaren gakoa Esportatu E2E geletako gakoak diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index e104225389..be6d4b97b7 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -678,7 +678,7 @@ این‌ها ویژگی‌های آزمایشی‌ای هستند که ممکن است به روش‌های نامنتظره‌ای حراب شوندا. با احتیاط استفاده کنید. تنظیم به عنوان نشانی اصلی نام عمومی - شناسهٔ نشست + شناسهٔ نشست کلید نشست برون‌ریزی کلید‌های اتاق‌های سرتاسری برون‌ریزی کلید‌های اتاق‌ها diff --git a/library/ui-strings/src/main/res/values-fi/strings.xml b/library/ui-strings/src/main/res/values-fi/strings.xml index fde2502ae0..a576e7f0dc 100644 --- a/library/ui-strings/src/main/res/values-fi/strings.xml +++ b/library/ui-strings/src/main/res/values-fi/strings.xml @@ -366,7 +366,7 @@ Kumoa pääosoitteeksi asettaminen Salauksenpurkuvirhe Julkinen nimi - Istunnon tunnus + Istunnon tunnus Istunnon avain Vie salatun huoneen avaimet Vie huoneen avaimet diff --git a/library/ui-strings/src/main/res/values-fr-rCA/strings.xml b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml index 29a618f415..94db2935a7 100644 --- a/library/ui-strings/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml @@ -778,7 +778,7 @@ Exporter les clés des salons Exporter les clés E2E des salons Clé de la session - Identifiant de session + Identifiant de session Nom public Erreur de déchiffrement Thème diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index 55b5f88134..d4738f7b2f 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -346,7 +346,7 @@ Désactiver comme adresse principale Erreur de déchiffrement Nom public - Identifiant de session + Identifiant de session Clé de la session Exporter les clés E2E des salons Exporter les clés des salons diff --git a/library/ui-strings/src/main/res/values-gl/strings.xml b/library/ui-strings/src/main/res/values-gl/strings.xml index e6d26a63e5..c1e4e40a81 100644 --- a/library/ui-strings/src/main/res/values-gl/strings.xml +++ b/library/ui-strings/src/main/res/values-gl/strings.xml @@ -380,7 +380,7 @@ Tema Fallo ao descifrar Nome do dispositivo - ID de sesión + ID de sesión Chave do dispositivo Exportar chaves E2E da sala Exportar chaves da sala diff --git a/library/ui-strings/src/main/res/values-hr/strings.xml b/library/ui-strings/src/main/res/values-hr/strings.xml index dc5930b933..6d52e5cd96 100644 --- a/library/ui-strings/src/main/res/values-hr/strings.xml +++ b/library/ui-strings/src/main/res/values-hr/strings.xml @@ -572,7 +572,7 @@ Tema Greška u dešifriranju Javni naziv - Identitet + Identitet Ključ sesije Izvezi sobne ključeve za E2E Izvezi sobne ključeve diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index af8bf26b2e..5a4d951dc1 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -351,7 +351,7 @@ Kiszedés fő címek közül Visszafejtés hiba Nyilvános név - Munkamenet-azonosító + Munkamenet-azonosító Munkamenet kulcs E2E szoba kulcsok exportálása Szoba kulcsok exportálása diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index d1e68b4529..d2861a326b 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -301,7 +301,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tema Kesalahan dekripsi Nama perangkat - ID Sesi + ID Sesi Kunci perangkat Ekspor kunci ruangan terenkripsi Ekspor ruangan kunci diff --git a/library/ui-strings/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml index 7818761145..d25d66bfba 100644 --- a/library/ui-strings/src/main/res/values-is/strings.xml +++ b/library/ui-strings/src/main/res/values-is/strings.xml @@ -193,7 +193,7 @@ Þema Afkóðunarvilla Heiti tækis - Auðkenni setu + Auðkenni setu Dulritunarlykill setu Flytja út Settu inn lykilsetningu diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index ecb29d1586..984837679a 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -430,7 +430,7 @@ Tema Errore di decriptazione Nome pubblico - ID sessione + ID sessione Chiave sessione Esporta le chiavi di crittografia E2E delle stanze Esporta le chiavi delle stanze diff --git a/library/ui-strings/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml index 6d9533852b..ff19310c8e 100644 --- a/library/ui-strings/src/main/res/values-iw/strings.xml +++ b/library/ui-strings/src/main/res/values-iw/strings.xml @@ -542,7 +542,7 @@ יצא מפתחות חדר ייצא מפתחות חדר E2E מזהה מפתח - מזהה מושב + מזהה מושב שם ציבורי שגיאת פענוח ערכת נושא diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index b781e4d7f0..3e817e398c 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -197,7 +197,7 @@ これらは予期しない不具合が生じるかもしれない実験的機能です。慎重に使用してください。 メインアドレスとして設定 メインアドレスとしての設定を解除 - セッションID + セッションID 文字の大きさ とても小さい 小さい diff --git a/library/ui-strings/src/main/res/values-kab/strings.xml b/library/ui-strings/src/main/res/values-kab/strings.xml index a79b72efde..353fb99f53 100644 --- a/library/ui-strings/src/main/res/values-kab/strings.xml +++ b/library/ui-strings/src/main/res/values-kab/strings.xml @@ -291,7 +291,7 @@ Talqayt Tinarimin Asentel - Asulay n tqimit + Asulay n tqimit Tasarut n tɣimit Sifeḍ tisura n texxamt E2E Sifeḍ tisura n texxamt diff --git a/library/ui-strings/src/main/res/values-ko/strings.xml b/library/ui-strings/src/main/res/values-ko/strings.xml index ba0cbe5abd..37e8849fa8 100644 --- a/library/ui-strings/src/main/res/values-ko/strings.xml +++ b/library/ui-strings/src/main/res/values-ko/strings.xml @@ -431,7 +431,7 @@ 테마 암호 복호화 오류 공개 이름 - ID + ID 기기 키 종단간 암호화 방 키 내보내기 방 키 내보내기 diff --git a/library/ui-strings/src/main/res/values-lo/strings.xml b/library/ui-strings/src/main/res/values-lo/strings.xml index 1a9a2820b8..a92adb0225 100644 --- a/library/ui-strings/src/main/res/values-lo/strings.xml +++ b/library/ui-strings/src/main/res/values-lo/strings.xml @@ -909,7 +909,7 @@ ສົ່ງອອກກະແຈຫ້ອງ ສົ່ງອອກກະແຈຫ້ອງ E2E ລະຫັດລະບົບ - ID ລະບົບ + ID ລະບົບ ຊື່ສາທາລະນະ ການຖອດລະຫັດຜິດພາດ ຫົວຂໍ້ diff --git a/library/ui-strings/src/main/res/values-lv/strings.xml b/library/ui-strings/src/main/res/values-lv/strings.xml index f1fa1502c1..1787653fae 100644 --- a/library/ui-strings/src/main/res/values-lv/strings.xml +++ b/library/ui-strings/src/main/res/values-lv/strings.xml @@ -469,7 +469,7 @@ Tēma Atšifrēšanas kļūda Ierīces nosaukums - Sesijas ID + Sesijas ID Sesijas atslēga Eksportēt istabas šifrēšanas atslēgas Eksportēt istabas atslēgas diff --git a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml index 031b380c7e..7af718d920 100644 --- a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml +++ b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml @@ -119,7 +119,7 @@ Bannlyste brukere Avansert Tema - Økt-ID + Økt-ID Øktnøkkel Eksporter Importer diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml index b1d239963e..2669143a7e 100644 --- a/library/ui-strings/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -275,7 +275,7 @@ Niet instellen als hoofdadres Ontsleutelingsfout Publieke naam - Sessie ID + Sessie ID Sessiesleutel E2E-gesprekssleutels exporteren Gesprekssleutels exporteren diff --git a/library/ui-strings/src/main/res/values-nn/strings.xml b/library/ui-strings/src/main/res/values-nn/strings.xml index a56ba0ac30..45c8679736 100644 --- a/library/ui-strings/src/main/res/values-nn/strings.xml +++ b/library/ui-strings/src/main/res/values-nn/strings.xml @@ -310,7 +310,7 @@ Preg Noko gjekk gale med dekrypteringa Offentleg namn - Økt-ID + Økt-ID Sesjonsnøkkel Eksporter E2E-romnøkklar Eksporter romnøkklar diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml index 18b0de078c..ce8b22bcd2 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -231,7 +231,7 @@ Ustaw jako główny adres Motyw Nazwa publiczna - ID sesji + ID sesji Eksportuj Wprowadź hasło Potwierdź hasło @@ -2734,4 +2734,4 @@ Niestety, ten pokój nie został znaleziony. \nSpróbuj ponownie później.%s Zaproszenia - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 08c41db365..e9d0c66fd9 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -418,7 +418,7 @@ Des-definir como endereço principal Erro de decriptação Nome público - ID de sessão + ID de sessão Chave de sessão Exportar chaves de sala E2E Exportar chaves de sala diff --git a/library/ui-strings/src/main/res/values-pt/strings.xml b/library/ui-strings/src/main/res/values-pt/strings.xml index 4daaef83b0..87b6297b2b 100644 --- a/library/ui-strings/src/main/res/values-pt/strings.xml +++ b/library/ui-strings/src/main/res/values-pt/strings.xml @@ -246,7 +246,7 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< Erro de decifragem Nome do dispositivo - ID do dispositivo + ID do dispositivo Chave do dispositivo Exportar chaves E2E da sala Exportar chaves de sala diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 4852be1f82..9bbb1dc1c9 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -432,7 +432,7 @@ Сбросить основной адрес Ошибка дешифровки Публичное имя - ID сессии + ID сессии Ключ сессии Экспорт E2E ключей комнаты Экспорт ключей комнаты @@ -2678,4 +2678,4 @@ Обзор комнат Начать беседу Создать комнату - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index 2cc2d0280e..328cbb78cb 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -388,7 +388,7 @@ Vzhľad Chyba dešifrovania Verejné meno - ID relácie + ID relácie Kľúč relácie Exportovať šifrovacie kľúče miestnosti Exportovať kľúče miestnosti diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index 8fdf4ee310..a6af0a4921 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -431,7 +431,7 @@ Temë Gabim shfshehtëzimi Emër publik - ID Sesioni + ID Sesioni Kyç sesioni Eksporto kyçe dhome E2E Eksporto kyçe dhome diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index 025713272c..30b63c213c 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -918,7 +918,7 @@ Sätt upp på den här enheten Generera en ny säkerhetskopia eller sätt en ny lösenfras för din existerande säkerhetskopia. Detta är experimentella funktioner som kan gå sönder på oväntade sätt. Använd varsamt. - Sessions-ID + Sessions-ID Sessionsnyckel Exportera krypteringsnycklar Exportera rumsnycklar diff --git a/library/ui-strings/src/main/res/values-te/strings.xml b/library/ui-strings/src/main/res/values-te/strings.xml index 5ed2462ce8..0154d54c2e 100644 --- a/library/ui-strings/src/main/res/values-te/strings.xml +++ b/library/ui-strings/src/main/res/values-te/strings.xml @@ -260,7 +260,7 @@ ప్రధాన చిరునామాగా సెట్ చేయండి పరికరం పేరు - పరికరం ID + పరికరం ID పరికరం కీ E2E గది కీలను ఎగుమతి చేయండి diff --git a/library/ui-strings/src/main/res/values-tr/strings.xml b/library/ui-strings/src/main/res/values-tr/strings.xml index c097bfce6a..1f0e5be153 100644 --- a/library/ui-strings/src/main/res/values-tr/strings.xml +++ b/library/ui-strings/src/main/res/values-tr/strings.xml @@ -376,7 +376,7 @@ Tema Çözme hatası Görünür Ad - Oturum kimliği + Oturum kimliği Oturum anahtarı E2E Oda anahtarlarını dışa aktar Oda anahtarlarını dışa aktar diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 1c809fff3e..8e390801f3 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -354,7 +354,7 @@ Зробити не основною адресою Помилка розшифрування Загальнодоступна назва - ID сеансу + ID сеансу Ключ сеансу Експортувати E2E ключі кімнати Експортувати ключі кімнати diff --git a/library/ui-strings/src/main/res/values-vi/strings.xml b/library/ui-strings/src/main/res/values-vi/strings.xml index 2803128843..c6dc97f782 100644 --- a/library/ui-strings/src/main/res/values-vi/strings.xml +++ b/library/ui-strings/src/main/res/values-vi/strings.xml @@ -594,7 +594,7 @@ Hủy tài khoản Xem lại ngay Chìa khóa phiên - Mã phiên + Mã phiên Tên công khai Lỗi giải mã 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. diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 4e1c8e61c8..e92aafc8c6 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -242,7 +242,7 @@ 你的密码已更新 解密错误 公开名称 - 会话 ID + 会话 ID 会话密钥 导入 已验证 @@ -2583,4 +2583,4 @@ 已验证的会话 未知的设备类型 邀请 - \ No newline at end of file + diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 0f5208bcde..4f699b1c02 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -469,7 +469,7 @@ 主題 解密錯誤 公開名稱 - 工作階段 ID + 工作階段 ID 工作階段金鑰 匯出聊天室的端到端加密金鑰 匯出聊天室的加密金鑰 diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 6a87ce82f4..fbe35f57ce 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1212,7 +1212,6 @@ Decryption error Public name - Session ID Session key Export E2E room keys @@ -3263,8 +3262,15 @@ Current Session Session + Device Last activity %1$s + Session details + Application, device, and activity information. + Session name + Session ID + Last activity + IP address %s\nis looking a little empty. diff --git a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml new file mode 100644 index 0000000000..d3884f247d --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml similarity index 51% rename from library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml rename to library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml index 97e0290815..d3b931e44a 100644 --- a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml @@ -2,8 +2,8 @@ - - + + diff --git a/library/ui-styles/src/main/res/values/styles_devices_management.xml b/library/ui-styles/src/main/res/values/styles_devices_management.xml index 6fb236d3e6..6b42b85ffd 100644 --- a/library/ui-styles/src/main/res/values/styles_devices_management.xml +++ b/library/ui-styles/src/main/res/values/styles_devices_management.xml @@ -1,6 +1,10 @@ + + diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index c55852d7d8..94f2abe78b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -340,6 +340,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 8bcfd4e422..9d2b6c8196 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -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<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/utils/CopyToClipboardUseCase.kt b/vector/src/main/java/im/vector/app/core/utils/CopyToClipboardUseCase.kt new file mode 100644 index 0000000000..19ad9e2bba --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/CopyToClipboardUseCase.kt @@ -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() + ?.setPrimaryClip(ClipData.newPlainText("", text)) + } +} diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt index 6cfe8acc16..cde4fe2a35 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt @@ -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()!! - clipboard.setPrimaryClip(ClipData.newPlainText("", text)) + CopyToClipboardUseCase(context).execute(text) if (showToast) { context.toast(toastMessage) } diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index 3681ba4c15..f31f8a7d92 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt index 4f1b235b4b..0cba387a36 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt @@ -28,7 +28,7 @@ import im.vector.app.features.navigation.Navigator import javax.inject.Inject @AndroidEntryPoint -class NewChatBottomSheet @Inject constructor() : VectorBaseBottomSheetDialogFragment() { +class NewChatBottomSheet : VectorBaseBottomSheetDialogFragment() { @Inject lateinit var navigator: Navigator diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt new file mode 100644 index 0000000000..25b5ddb0e8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt @@ -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() + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCase.kt new file mode 100644 index 0000000000..4998b4b5d3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCase.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsAction.kt new file mode 100644 index 0000000000..0fa524dab4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsAction.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt new file mode 100644 index 0000000000..101bf1da2e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt @@ -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)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsArgs.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsArgs.kt new file mode 100644 index 0000000000..97716d7c47 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsArgs.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt new file mode 100644 index 0000000000..665ba5126c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt @@ -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(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(R.id.sessionDetailsContentTitle) + val sessionDetailsContentDescription by bind(R.id.sessionDetailsContentDescription) + val sessionDetailsContentDivider by bind(R.id.sessionDetailsContentDivider) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt new file mode 100644 index 0000000000..1fb5be4d78 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt @@ -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() { + + 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) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt new file mode 100644 index 0000000000..5d7717e5f7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt @@ -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() { + + @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 + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt new file mode 100644 index 0000000000..ff6ce3faad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt @@ -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(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 { + updateMargins(top = topMargin) + } + } + + class Holder : VectorEpoxyHolder() { + val sessionDetailsHeaderTitle by bind(R.id.sessionDetailsHeaderTitle) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewEvent.kt new file mode 100644 index 0000000000..02b313319e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewEvent.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt new file mode 100644 index 0000000000..c37858cc54 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt @@ -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(initialState) { + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + 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) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt new file mode 100644 index 0000000000..15868d3110 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt @@ -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 = Uninitialized, +) : MavericksState { + constructor(args: SessionDetailsArgs) : this( + deviceId = args.deviceId + ) +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt index 0cb621a502..555d216dfc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt index 547ed93f24..ebcf801695 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt @@ -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 ) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt index fff81b6dc5..5a8106f2fd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt @@ -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> { + fun execute(deviceId: String): Flow { return activeSessionHolder.getSafeActiveSession()?.let { session -> combine( getCurrentSessionCrossSigningInfoUseCase.execute(), @@ -58,7 +58,7 @@ class GetDeviceFullInfoUseCase @Inject constructor( null } fullInfo.toOptional() - } + }.unwrap() } ?: emptyFlow() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntryView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntryView.kt new file mode 100644 index 0000000000..5c4f0047ce --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntryView.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index c5cd80bd3c..3fea7a9316 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -45,6 +45,8 @@ import javax.inject.Inject class SessionOverviewFragment : VectorBaseFragment() { + @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( diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index 1a1d3640a2..bdcdc40c56 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewNavigator.kt new file mode 100644 index 0000000000..ef61856255 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewNavigator.kt @@ -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)) + } +} diff --git a/vector/src/main/res/layout/dialog_device_verify.xml b/vector/src/main/res/layout/dialog_device_verify.xml index bbf346c8dc..475ffc69af 100644 --- a/vector/src/main/res/layout/dialog_device_verify.xml +++ b/vector/src/main/res/layout/dialog_device_verify.xml @@ -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" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/fragment_session_details.xml b/vector/src/main/res/layout/fragment_session_details.xml new file mode 100644 index 0000000000..de0ce27798 --- /dev/null +++ b/vector/src/main/res/layout/fragment_session_details.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/vector/src/main/res/layout/fragment_session_overview.xml b/vector/src/main/res/layout/fragment_session_overview.xml index 156e61673b..f4573ef85e 100644 --- a/vector/src/main/res/layout/fragment_session_overview.xml +++ b/vector/src/main/res/layout/fragment_session_overview.xml @@ -1,20 +1,36 @@ - - + - + + + + + + + diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml index 9cefd6aa24..b53aef33d7 100644 --- a/vector/src/main/res/layout/fragment_settings_devices.xml +++ b/vector/src/main/res/layout/fragment_settings_devices.xml @@ -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" /> diff --git a/vector/src/main/res/layout/item_session_details_content.xml b/vector/src/main/res/layout/item_session_details_content.xml new file mode 100644 index 0000000000..fefae65b3d --- /dev/null +++ b/vector/src/main/res/layout/item_session_details_content.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_session_details_header.xml b/vector/src/main/res/layout/item_session_details_header.xml new file mode 100644 index 0000000000..571a541b2b --- /dev/null +++ b/vector/src/main/res/layout/item_session_details_header.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/vector/src/main/res/layout/view_session_overview_entry.xml b/vector/src/main/res/layout/view_session_overview_entry.xml new file mode 100644 index 0000000000..464f775192 --- /dev/null +++ b/vector/src/main/res/layout/view_session_overview_entry.xml @@ -0,0 +1,42 @@ + + + + + + + + + + diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index c246a40f71..1e8997e9c8 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -35,7 +35,7 @@ () + every { ClipData.newPlainText(any(), any()) } returns clipData + + // When + copyToClipboardUseCase.execute(A_TEXT) + + // Then + clipboardManager.verifySetPrimaryClip(clipData) + verify { ClipData.newPlainText("", A_TEXT) } + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt new file mode 100644 index 0000000000..b618c58b7e --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt @@ -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() + every { info.lastSeenIp } returns ipAddress + return info + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt new file mode 100644 index 0000000000..806c86d175 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt @@ -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() + every { info.displayName } returns sessionName + every { info.deviceId } returns sessionId + every { info.lastSeenTs } returns lastSeenTs + return info + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt new file mode 100644 index 0000000000..df0613e06b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt @@ -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() + private val copyToClipboardUseCase = mockk() + + 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() + val deviceInfo = mockk() + 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() + val deviceInfo = mockk() + 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) } + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt index 7dc8e08a4e..9c7515f2da 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt @@ -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,15 +87,15 @@ class GetDeviceFullInfoUseCaseTest { val isInactive = false every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive + // When val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull() - deviceFullInfo shouldBeEqualTo Optional( - DeviceFullInfo( - deviceInfo = deviceInfo, - cryptoDeviceInfo = cryptoDeviceInfo, - roomEncryptionTrustLevel = trustLevel, - isInactive = isInactive, - ) + // Then + deviceFullInfo shouldBeEqualTo DeviceFullInfo( + deviceInfo = deviceInfo, + cryptoDeviceInfo = cryptoDeviceInfo, + roomEncryptionTrustLevel = trustLevel, + isInactive = isInactive, ) verify { fakeActiveSessionHolder.instance.getSafeActiveSession() } verify { getCurrentSessionCrossSigningInfoUseCase.execute() } @@ -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() } } diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt index 4a26fc4adc..8d4e49ef85 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt @@ -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() - 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() diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewNavigatorTest.kt new file mode 100644 index 0000000000..56f1e5920d --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewNavigatorTest.kt @@ -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() + every { SessionDetailsActivity.newIntent(context.instance, sessionId) } returns intent + return intent + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeClipboardManager.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeClipboardManager.kt new file mode 100644 index 0000000000..983902b496 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeClipboardManager.kt @@ -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() + + fun givenSetPrimaryClip() { + every { instance.setPrimaryClip(any()) } just runs + } + + fun verifySetPrimaryClip(clipData: ClipData) { + verify { instance.setPrimaryClip(clipData) } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index d74ebcb678..9a94313fec 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -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 + } }