Merge pull request #7046 from vector-im/feature/ons/device_manager_filter
[Device Manager] Filter Other Sessions (PSG-684)
This commit is contained in:
commit
5902c9cd83
1
changelog.d/7045.wip
Normal file
1
changelog.d/7045.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Device Manager] Filter Other Sessions
|
@ -2602,8 +2602,8 @@
|
|||||||
<string name="all_chats">Tots els xats</string>
|
<string name="all_chats">Tots els xats</string>
|
||||||
<string name="home_layout_preferences">Preferències de disseny</string>
|
<string name="home_layout_preferences">Preferències de disseny</string>
|
||||||
<string name="explore_rooms">Explora sales</string>
|
<string name="explore_rooms">Explora sales</string>
|
||||||
<string name="settings_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string>
|
<string name="device_manager_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string>
|
||||||
<string name="settings_sessions_other_title">Altres sessions</string>
|
<string name="device_manager_sessions_other_title">Altres sessions</string>
|
||||||
<string name="settings_sessions_list">Sessions</string>
|
<string name="settings_sessions_list">Sessions</string>
|
||||||
<string name="a11y_open_spaces">Obre la llista d\'espais</string>
|
<string name="a11y_open_spaces">Obre la llista d\'espais</string>
|
||||||
<string name="a11y_create_message">Crea un nou xat o sala</string>
|
<string name="a11y_create_message">Crea un nou xat o sala</string>
|
||||||
|
@ -2651,8 +2651,8 @@
|
|||||||
<string name="a11y_open_settings">Otevřít nastavení</string>
|
<string name="a11y_open_settings">Otevřít nastavení</string>
|
||||||
<string name="all_chats">Všechny konverzace</string>
|
<string name="all_chats">Všechny konverzace</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Zobrazit všechny relace (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Zobrazit všechny relace (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string>
|
<string name="device_manager_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string>
|
||||||
<string name="settings_sessions_other_title">Ostatní relace</string>
|
<string name="device_manager_sessions_other_title">Ostatní relace</string>
|
||||||
<string name="settings_sessions_list">Relace</string>
|
<string name="settings_sessions_list">Relace</string>
|
||||||
<string name="a11y_open_spaces">Seznam otevřených prostorů</string>
|
<string name="a11y_open_spaces">Seznam otevřených prostorů</string>
|
||||||
<string name="a11y_create_message">Vytvořit novou konverzaci nebo místnost</string>
|
<string name="a11y_create_message">Vytvořit novou konverzaci nebo místnost</string>
|
||||||
|
@ -2587,8 +2587,8 @@
|
|||||||
<string name="room_list_filter_people">Personen</string>
|
<string name="room_list_filter_people">Personen</string>
|
||||||
<string name="send_your_first_msg_to_invite">Schreibe deine erste Nachricht, um %s zur Konversation einzuladen</string>
|
<string name="send_your_first_msg_to_invite">Schreibe deine erste Nachricht, um %s zur Konversation einzuladen</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string>
|
||||||
<string name="settings_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string>
|
<string name="device_manager_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string>
|
||||||
<string name="settings_sessions_other_title">Andere Sitzungen</string>
|
<string name="device_manager_sessions_other_title">Andere Sitzungen</string>
|
||||||
<string name="settings_sessions_list">Sitzungen</string>
|
<string name="settings_sessions_list">Sitzungen</string>
|
||||||
<string name="a11y_open_spaces">Space-Liste öffnen</string>
|
<string name="a11y_open_spaces">Space-Liste öffnen</string>
|
||||||
<string name="a11y_create_message">Beginne ein Gespräch oder erstelle einen Raum</string>
|
<string name="a11y_create_message">Beginne ein Gespräch oder erstelle einen Raum</string>
|
||||||
|
@ -2592,8 +2592,8 @@
|
|||||||
<string name="a11y_open_settings">Ava seadistused</string>
|
<string name="a11y_open_settings">Ava seadistused</string>
|
||||||
<string name="all_chats">Kõik vestlused</string>
|
<string name="all_chats">Kõik vestlused</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string>
|
<string name="device_manager_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string>
|
||||||
<string name="settings_sessions_other_title">Muud sessioonid</string>
|
<string name="device_manager_sessions_other_title">Muud sessioonid</string>
|
||||||
<string name="settings_sessions_list">Sessionid</string>
|
<string name="settings_sessions_list">Sessionid</string>
|
||||||
<string name="a11y_open_spaces">Ava kogukondade loend</string>
|
<string name="a11y_open_spaces">Ava kogukondade loend</string>
|
||||||
<string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string>
|
<string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string>
|
||||||
|
@ -2601,8 +2601,8 @@
|
|||||||
<string name="a11y_open_settings">گشودن تنظیمات</string>
|
<string name="a11y_open_settings">گشودن تنظیمات</string>
|
||||||
<string name="all_chats">تمامی گپها</string>
|
<string name="all_chats">تمامی گپها</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشستها (ن۲، دحت)</string>
|
<string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشستها (ن۲، دحت)</string>
|
||||||
<string name="settings_sessions_other_description">برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.</string>
|
<string name="device_manager_sessions_other_description">برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.</string>
|
||||||
<string name="settings_sessions_other_title">دیگر نشستها</string>
|
<string name="device_manager_sessions_other_title">دیگر نشستها</string>
|
||||||
<string name="settings_sessions_list">نشستها</string>
|
<string name="settings_sessions_list">نشستها</string>
|
||||||
<string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string>
|
<string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string>
|
||||||
<string name="a11y_create_message">ایجاد اتاق یا گفتوگویی جدید</string>
|
<string name="a11y_create_message">ایجاد اتاق یا گفتوگویی جدید</string>
|
||||||
|
@ -2601,8 +2601,8 @@
|
|||||||
<string name="a11y_open_settings">Ouvrir les paramètres</string>
|
<string name="a11y_open_settings">Ouvrir les paramètres</string>
|
||||||
<string name="all_chats">Toutes les conversations</string>
|
<string name="all_chats">Toutes les conversations</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Afficher toutes les sessions (V2, en cours)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Afficher toutes les sessions (V2, en cours)</string>
|
||||||
<string name="settings_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string>
|
<string name="device_manager_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string>
|
||||||
<string name="settings_sessions_other_title">Autres sessions</string>
|
<string name="device_manager_sessions_other_title">Autres sessions</string>
|
||||||
<string name="settings_sessions_list">Sessions</string>
|
<string name="settings_sessions_list">Sessions</string>
|
||||||
<string name="a11y_open_spaces">Ouvrir la liste des espaces</string>
|
<string name="a11y_open_spaces">Ouvrir la liste des espaces</string>
|
||||||
<string name="a11y_create_message">Créer une nouvelle conversation ou salon</string>
|
<string name="a11y_create_message">Créer une nouvelle conversation ou salon</string>
|
||||||
|
@ -2615,8 +2615,8 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
|
|||||||
<string name="a11y_device_manager_device_type_web">Web</string>
|
<string name="a11y_device_manager_device_type_web">Web</string>
|
||||||
<string name="a11y_device_manager_device_type_mobile">Mobil</string>
|
<string name="a11y_device_manager_device_type_mobile">Mobil</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Minden munkamenet megjelenítése (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Minden munkamenet megjelenítése (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string>
|
<string name="device_manager_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string>
|
||||||
<string name="settings_sessions_other_title">Más munkamenetek</string>
|
<string name="device_manager_sessions_other_title">Más munkamenetek</string>
|
||||||
<string name="settings_sessions_list">Munkamenetek</string>
|
<string name="settings_sessions_list">Munkamenetek</string>
|
||||||
<string name="a11y_open_spaces">Nyitott területek listája</string>
|
<string name="a11y_open_spaces">Nyitott területek listája</string>
|
||||||
<string name="a11y_create_message">Új beszélgetés vagy szoba létrehozása</string>
|
<string name="a11y_create_message">Új beszélgetés vagy szoba létrehozása</string>
|
||||||
|
@ -2553,8 +2553,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
|
|||||||
<string name="auth_reset_password_error_unverified">Email belum diverifikasi, periksa kotak masuk Anda</string>
|
<string name="auth_reset_password_error_unverified">Email belum diverifikasi, periksa kotak masuk Anda</string>
|
||||||
<string name="all_chats">Semua Obrolan</string>
|
<string name="all_chats">Semua Obrolan</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Tampilkan Semua Sesi (V2, Dalam Pengembangan)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Tampilkan Semua Sesi (V2, Dalam Pengembangan)</string>
|
||||||
<string name="settings_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string>
|
<string name="device_manager_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string>
|
||||||
<string name="settings_sessions_other_title">Sesi lainnya</string>
|
<string name="device_manager_sessions_other_title">Sesi lainnya</string>
|
||||||
<string name="settings_sessions_list">Sesi</string>
|
<string name="settings_sessions_list">Sesi</string>
|
||||||
<string name="a11y_open_spaces">Buka daftar space</string>
|
<string name="a11y_open_spaces">Buka daftar space</string>
|
||||||
<string name="a11y_create_message">Buat percakapan atau ruangan baru</string>
|
<string name="a11y_create_message">Buat percakapan atau ruangan baru</string>
|
||||||
|
@ -2592,8 +2592,8 @@
|
|||||||
<string name="a11y_open_settings">Apri le impostazioni</string>
|
<string name="a11y_open_settings">Apri le impostazioni</string>
|
||||||
<string name="all_chats">Tutte le chat</string>
|
<string name="all_chats">Tutte le chat</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Mostra tutte le sessioni (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Mostra tutte le sessioni (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string>
|
<string name="device_manager_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string>
|
||||||
<string name="settings_sessions_other_title">Altre sessioni</string>
|
<string name="device_manager_sessions_other_title">Altre sessioni</string>
|
||||||
<string name="settings_sessions_list">Sessioni</string>
|
<string name="settings_sessions_list">Sessioni</string>
|
||||||
<string name="a11y_open_spaces">Apri elenco spazi</string>
|
<string name="a11y_open_spaces">Apri elenco spazi</string>
|
||||||
<string name="a11y_create_message">Crea una nuova conversazione o stanza</string>
|
<string name="a11y_create_message">Crea una nuova conversazione o stanza</string>
|
||||||
|
@ -2600,8 +2600,8 @@
|
|||||||
<string name="location_share_loading_map_error">Kan kaart niet laden
|
<string name="location_share_loading_map_error">Kan kaart niet laden
|
||||||
\nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven.</string>
|
\nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven.</string>
|
||||||
<string name="a11y_open_settings">Open instellingen</string>
|
<string name="a11y_open_settings">Open instellingen</string>
|
||||||
<string name="settings_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string>
|
<string name="device_manager_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string>
|
||||||
<string name="settings_sessions_other_title">Andere sessies</string>
|
<string name="device_manager_sessions_other_title">Andere sessies</string>
|
||||||
<string name="settings_sessions_list">Sessies</string>
|
<string name="settings_sessions_list">Sessies</string>
|
||||||
<string name="a11y_open_spaces">Lijst met publieke spaces</string>
|
<string name="a11y_open_spaces">Lijst met publieke spaces</string>
|
||||||
<string name="a11y_create_message">Maak een nieuw gesprek of een nieuwe kamer</string>
|
<string name="a11y_create_message">Maak een nieuw gesprek of een nieuwe kamer</string>
|
||||||
|
@ -2697,8 +2697,8 @@
|
|||||||
<string name="location_share_loading_map_error">Nie można wczytać mapy.
|
<string name="location_share_loading_map_error">Nie można wczytać mapy.
|
||||||
\nTen serwer macierzysty może nie być skonfigurowany do wyświetlania map.</string>
|
\nTen serwer macierzysty może nie być skonfigurowany do wyświetlania map.</string>
|
||||||
<string name="a11y_open_settings">Otwórz ustawienia</string>
|
<string name="a11y_open_settings">Otwórz ustawienia</string>
|
||||||
<string name="settings_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string>
|
<string name="device_manager_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string>
|
||||||
<string name="settings_sessions_other_title">Inne sesje</string>
|
<string name="device_manager_sessions_other_title">Inne sesje</string>
|
||||||
<string name="settings_sessions_list">Sesje</string>
|
<string name="settings_sessions_list">Sesje</string>
|
||||||
<string name="a11y_open_spaces">Lista otwartych przestrzeni</string>
|
<string name="a11y_open_spaces">Lista otwartych przestrzeni</string>
|
||||||
<string name="a11y_create_message">Utwórz nową rozmowę lub pokój</string>
|
<string name="a11y_create_message">Utwórz nową rozmowę lub pokój</string>
|
||||||
|
@ -2601,8 +2601,8 @@
|
|||||||
<string name="a11y_open_settings">Abrir configurações</string>
|
<string name="a11y_open_settings">Abrir configurações</string>
|
||||||
<string name="all_chats">Todos os Chats</string>
|
<string name="all_chats">Todos os Chats</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Mostrar Todas Sessões (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Mostrar Todas Sessões (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string>
|
<string name="device_manager_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string>
|
||||||
<string name="settings_sessions_other_title">Outras sessões</string>
|
<string name="device_manager_sessions_other_title">Outras sessões</string>
|
||||||
<string name="settings_sessions_list">Sessões</string>
|
<string name="settings_sessions_list">Sessões</string>
|
||||||
<string name="a11y_open_spaces">Abrir lista de espaços</string>
|
<string name="a11y_open_spaces">Abrir lista de espaços</string>
|
||||||
<string name="a11y_create_message">Criar uma nova conversa ou sala</string>
|
<string name="a11y_create_message">Criar uma nova conversa ou sala</string>
|
||||||
|
@ -2660,8 +2660,8 @@
|
|||||||
<string name="location_share_loading_map_error">Не удалось загрузить карту
|
<string name="location_share_loading_map_error">Не удалось загрузить карту
|
||||||
\nВозможно, этот домашний сервер не настроен для отображения карт.</string>
|
\nВозможно, этот домашний сервер не настроен для отображения карт.</string>
|
||||||
<string name="all_chats">Все беседы</string>
|
<string name="all_chats">Все беседы</string>
|
||||||
<string name="settings_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
|
<string name="device_manager_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
|
||||||
<string name="settings_sessions_other_title">Другие сессии</string>
|
<string name="device_manager_sessions_other_title">Другие сессии</string>
|
||||||
<string name="settings_sessions_list">Сессии</string>
|
<string name="settings_sessions_list">Сессии</string>
|
||||||
<string name="a11y_create_message">Создать беседу или комнату</string>
|
<string name="a11y_create_message">Создать беседу или комнату</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string>
|
||||||
|
@ -2651,8 +2651,8 @@
|
|||||||
<string name="a11y_open_settings">Otvoriť nastavenia</string>
|
<string name="a11y_open_settings">Otvoriť nastavenia</string>
|
||||||
<string name="all_chats">Všetky konverzácie</string>
|
<string name="all_chats">Všetky konverzácie</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Zobraziť všetky relácie (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Zobraziť všetky relácie (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string>
|
<string name="device_manager_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string>
|
||||||
<string name="settings_sessions_other_title">Iné relácie</string>
|
<string name="device_manager_sessions_other_title">Iné relácie</string>
|
||||||
<string name="settings_sessions_list">Relácie</string>
|
<string name="settings_sessions_list">Relácie</string>
|
||||||
<string name="a11y_open_spaces">Otvoriť zoznam priestorov</string>
|
<string name="a11y_open_spaces">Otvoriť zoznam priestorov</string>
|
||||||
<string name="a11y_create_message">Vytvoriť novú konverzáciu alebo miestnosť</string>
|
<string name="a11y_create_message">Vytvoriť novú konverzáciu alebo miestnosť</string>
|
||||||
|
@ -2701,8 +2701,8 @@
|
|||||||
<string name="a11y_open_settings">Відкрити налаштування</string>
|
<string name="a11y_open_settings">Відкрити налаштування</string>
|
||||||
<string name="all_chats">Усі бесіди</string>
|
<string name="all_chats">Усі бесіди</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
|
<string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
|
||||||
<string name="settings_sessions_other_title">Інші сеанси</string>
|
<string name="device_manager_sessions_other_title">Інші сеанси</string>
|
||||||
<string name="settings_sessions_list">Сеанси</string>
|
<string name="settings_sessions_list">Сеанси</string>
|
||||||
<string name="a11y_open_spaces">Відкрити список кімнат</string>
|
<string name="a11y_open_spaces">Відкрити список кімнат</string>
|
||||||
<string name="a11y_create_message">Створити нову розмову або кімнату</string>
|
<string name="a11y_create_message">Створити нову розмову або кімнату</string>
|
||||||
|
@ -2551,8 +2551,8 @@
|
|||||||
<string name="a11y_open_settings">打开设置</string>
|
<string name="a11y_open_settings">打开设置</string>
|
||||||
<string name="all_chats">全部聊天</string>
|
<string name="all_chats">全部聊天</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
|
<string name="device_manager_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
|
||||||
<string name="settings_sessions_other_title">其他会话</string>
|
<string name="device_manager_sessions_other_title">其他会话</string>
|
||||||
<string name="settings_sessions_list">会话</string>
|
<string name="settings_sessions_list">会话</string>
|
||||||
<string name="a11y_open_spaces">打开空间列表</string>
|
<string name="a11y_open_spaces">打开空间列表</string>
|
||||||
<string name="a11y_create_message">创建新对话或房间</string>
|
<string name="a11y_create_message">创建新对话或房间</string>
|
||||||
|
@ -2551,8 +2551,8 @@
|
|||||||
<string name="a11y_open_settings">開啟設定</string>
|
<string name="a11y_open_settings">開啟設定</string>
|
||||||
<string name="all_chats">所有聊天</string>
|
<string name="all_chats">所有聊天</string>
|
||||||
<string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string>
|
<string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string>
|
||||||
<string name="settings_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
|
<string name="device_manager_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
|
||||||
<string name="settings_sessions_other_title">其他工作階段</string>
|
<string name="device_manager_sessions_other_title">其他工作階段</string>
|
||||||
<string name="settings_sessions_list">工作階段</string>
|
<string name="settings_sessions_list">工作階段</string>
|
||||||
<string name="a11y_open_spaces">開啟空間清單</string>
|
<string name="a11y_open_spaces">開啟空間清單</string>
|
||||||
<string name="a11y_create_message">建立新的對話或聊天室</string>
|
<string name="a11y_create_message">建立新的對話或聊天室</string>
|
||||||
|
@ -2361,8 +2361,8 @@
|
|||||||
<string name="settings_active_sessions_manage">Manage Sessions</string>
|
<string name="settings_active_sessions_manage">Manage Sessions</string>
|
||||||
<string name="settings_active_sessions_signout_device">Sign out of this session</string>
|
<string name="settings_active_sessions_signout_device">Sign out of this session</string>
|
||||||
<string name="settings_sessions_list">Sessions</string>
|
<string name="settings_sessions_list">Sessions</string>
|
||||||
<string name="settings_sessions_other_title">Other sessions</string>
|
<string name="device_manager_sessions_other_title">Other sessions</string>
|
||||||
<string name="settings_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string>
|
<string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string>
|
||||||
|
|
||||||
<string name="settings_server_name">Server name</string>
|
<string name="settings_server_name">Server name</string>
|
||||||
<string name="settings_server_version">Server version</string>
|
<string name="settings_server_version">Server version</string>
|
||||||
@ -3265,6 +3265,31 @@
|
|||||||
<string name="device_manager_device_title">Device</string>
|
<string name="device_manager_device_title">Device</string>
|
||||||
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
||||||
<string name="device_manager_session_last_activity">Last activity %1$s</string>
|
<string name="device_manager_session_last_activity">Last activity %1$s</string>
|
||||||
|
<string name="device_manager_filter_bottom_sheet_title">Filter</string>
|
||||||
|
<string name="device_manager_filter_option_all_sessions">All sessions</string>
|
||||||
|
<string name="device_manager_filter_option_verified">Verified</string>
|
||||||
|
<string name="device_manager_filter_option_verified_description">Ready for secure messaging</string>
|
||||||
|
<string name="device_manager_filter_option_unverified">Unverified</string>
|
||||||
|
<string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
|
||||||
|
<string name="device_manager_filter_option_inactive">Inactive</string>
|
||||||
|
<plurals name="device_manager_filter_option_inactive_description">
|
||||||
|
<item quantity="one">Inactive for %1$d day or longer</item>
|
||||||
|
<item quantity="other">Inactive for %1$d days or longer</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="a11y_device_manager_filter">Filter</string>
|
||||||
|
<string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
|
||||||
|
<string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you don’t recognize or use anymore.</string>
|
||||||
|
<string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
|
||||||
|
<string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore.</string>
|
||||||
|
<string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
|
||||||
|
<plurals name="device_manager_other_sessions_recommendation_description_inactive">
|
||||||
|
<item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
|
||||||
|
<item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="device_manager_other_sessions_no_verified_sessions_found">No verified sessions found.</string>
|
||||||
|
<string name="device_manager_other_sessions_no_unverified_sessions_found">No unverified sessions found.</string>
|
||||||
|
<string name="device_manager_other_sessions_no_inactive_sessions_found">No inactive sessions found.</string>
|
||||||
|
<string name="device_manager_other_sessions_clear_filter">Clear Filter</string>
|
||||||
<string name="device_manager_session_details_title">Session details</string>
|
<string name="device_manager_session_details_title">Session details</string>
|
||||||
<string name="device_manager_session_details_description">Application, device, and activity information.</string>
|
<string name="device_manager_session_details_description">Application, device, and activity information.</string>
|
||||||
<string name="device_manager_session_details_session_name">Session name</string>
|
<string name="device_manager_session_details_session_name">Session name</string>
|
||||||
|
@ -141,6 +141,7 @@
|
|||||||
|
|
||||||
<!-- Shield colors -->
|
<!-- Shield colors -->
|
||||||
<color name="shield_color_trust">#0DBD8B</color>
|
<color name="shield_color_trust">#0DBD8B</color>
|
||||||
|
<color name="shield_color_trust_background">#0F0DBD8B</color>
|
||||||
<color name="shield_color_black">#17191C</color>
|
<color name="shield_color_black">#17191C</color>
|
||||||
<color name="shield_color_warning">#FF4B55</color>
|
<color name="shield_color_warning">#FF4B55</color>
|
||||||
<color name="shield_color_warning_background">#0FFF4B55</color>
|
<color name="shield_color_warning_background">#0FFF4B55</color>
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<declare-styleable name="OtherSessionsSecurityRecommendationView">
|
||||||
|
<attr name="otherSessionsRecommendationTitle" format="string" />
|
||||||
|
<attr name="otherSessionsRecommendationDescription" format="string" />
|
||||||
|
<attr name="otherSessionsRecommendationImageResource" format="reference" />
|
||||||
|
<attr name="otherSessionsRecommendationImageBackgroundTint" format="color" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
</resources>
|
@ -323,6 +323,7 @@
|
|||||||
<activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
|
<activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
|
||||||
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
|
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
|
||||||
<activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
|
<activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
|
||||||
|
<activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
|
||||||
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity"/>
|
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity"/>
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
@ -89,6 +89,7 @@ import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewMode
|
|||||||
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
|
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
|
||||||
import im.vector.app.features.settings.devices.DevicesViewModel
|
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.details.SessionDetailsViewModel
|
||||||
|
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
|
||||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
|
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.AccountDataViewModel
|
||||||
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
||||||
@ -643,6 +644,11 @@ interface MavericksViewModelModule {
|
|||||||
@MavericksViewModelKey(SessionOverviewViewModel::class)
|
@MavericksViewModelKey(SessionOverviewViewModel::class)
|
||||||
fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(OtherSessionsViewModel::class)
|
||||||
|
fun otherSessionsViewModelFactory(factory: OtherSessionsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(SessionDetailsViewModel::class)
|
@MavericksViewModelKey(SessionDetailsViewModel::class)
|
||||||
|
@ -24,26 +24,20 @@ import dagger.assisted.AssistedInject
|
|||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
import im.vector.app.core.utils.PublishDataSource
|
|
||||||
import im.vector.lib.core.utils.flow.throttleFirst
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
class DevicesViewModel @AssistedInject constructor(
|
class DevicesViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: DevicesViewState,
|
@Assisted initialState: DevicesViewState,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||||
private val refreshDevicesUseCase: RefreshDevicesUseCase,
|
|
||||||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState), VerificationService.Listener {
|
refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||||
|
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
|
interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
|
||||||
@ -52,35 +46,11 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
|
companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
private val refreshSource = PublishDataSource<Unit>()
|
|
||||||
private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addVerificationListener()
|
|
||||||
observeCurrentSessionCrossSigningInfo()
|
observeCurrentSessionCrossSigningInfo()
|
||||||
observeDevices()
|
observeDevices()
|
||||||
observeRefreshSource()
|
|
||||||
refreshDevicesOnCryptoDevicesChange()
|
refreshDevicesOnCryptoDevicesChange()
|
||||||
queryRefreshDevicesList()
|
refreshDeviceList()
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
removeVerificationListener()
|
|
||||||
super.onCleared()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addVerificationListener() {
|
|
||||||
activeSessionHolder.getSafeActiveSession()
|
|
||||||
?.cryptoService()
|
|
||||||
?.verificationService()
|
|
||||||
?.addListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeVerificationListener() {
|
|
||||||
activeSessionHolder.getSafeActiveSession()
|
|
||||||
?.cryptoService()
|
|
||||||
?.verificationService()
|
|
||||||
?.removeListener(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeCurrentSessionCrossSigningInfo() {
|
private fun observeCurrentSessionCrossSigningInfo() {
|
||||||
@ -94,7 +64,10 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeDevices() {
|
private fun observeDevices() {
|
||||||
getDeviceFullInfoListUseCase.execute()
|
getDeviceFullInfoListUseCase.execute(
|
||||||
|
filterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||||
|
excludeCurrentDevice = false
|
||||||
|
)
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
if (async is Success) {
|
if (async is Success) {
|
||||||
val deviceFullInfoList = async.invoke()
|
val deviceFullInfoList = async.invoke()
|
||||||
@ -119,28 +92,6 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRefreshSource() {
|
|
||||||
refreshSource.stream()
|
|
||||||
.throttleFirst(refreshThrottleDelayMs)
|
|
||||||
.onEach { refreshDevicesUseCase.execute() }
|
|
||||||
.launchIn(viewModelScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if (tx.state == VerificationTxState.Verified) {
|
|
||||||
queryRefreshDevicesList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force the refresh of the devices list.
|
|
||||||
* The devices list is the list of the devices where the user is logged in.
|
|
||||||
* It can be any mobile devices, and any browsers.
|
|
||||||
*/
|
|
||||||
private fun queryRefreshDevicesList() {
|
|
||||||
refreshSource.post(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handle(action: DevicesAction) {
|
override fun handle(action: DevicesAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
package im.vector.app.features.settings.devices.v2
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
@ -32,16 +34,23 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
|||||||
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||||
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
private val filterDevicesUseCase: FilterDevicesUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(): Flow<List<DeviceFullInfo>> {
|
fun execute(filterType: DeviceManagerFilterType, excludeCurrentDevice: Boolean = false): Flow<List<DeviceFullInfo>> {
|
||||||
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
val deviceFullInfoFlow = combine(
|
val deviceFullInfoFlow = combine(
|
||||||
getCurrentSessionCrossSigningInfoUseCase.execute(),
|
getCurrentSessionCrossSigningInfoUseCase.execute(),
|
||||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||||
session.flow().liveMyDevicesInfo()
|
session.flow().liveMyDevicesInfo()
|
||||||
) { currentSessionCrossSigningInfo, cryptoList, infoList ->
|
) { currentSessionCrossSigningInfo, cryptoList, infoList ->
|
||||||
convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
|
val deviceFullInfoList = convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
|
||||||
|
val excludedDeviceIds = if (excludeCurrentDevice) {
|
||||||
|
listOf(currentSessionCrossSigningInfo.deviceId)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
filterDevicesUseCase.execute(deviceFullInfoList, filterType, excludedDeviceIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceFullInfoFlow.distinctUntilChanged()
|
deviceFullInfoFlow.distinctUntilChanged()
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.app.core.utils.PublishDataSource
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
abstract class VectorSessionsListViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(
|
||||||
|
initialState: S,
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||||
|
) : VectorViewModel<S, VA, VE>(initialState), VerificationService.Listener {
|
||||||
|
|
||||||
|
private val refreshSource = PublishDataSource<Unit>()
|
||||||
|
private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
|
||||||
|
|
||||||
|
init {
|
||||||
|
addVerificationListener()
|
||||||
|
observeRefreshSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
removeVerificationListener()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addVerificationListener() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.cryptoService()
|
||||||
|
?.verificationService()
|
||||||
|
?.addListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeVerificationListener() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.cryptoService()
|
||||||
|
?.verificationService()
|
||||||
|
?.removeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRefreshSource() {
|
||||||
|
refreshSource.stream()
|
||||||
|
.throttleFirst(refreshThrottleDelayMs)
|
||||||
|
.onEach { refreshDevicesUseCase.execute() }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx.state == VerificationTxState.Verified) {
|
||||||
|
refreshDeviceList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the refresh of the devices list.
|
||||||
|
* The devices list is the list of the devices where the user is logged in.
|
||||||
|
* It can be any mobile devices, and any browsers.
|
||||||
|
*/
|
||||||
|
fun refreshDeviceList() {
|
||||||
|
refreshSource.post(Unit)
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,8 @@ import im.vector.app.core.resources.DrawableProvider
|
|||||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||||
import im.vector.app.features.crypto.recover.SetupMode
|
import im.vector.app.features.crypto.recover.SetupMode
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
|
import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||||
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||||
@ -48,7 +49,8 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class VectorSettingsDevicesFragment :
|
class VectorSettingsDevicesFragment :
|
||||||
VectorBaseFragment<FragmentSettingsDevicesBinding>() {
|
VectorBaseFragment<FragmentSettingsDevicesBinding>(),
|
||||||
|
OtherSessionsView.Callback {
|
||||||
|
|
||||||
@Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
|
@Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
|
||||||
|
|
||||||
@ -120,11 +122,7 @@ class VectorSettingsDevicesFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initOtherSessionsView() {
|
private fun initOtherSessionsView() {
|
||||||
views.deviceListOtherSessions.setCallback(object : OtherSessionsController.Callback {
|
views.deviceListOtherSessions.callback = this
|
||||||
override fun onItemClicked(deviceId: String) {
|
|
||||||
navigateToSessionOverview(deviceId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -201,7 +199,11 @@ class VectorSettingsDevicesFragment :
|
|||||||
} else {
|
} else {
|
||||||
views.deviceListHeaderOtherSessions.isVisible = true
|
views.deviceListHeaderOtherSessions.isVisible = true
|
||||||
views.deviceListOtherSessions.isVisible = true
|
views.deviceListOtherSessions.isVisible = true
|
||||||
views.deviceListOtherSessions.render(otherDevices)
|
views.deviceListOtherSessions.render(
|
||||||
|
devices = otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER),
|
||||||
|
totalNumberOfDevices = otherDevices.size,
|
||||||
|
showViewAll = otherDevices.size > NUMBER_OF_OTHER_DEVICES_TO_RENDER
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,4 +254,12 @@ class VectorSettingsDevicesFragment :
|
|||||||
private fun handleLoadingStatus(isLoading: Boolean) {
|
private fun handleLoadingStatus(isLoading: Boolean) {
|
||||||
views.waitingView.root.isVisible = isLoading
|
views.waitingView.root.isVisible = isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOtherSessionClicked(deviceId: String) {
|
||||||
|
navigateToSessionOverview(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAllOtherSessionsClicked() {
|
||||||
|
viewNavigator.navigateToOtherSessions(requireActivity())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.app.features.settings.devices.v2
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
|
||||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -25,4 +26,8 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
|||||||
fun navigateToSessionOverview(context: Context, deviceId: String) {
|
fun navigateToSessionOverview(context: Context, deviceId: String) {
|
||||||
context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
|
context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun navigateToOtherSessions(context: Context) {
|
||||||
|
context.startActivity(OtherSessionsActivity.newIntent(context))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.filter
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
|
||||||
|
import im.vector.app.databinding.BottomSheetDeviceManagerFilterBinding
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class DeviceManagerFilterBottomSheetArgs(
|
||||||
|
val initialFilterType: DeviceManagerFilterType,
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetDeviceManagerFilterBinding>() {
|
||||||
|
|
||||||
|
private val args: DeviceManagerFilterBottomSheetArgs by args()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetDeviceManagerFilterBinding {
|
||||||
|
return BottomSheetDeviceManagerFilterBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
initFilterRadioGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initFilterRadioGroup() {
|
||||||
|
views.filterOptionInactiveTextView.text = resources.getQuantityString(
|
||||||
|
R.plurals.device_manager_filter_option_inactive_description,
|
||||||
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||||
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
|
)
|
||||||
|
|
||||||
|
val radioButtonId = when (args.initialFilterType) {
|
||||||
|
DeviceManagerFilterType.ALL_SESSIONS -> R.id.filterOptionAllSessionsRadioButton
|
||||||
|
DeviceManagerFilterType.VERIFIED -> R.id.filterOptionVerifiedRadioButton
|
||||||
|
DeviceManagerFilterType.UNVERIFIED -> R.id.filterOptionUnverifiedRadioButton
|
||||||
|
DeviceManagerFilterType.INACTIVE -> R.id.filterOptionInactiveRadioButton
|
||||||
|
}
|
||||||
|
views.filterOptionsRadioGroup.check(radioButtonId)
|
||||||
|
|
||||||
|
views.filterOptionVerifiedTextView.debouncedClicks {
|
||||||
|
views.filterOptionsRadioGroup.check(R.id.filterOptionVerifiedRadioButton)
|
||||||
|
}
|
||||||
|
views.filterOptionUnverifiedTextView.debouncedClicks {
|
||||||
|
views.filterOptionsRadioGroup.check(R.id.filterOptionUnverifiedRadioButton)
|
||||||
|
}
|
||||||
|
views.filterOptionInactiveTextView.debouncedClicks {
|
||||||
|
views.filterOptionsRadioGroup.check(R.id.filterOptionInactiveRadioButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
views.filterOptionsRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||||
|
onFilterTypeChanged(checkedId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFilterTypeChanged(checkedId: Int) {
|
||||||
|
val filterType = when (checkedId) {
|
||||||
|
R.id.filterOptionAllSessionsRadioButton -> DeviceManagerFilterType.ALL_SESSIONS
|
||||||
|
R.id.filterOptionVerifiedRadioButton -> DeviceManagerFilterType.VERIFIED
|
||||||
|
R.id.filterOptionUnverifiedRadioButton -> DeviceManagerFilterType.UNVERIFIED
|
||||||
|
R.id.filterOptionInactiveRadioButton -> DeviceManagerFilterType.INACTIVE
|
||||||
|
else -> DeviceManagerFilterType.ALL_SESSIONS
|
||||||
|
}
|
||||||
|
resultListener?.onBottomSheetResult(RESULT_OK, filterType)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(initialFilterType: DeviceManagerFilterType, resultListener: ResultListener): DeviceManagerFilterBottomSheet {
|
||||||
|
return DeviceManagerFilterBottomSheet().apply {
|
||||||
|
this.resultListener = resultListener
|
||||||
|
setArguments(DeviceManagerFilterBottomSheetArgs(initialFilterType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.filter
|
||||||
|
|
||||||
|
enum class DeviceManagerFilterType {
|
||||||
|
ALL_SESSIONS,
|
||||||
|
VERIFIED,
|
||||||
|
UNVERIFIED,
|
||||||
|
INACTIVE,
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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.filter
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FilterDevicesUseCase @Inject constructor() {
|
||||||
|
|
||||||
|
fun execute(
|
||||||
|
devices: List<DeviceFullInfo>,
|
||||||
|
filterType: DeviceManagerFilterType,
|
||||||
|
excludedDeviceIds: List<String> = emptyList(),
|
||||||
|
): List<DeviceFullInfo> {
|
||||||
|
return devices
|
||||||
|
.filter {
|
||||||
|
when (filterType) {
|
||||||
|
DeviceManagerFilterType.ALL_SESSIONS -> true
|
||||||
|
DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse()
|
||||||
|
DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse()
|
||||||
|
DeviceManagerFilterType.INACTIVE -> it.isInactive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter { it.deviceInfo.deviceId !in excludedDeviceIds }
|
||||||
|
}
|
||||||
|
}
|
@ -50,7 +50,7 @@ class OtherSessionsController @Inject constructor(
|
|||||||
text(host.stringProvider.getString(R.string.no_result_placeholder))
|
text(host.stringProvider.getString(R.string.no_result_placeholder))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER).forEach { device ->
|
data.forEach { device ->
|
||||||
val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
|
val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
|
||||||
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
|
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
|
||||||
val description = if (device.isInactive) {
|
val description = if (device.isInactive) {
|
||||||
|
@ -19,8 +19,13 @@ package im.vector.app.features.settings.devices.v2.list
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.databinding.ViewOtherSessionsBinding
|
import im.vector.app.databinding.ViewOtherSessionsBinding
|
||||||
@ -32,30 +37,74 @@ class OtherSessionsView @JvmOverloads constructor(
|
|||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
) : ConstraintLayout(context, attrs, defStyleAttr), OtherSessionsController.Callback {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onOtherSessionClicked(deviceId: String)
|
||||||
|
fun onViewAllOtherSessionsClicked()
|
||||||
|
}
|
||||||
|
|
||||||
@Inject lateinit var otherSessionsController: OtherSessionsController
|
@Inject lateinit var otherSessionsController: OtherSessionsController
|
||||||
|
|
||||||
private val views: ViewOtherSessionsBinding
|
private val views: ViewOtherSessionsBinding
|
||||||
|
private lateinit var recyclerViewDataObserver: RecyclerView.AdapterDataObserver
|
||||||
|
private lateinit var stateRestorer: LayoutManagerStateRestorer
|
||||||
|
private var modelBuildListener: OnModelBuildFinishedListener? = null
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
inflate(context, R.layout.view_other_sessions, this)
|
inflate(context, R.layout.view_other_sessions, this)
|
||||||
views = ViewOtherSessionsBinding.bind(this)
|
views = ViewOtherSessionsBinding.bind(this)
|
||||||
|
|
||||||
|
configureOtherSessionsRecyclerView()
|
||||||
|
|
||||||
|
views.otherSessionsViewAllButton.setOnClickListener {
|
||||||
|
callback?.onViewAllOtherSessionsClicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(devices: List<DeviceFullInfo>) {
|
private fun configureOtherSessionsRecyclerView() {
|
||||||
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
|
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = false)
|
||||||
views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, devices.size)
|
|
||||||
|
val layoutManager = LinearLayoutManager(context)
|
||||||
|
stateRestorer = LayoutManagerStateRestorer(layoutManager)
|
||||||
|
views.otherSessionsRecyclerView.layoutManager = layoutManager
|
||||||
|
layoutManager.recycleChildrenOnDetach = true
|
||||||
|
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||||
|
otherSessionsController.addModelBuildListener(modelBuildListener)
|
||||||
|
|
||||||
|
recyclerViewDataObserver = object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||||
|
super.onItemRangeInserted(positionStart, itemCount)
|
||||||
|
views.otherSessionsRecyclerView.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
otherSessionsController.adapter.registerAdapterDataObserver(recyclerViewDataObserver)
|
||||||
|
|
||||||
|
otherSessionsController.callback = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int, showViewAll: Boolean) {
|
||||||
|
if (showViewAll) {
|
||||||
|
views.otherSessionsViewAllButton.isVisible = true
|
||||||
|
views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices)
|
||||||
|
} else {
|
||||||
|
views.otherSessionsViewAllButton.isVisible = false
|
||||||
|
}
|
||||||
otherSessionsController.setData(devices)
|
otherSessionsController.setData(devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCallback(callback: OtherSessionsController.Callback) {
|
|
||||||
otherSessionsController.callback = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
|
otherSessionsController.removeModelBuildListener(modelBuildListener)
|
||||||
|
modelBuildListener = null
|
||||||
otherSessionsController.callback = null
|
otherSessionsController.callback = null
|
||||||
|
otherSessionsController.adapter.unregisterAdapterDataObserver(recyclerViewDataObserver)
|
||||||
views.otherSessionsRecyclerView.cleanup()
|
views.otherSessionsRecyclerView.cleanup()
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onItemClicked(deviceId: String) {
|
||||||
|
callback?.onOtherSessionClicked(deviceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.extensions.setTextWithColoredPart
|
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||||
import im.vector.app.databinding.ViewSessionsListHeaderBinding
|
import im.vector.app.databinding.ViewSessionsListHeaderBinding
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ class SessionsListHeaderView @JvmOverloads constructor(
|
|||||||
|
|
||||||
private fun setTitle(typedArray: TypedArray) {
|
private fun setTitle(typedArray: TypedArray) {
|
||||||
val title = typedArray.getString(R.styleable.SessionsListHeaderView_sessionsListHeaderTitle)
|
val title = typedArray.getString(R.styleable.SessionsListHeaderView_sessionsListHeaderTitle)
|
||||||
binding.sessionsListHeaderTitle.text = title
|
binding.sessionsListHeaderTitle.setTextOrHide(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setDescription(typedArray: TypedArray) {
|
private fun setDescription(typedArray: TypedArray) {
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
|
|
||||||
|
sealed class OtherSessionsAction : VectorViewModelAction {
|
||||||
|
data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction()
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.core.extensions.addFragment
|
||||||
|
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class OtherSessionsActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
views.toolbar.visibility = View.GONE
|
||||||
|
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(
|
||||||
|
container = views.container,
|
||||||
|
fragmentClass = OtherSessionsFragment::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newIntent(context: Context): Intent {
|
||||||
|
return Intent(context, OtherSessionsActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
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.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.databinding.FragmentOtherSessionsBinding
|
||||||
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class OtherSessionsFragment :
|
||||||
|
VectorBaseFragment<FragmentOtherSessionsBinding>(),
|
||||||
|
VectorBaseBottomSheetDialogFragment.ResultListener,
|
||||||
|
OtherSessionsView.Callback {
|
||||||
|
|
||||||
|
private val viewModel: OtherSessionsViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
|
@Inject lateinit var viewNavigator: OtherSessionsViewNavigator
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
|
||||||
|
return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupToolbar(views.otherSessionsToolbar).allowBack()
|
||||||
|
observeViewEvents()
|
||||||
|
initFilterView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeViewEvents() {
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is OtherSessionsViewEvents.Loading -> showLoading(it.message)
|
||||||
|
is OtherSessionsViewEvents.Failure -> showFailure(it.throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initFilterView() {
|
||||||
|
views.otherSessionsFilterFrameLayout.debouncedClicks {
|
||||||
|
withState(viewModel) { state ->
|
||||||
|
DeviceManagerFilterBottomSheet
|
||||||
|
.newInstance(state.currentFilter, this)
|
||||||
|
.show(requireActivity().supportFragmentManager, "SHOW_DEVICE_MANAGER_FILTER_BOTTOM_SHEET")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
views.otherSessionsClearFilterButton.debouncedClicks {
|
||||||
|
viewModel.handle(OtherSessionsAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
|
||||||
|
}
|
||||||
|
|
||||||
|
views.deviceListOtherSessions.callback = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBottomSheetResult(resultCode: Int, data: Any?) {
|
||||||
|
if (resultCode == RESULT_OK && data != null && data is DeviceManagerFilterType) {
|
||||||
|
viewModel.handle(OtherSessionsAction.FilterDevices(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
if (state.devices is Success) {
|
||||||
|
renderDevices(state.devices(), state.currentFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderDevices(devices: List<DeviceFullInfo>?, currentFilter: DeviceManagerFilterType) {
|
||||||
|
views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
|
||||||
|
views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
|
||||||
|
views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS
|
||||||
|
|
||||||
|
when (currentFilter) {
|
||||||
|
DeviceManagerFilterType.VERIFIED -> {
|
||||||
|
views.otherSessionsSecurityRecommendationView.render(
|
||||||
|
OtherSessionsSecurityRecommendationViewState(
|
||||||
|
title = getString(R.string.device_manager_other_sessions_recommendation_title_verified),
|
||||||
|
description = getString(R.string.device_manager_other_sessions_recommendation_description_verified),
|
||||||
|
imageResourceId = R.drawable.ic_shield_trusted_no_border,
|
||||||
|
imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_trust_background)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_verified_sessions_found)
|
||||||
|
}
|
||||||
|
DeviceManagerFilterType.UNVERIFIED -> {
|
||||||
|
views.otherSessionsSecurityRecommendationView.render(
|
||||||
|
OtherSessionsSecurityRecommendationViewState(
|
||||||
|
title = getString(R.string.device_manager_other_sessions_recommendation_title_unverified),
|
||||||
|
description = getString(R.string.device_manager_other_sessions_recommendation_description_unverified),
|
||||||
|
imageResourceId = R.drawable.ic_shield_warning_no_border,
|
||||||
|
imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_warning_background)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_unverified_sessions_found)
|
||||||
|
}
|
||||||
|
DeviceManagerFilterType.INACTIVE -> {
|
||||||
|
views.otherSessionsSecurityRecommendationView.render(
|
||||||
|
OtherSessionsSecurityRecommendationViewState(
|
||||||
|
title = getString(R.string.device_manager_other_sessions_recommendation_title_inactive),
|
||||||
|
description = resources.getQuantityString(
|
||||||
|
R.plurals.device_manager_other_sessions_recommendation_description_inactive,
|
||||||
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||||
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
|
),
|
||||||
|
imageResourceId = R.drawable.ic_inactive_sessions,
|
||||||
|
imageTintColorResourceId = ThemeUtils.getColor(requireContext(), R.attr.vctr_system)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_inactive_sessions_found)
|
||||||
|
}
|
||||||
|
DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devices.isNullOrEmpty()) {
|
||||||
|
views.deviceListOtherSessions.isVisible = false
|
||||||
|
views.otherSessionsNotFoundLayout.isVisible = true
|
||||||
|
} else {
|
||||||
|
views.deviceListOtherSessions.isVisible = true
|
||||||
|
views.otherSessionsNotFoundLayout.isVisible = false
|
||||||
|
views.deviceListOtherSessions.render(devices = devices, totalNumberOfDevices = devices.size, showViewAll = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOtherSessionClicked(deviceId: String) {
|
||||||
|
viewNavigator.navigateToSessionOverview(
|
||||||
|
context = requireActivity(),
|
||||||
|
deviceId = deviceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAllOtherSessionsClicked() {
|
||||||
|
// NOOP. We don't have this button in this screen
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.res.use
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||||
|
import im.vector.app.databinding.ViewOtherSessionSecurityRecommendationBinding
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class OtherSessionsSecurityRecommendationView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private val views: ViewOtherSessionSecurityRecommendationBinding
|
||||||
|
var onLearnMoreClickListener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.view_other_session_security_recommendation, this)
|
||||||
|
views = ViewOtherSessionSecurityRecommendationBinding.bind(this)
|
||||||
|
|
||||||
|
context.obtainStyledAttributes(
|
||||||
|
attrs,
|
||||||
|
R.styleable.OtherSessionsSecurityRecommendationView,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
).use {
|
||||||
|
setTitle(it)
|
||||||
|
setDescription(it)
|
||||||
|
setImage(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTitle(typedArray: TypedArray) {
|
||||||
|
val title = typedArray.getString(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationTitle)
|
||||||
|
setTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTitle(title: String?) {
|
||||||
|
views.recommendationTitleTextView.text = title
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDescription(typedArray: TypedArray) {
|
||||||
|
val description = typedArray.getString(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationDescription)
|
||||||
|
setDescription(description)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setImage(typedArray: TypedArray) {
|
||||||
|
val imageResource = typedArray.getResourceId(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationImageResource, 0)
|
||||||
|
val backgroundTint = typedArray.getColor(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationImageBackgroundTint, 0)
|
||||||
|
setImageResource(imageResource)
|
||||||
|
setImageBackgroundTint(backgroundTint)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setImageResource(resourceId: Int) {
|
||||||
|
views.recommendationShieldImageView.setImageResource(resourceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setImageBackgroundTint(backgroundTintColor: Int) {
|
||||||
|
views.recommendationShieldImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDescription(description: String?) {
|
||||||
|
val learnMore = context.getString(R.string.action_learn_more)
|
||||||
|
val formattedDescription = buildString {
|
||||||
|
append(description)
|
||||||
|
append(" ")
|
||||||
|
append(learnMore)
|
||||||
|
}
|
||||||
|
|
||||||
|
views.recommendationDescriptionTextView.setTextWithColoredPart(
|
||||||
|
fullText = formattedDescription,
|
||||||
|
coloredPart = learnMore,
|
||||||
|
underline = false
|
||||||
|
) {
|
||||||
|
onLearnMoreClickListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(viewState: OtherSessionsSecurityRecommendationViewState) {
|
||||||
|
setTitle(viewState.title)
|
||||||
|
setDescription(viewState.description)
|
||||||
|
setImageResource(viewState.imageResourceId)
|
||||||
|
setImageBackgroundTint(viewState.imageTintColorResourceId)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
data class OtherSessionsSecurityRecommendationViewState(
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val imageResourceId: Int,
|
||||||
|
val imageTintColorResourceId: Int,
|
||||||
|
)
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class OtherSessionsViewEvents : VectorViewEvents {
|
||||||
|
data class Loading(val message: CharSequence? = null) : OtherSessionsViewEvents()
|
||||||
|
data class Failure(val throwable: Throwable) : OtherSessionsViewEvents()
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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.othersessions
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase
|
||||||
|
import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
|
||||||
|
import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
|
||||||
|
class OtherSessionsViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: OtherSessionsViewState,
|
||||||
|
activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||||
|
refreshDevicesUseCase: RefreshDevicesUseCase
|
||||||
|
) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(
|
||||||
|
initialState, activeSessionHolder, refreshDevicesUseCase
|
||||||
|
) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> {
|
||||||
|
override fun create(initialState: OtherSessionsViewState): OtherSessionsViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MavericksViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
private var observeDevicesJob: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeDevices(initialState.currentFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeDevices(currentFilter: DeviceManagerFilterType) {
|
||||||
|
observeDevicesJob?.cancel()
|
||||||
|
observeDevicesJob = getDeviceFullInfoListUseCase.execute(
|
||||||
|
filterType = currentFilter,
|
||||||
|
excludeCurrentDevice = true
|
||||||
|
)
|
||||||
|
.execute { async ->
|
||||||
|
copy(
|
||||||
|
devices = async,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: OtherSessionsAction) {
|
||||||
|
when (action) {
|
||||||
|
is OtherSessionsAction.FilterDevices -> handleFilterDevices(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFilterDevices(action: OtherSessionsAction.FilterDevices) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
currentFilter = action.filterType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
observeDevices(action.filterType)
|
||||||
|
}
|
||||||
|
}
|
@ -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.othersessions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class OtherSessionsViewNavigator @Inject constructor() {
|
||||||
|
|
||||||
|
fun navigateToSessionOverview(context: Context, deviceId: String) {
|
||||||
|
context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
|
||||||
|
}
|
||||||
|
}
|
@ -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.othersessions
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
|
|
||||||
|
data class OtherSessionsViewState(
|
||||||
|
val devices: Async<List<DeviceFullInfo>> = Uninitialized,
|
||||||
|
val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||||
|
) : MavericksState
|
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="ring"
|
||||||
|
android:innerRadius="0dp"
|
||||||
|
android:thicknessRatio="2"
|
||||||
|
android:useLevel="false">
|
||||||
|
|
||||||
|
<solid android:color="?colorPrimary" />
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="3dp"
|
||||||
|
android:color="?vctr_toolbar_background" />
|
||||||
|
|
||||||
|
</shape>
|
@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingBottom="32dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="6dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/ic_bottom_sheet_handle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle.Medium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/device_manager_filter_bottom_sheet_title" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/filterOptionsRadioGroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layoutDirection="rtl"
|
||||||
|
android:showDividers="none">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/filterOptionAllSessionsRadioButton"
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/device_manager_filter_option_all_sessions" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/filterOptionVerifiedRadioButton"
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/device_manager_filter_option_verified" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filterOptionVerifiedTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/device_manager_filter_option_verified_description" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/filterOptionUnverifiedRadioButton"
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/device_manager_filter_option_unverified" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filterOptionUnverifiedTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/device_manager_filter_option_unverified_description" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/filterOptionInactiveRadioButton"
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/device_manager_filter_option_inactive" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filterOptionInactiveTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end" />
|
||||||
|
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
</LinearLayout>
|
119
vector/src/main/res/layout/fragment_other_sessions.xml
Normal file
119
vector/src/main/res/layout/fragment_other_sessions.xml
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appBarLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/otherSessionsToolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:navigationIcon="@drawable/ic_back_24dp"
|
||||||
|
app:title="@string/device_manager_sessions_other_title">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/otherSessionsFilterFrameLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/a11y_device_manager_filter"
|
||||||
|
android:src="@drawable/ic_filter" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/otherSessionsFilterBadgeImageView"
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/circle_with_transparent_border" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
|
||||||
|
android:id="@+id/deviceListHeaderOtherSessions"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
|
||||||
|
app:sessionsListHeaderTitle=""
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
||||||
|
|
||||||
|
<im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsSecurityRecommendationView
|
||||||
|
android:id="@+id/otherSessionsSecurityRecommendationView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions"
|
||||||
|
app:otherSessionsRecommendationDescription="@string/device_manager_other_sessions_recommendation_description_unverified"
|
||||||
|
app:otherSessionsRecommendationImageBackgroundTint="@color/shield_color_warning_background"
|
||||||
|
app:otherSessionsRecommendationImageResource="@drawable/ic_shield_warning_no_border"
|
||||||
|
app:otherSessionsRecommendationTitle="@string/device_manager_other_sessions_recommendation_title_unverified"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/otherSessionsNotFoundLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="72dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/otherSessionsSecurityRecommendationView">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/otherSessionsNotFoundTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="@string/device_manager_other_sessions_no_verified_sessions_found" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/otherSessionsClearFilterButton"
|
||||||
|
style="@style/Widget.Vector.Button.Text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:gravity="start"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:text="@string/device_manager_other_sessions_clear_filter" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||||
|
android:id="@+id/deviceListOtherSessions"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/otherSessionsSecurityRecommendationView" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -90,8 +90,8 @@
|
|||||||
android:id="@+id/deviceListHeaderOtherSessions"
|
android:id="@+id/deviceListHeaderOtherSessions"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:sessionsListHeaderDescription="@string/settings_sessions_other_description"
|
app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
|
||||||
app:sessionsListHeaderTitle="@string/settings_sessions_other_title"
|
app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" />
|
app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" />
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/recommendationShieldImageView"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@drawable/bg_security_recommendation_shield"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:backgroundTint="@color/shield_color_warning_background"
|
||||||
|
tools:src="@drawable/ic_shield_warning_no_border" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recommendationTitleTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/recommendationShieldImageView"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/recommendationShieldImageView"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/recommendationShieldImageView"
|
||||||
|
tools:text="@string/device_manager_other_sessions_recommendation_title_unverified" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recommendationDescriptionTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="40dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/recommendationTitleTextView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/recommendationTitleTextView"
|
||||||
|
tools:text="@string/device_manager_other_sessions_recommendation_description_unverified" />
|
||||||
|
|
||||||
|
</merge>
|
@ -23,10 +23,10 @@
|
|||||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||||
android:layout_marginTop="18.5dp"
|
android:layout_marginTop="18.5dp"
|
||||||
android:layout_marginEnd="40dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/sessions_list_header_title"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title"
|
app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title"
|
||||||
tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />
|
tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />
|
||||||
</merge>
|
</merge>
|
||||||
|
@ -52,8 +52,8 @@ class DevicesViewModelTest {
|
|||||||
fakeActiveSessionHolder.instance,
|
fakeActiveSessionHolder.instance,
|
||||||
getCurrentSessionCrossSigningInfoUseCase,
|
getCurrentSessionCrossSigningInfoUseCase,
|
||||||
getDeviceFullInfoListUseCase,
|
getDeviceFullInfoListUseCase,
|
||||||
refreshDevicesUseCase,
|
|
||||||
refreshDevicesOnCryptoDevicesChangeUseCase,
|
refreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
|
refreshDevicesUseCase,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ class DevicesViewModelTest {
|
|||||||
)
|
)
|
||||||
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
||||||
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
||||||
every { getDeviceFullInfoListUseCase.execute() } returns deviceFullInfoListFlow
|
every { getDeviceFullInfoListUseCase.execute(any(), any()) } returns deviceFullInfoListFlow
|
||||||
return deviceFullInfoList
|
return deviceFullInfoList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.app.features.settings.devices.v2
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||||
|
import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
import im.vector.app.test.test
|
import im.vector.app.test.test
|
||||||
@ -47,12 +49,14 @@ class GetDeviceFullInfoListUseCaseTest {
|
|||||||
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
|
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
|
||||||
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
||||||
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
||||||
|
private val filterDevicesUseCase = mockk<FilterDevicesUseCase>()
|
||||||
|
|
||||||
private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
|
private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
|
||||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||||
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
|
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
|
||||||
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
||||||
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
filterDevicesUseCase = filterDevicesUseCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -117,9 +121,10 @@ class GetDeviceFullInfoListUseCaseTest {
|
|||||||
isInactive = false
|
isInactive = false
|
||||||
)
|
)
|
||||||
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
|
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
|
||||||
|
every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val result = getDeviceFullInfoListUseCase.execute()
|
val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false)
|
||||||
.test(this)
|
.test(this)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@ -144,7 +149,7 @@ class GetDeviceFullInfoListUseCaseTest {
|
|||||||
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val result = getDeviceFullInfoListUseCase.execute()
|
val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false)
|
||||||
.test(this)
|
.test(this)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.app.features.settings.devices.v2
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
|
||||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||||
import im.vector.app.test.fakes.FakeContext
|
import im.vector.app.test.fakes.FakeContext
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@ -38,6 +39,7 @@ class VectorSettingsDevicesViewNavigatorTest {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockkObject(SessionOverviewActivity.Companion)
|
mockkObject(SessionOverviewActivity.Companion)
|
||||||
|
mockkObject(OtherSessionsActivity.Companion)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -57,9 +59,27 @@ class VectorSettingsDevicesViewNavigatorTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an intent when navigating to other sessions list then it starts the correct activity`() {
|
||||||
|
val intent = givenIntentForOtherSessions()
|
||||||
|
context.givenStartActivity(intent)
|
||||||
|
|
||||||
|
vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.instance.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun givenIntentForSessionOverview(sessionId: String): Intent {
|
private fun givenIntentForSessionOverview(sessionId: String): Intent {
|
||||||
val intent = mockk<Intent>()
|
val intent = mockk<Intent>()
|
||||||
every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns intent
|
every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns intent
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun givenIntentForOtherSessions(): Intent {
|
||||||
|
val intent = mockk<Intent>()
|
||||||
|
every { OtherSessionsActivity.newIntent(context.instance) } returns intent
|
||||||
|
return intent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.filter
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldContainAll
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
private val activeVerifiedDevice = DeviceFullInfo(
|
||||||
|
deviceInfo = DeviceInfo(deviceId = "ACTIVE_VERIFIED_DEVICE"),
|
||||||
|
cryptoDeviceInfo = CryptoDeviceInfo(
|
||||||
|
userId = "USER_ID_1",
|
||||||
|
deviceId = "ACTIVE_VERIFIED_DEVICE",
|
||||||
|
trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||||
|
),
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
isInactive = false
|
||||||
|
)
|
||||||
|
private val inactiveVerifiedDevice = DeviceFullInfo(
|
||||||
|
deviceInfo = DeviceInfo(deviceId = "INACTIVE_VERIFIED_DEVICE"),
|
||||||
|
cryptoDeviceInfo = CryptoDeviceInfo(
|
||||||
|
userId = "USER_ID_1",
|
||||||
|
deviceId = "INACTIVE_VERIFIED_DEVICE",
|
||||||
|
trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||||
|
),
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
isInactive = true
|
||||||
|
)
|
||||||
|
private val activeUnverifiedDevice = DeviceFullInfo(
|
||||||
|
deviceInfo = DeviceInfo(deviceId = "ACTIVE_UNVERIFIED_DEVICE"),
|
||||||
|
cryptoDeviceInfo = CryptoDeviceInfo(
|
||||||
|
userId = "USER_ID_1",
|
||||||
|
deviceId = "ACTIVE_UNVERIFIED_DEVICE",
|
||||||
|
trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||||
|
),
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||||
|
isInactive = false
|
||||||
|
)
|
||||||
|
private val inactiveUnverifiedDevice = DeviceFullInfo(
|
||||||
|
deviceInfo = DeviceInfo(deviceId = "INACTIVE_UNVERIFIED_DEVICE"),
|
||||||
|
cryptoDeviceInfo = CryptoDeviceInfo(
|
||||||
|
userId = "USER_ID_1",
|
||||||
|
deviceId = "INACTIVE_UNVERIFIED_DEVICE",
|
||||||
|
trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||||
|
),
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||||
|
isInactive = true
|
||||||
|
)
|
||||||
|
|
||||||
|
private val devices = listOf(
|
||||||
|
activeVerifiedDevice,
|
||||||
|
inactiveVerifiedDevice,
|
||||||
|
activeUnverifiedDevice,
|
||||||
|
inactiveUnverifiedDevice,
|
||||||
|
)
|
||||||
|
|
||||||
|
class FilterDevicesUseCaseTest {
|
||||||
|
|
||||||
|
private val filterDevicesUseCase = FilterDevicesUseCase()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a device list when filter type is ALL_SESSIONS then returns the same list`() {
|
||||||
|
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.ALL_SESSIONS, emptyList())
|
||||||
|
|
||||||
|
filteredDeviceList.size shouldBeEqualTo devices.size
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a device list when filter type is VERIFIED then returns only verified devices`() {
|
||||||
|
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.VERIFIED, emptyList())
|
||||||
|
|
||||||
|
filteredDeviceList.size shouldBeEqualTo 2
|
||||||
|
filteredDeviceList shouldContainAll listOf(activeVerifiedDevice, inactiveVerifiedDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a device list when filter type is UNVERIFIED then returns only unverified devices`() {
|
||||||
|
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
|
||||||
|
|
||||||
|
filteredDeviceList.size shouldBeEqualTo 2
|
||||||
|
filteredDeviceList shouldContainAll listOf(activeUnverifiedDevice, inactiveUnverifiedDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a device list when filter type is INACTIVE then returns only inactive devices`() {
|
||||||
|
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.INACTIVE, emptyList())
|
||||||
|
|
||||||
|
filteredDeviceList.size shouldBeEqualTo 2
|
||||||
|
filteredDeviceList shouldContainAll listOf(inactiveVerifiedDevice, inactiveUnverifiedDevice)
|
||||||
|
}
|
||||||
|
}
|
@ -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.othersessions
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||||
|
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_DEVICE_ID = "A_DEVICE_ID"
|
||||||
|
|
||||||
|
class OtherSessionsViewNavigatorTest {
|
||||||
|
|
||||||
|
private val context = FakeContext()
|
||||||
|
private val otherSessionsViewNavigator = OtherSessionsViewNavigator()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkObject(SessionOverviewActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a device id when navigating to overview then it starts the correct activity`() {
|
||||||
|
val intent = givenIntentForDeviceOverview(A_DEVICE_ID)
|
||||||
|
context.givenStartActivity(intent)
|
||||||
|
|
||||||
|
otherSessionsViewNavigator.navigateToSessionOverview(context.instance, A_DEVICE_ID)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.instance.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenIntentForDeviceOverview(deviceId: String): Intent {
|
||||||
|
val intent = mockk<Intent>()
|
||||||
|
every { SessionOverviewActivity.newIntent(context.instance, deviceId) } returns intent
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user