mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 04:08:44 +03:00
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
54 changed files with 1450 additions and 122 deletions
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="home_layout_preferences">Preferències de disseny</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="settings_sessions_other_title">Altres sessions</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="device_manager_sessions_other_title">Altres sessions</string>
|
||||
<string name="settings_sessions_list">Sessions</string>
|
||||
<string name="a11y_open_spaces">Obre la llista d\'espais</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="all_chats">Všechny konverzace</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="settings_sessions_other_title">Ostatní relace</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="device_manager_sessions_other_title">Ostatní relace</string>
|
||||
<string name="settings_sessions_list">Relace</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>
|
||||
|
|
|
@ -2587,8 +2587,8 @@
|
|||
<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="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="settings_sessions_other_title">Andere Sitzungen</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="device_manager_sessions_other_title">Andere Sitzungen</string>
|
||||
<string name="settings_sessions_list">Sitzungen</string>
|
||||
<string name="a11y_open_spaces">Space-Liste öffnen</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="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="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="settings_sessions_other_title">Muud sessioonid</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="device_manager_sessions_other_title">Muud sessioonid</string>
|
||||
<string name="settings_sessions_list">Sessionid</string>
|
||||
<string name="a11y_open_spaces">Ava kogukondade loend</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="all_chats">تمامی گپها</string>
|
||||
<string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشستها (ن۲، دحت)</string>
|
||||
<string name="settings_sessions_other_description">برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.</string>
|
||||
<string name="settings_sessions_other_title">دیگر نشستها</string>
|
||||
<string name="device_manager_sessions_other_description">برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.</string>
|
||||
<string name="device_manager_sessions_other_title">دیگر نشستها</string>
|
||||
<string name="settings_sessions_list">نشستها</string>
|
||||
<string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string>
|
||||
<string name="a11y_create_message">ایجاد اتاق یا گفتوگویی جدید</string>
|
||||
|
|
|
@ -2601,8 +2601,8 @@
|
|||
<string name="a11y_open_settings">Ouvrir les paramètres</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="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="settings_sessions_other_title">Autres sessions</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="device_manager_sessions_other_title">Autres sessions</string>
|
||||
<string name="settings_sessions_list">Sessions</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>
|
||||
|
|
|
@ -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_mobile">Mobil</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="settings_sessions_other_title">Más munkamenetek</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="device_manager_sessions_other_title">Más munkamenetek</string>
|
||||
<string name="settings_sessions_list">Munkamenetek</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>
|
||||
|
|
|
@ -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="all_chats">Semua Obrolan</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="settings_sessions_other_title">Sesi lainnya</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="device_manager_sessions_other_title">Sesi lainnya</string>
|
||||
<string name="settings_sessions_list">Sesi</string>
|
||||
<string name="a11y_open_spaces">Buka daftar space</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="all_chats">Tutte le chat</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="settings_sessions_other_title">Altre sessioni</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="device_manager_sessions_other_title">Altre sessioni</string>
|
||||
<string name="settings_sessions_list">Sessioni</string>
|
||||
<string name="a11y_open_spaces">Apri elenco spazi</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
|
||||
\nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven.</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="settings_sessions_other_title">Andere sessies</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="device_manager_sessions_other_title">Andere sessies</string>
|
||||
<string name="settings_sessions_list">Sessies</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>
|
||||
|
|
|
@ -2697,8 +2697,8 @@
|
|||
<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>
|
||||
<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="settings_sessions_other_title">Inne sesje</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="device_manager_sessions_other_title">Inne sesje</string>
|
||||
<string name="settings_sessions_list">Sesje</string>
|
||||
<string name="a11y_open_spaces">Lista otwartych przestrzeni</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="all_chats">Todos os Chats</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="settings_sessions_other_title">Outras sessões</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="device_manager_sessions_other_title">Outras 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_create_message">Criar uma nova conversa ou sala</string>
|
||||
|
|
|
@ -2660,8 +2660,8 @@
|
|||
<string name="location_share_loading_map_error">Не удалось загрузить карту
|
||||
\nВозможно, этот домашний сервер не настроен для отображения карт.</string>
|
||||
<string name="all_chats">Все беседы</string>
|
||||
<string name="settings_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
|
||||
<string name="settings_sessions_other_title">Другие сессии</string>
|
||||
<string name="device_manager_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
|
||||
<string name="device_manager_sessions_other_title">Другие сессии</string>
|
||||
<string name="settings_sessions_list">Сессии</string>
|
||||
<string name="a11y_create_message">Создать беседу или комнату</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="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="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="settings_sessions_other_title">Iné relácie</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="device_manager_sessions_other_title">Iné relácie</string>
|
||||
<string name="settings_sessions_list">Relácie</string>
|
||||
<string name="a11y_open_spaces">Otvoriť zoznam priestorov</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="all_chats">Усі бесіди</string>
|
||||
<string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string>
|
||||
<string name="settings_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
|
||||
<string name="settings_sessions_other_title">Інші сеанси</string>
|
||||
<string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
|
||||
<string name="device_manager_sessions_other_title">Інші сеанси</string>
|
||||
<string name="settings_sessions_list">Сеанси</string>
|
||||
<string name="a11y_open_spaces">Відкрити список кімнат</string>
|
||||
<string name="a11y_create_message">Створити нову розмову або кімнату</string>
|
||||
|
|
|
@ -2551,8 +2551,8 @@
|
|||
<string name="a11y_open_settings">打开设置</string>
|
||||
<string name="all_chats">全部聊天</string>
|
||||
<string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string>
|
||||
<string name="settings_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
|
||||
<string name="settings_sessions_other_title">其他会话</string>
|
||||
<string name="device_manager_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
|
||||
<string name="device_manager_sessions_other_title">其他会话</string>
|
||||
<string name="settings_sessions_list">会话</string>
|
||||
<string name="a11y_open_spaces">打开空间列表</string>
|
||||
<string name="a11y_create_message">创建新对话或房间</string>
|
||||
|
|
|
@ -2551,8 +2551,8 @@
|
|||
<string name="a11y_open_settings">開啟設定</string>
|
||||
<string name="all_chats">所有聊天</string>
|
||||
<string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string>
|
||||
<string name="settings_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
|
||||
<string name="settings_sessions_other_title">其他工作階段</string>
|
||||
<string name="device_manager_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
|
||||
<string name="device_manager_sessions_other_title">其他工作階段</string>
|
||||
<string name="settings_sessions_list">工作階段</string>
|
||||
<string name="a11y_open_spaces">開啟空間清單</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_signout_device">Sign out of this session</string>
|
||||
<string name="settings_sessions_list">Sessions</string>
|
||||
<string name="settings_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_title">Other sessions</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_version">Server version</string>
|
||||
|
@ -3265,6 +3265,31 @@
|
|||
<string name="device_manager_device_title">Device</string>
|
||||
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
||||
<string name="device_manager_session_last_activity">Last activity %1$s</string>
|
||||
<string name="device_manager_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_description">Application, device, and activity information.</string>
|
||||
<string name="device_manager_session_details_session_name">Session name</string>
|
||||
|
|
|
@ -141,6 +141,7 @@
|
|||
|
||||
<!-- Shield colors -->
|
||||
<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_warning">#FF4B55</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.release.ReleaseNotesActivity"/>
|
||||
<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"/>
|
||||
|
||||
<!-- 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.DevicesViewModel
|
||||
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.devtools.AccountDataViewModel
|
||||
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
||||
|
@ -643,6 +644,11 @@ interface MavericksViewModelModule {
|
|||
@MavericksViewModelKey(SessionOverviewViewModel::class)
|
||||
fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(OtherSessionsViewModel::class)
|
||||
fun otherSessionsViewModelFactory(factory: OtherSessionsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(SessionDetailsViewModel::class)
|
||||
|
|
|
@ -24,26 +24,20 @@ 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.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.PublishDataSource
|
||||
import im.vector.lib.core.utils.flow.throttleFirst
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
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(
|
||||
@Assisted initialState: DevicesViewState,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
activeSessionHolder: ActiveSessionHolder,
|
||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||
private val refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||
) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState), VerificationService.Listener {
|
||||
refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
|
||||
|
@ -52,35 +46,11 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
|
||||
companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private val refreshSource = PublishDataSource<Unit>()
|
||||
private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
|
||||
|
||||
init {
|
||||
addVerificationListener()
|
||||
observeCurrentSessionCrossSigningInfo()
|
||||
observeDevices()
|
||||
observeRefreshSource()
|
||||
refreshDevicesOnCryptoDevicesChange()
|
||||
queryRefreshDevicesList()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
removeVerificationListener()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
private fun addVerificationListener() {
|
||||
activeSessionHolder.getSafeActiveSession()
|
||||
?.cryptoService()
|
||||
?.verificationService()
|
||||
?.addListener(this)
|
||||
}
|
||||
|
||||
private fun removeVerificationListener() {
|
||||
activeSessionHolder.getSafeActiveSession()
|
||||
?.cryptoService()
|
||||
?.verificationService()
|
||||
?.removeListener(this)
|
||||
refreshDeviceList()
|
||||
}
|
||||
|
||||
private fun observeCurrentSessionCrossSigningInfo() {
|
||||
|
@ -94,7 +64,10 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun observeDevices() {
|
||||
getDeviceFullInfoListUseCase.execute()
|
||||
getDeviceFullInfoListUseCase.execute(
|
||||
filterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
excludeCurrentDevice = false
|
||||
)
|
||||
.execute { async ->
|
||||
if (async is Success) {
|
||||
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) {
|
||||
when (action) {
|
||||
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
@ -32,16 +34,23 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
|||
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||
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 ->
|
||||
val deviceFullInfoFlow = combine(
|
||||
getCurrentSessionCrossSigningInfoUseCase.execute(),
|
||||
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||
session.flow().liveMyDevicesInfo()
|
||||
) { 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()
|
||||
|
|
|
@ -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.features.crypto.recover.SetupMode
|
||||
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.SecurityRecommendationViewState
|
||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||
|
@ -48,7 +49,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
@AndroidEntryPoint
|
||||
class VectorSettingsDevicesFragment :
|
||||
VectorBaseFragment<FragmentSettingsDevicesBinding>() {
|
||||
VectorBaseFragment<FragmentSettingsDevicesBinding>(),
|
||||
OtherSessionsView.Callback {
|
||||
|
||||
@Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
|
||||
|
||||
|
@ -120,11 +122,7 @@ class VectorSettingsDevicesFragment :
|
|||
}
|
||||
|
||||
private fun initOtherSessionsView() {
|
||||
views.deviceListOtherSessions.setCallback(object : OtherSessionsController.Callback {
|
||||
override fun onItemClicked(deviceId: String) {
|
||||
navigateToSessionOverview(deviceId)
|
||||
}
|
||||
})
|
||||
views.deviceListOtherSessions.callback = this
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -201,7 +199,11 @@ class VectorSettingsDevicesFragment :
|
|||
} else {
|
||||
views.deviceListHeaderOtherSessions.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) {
|
||||
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
|
||||
|
||||
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 javax.inject.Inject
|
||||
|
||||
|
@ -25,4 +26,8 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
|||
fun navigateToSessionOverview(context: Context, deviceId: String) {
|
||||
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))
|
||||
}
|
||||
} 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 formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
|
||||
val description = if (device.isInactive) {
|
||||
|
|
|
@ -19,8 +19,13 @@ package im.vector.app.features.settings.devices.v2.list
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
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 im.vector.app.R
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.databinding.ViewOtherSessionsBinding
|
||||
|
@ -32,30 +37,74 @@ class OtherSessionsView @JvmOverloads constructor(
|
|||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
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
|
||||
|
||||
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 {
|
||||
inflate(context, R.layout.view_other_sessions, this)
|
||||
views = ViewOtherSessionsBinding.bind(this)
|
||||
|
||||
configureOtherSessionsRecyclerView()
|
||||
|
||||
views.otherSessionsViewAllButton.setOnClickListener {
|
||||
callback?.onViewAllOtherSessionsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
fun render(devices: List<DeviceFullInfo>) {
|
||||
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
|
||||
views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, devices.size)
|
||||
private fun configureOtherSessionsRecyclerView() {
|
||||
views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = false)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun setCallback(callback: OtherSessionsController.Callback) {
|
||||
otherSessionsController.callback = callback
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
otherSessionsController.removeModelBuildListener(modelBuildListener)
|
||||
modelBuildListener = null
|
||||
otherSessionsController.callback = null
|
||||
otherSessionsController.adapter.unregisterAdapterDataObserver(recyclerViewDataObserver)
|
||||
views.otherSessionsRecyclerView.cleanup()
|
||||
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.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||
import im.vector.app.databinding.ViewSessionsListHeaderBinding
|
||||
|
||||
|
@ -54,7 +55,7 @@ class SessionsListHeaderView @JvmOverloads constructor(
|
|||
|
||||
private fun setTitle(typedArray: TypedArray) {
|
||||
val title = typedArray.getString(R.styleable.SessionsListHeaderView_sessionsListHeaderTitle)
|
||||
binding.sessionsListHeaderTitle.text = title
|
||||
binding.sessionsListHeaderTitle.setTextOrHide(title)
|
||||
}
|
||||
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:sessionsListHeaderDescription="@string/settings_sessions_other_description"
|
||||
app:sessionsListHeaderTitle="@string/settings_sessions_other_title"
|
||||
app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
|
||||
app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginTop="18.5dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
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"
|
||||
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>
|
||||
|
|
|
@ -52,8 +52,8 @@ class DevicesViewModelTest {
|
|||
fakeActiveSessionHolder.instance,
|
||||
getCurrentSessionCrossSigningInfoUseCase,
|
||||
getDeviceFullInfoListUseCase,
|
||||
refreshDevicesUseCase,
|
||||
refreshDevicesOnCryptoDevicesChangeUseCase,
|
||||
refreshDevicesUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ class DevicesViewModelTest {
|
|||
)
|
||||
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
||||
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
||||
every { getDeviceFullInfoListUseCase.execute() } returns deviceFullInfoListFlow
|
||||
every { getDeviceFullInfoListUseCase.execute(any(), any()) } returns deviceFullInfoListFlow
|
||||
return deviceFullInfoList
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
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.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.test
|
||||
|
@ -47,12 +49,14 @@ class GetDeviceFullInfoListUseCaseTest {
|
|||
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
|
||||
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
||||
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
||||
private val filterDevicesUseCase = mockk<FilterDevicesUseCase>()
|
||||
|
||||
private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
|
||||
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
||||
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
||||
filterDevicesUseCase = filterDevicesUseCase,
|
||||
)
|
||||
|
||||
@Before
|
||||
|
@ -117,9 +121,10 @@ class GetDeviceFullInfoListUseCaseTest {
|
|||
isInactive = false
|
||||
)
|
||||
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
|
||||
every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult
|
||||
|
||||
// When
|
||||
val result = getDeviceFullInfoListUseCase.execute()
|
||||
val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false)
|
||||
.test(this)
|
||||
|
||||
// Then
|
||||
|
@ -144,7 +149,7 @@ class GetDeviceFullInfoListUseCaseTest {
|
|||
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||
|
||||
// When
|
||||
val result = getDeviceFullInfoListUseCase.execute()
|
||||
val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false)
|
||||
.test(this)
|
||||
|
||||
// Then
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
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.test.fakes.FakeContext
|
||||
import io.mockk.every
|
||||
|
@ -38,6 +39,7 @@ class VectorSettingsDevicesViewNavigatorTest {
|
|||
@Before
|
||||
fun setUp() {
|
||||
mockkObject(SessionOverviewActivity.Companion)
|
||||
mockkObject(OtherSessionsActivity.Companion)
|
||||
}
|
||||
|
||||
@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 {
|
||||
val intent = mockk<Intent>()
|
||||
every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns 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…
Add table
Reference in a new issue