You've already forked element-android
mirror of
https://github.com/vector-im/element-android.git
synced 2025-07-31 07:04:23 +03:00
Merge branch 'develop' into feature/bca/rust_flavor
This commit is contained in:
1
changelog.d/7864.sdk
Normal file
1
changelog.d/7864.sdk
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Poll] Adding PollHistoryService
|
1
changelog.d/7864.wip
Normal file
1
changelog.d/7864.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Poll] History list: unmock data
|
1
changelog.d/8005.sdk
Normal file
1
changelog.d/8005.sdk
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Push rules] Call /actions api before /enabled api
|
@ -27,7 +27,7 @@ def jjwt = "0.11.5"
|
|||||||
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
||||||
// the whole commit which set version 0.16.0-SNAPSHOT
|
// the whole commit which set version 0.16.0-SNAPSHOT
|
||||||
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
||||||
def sentry = "6.12.1"
|
def sentry = "6.13.0"
|
||||||
// Use 1.6.0 alpha to fix issue with test
|
// Use 1.6.0 alpha to fix issue with test
|
||||||
def fragment = "1.6.0-alpha04"
|
def fragment = "1.6.0-alpha04"
|
||||||
// Testing
|
// Testing
|
||||||
|
2
fastlane/metadata/android/ru-RU/changelogs/40105110.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/40105110.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Главные изменения в этой версии: Новый полноэкранный режим в улучшенном редакторе текста и исправления багов.
|
||||||
|
Полный список: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/ru-RU/changelogs/40105140.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/40105140.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Главные изменения в этой версии: Обсуждения включены по умолчанию.
|
||||||
|
Полный список: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/ru-RU/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Главные изменения в этой версии: Обсуждения включены по умолчанию.
|
||||||
|
Полный список: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/ru-RU/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Главные изменения в этой версии: Обсуждения включены по умолчанию.
|
||||||
|
Полный список: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/ru-RU/changelogs/40105200.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/40105200.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Главные изменения в этой версии: Устранения багов!
|
||||||
|
Полный список: https://github.com/vector-im/element-android/releases
|
@ -2978,4 +2978,5 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Hlasovou zprávu nelze spustit, protože právě nahráváte živé vysílání. Ukončete prosím živé vysílání, abyste mohli začít nahrávat hlasovou zprávu</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Hlasovou zprávu nelze spustit, protože právě nahráváte živé vysílání. Ukončete prosím živé vysílání, abyste mohli začít nahrávat hlasovou zprávu</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Nelze spustit hlasovou zprávu</string>
|
<string name="error_voice_message_broadcast_in_progress">Nelze spustit hlasovou zprávu</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Chyba připojení - nahrávání pozastaveno</string>
|
||||||
</resources>
|
</resources>
|
@ -2917,4 +2917,5 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Du kannst keine Sprachnachricht beginnen, da du im Moment eine Echtzeitübertragung aufzeichnest. Bitte beende deine Sprachübertragung, um ein Gespräch zu beginnen</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Du kannst keine Sprachnachricht beginnen, da du im Moment eine Echtzeitübertragung aufzeichnest. Bitte beende deine Sprachübertragung, um ein Gespräch zu beginnen</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Kann Sprachnachricht nicht beginnen</string>
|
<string name="error_voice_message_broadcast_in_progress">Kann Sprachnachricht nicht beginnen</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Verbindungsfehler − Aufnahme pausiert</string>
|
||||||
</resources>
|
</resources>
|
@ -2909,4 +2909,5 @@
|
|||||||
<string name="room_polls_loading_error">Viga küsitluste laadimisel.</string>
|
<string name="room_polls_loading_error">Viga küsitluste laadimisel.</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Häälsõnumi esitamine ei õnnestu</string>
|
<string name="error_voice_message_broadcast_in_progress">Häälsõnumi esitamine ei õnnestu</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Kuna sa hetkel salvestad ringhäälingukõnet, siis häälsõnumi salvestamine või esitamine ei õnnestu. Selleks palun lõpeta ringhäälingukõne</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Kuna sa hetkel salvestad ringhäälingukõnet, siis häälsõnumi salvestamine või esitamine ei õnnestu. Selleks palun lõpeta ringhäälingukõne</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Viga võrguühenduses - salvestamine on peatatud</string>
|
||||||
</resources>
|
</resources>
|
@ -2918,4 +2918,5 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">از آنجا که در حال ضبط پخشی زندهاید، نمیتوانید پیامی صوتی را آغاز کنید. لطفاً برای آغاز ضبط یک پیام صوتی، پخش زندهتان را پایان دهید</string>
|
<string name="error_voice_message_broadcast_in_progress_message">از آنجا که در حال ضبط پخشی زندهاید، نمیتوانید پیامی صوتی را آغاز کنید. لطفاً برای آغاز ضبط یک پیام صوتی، پخش زندهتان را پایان دهید</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">نمیتوان پخش صوتی را آغاز کرد</string>
|
<string name="error_voice_message_broadcast_in_progress">نمیتوان پخش صوتی را آغاز کرد</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">خطای اتّصال - ضبط مکث شد</string>
|
||||||
</resources>
|
</resources>
|
@ -2918,4 +2918,5 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Vous ne pouvez pas commencer un message vocal car vous êtes en train d’enregistrer une diffusion en direct. Veuillez terminer cette diffusion pour commencer un message vocal</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Vous ne pouvez pas commencer un message vocal car vous êtes en train d’enregistrer une diffusion en direct. Veuillez terminer cette diffusion pour commencer un message vocal</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Impossible de démarrer un message vocal</string>
|
<string name="error_voice_message_broadcast_in_progress">Impossible de démarrer un message vocal</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Erreur de connexion – Enregistrement en pause</string>
|
||||||
</resources>
|
</resources>
|
@ -2918,4 +2918,5 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Nem lehet hang üzenetet indítani élő közvetítés felvétele közben. Az élő közvetítés bejezése szükséges a hang üzenet indításához</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Nem lehet hang üzenetet indítani élő közvetítés felvétele közben. Az élő közvetítés bejezése szükséges a hang üzenet indításához</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Hang üzenetet nem lehet elindítani</string>
|
<string name="error_voice_message_broadcast_in_progress">Hang üzenetet nem lehet elindítani</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Kapcsolódási hiba – Felvétel szüneteltetve</string>
|
||||||
</resources>
|
</resources>
|
@ -2858,4 +2858,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
|
|||||||
<item quantity="other">Tidak ada pemungutan suara aktif %1$d hari terakhir.
|
<item quantity="other">Tidak ada pemungutan suara aktif %1$d hari terakhir.
|
||||||
\nMuat lebih banyak pemungutan suara untuk melihat pemungutan suara untuk hari sebelumnya.</item>
|
\nMuat lebih banyak pemungutan suara untuk melihat pemungutan suara untuk hari sebelumnya.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Kesalahan koneksi - Perekaman dijeda</string>
|
||||||
|
<string name="error_voice_message_broadcast_in_progress_message">Anda tidak dapat memulai sebuah pesan suara karena Anda saat ini merekam sebuah siaran langsung. Silakan mengakhiri siaran langsung Anda untuk memulai merekam sebuah pesan suara</string>
|
||||||
|
<string name="error_voice_message_broadcast_in_progress">Tidak dapat memulai pesan suara</string>
|
||||||
</resources>
|
</resources>
|
@ -198,7 +198,7 @@
|
|||||||
<string name="room_settings_set_main_address">メインアドレスとして設定</string>
|
<string name="room_settings_set_main_address">メインアドレスとして設定</string>
|
||||||
<string name="room_settings_unset_main_address">メインアドレスとしての設定を解除</string>
|
<string name="room_settings_unset_main_address">メインアドレスとしての設定を解除</string>
|
||||||
<string name="device_manager_session_details_session_id">セッションID</string>
|
<string name="device_manager_session_details_session_id">セッションID</string>
|
||||||
<string name="font_size">文字の大きさ</string>
|
<string name="font_size">フォントの大きさ</string>
|
||||||
<string name="tiny">とても小さい</string>
|
<string name="tiny">とても小さい</string>
|
||||||
<string name="small">小さい</string>
|
<string name="small">小さい</string>
|
||||||
<string name="normal">標準</string>
|
<string name="normal">標準</string>
|
||||||
@ -2391,7 +2391,7 @@
|
|||||||
<string name="invites_title">招待</string>
|
<string name="invites_title">招待</string>
|
||||||
<string name="device_manager_push_notifications_title">プッシュ通知</string>
|
<string name="device_manager_push_notifications_title">プッシュ通知</string>
|
||||||
<string name="device_manager_session_rename_edit_hint">セッション名</string>
|
<string name="device_manager_session_rename_edit_hint">セッション名</string>
|
||||||
<string name="device_manager_session_rename">セッションを改名</string>
|
<string name="device_manager_session_rename">セッション名を変更</string>
|
||||||
<string name="device_manager_session_details_device_ip_address">IPアドレス</string>
|
<string name="device_manager_session_details_device_ip_address">IPアドレス</string>
|
||||||
<string name="device_manager_session_details_device_operating_system">オペレーティングシステム</string>
|
<string name="device_manager_session_details_device_operating_system">オペレーティングシステム</string>
|
||||||
<string name="device_manager_session_details_device_model">形式</string>
|
<string name="device_manager_session_details_device_model">形式</string>
|
||||||
@ -2487,4 +2487,17 @@
|
|||||||
<plurals name="x_selected">
|
<plurals name="x_selected">
|
||||||
<item quantity="other">%1$dを選択しました</item>
|
<item quantity="other">%1$dを選択しました</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="settings_presence_user_always_appears_offline_summary">有効にすると、このアプリケーションを使用している際にも、他のユーザーにオフラインとして表示されます。</string>
|
||||||
|
<string name="settings_enable_direct_share_summary">最近のチャットをシステムの共有メニューに表示</string>
|
||||||
|
<string name="font_size_use_system">システムの既定値を使用</string>
|
||||||
|
<string name="font_size_section_manually">手動で設定</string>
|
||||||
|
<string name="font_size_section_auto">自動的に設定</string>
|
||||||
|
<string name="font_size_title">フォントの大きさを選択</string>
|
||||||
|
<string name="some_devices_will_not_be_able_to_decrypt">⚠ 未認証の端末がこのルームにあります。あなたが送信するメッセージを復号化することはできません。</string>
|
||||||
|
<string name="encryption_never_send_to_unverified_devices_in_room">このルームの未認証のセッションに暗号化されたメッセージを送信しない。</string>
|
||||||
|
<string name="thread_list_not_available">あなたのホームサーバーはスレッドの一覧表示をまだサポートしていません。</string>
|
||||||
|
<string name="invites_empty_message">ここに新しいリクエストと招待が表示されます。</string>
|
||||||
|
<string name="labs_enable_rich_text_editor_summary">リッチテキストエディターを試してみる(プレーンテキストモードは近日公開)</string>
|
||||||
|
<string name="labs_enable_new_app_layout_summary">タブを使用してElementの表示をシンプルにする</string>
|
||||||
|
<string name="device_manager_session_details_title">セッションの詳細</string>
|
||||||
</resources>
|
</resources>
|
@ -345,7 +345,7 @@
|
|||||||
<string name="encryption_import_room_keys_summary">Importuj klucze z lokalnego pliku</string>
|
<string name="encryption_import_room_keys_summary">Importuj klucze z lokalnego pliku</string>
|
||||||
<string name="encryption_import_import">Importuj</string>
|
<string name="encryption_import_import">Importuj</string>
|
||||||
<string name="encryption_never_send_to_unverified_devices_title">Szyfruj wiadomości tylko do zaufanych sesji</string>
|
<string name="encryption_never_send_to_unverified_devices_title">Szyfruj wiadomości tylko do zaufanych sesji</string>
|
||||||
<string name="encryption_never_send_to_unverified_devices_summary">Nigdy nie wysyłaj szyfrowanych wiadomości do sesji (np urządzeń innych użytkowników) które nie zostały zweryfikowane.</string>
|
<string name="encryption_never_send_to_unverified_devices_summary">Nigdy nie wysyłaj szyfrowanych wiadomości do niezweryfikowanych sesji (bez zielonej tarczy) z tego urządzenia.</string>
|
||||||
<string name="encryption_information_verify_device_warning">Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej:</string>
|
<string name="encryption_information_verify_device_warning">Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej:</string>
|
||||||
<string name="encryption_information_verify_device_warning2">Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany.</string>
|
<string name="encryption_information_verify_device_warning2">Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany.</string>
|
||||||
<string name="title_activity_choose_sticker">Wyślij naklejkę</string>
|
<string name="title_activity_choose_sticker">Wyślij naklejkę</string>
|
||||||
@ -1115,7 +1115,7 @@
|
|||||||
\nKlucze nie są zaufane</string>
|
\nKlucze nie są zaufane</string>
|
||||||
<string name="encryption_information_dg_xsigning_disabled">Podpis krzyżowy nie jest aktywowany</string>
|
<string name="encryption_information_dg_xsigning_disabled">Podpis krzyżowy nie jest aktywowany</string>
|
||||||
<string name="settings_active_sessions_list">Aktywne Sesje</string>
|
<string name="settings_active_sessions_list">Aktywne Sesje</string>
|
||||||
<string name="settings_active_sessions_show_all">Pokaż wszystkie Sesje</string>
|
<string name="settings_active_sessions_show_all">Pokaż wszystkie sesje</string>
|
||||||
<string name="settings_active_sessions_manage">Zarządzaj Sesjami</string>
|
<string name="settings_active_sessions_manage">Zarządzaj Sesjami</string>
|
||||||
<string name="settings_active_sessions_signout_device">Wyloguj z tej sesji</string>
|
<string name="settings_active_sessions_signout_device">Wyloguj z tej sesji</string>
|
||||||
<string name="settings_failed_to_get_crypto_device_info">Brak dostępnej informacji o kryptografii</string>
|
<string name="settings_failed_to_get_crypto_device_info">Brak dostępnej informacji o kryptografii</string>
|
||||||
@ -1242,7 +1242,7 @@
|
|||||||
<string name="bottom_sheet_save_your_recovery_key_title">Zapisz Klucz Bezpieczeństwa</string>
|
<string name="bottom_sheet_save_your_recovery_key_title">Zapisz Klucz Bezpieczeństwa</string>
|
||||||
<string name="bottom_sheet_setup_secure_backup_security_phrase_title">Użyj Frazy Bezpieczeństwa</string>
|
<string name="bottom_sheet_setup_secure_backup_security_phrase_title">Użyj Frazy Bezpieczeństwa</string>
|
||||||
<string name="bottom_sheet_setup_secure_backup_security_key_title">Użyj klucza bezpieczeństwa</string>
|
<string name="bottom_sheet_setup_secure_backup_security_key_title">Użyj klucza bezpieczeństwa</string>
|
||||||
<string name="bottom_sheet_setup_secure_backup_subtitle">Zabezpiecza przeciwko utracie dostępu do zaszyfrowanych wiadomości oraz danych poprzez zapisanie zaszyfrowanych kluczy na Twoim serwerze.</string>
|
<string name="bottom_sheet_setup_secure_backup_subtitle">Zabezpiecza przed utratą dostępu do zaszyfrowanych wiadomości poprzez zapisanie kluczy szyfrujących na twoim serwerze.</string>
|
||||||
<string name="a11y_start_camera">Włącz aparat</string>
|
<string name="a11y_start_camera">Włącz aparat</string>
|
||||||
<string name="a11y_stop_camera">Wyłącz aparat</string>
|
<string name="a11y_stop_camera">Wyłącz aparat</string>
|
||||||
<string name="a11y_unmute_microphone">Wyłącz wyciszenie mikrofonu</string>
|
<string name="a11y_unmute_microphone">Wyłącz wyciszenie mikrofonu</string>
|
||||||
@ -1493,7 +1493,7 @@
|
|||||||
<string name="disabled_integration_dialog_title">Integracje są zablokowane</string>
|
<string name="disabled_integration_dialog_title">Integracje są zablokowane</string>
|
||||||
<string name="reset_secure_backup_warning">To zastąpi obecny Klucz bądź Hasło.</string>
|
<string name="reset_secure_backup_warning">To zastąpi obecny Klucz bądź Hasło.</string>
|
||||||
<string name="reset_secure_backup_title">Wygeneruj nowy klucz bezpieczeństwa albo hasło dla istniejącej kopii zapasowej.</string>
|
<string name="reset_secure_backup_title">Wygeneruj nowy klucz bezpieczeństwa albo hasło dla istniejącej kopii zapasowej.</string>
|
||||||
<string name="settings_secure_backup_section_info">Zabezpiecza przeciwko utracie dostępu do zaszyfrowanych wiadomości oraz danych poprzez zapisanie zaszyfrowanych kluczy na Twoim serwerze.</string>
|
<string name="settings_secure_backup_section_info">Zabezpiecza przed utratą dostępu do zaszyfrowanych wiadomości poprzez zapisanie kluczy szyfrujących na twoim serwerze.</string>
|
||||||
<string name="settings_troubleshoot_test_notification_notification_clicked">Powiadomienie zostało kliknięte!</string>
|
<string name="settings_troubleshoot_test_notification_notification_clicked">Powiadomienie zostało kliknięte!</string>
|
||||||
<string name="settings_troubleshoot_test_notification_notice">Proszę kliknąć na powiadomieniu, Jeżeli nie widzisz powiadomienia, sprawdź ustawienia systemowe.</string>
|
<string name="settings_troubleshoot_test_notification_notice">Proszę kliknąć na powiadomieniu, Jeżeli nie widzisz powiadomienia, sprawdź ustawienia systemowe.</string>
|
||||||
<string name="settings_troubleshoot_test_push_notification_content">Widzisz powiadomienia! Kliknij na mnie!</string>
|
<string name="settings_troubleshoot_test_push_notification_content">Widzisz powiadomienia! Kliknij na mnie!</string>
|
||||||
@ -2795,4 +2795,36 @@
|
|||||||
<string name="action_got_it">Rozumiem</string>
|
<string name="action_got_it">Rozumiem</string>
|
||||||
<string name="a11y_collapse_space_children">Zwiń %s pokojów</string>
|
<string name="a11y_collapse_space_children">Zwiń %s pokojów</string>
|
||||||
<string name="a11y_expand_space_children">Rozwiń %s pokojów</string>
|
<string name="a11y_expand_space_children">Rozwiń %s pokojów</string>
|
||||||
|
<string name="device_manager_inactive_sessions_title">Nieaktywne sesje</string>
|
||||||
|
<string name="device_manager_verification_status_detail_other_session_verified">Ta sesja jest gotowa do bezpiecznego przesyłania wiadomości.</string>
|
||||||
|
<string name="device_manager_verification_status_detail_current_session_verified">Twoja bieżąca sesja jest gotowa do bezpiecznego przesyłania wiadomości.</string>
|
||||||
|
<string name="attachment_type_selector_contact">Kontakt</string>
|
||||||
|
<string name="attachment_type_selector_location">Lokalizacja</string>
|
||||||
|
<string name="attachment_type_selector_camera">Aparat</string>
|
||||||
|
<string name="attachment_type_selector_voice_broadcast">Transmisja głosowa</string>
|
||||||
|
<string name="tooltip_attachment_voice_broadcast">Rozpocznij transmisję głosową</string>
|
||||||
|
<string name="room_polls_ended">Ostatnie ankiety</string>
|
||||||
|
<string name="room_polls_active_no_item">W tym pokoju nie ma aktywnych ankiet</string>
|
||||||
|
<string name="room_polls_active">Aktywne ankiety</string>
|
||||||
|
<string name="unable_to_decrypt_some_events_in_poll">Niektóre głosy mogą nie zostać policzone z powodu błędów w odszyfrowaniu</string>
|
||||||
|
<string name="ended_poll_indicator">Zakończono ankietę.</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Błąd połączenia - Nagrywanie wstrzymane</string>
|
||||||
|
<string name="error_voice_broadcast_unable_to_play">Nie można odtworzyć tej transmisji głosowej.</string>
|
||||||
|
<string name="error_voice_broadcast_already_in_progress_message">Jesteś już w trakcie nagrywania transmisji głosowej. Proszę zakończyć bieżącą transmisję, aby rozpocząć nową.</string>
|
||||||
|
<string name="error_voice_broadcast_blocked_by_someone_else_message">Ktoś inny nagrywa już transmisję głosową. Aby rozpocząć nową transmisję, należy poczekać na jej zakończenie.</string>
|
||||||
|
<string name="error_voice_broadcast_permission_denied_message">Nie masz wymaganych uprawnień do rozpoczęcia transmisji głosowej w tym pokoju. Skontaktuj się z administratorem pokoju, aby przyznał ci uprawnienia.</string>
|
||||||
|
<string name="error_voice_broadcast_unauthorized_title">Nie można rozpocząć nowej transmisji głosowej</string>
|
||||||
|
<string name="voice_broadcast_buffering">Buforowanie…</string>
|
||||||
|
<string name="error_voice_message_broadcast_in_progress">Nie można rozpocząć wiadomości głosowej</string>
|
||||||
|
<string name="review_unverified_sessions_title">Masz niezweryfikowane sesje</string>
|
||||||
|
<string name="key_authenticity_not_guaranteed">Autentyczność tej zaszyfrowanej wiadomości nie może być zagwarantowana na tym urządzeniu.</string>
|
||||||
|
<string name="room_profile_section_more_polls">Historia ankiet</string>
|
||||||
|
<string name="command_description_table_flip">Dodaje (╯°□°)╯︵ ┻━┻ do wiadomości tekstowej</string>
|
||||||
|
<string name="login_scan_qr_code">Skanuj kod QR</string>
|
||||||
|
<string name="notice_voice_broadcast_ended">%1$s zakończył(a) transmisję głosową.</string>
|
||||||
|
<string name="settings_security_incognito_keyboard_summary">Zarządaj od systemu Android aby klawiatura nie zapisywała żadnych danych takich jak historia pisania lub słownik. Pamiętaj, nie niektóre klawiatury mogą nie zastosować się do tego ustawienia.</string>
|
||||||
|
<string name="settings_security_incognito_keyboard_title">Klawiatura incognito</string>
|
||||||
|
<string name="home_empty_no_rooms_title">Witaj w ${app_name},
|
||||||
|
\n%s.</string>
|
||||||
|
<string name="home_empty_no_rooms_message">Wszechstronna, bezpieczna aplikacja do czatowania dla zespołów, przyjaciół i organizacji. Utwórz czat lub dołącz do istniejącego pokoju, aby rozpocząć.</string>
|
||||||
</resources>
|
</resources>
|
@ -6,7 +6,7 @@
|
|||||||
<string name="notice_room_join">%1$s вошёл(шла) в комнату</string>
|
<string name="notice_room_join">%1$s вошёл(шла) в комнату</string>
|
||||||
<string name="notice_room_leave">%1$s покинул(а) комнату</string>
|
<string name="notice_room_leave">%1$s покинул(а) комнату</string>
|
||||||
<string name="notice_room_reject">%1$s отклонил(а) приглашение</string>
|
<string name="notice_room_reject">%1$s отклонил(а) приглашение</string>
|
||||||
<string name="notice_room_remove">%1$s выгнан %2$s</string>
|
<string name="notice_room_remove">%1$s выгнал %2$s</string>
|
||||||
<string name="notice_room_unban">%1$s разблокировал(а) %2$s</string>
|
<string name="notice_room_unban">%1$s разблокировал(а) %2$s</string>
|
||||||
<string name="notice_room_ban">%1$s заблокировал(а) %2$s</string>
|
<string name="notice_room_ban">%1$s заблокировал(а) %2$s</string>
|
||||||
<string name="notice_room_withdraw">%1$s отозвал(а) приглашение %2$s</string>
|
<string name="notice_room_withdraw">%1$s отозвал(а) приглашение %2$s</string>
|
||||||
@ -65,7 +65,7 @@
|
|||||||
<string name="notice_room_reject_with_reason">%1$s отклонил приглашение. Причина: %2$s</string>
|
<string name="notice_room_reject_with_reason">%1$s отклонил приглашение. Причина: %2$s</string>
|
||||||
<string name="notice_room_remove_with_reason">%1$s выгнали %2$s. Причина: %3$s</string>
|
<string name="notice_room_remove_with_reason">%1$s выгнали %2$s. Причина: %3$s</string>
|
||||||
<string name="notice_room_unban_with_reason">%1$s разблокировано %2$s. Причина: %3$s</string>
|
<string name="notice_room_unban_with_reason">%1$s разблокировано %2$s. Причина: %3$s</string>
|
||||||
<string name="notice_room_ban_with_reason">%1$s забанен %2$s. Причина: %3$s</string>
|
<string name="notice_room_ban_with_reason">%1$s забанил %2$s. Причина: %3$s</string>
|
||||||
<string name="notice_room_third_party_registered_invite_with_reason">%1$s принял приглашение для %2$s. Причина: %3$s</string>
|
<string name="notice_room_third_party_registered_invite_with_reason">%1$s принял приглашение для %2$s. Причина: %3$s</string>
|
||||||
<string name="notice_room_withdraw_with_reason">%1$s отозвал приглашение %2$s. Причина: %3$s</string>
|
<string name="notice_room_withdraw_with_reason">%1$s отозвал приглашение %2$s. Причина: %3$s</string>
|
||||||
<string name="notice_room_created">%1$s создал(а) комнату</string>
|
<string name="notice_room_created">%1$s создал(а) комнату</string>
|
||||||
@ -1420,7 +1420,7 @@
|
|||||||
<string name="command_description_rainbow">Посылает сообщение, окрашенное в цвет радуги</string>
|
<string name="command_description_rainbow">Посылает сообщение, окрашенное в цвет радуги</string>
|
||||||
<string name="command_description_rainbow_emote">Посылает данную эмоцию, окрашенную в цвет радуги</string>
|
<string name="command_description_rainbow_emote">Посылает данную эмоцию, окрашенную в цвет радуги</string>
|
||||||
<string name="settings_category_composer">Редактор сообщений</string>
|
<string name="settings_category_composer">Редактор сообщений</string>
|
||||||
<string name="room_settings_enable_encryption">Включаем сквозное шифрование…</string>
|
<string name="room_settings_enable_encryption">Включить сквозное шифрование…</string>
|
||||||
<string name="room_settings_enable_encryption_dialog_title">Включить шифрование\?</string>
|
<string name="room_settings_enable_encryption_dialog_title">Включить шифрование\?</string>
|
||||||
<string name="room_settings_enable_encryption_dialog_content">После включения шифрование для комнаты нельзя отключить. Сообщения отправленные в зашифрованной комнате не будут видны серверу, только участникам комнаты. Включение шифрования может помешать правильной работе многих ботов и мостов.</string>
|
<string name="room_settings_enable_encryption_dialog_content">После включения шифрование для комнаты нельзя отключить. Сообщения отправленные в зашифрованной комнате не будут видны серверу, только участникам комнаты. Включение шифрования может помешать правильной работе многих ботов и мостов.</string>
|
||||||
<string name="room_settings_enable_encryption_dialog_submit">Включить шифрование</string>
|
<string name="room_settings_enable_encryption_dialog_submit">Включить шифрование</string>
|
||||||
@ -2433,7 +2433,7 @@
|
|||||||
<string name="location_timeline_failed_to_load_map">Не удалось загрузить карту</string>
|
<string name="location_timeline_failed_to_load_map">Не удалось загрузить карту</string>
|
||||||
<string name="a11y_static_map_image">Карта</string>
|
<string name="a11y_static_map_image">Карта</string>
|
||||||
<string name="labs_enable_thread_messages_desc">Примечание: приложение будет перезапущено</string>
|
<string name="labs_enable_thread_messages_desc">Примечание: приложение будет перезапущено</string>
|
||||||
<string name="labs_enable_thread_messages">Обсуждения сообщений</string>
|
<string name="labs_enable_thread_messages">Включить обсуждения сообщений</string>
|
||||||
<string name="ftue_auth_use_case_connect_to_server">Подключиться к серверу</string>
|
<string name="ftue_auth_use_case_connect_to_server">Подключиться к серверу</string>
|
||||||
<string name="ftue_auth_use_case_join_existing_server">Хотите присоединиться к существующему серверу\?</string>
|
<string name="ftue_auth_use_case_join_existing_server">Хотите присоединиться к существующему серверу\?</string>
|
||||||
<string name="ftue_auth_use_case_skip_partial">Пропустить вопрос</string>
|
<string name="ftue_auth_use_case_skip_partial">Пропустить вопрос</string>
|
||||||
@ -2540,7 +2540,7 @@
|
|||||||
<string name="error_forbidden_digits_only_username">Домашний сервер не принимает имя пользователя, состоящее только из цифр.</string>
|
<string name="error_forbidden_digits_only_username">Домашний сервер не принимает имя пользователя, состоящее только из цифр.</string>
|
||||||
<string name="ftue_personalize_skip_this_step">Пропустить этот шаг</string>
|
<string name="ftue_personalize_skip_this_step">Пропустить этот шаг</string>
|
||||||
<string name="ftue_personalize_submit">Сохранить и продолжить</string>
|
<string name="ftue_personalize_submit">Сохранить и продолжить</string>
|
||||||
<string name="ftue_personalize_complete_subtitle">Ваши предпочтения были сохранены.</string>
|
<string name="ftue_personalize_complete_subtitle">Зайдите в настройки чтобы изменить Ваш профиль</string>
|
||||||
<string name="ftue_personalize_complete_title">Выглядит хорошо!</string>
|
<string name="ftue_personalize_complete_title">Выглядит хорошо!</string>
|
||||||
<string name="ftue_auth_carousel_workplace_body">${app_name} также отлично подходит для работы. Ему доверяют самые надёжные организации в мире.</string>
|
<string name="ftue_auth_carousel_workplace_body">${app_name} также отлично подходит для работы. Ему доверяют самые надёжные организации в мире.</string>
|
||||||
<string name="keys_backup_settings_signature_from_this_user">Резервная копия имеет действительную подпись для данного пользователя.</string>
|
<string name="keys_backup_settings_signature_from_this_user">Резервная копия имеет действительную подпись для данного пользователя.</string>
|
||||||
@ -2791,7 +2791,7 @@
|
|||||||
<item quantity="other">Рассмотрите возможность выхода из старых сеансов (%1$d дней или дольше), которые вы более не используете.</item>
|
<item quantity="other">Рассмотрите возможность выхода из старых сеансов (%1$d дней или дольше), которые вы более не используете.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="attachment_type_voice_broadcast">Голосовая трансляция</string>
|
<string name="attachment_type_voice_broadcast">Голосовая трансляция</string>
|
||||||
<string name="labs_enable_voice_broadcast_title">Голосовые трансляции</string>
|
<string name="labs_enable_voice_broadcast_title">Включить голосовые трансляции</string>
|
||||||
<string name="labs_enable_client_info_recording_summary">Записывает название клиента, версию и URL-адрес для более лёгкого распознавания сеансов в менеджере сеансов.</string>
|
<string name="labs_enable_client_info_recording_summary">Записывает название клиента, версию и URL-адрес для более лёгкого распознавания сеансов в менеджере сеансов.</string>
|
||||||
<string name="labs_enable_client_info_recording_title">Записывать информацию о клиенте</string>
|
<string name="labs_enable_client_info_recording_title">Записывать информацию о клиенте</string>
|
||||||
<string name="attachment_type_selector_gallery">Галерея</string>
|
<string name="attachment_type_selector_gallery">Галерея</string>
|
||||||
@ -2824,9 +2824,9 @@
|
|||||||
<string name="a11y_expand_space_children">Развернуть дочерние элементы %s</string>
|
<string name="a11y_expand_space_children">Развернуть дочерние элементы %s</string>
|
||||||
<plurals name="x_selected">
|
<plurals name="x_selected">
|
||||||
<item quantity="one">Выбрано %1$d</item>
|
<item quantity="one">Выбрано %1$d</item>
|
||||||
<item quantity="few">Выбрано %1$d</item>
|
<item quantity="few">Выбраны %1$d</item>
|
||||||
<item quantity="many">Выбрано %1$d</item>
|
<item quantity="many">Выбраны %1$d</item>
|
||||||
<item quantity="other">Выбрано %1$d</item>
|
<item quantity="other">Выбраны %1$d</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="rich_text_editor_full_screen_toggle">Войти в полноэкранный режим</string>
|
<string name="rich_text_editor_full_screen_toggle">Войти в полноэкранный режим</string>
|
||||||
<string name="rich_text_editor_format_underline">Применить форматирование подчёркиванием</string>
|
<string name="rich_text_editor_format_underline">Применить форматирование подчёркиванием</string>
|
||||||
@ -2970,4 +2970,58 @@
|
|||||||
<string name="device_manager_verification_status_detail_session_encryption_not_supported">Этот сеанс не поддерживает шифрование и поэтому не может быть заверен.</string>
|
<string name="device_manager_verification_status_detail_session_encryption_not_supported">Этот сеанс не поддерживает шифрование и поэтому не может быть заверен.</string>
|
||||||
<string name="notice_voice_broadcast_ended">%1$s завершил(а) голосовую трансляцию.</string>
|
<string name="notice_voice_broadcast_ended">%1$s завершил(а) голосовую трансляцию.</string>
|
||||||
<string name="notice_voice_broadcast_ended_by_you">Вы завершили голосовую трансляцию.</string>
|
<string name="notice_voice_broadcast_ended_by_you">Вы завершили голосовую трансляцию.</string>
|
||||||
|
<plurals name="room_polls_active_no_item_for_loaded_period">
|
||||||
|
<item quantity="one">Нет активных опросов за %1$d день.
|
||||||
|
\nЗагрузите больше чтобы показать опросы за прошедшие дни.</item>
|
||||||
|
<item quantity="few">Нет активных опросов за %1$d дней.
|
||||||
|
\nЗагрузите больше чтобы показать опросы за прошедшие дни.</item>
|
||||||
|
<item quantity="many">Нет активных опросов за %1$d дней.
|
||||||
|
\nЗагрузите больше чтобы показать опросы за прошедшие дни.</item>
|
||||||
|
<item quantity="other">Нет активных опросов за %1$d дней.
|
||||||
|
\nЗагрузите больше чтобы показать опросы за прошедшие дни.</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="room_polls_ended_no_item_for_loaded_period">
|
||||||
|
<item quantity="one">Нет завершённых опросов за день %1$d.
|
||||||
|
\nЗагрузите больше чтобы показать опросы за предыдущие дни.</item>
|
||||||
|
<item quantity="few">Нет завершённых опросов за %1$d дней
|
||||||
|
\nЗагрузите больше чтобы показать опросы за предыдущие дни.</item>
|
||||||
|
<item quantity="many">Нет завершённых опросов за %1$d дней
|
||||||
|
\nЗагрузите больше чтобы показать опросы за предыдущие дни.</item>
|
||||||
|
<item quantity="other">Нет завершённых опросов за %1$d дней
|
||||||
|
\nЗагрузите больше чтобы показать опросы за предыдущие дни.</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="settings_access_token_summary">Токен доступа даёт полный доступ к аккаунту. Не делитесь им ни с кем.</string>
|
||||||
|
<string name="settings_access_token">Токен доступа</string>
|
||||||
|
<string name="message_reply_to_ended_poll_preview">Завершённый опрос</string>
|
||||||
|
<string name="message_reply_to_poll_preview">Опрос</string>
|
||||||
|
<string name="message_reply_to_sender_ended_poll">завершённый опрос.</string>
|
||||||
|
<string name="set_link_edit">Изменить ссылку</string>
|
||||||
|
<string name="set_link_create">Создать ссылку</string>
|
||||||
|
<string name="set_link_link">Ссылка</string>
|
||||||
|
<string name="set_link_text">Текст</string>
|
||||||
|
<string name="rich_text_editor_bullet_list">Список</string>
|
||||||
|
<string name="rich_text_editor_numbered_list">Пронумерованный список</string>
|
||||||
|
<string name="rich_text_editor_link">Ссылка</string>
|
||||||
|
<string name="room_polls_loading_error">Ошибка считывания опросов.</string>
|
||||||
|
<string name="room_polls_load_more">Загрузить больше опросов</string>
|
||||||
|
<string name="room_polls_wait_for_display">Показываем опросы</string>
|
||||||
|
<string name="room_polls_ended_no_item">Нет завершённых опросов</string>
|
||||||
|
<string name="room_polls_ended">Завершённые опросы</string>
|
||||||
|
<string name="room_polls_active_no_item">Нет активных опросов</string>
|
||||||
|
<string name="room_polls_active">Активные опросы</string>
|
||||||
|
<string name="unable_to_decrypt_some_events_in_poll">Из-за ошибок расшифровки, некоторые голоса могут быть не засчитаны</string>
|
||||||
|
<string name="ended_poll_indicator">Опрос завершён.</string>
|
||||||
|
<string name="stop_voice_broadcast_content">Вы уверены что хотите завершить голосовую трансляцию\? Это завершит трансляцию и полная запись будет доступна в чате.</string>
|
||||||
|
<string name="stop_voice_broadcast_dialog_title">Завершить голосовую трансляцию\?</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Ошибка подключения - Запись приостановлена</string>
|
||||||
|
<string name="error_voice_broadcast_unable_to_play">Невозможно прослушать голосовую трансляцию.</string>
|
||||||
|
<string name="voice_broadcast_live_broadcast">Голосовая трансляция</string>
|
||||||
|
<string name="error_voice_message_broadcast_in_progress_message">Вы не можете записать голосовое сообщение, потому-что Вы записываете голосовую трансляцию. Завершите голосовую трансляцию, чтобы записать голосовое сообщение</string>
|
||||||
|
<string name="error_voice_message_broadcast_in_progress">Не удалось записать голосовое сообщение</string>
|
||||||
|
<string name="review_unverified_sessions_description">Убедиться что Ваш аккаунт в безопасности</string>
|
||||||
|
<string name="settings_nightly_build_update">Получить последнюю сборку (у вас могут быть проблемы со входом)</string>
|
||||||
|
<string name="room_profile_section_more_polls">История опроса</string>
|
||||||
|
<string name="started_a_voice_broadcast">Голосовая трансляция начата</string>
|
||||||
|
<string name="thread_list_not_available">Ваш домашний сервер не поддерживает список обсуждений.</string>
|
||||||
|
<string name="action_stop">Остановить</string>
|
||||||
</resources>
|
</resources>
|
@ -2978,4 +2978,5 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Nemôžete spustiť hlasovú správu, pretože práve nahrávate živé vysielanie. Ukončite prosím živé vysielanie, aby ste mohli začať nahrávať hlasovú správu</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Nemôžete spustiť hlasovú správu, pretože práve nahrávate živé vysielanie. Ukončite prosím živé vysielanie, aby ste mohli začať nahrávať hlasovú správu</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Nemožno spustiť hlasovú správu</string>
|
<string name="error_voice_message_broadcast_in_progress">Nemožno spustiť hlasovú správu</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Chyba pripojenia - nahrávanie pozastavené</string>
|
||||||
</resources>
|
</resources>
|
@ -3038,4 +3038,5 @@
|
|||||||
<string name="room_polls_wait_for_display">Показ опитувань</string>
|
<string name="room_polls_wait_for_display">Показ опитувань</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress_message">Ви не можете розпочати запис голосового повідомлення, оскільки ви записуєте трансляцію наживо. Будь ласка, заверште її, щоб розпочати запис голосового повідомлення</string>
|
<string name="error_voice_message_broadcast_in_progress_message">Ви не можете розпочати запис голосового повідомлення, оскільки ви записуєте трансляцію наживо. Будь ласка, заверште її, щоб розпочати запис голосового повідомлення</string>
|
||||||
<string name="error_voice_message_broadcast_in_progress">Не вдалося розпочати запис голосового повідомлення</string>
|
<string name="error_voice_message_broadcast_in_progress">Не вдалося розпочати запис голосового повідомлення</string>
|
||||||
|
<string name="error_voice_broadcast_no_connection_recording">Помилка з\'єднання - Запис призупинено</string>
|
||||||
</resources>
|
</resources>
|
@ -10,6 +10,8 @@
|
|||||||
<!-- onboarding english only word play -->
|
<!-- onboarding english only word play -->
|
||||||
<string name="cut_the_slack_from_teams" translatable="false">Cut the slack from teams.</string>
|
<string name="cut_the_slack_from_teams" translatable="false">Cut the slack from teams.</string>
|
||||||
|
|
||||||
|
<string name="command_description_crash_application" translatable="false">Crash the application.</string>
|
||||||
|
|
||||||
<!-- WIP -->
|
<!-- WIP -->
|
||||||
<string name="location_map_view_copyright" translatable="false">© MapTiler © OpenStreetMap contributors</string>
|
<string name="location_map_view_copyright" translatable="false">© MapTiler © OpenStreetMap contributors</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -80,6 +80,9 @@ class FlowSession(private val session: Session) {
|
|||||||
|
|
||||||
fun liveSyncState(): Flow<SyncState> {
|
fun liveSyncState(): Flow<SyncState> {
|
||||||
return session.syncService().getSyncStateLive().asFlow()
|
return session.syncService().getSyncStateLive().asFlow()
|
||||||
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
|
session.syncService().getSyncState()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePushers(): Flow<List<Pusher>> {
|
fun livePushers(): Flow<List<Pusher>> {
|
||||||
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
|
|||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
import org.matrix.android.sdk.api.session.room.read.ReadService
|
import org.matrix.android.sdk.api.session.room.read.ReadService
|
||||||
import org.matrix.android.sdk.api.session.room.reporting.ReportingService
|
import org.matrix.android.sdk.api.session.room.reporting.ReportingService
|
||||||
import org.matrix.android.sdk.api.session.room.send.DraftService
|
import org.matrix.android.sdk.api.session.room.send.DraftService
|
||||||
@ -181,4 +182,9 @@ interface Room {
|
|||||||
* Get the LocationSharingService associated to this Room.
|
* Get the LocationSharingService associated to this Room.
|
||||||
*/
|
*/
|
||||||
fun locationSharingService(): LocationSharingService
|
fun locationSharingService(): LocationSharingService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the PollHistoryService associated to this Room.
|
||||||
|
*/
|
||||||
|
fun pollHistoryService(): PollHistoryService
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.api.session.room.poll
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent the status of the loaded polls for a room.
|
||||||
|
*/
|
||||||
|
data class LoadedPollsStatus(
|
||||||
|
/**
|
||||||
|
* Indicate whether more polls can be loaded from timeline.
|
||||||
|
* A false value would mean the start of the timeline has been reached.
|
||||||
|
*/
|
||||||
|
val canLoadMore: Boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of days of timeline events currently synced (fetched and stored in local).
|
||||||
|
*/
|
||||||
|
val daysSynced: Int,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether a sync of timeline events has been completely done in backward. It would
|
||||||
|
* mean timeline events have been synced for at least a number of days defined by [PollHistoryService.loadingPeriodInDays].
|
||||||
|
*/
|
||||||
|
val hasCompletedASyncBackward: Boolean,
|
||||||
|
)
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.api.session.room.poll
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose methods to get history of polls in rooms.
|
||||||
|
*/
|
||||||
|
interface PollHistoryService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of days covered when requesting to load more polls.
|
||||||
|
*/
|
||||||
|
val loadingPeriodInDays: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This must be called when you don't need the service anymore.
|
||||||
|
* It ensures the underlying database get closed.
|
||||||
|
*/
|
||||||
|
fun dispose()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask to load more polls starting from last loaded polls for a period defined by
|
||||||
|
* [loadingPeriodInDays].
|
||||||
|
*/
|
||||||
|
suspend fun loadMore(): LoadedPollsStatus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current status of the loaded polls.
|
||||||
|
*/
|
||||||
|
suspend fun getLoadedPollsStatus(): LoadedPollsStatus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync polls from last loaded polls until now.
|
||||||
|
*/
|
||||||
|
suspend fun syncPolls()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently loaded list of poll events. See [loadMore].
|
||||||
|
*/
|
||||||
|
fun getPollEvents(): LiveData<List<TimelineEvent>>
|
||||||
|
}
|
@ -66,6 +66,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
|
|||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049
|
||||||
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050
|
||||||
import org.matrix.android.sdk.internal.util.Normalizer
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -74,7 +75,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 49L,
|
schemaVersion = 50L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
@ -133,5 +134,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||||||
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
|
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
|
||||||
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
|
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
|
||||||
if (oldVersion < 49) MigrateSessionTo049(realm).perform()
|
if (oldVersion < 49) MigrateSessionTo049(realm).perform()
|
||||||
|
if (oldVersion < 50) MigrateSessionTo050(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.database.migration
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adding new entity PollHistoryStatusEntity.
|
||||||
|
*/
|
||||||
|
internal class MigrateSessionTo050(realm: DynamicRealm) : RealmMigrator(realm, 50) {
|
||||||
|
|
||||||
|
override fun doMigrate(realm: DynamicRealm) {
|
||||||
|
realm.schema.create("PollHistoryStatusEntity")
|
||||||
|
.addField(PollHistoryStatusEntityFields.ROOM_ID, String::class.java)
|
||||||
|
.addPrimaryKey(PollHistoryStatusEntityFields.ROOM_ID)
|
||||||
|
.setRequired(PollHistoryStatusEntityFields.ROOM_ID, true)
|
||||||
|
.addField(PollHistoryStatusEntityFields.CURRENT_TIMESTAMP_TARGET_BACKWARD_MS, Long::class.java)
|
||||||
|
.setNullable(PollHistoryStatusEntityFields.CURRENT_TIMESTAMP_TARGET_BACKWARD_MS, true)
|
||||||
|
.addField(PollHistoryStatusEntityFields.OLDEST_TIMESTAMP_TARGET_REACHED_MS, Long::class.java)
|
||||||
|
.setNullable(PollHistoryStatusEntityFields.OLDEST_TIMESTAMP_TARGET_REACHED_MS, true)
|
||||||
|
.addField(PollHistoryStatusEntityFields.OLDEST_EVENT_ID_REACHED, String::class.java)
|
||||||
|
.addField(PollHistoryStatusEntityFields.MOST_RECENT_EVENT_ID_REACHED, String::class.java)
|
||||||
|
.addField(PollHistoryStatusEntityFields.IS_END_OF_POLLS_BACKWARD, Boolean::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.PollConstants
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of the loading process of the poll history.
|
||||||
|
*/
|
||||||
|
internal open class PollHistoryStatusEntity(
|
||||||
|
/**
|
||||||
|
* The related room id.
|
||||||
|
*/
|
||||||
|
@PrimaryKey
|
||||||
|
var roomId: String = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of the in progress poll sync target in backward direction in milliseconds.
|
||||||
|
*/
|
||||||
|
var currentTimestampTargetBackwardMs: Long? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of the oldest event synced once target has been reached in milliseconds.
|
||||||
|
*/
|
||||||
|
var oldestTimestampTargetReachedMs: Long? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the oldest event synced.
|
||||||
|
*/
|
||||||
|
var oldestEventIdReached: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id of the most recent event synced.
|
||||||
|
*/
|
||||||
|
var mostRecentEventIdReached: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether all polls in a room have been synced in backward direction.
|
||||||
|
*/
|
||||||
|
var isEndOfPollsBackward: Boolean = false,
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of the entity with the same content.
|
||||||
|
*/
|
||||||
|
fun copy(): PollHistoryStatusEntity {
|
||||||
|
return PollHistoryStatusEntity(
|
||||||
|
roomId = roomId,
|
||||||
|
currentTimestampTargetBackwardMs = currentTimestampTargetBackwardMs,
|
||||||
|
oldestTimestampTargetReachedMs = oldestTimestampTargetReachedMs,
|
||||||
|
oldestEventIdReached = oldestEventIdReached,
|
||||||
|
mostRecentEventIdReached = mostRecentEventIdReached,
|
||||||
|
isEndOfPollsBackward = isEndOfPollsBackward,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether at least one poll sync has been fully completed backward for the given room.
|
||||||
|
*/
|
||||||
|
val hasCompletedASyncBackward: Boolean
|
||||||
|
get() = oldestTimestampTargetReachedMs != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether all polls in a room have been synced for the current timestamp target in backward direction.
|
||||||
|
*/
|
||||||
|
val currentTimestampTargetBackwardReached: Boolean
|
||||||
|
get() = checkIfCurrentTimestampTargetBackwardIsReached()
|
||||||
|
|
||||||
|
private fun checkIfCurrentTimestampTargetBackwardIsReached(): Boolean {
|
||||||
|
val currentTarget = currentTimestampTargetBackwardMs
|
||||||
|
val lastTarget = oldestTimestampTargetReachedMs
|
||||||
|
// last timestamp target should be older or equal to the current target
|
||||||
|
return currentTarget != null && lastTarget != null && lastTarget <= currentTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of days of history currently synced.
|
||||||
|
*/
|
||||||
|
fun getNbSyncedDays(currentMs: Long): Int {
|
||||||
|
val oldestTimestamp = oldestTimestampTargetReachedMs
|
||||||
|
return if (oldestTimestamp == null) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
((currentMs - oldestTimestamp).coerceAtLeast(0) / PollConstants.MILLISECONDS_PER_DAY).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,4 @@ internal open class PollResponseAggregatedSummaryEntity(
|
|||||||
var sourceLocalEchoEvents: RealmList<String> = RealmList(),
|
var sourceLocalEchoEvents: RealmList<String> = RealmList(),
|
||||||
// list of related event ids which are encrypted due to decryption failure
|
// list of related event ids which are encrypted due to decryption failure
|
||||||
var encryptedRelatedEventIds: RealmList<String> = RealmList(),
|
var encryptedRelatedEventIds: RealmList<String> = RealmList(),
|
||||||
) : RealmObject() {
|
) : RealmObject()
|
||||||
|
|
||||||
companion object
|
|
||||||
}
|
|
||||||
|
@ -73,6 +73,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
|||||||
UserPresenceEntity::class,
|
UserPresenceEntity::class,
|
||||||
ThreadSummaryEntity::class,
|
ThreadSummaryEntity::class,
|
||||||
ThreadListPageEntity::class,
|
ThreadListPageEntity::class,
|
||||||
|
PollHistoryStatusEntity::class,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.database.query
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
|
||||||
|
internal fun PollHistoryStatusEntity.Companion.get(realm: Realm, roomId: String): PollHistoryStatusEntity? {
|
||||||
|
return realm.where<PollHistoryStatusEntity>().equalTo(PollHistoryStatusEntityFields.ROOM_ID, roomId).findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun PollHistoryStatusEntity.Companion.getOrCreate(realm: Realm, roomId: String): PollHistoryStatusEntity {
|
||||||
|
return get(realm, roomId) ?: realm.createObject(roomId)
|
||||||
|
}
|
@ -34,10 +34,16 @@ internal interface UpdatePushRuleActionsTask : Task<UpdatePushRuleActionsTask.Pa
|
|||||||
|
|
||||||
internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
|
internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
|
||||||
private val pushRulesApi: PushRulesApi,
|
private val pushRulesApi: PushRulesApi,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
) : UpdatePushRuleActionsTask {
|
) : UpdatePushRuleActionsTask {
|
||||||
|
|
||||||
override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
|
override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
|
||||||
|
if (params.actions != null) {
|
||||||
|
val body = mapOf("actions" to params.actions.toJson())
|
||||||
|
executeRequest(globalErrorReceiver) {
|
||||||
|
pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
executeRequest(globalErrorReceiver) {
|
executeRequest(globalErrorReceiver) {
|
||||||
pushRulesApi.updateEnableRuleStatus(
|
pushRulesApi.updateEnableRuleStatus(
|
||||||
params.kind.value,
|
params.kind.value,
|
||||||
@ -45,11 +51,5 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
|
|||||||
EnabledBody(params.enable)
|
EnabledBody(params.enable)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (params.actions != null) {
|
|
||||||
val body = mapOf("actions" to params.actions.toJson())
|
|
||||||
executeRequest(globalErrorReceiver) {
|
|
||||||
pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|||||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
import org.matrix.android.sdk.api.session.room.read.ReadService
|
import org.matrix.android.sdk.api.session.room.read.ReadService
|
||||||
import org.matrix.android.sdk.api.session.room.reporting.ReportingService
|
import org.matrix.android.sdk.api.session.room.reporting.ReportingService
|
||||||
import org.matrix.android.sdk.api.session.room.send.DraftService
|
import org.matrix.android.sdk.api.session.room.send.DraftService
|
||||||
@ -72,6 +73,7 @@ internal class DefaultRoom(
|
|||||||
private val roomVersionService: RoomVersionService,
|
private val roomVersionService: RoomVersionService,
|
||||||
private val viaParameterFinder: ViaParameterFinder,
|
private val viaParameterFinder: ViaParameterFinder,
|
||||||
private val locationSharingService: LocationSharingService,
|
private val locationSharingService: LocationSharingService,
|
||||||
|
private val pollHistoryService: PollHistoryService,
|
||||||
override val coroutineDispatchers: MatrixCoroutineDispatchers
|
override val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||||
) : Room {
|
) : Room {
|
||||||
|
|
||||||
@ -116,4 +118,5 @@ internal class DefaultRoom(
|
|||||||
override fun roomAccountDataService() = roomAccountDataService
|
override fun roomAccountDataService() = roomAccountDataService
|
||||||
override fun roomVersionService() = roomVersionService
|
override fun roomVersionService() = roomVersionService
|
||||||
override fun locationSharingService() = locationSharingService
|
override fun locationSharingService() = locationSharingService
|
||||||
|
override fun pollHistoryService() = pollHistoryService
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
|
|||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultLocationSharingService
|
import org.matrix.android.sdk.internal.session.room.location.DefaultLocationSharingService
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
|
import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
|
||||||
import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService
|
import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.DefaultPollHistoryService
|
||||||
import org.matrix.android.sdk.internal.session.room.read.DefaultReadService
|
import org.matrix.android.sdk.internal.session.room.read.DefaultReadService
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationService
|
import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationService
|
||||||
import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService
|
import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService
|
||||||
@ -71,15 +72,17 @@ internal class DefaultRoomFactory @Inject constructor(
|
|||||||
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
|
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
|
||||||
private val viaParameterFinder: ViaParameterFinder,
|
private val viaParameterFinder: ViaParameterFinder,
|
||||||
private val locationSharingServiceFactory: DefaultLocationSharingService.Factory,
|
private val locationSharingServiceFactory: DefaultLocationSharingService.Factory,
|
||||||
|
private val pollHistoryServiceFactory: DefaultPollHistoryService.Factory,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||||
) : RoomFactory {
|
) : RoomFactory {
|
||||||
|
|
||||||
override fun create(roomId: String): Room {
|
override fun create(roomId: String): Room {
|
||||||
|
val timelineService = timelineServiceFactory.create(roomId)
|
||||||
return DefaultRoom(
|
return DefaultRoom(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomSummaryDataSource = roomSummaryDataSource,
|
roomSummaryDataSource = roomSummaryDataSource,
|
||||||
roomCryptoService = roomCryptoServiceFactory.create(roomId),
|
roomCryptoService = roomCryptoServiceFactory.create(roomId),
|
||||||
timelineService = timelineServiceFactory.create(roomId),
|
timelineService = timelineService,
|
||||||
threadsService = threadsServiceFactory.create(roomId),
|
threadsService = threadsServiceFactory.create(roomId),
|
||||||
threadsLocalService = threadsLocalServiceFactory.create(roomId),
|
threadsLocalService = threadsLocalServiceFactory.create(roomId),
|
||||||
sendService = sendServiceFactory.create(roomId),
|
sendService = sendServiceFactory.create(roomId),
|
||||||
@ -99,6 +102,7 @@ internal class DefaultRoomFactory @Inject constructor(
|
|||||||
roomVersionService = roomVersionServiceFactory.create(roomId),
|
roomVersionService = roomVersionServiceFactory.create(roomId),
|
||||||
viaParameterFinder = viaParameterFinder,
|
viaParameterFinder = viaParameterFinder,
|
||||||
locationSharingService = locationSharingServiceFactory.create(roomId),
|
locationSharingService = locationSharingServiceFactory.create(roomId),
|
||||||
|
pollHistoryService = pollHistoryServiceFactory.create(roomId, timelineService),
|
||||||
coroutineDispatchers = coroutineDispatchers
|
coroutineDispatchers = coroutineDispatchers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,8 @@ import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDire
|
|||||||
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
|
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
|
||||||
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
|
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.event.DefaultFilterAndStoreEventsTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask
|
import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask
|
||||||
import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask
|
import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask
|
||||||
@ -89,6 +91,12 @@ import org.matrix.android.sdk.internal.session.room.peeking.DefaultPeekRoomTask
|
|||||||
import org.matrix.android.sdk.internal.session.room.peeking.DefaultResolveRoomStateTask
|
import org.matrix.android.sdk.internal.session.room.peeking.DefaultResolveRoomStateTask
|
||||||
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
|
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.DefaultGetLoadedPollsStatusTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.DefaultLoadMorePollsTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.DefaultSyncPollsTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.GetLoadedPollsStatusTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.LoadMorePollsTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.SyncPollsTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask
|
import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask
|
import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
@ -359,4 +367,16 @@ internal abstract class RoomModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFetchPollResponseEventsTask(task: DefaultFetchPollResponseEventsTask): FetchPollResponseEventsTask
|
abstract fun bindFetchPollResponseEventsTask(task: DefaultFetchPollResponseEventsTask): FetchPollResponseEventsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindLoadMorePollsTask(task: DefaultLoadMorePollsTask): LoadMorePollsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetLoadedPollsStatusTask(task: DefaultGetLoadedPollsStatusTask): GetLoadedPollsStatusTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindFilterAndStoreEventsTask(task: DefaultFilterAndStoreEventsTask): FilterAndStoreEventsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSyncPollsTask(task: DefaultSyncPollsTask): SyncPollsTask
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.event
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
|
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface FilterAndStoreEventsTask : Task<FilterAndStoreEventsTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val roomId: String,
|
||||||
|
val events: List<Event>,
|
||||||
|
val filterPredicate: (Event) -> Boolean,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultFilterAndStoreEventsTask @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val clock: Clock,
|
||||||
|
private val eventDecryptor: EventDecryptor,
|
||||||
|
) : FilterAndStoreEventsTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: FilterAndStoreEventsTask.Params) {
|
||||||
|
val filteredEvents = params.events
|
||||||
|
.map { decryptEventIfNeeded(it) }
|
||||||
|
// we also filter in the encrypted events since it means there was decryption error for them
|
||||||
|
// and they may be decrypted later
|
||||||
|
.filter { params.filterPredicate(it) || it.getClearType() == EventType.ENCRYPTED }
|
||||||
|
|
||||||
|
addMissingEventsInDB(params.roomId, filteredEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun addMissingEventsInDB(roomId: String, events: List<Event>) {
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
|
val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() }
|
||||||
|
if (eventIdsToCheck.isNotEmpty()) {
|
||||||
|
val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId }
|
||||||
|
|
||||||
|
events.filterNot { it.eventId in existingIds }
|
||||||
|
.map { it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = computeLocalTs(it)) }
|
||||||
|
.forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun decryptEventIfNeeded(event: Event): Event {
|
||||||
|
if (event.isEncrypted()) {
|
||||||
|
eventDecryptor.decryptEventAndSaveResult(event, timeline = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
event.ageLocalTs = computeLocalTs(event)
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeLocalTs(event: Event) = clock.epochMillis() - (event.unsignedData?.age ?: 0)
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
|
||||||
|
private const val LOADING_PERIOD_IN_DAYS = 30
|
||||||
|
private const val EVENTS_PAGE_SIZE = 250
|
||||||
|
|
||||||
|
internal class DefaultPollHistoryService @AssistedInject constructor(
|
||||||
|
@Assisted private val roomId: String,
|
||||||
|
@Assisted private val timelineService: TimelineService,
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val clock: Clock,
|
||||||
|
private val loadMorePollsTask: LoadMorePollsTask,
|
||||||
|
private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask,
|
||||||
|
private val syncPollsTask: SyncPollsTask,
|
||||||
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
|
) : PollHistoryService {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String, timelineService: TimelineService): DefaultPollHistoryService
|
||||||
|
}
|
||||||
|
|
||||||
|
override val loadingPeriodInDays: Int
|
||||||
|
get() = LOADING_PERIOD_IN_DAYS
|
||||||
|
|
||||||
|
private val timeline by lazy {
|
||||||
|
val settings = TimelineSettings(
|
||||||
|
initialSize = EVENTS_PAGE_SIZE,
|
||||||
|
buildReadReceipts = false,
|
||||||
|
rootThreadEventId = null,
|
||||||
|
useLiveSenderInfo = false,
|
||||||
|
)
|
||||||
|
timelineService.createTimeline(eventId = null, settings = settings).also { it.start() }
|
||||||
|
}
|
||||||
|
private val timelineMutex = Mutex()
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
timeline.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadMore(): LoadedPollsStatus {
|
||||||
|
return timelineMutex.withLock {
|
||||||
|
val params = LoadMorePollsTask.Params(
|
||||||
|
timeline = timeline,
|
||||||
|
roomId = roomId,
|
||||||
|
currentTimestampMs = clock.epochMillis(),
|
||||||
|
loadingPeriodInDays = loadingPeriodInDays,
|
||||||
|
eventsPageSize = EVENTS_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
loadMorePollsTask.execute(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getLoadedPollsStatus(): LoadedPollsStatus {
|
||||||
|
val params = GetLoadedPollsStatusTask.Params(
|
||||||
|
roomId = roomId,
|
||||||
|
currentTimestampMs = clock.epochMillis(),
|
||||||
|
)
|
||||||
|
return getLoadedPollsStatusTask.execute(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun syncPolls() {
|
||||||
|
timelineMutex.withLock {
|
||||||
|
val params = SyncPollsTask.Params(
|
||||||
|
timeline = timeline,
|
||||||
|
roomId = roomId,
|
||||||
|
currentTimestampMs = clock.epochMillis(),
|
||||||
|
eventsPageSize = EVENTS_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
syncPollsTask.execute(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPollEvents(): LiveData<List<TimelineEvent>> {
|
||||||
|
val pollHistoryStatusLiveData = getPollHistoryStatus()
|
||||||
|
|
||||||
|
return Transformations.switchMap(pollHistoryStatusLiveData) { results ->
|
||||||
|
val oldestTimestamp = results.firstOrNull()?.oldestTimestampTargetReachedMs ?: clock.epochMillis()
|
||||||
|
getPollStartEventsAfter(oldestTimestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPollStartEventsAfter(timestampMs: Long): LiveData<List<TimelineEvent>> {
|
||||||
|
val eventsLiveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
val pollTypes = (EventType.POLL_START.values + EventType.ENCRYPTED).toTypedArray()
|
||||||
|
realm.where<TimelineEventEntity>()
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||||
|
.`in`(TimelineEventEntityFields.ROOT.TYPE, pollTypes)
|
||||||
|
.greaterThan(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, timestampMs)
|
||||||
|
},
|
||||||
|
{ result ->
|
||||||
|
timelineEventMapper.map(result, buildReadReceipts = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return Transformations.map(eventsLiveData) { events ->
|
||||||
|
events.filter { it.root.getClearType() in EventType.POLL_START.values }
|
||||||
|
.distinctBy { it.eventId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPollHistoryStatus(): LiveData<List<PollHistoryStatusEntity>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
realm.where<PollHistoryStatusEntity>()
|
||||||
|
.equalTo(PollHistoryStatusEntityFields.ROOM_ID, roomId)
|
||||||
|
},
|
||||||
|
{ result ->
|
||||||
|
// make a copy of the Realm object since it will be used in another transformations
|
||||||
|
result.copy()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetLoadedPollsStatusTask : Task<GetLoadedPollsStatusTask.Params, LoadedPollsStatus> {
|
||||||
|
data class Params(
|
||||||
|
val roomId: String,
|
||||||
|
val currentTimestampMs: Long,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetLoadedPollsStatusTask @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
) : GetLoadedPollsStatusTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetLoadedPollsStatusTask.Params): LoadedPollsStatus {
|
||||||
|
return monarchy.awaitTransaction { realm ->
|
||||||
|
val status = PollHistoryStatusEntity
|
||||||
|
.getOrCreate(realm, params.roomId)
|
||||||
|
.copy()
|
||||||
|
LoadedPollsStatus(
|
||||||
|
canLoadMore = status.isEndOfPollsBackward.not(),
|
||||||
|
daysSynced = status.getNbSyncedDays(params.currentTimestampMs),
|
||||||
|
hasCompletedASyncBackward = status.hasCompletedASyncBackward,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface LoadMorePollsTask : Task<LoadMorePollsTask.Params, LoadedPollsStatus> {
|
||||||
|
data class Params(
|
||||||
|
val timeline: Timeline,
|
||||||
|
val roomId: String,
|
||||||
|
val currentTimestampMs: Long,
|
||||||
|
val loadingPeriodInDays: Int,
|
||||||
|
val eventsPageSize: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultLoadMorePollsTask @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
) : LoadMorePollsTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: LoadMorePollsTask.Params): LoadedPollsStatus {
|
||||||
|
var currentPollHistoryStatus = updatePollHistoryStatus(params)
|
||||||
|
|
||||||
|
params.timeline.restartWithEventId(eventId = currentPollHistoryStatus.oldestEventIdReached)
|
||||||
|
|
||||||
|
while (shouldFetchMoreEventsBackward(currentPollHistoryStatus)) {
|
||||||
|
currentPollHistoryStatus = fetchMorePollEventsBackward(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadedPollsStatus(
|
||||||
|
canLoadMore = currentPollHistoryStatus.isEndOfPollsBackward.not(),
|
||||||
|
daysSynced = currentPollHistoryStatus.getNbSyncedDays(params.currentTimestampMs),
|
||||||
|
hasCompletedASyncBackward = currentPollHistoryStatus.hasCompletedASyncBackward,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldFetchMoreEventsBackward(status: PollHistoryStatusEntity): Boolean {
|
||||||
|
return status.currentTimestampTargetBackwardReached.not() && status.isEndOfPollsBackward.not()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updatePollHistoryStatus(params: LoadMorePollsTask.Params): PollHistoryStatusEntity {
|
||||||
|
return monarchy.awaitTransaction { realm ->
|
||||||
|
val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId)
|
||||||
|
val currentTargetTimestampMs = status.currentTimestampTargetBackwardMs
|
||||||
|
val lastTargetTimestampMs = status.oldestTimestampTargetReachedMs
|
||||||
|
val loadingPeriodMs: Long = MILLISECONDS_PER_DAY * params.loadingPeriodInDays.toLong()
|
||||||
|
if (currentTargetTimestampMs == null) {
|
||||||
|
// first load, compute the target timestamp
|
||||||
|
status.currentTimestampTargetBackwardMs = params.currentTimestampMs - loadingPeriodMs
|
||||||
|
} else if (lastTargetTimestampMs != null && status.currentTimestampTargetBackwardReached) {
|
||||||
|
// previous load has finished, update the target timestamp
|
||||||
|
status.currentTimestampTargetBackwardMs = lastTargetTimestampMs - loadingPeriodMs
|
||||||
|
}
|
||||||
|
// return a copy of the Realm object
|
||||||
|
status.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchMorePollEventsBackward(params: LoadMorePollsTask.Params): PollHistoryStatusEntity {
|
||||||
|
val events = params.timeline.awaitPaginate(
|
||||||
|
direction = Timeline.Direction.BACKWARDS,
|
||||||
|
count = params.eventsPageSize,
|
||||||
|
)
|
||||||
|
|
||||||
|
val paginationState = params.timeline.getPaginationState(direction = Timeline.Direction.BACKWARDS)
|
||||||
|
|
||||||
|
return updatePollHistoryStatus(
|
||||||
|
roomId = params.roomId,
|
||||||
|
events = events,
|
||||||
|
paginationState = paginationState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updatePollHistoryStatus(
|
||||||
|
roomId: String,
|
||||||
|
events: List<TimelineEvent>,
|
||||||
|
paginationState: Timeline.PaginationState,
|
||||||
|
): PollHistoryStatusEntity {
|
||||||
|
return monarchy.awaitTransaction { realm ->
|
||||||
|
val status = PollHistoryStatusEntity.getOrCreate(realm, roomId)
|
||||||
|
val mostRecentEventIdReached = status.mostRecentEventIdReached
|
||||||
|
|
||||||
|
if (mostRecentEventIdReached == null) {
|
||||||
|
// save it for next forward pagination
|
||||||
|
val mostRecentEvent = events
|
||||||
|
.maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE }
|
||||||
|
?.root
|
||||||
|
status.mostRecentEventIdReached = mostRecentEvent?.eventId
|
||||||
|
}
|
||||||
|
|
||||||
|
val oldestEvent = events
|
||||||
|
.minByOrNull { it.root.originServerTs ?: Long.MAX_VALUE }
|
||||||
|
?.root
|
||||||
|
val oldestEventTimestamp = oldestEvent?.originServerTs
|
||||||
|
val oldestEventId = oldestEvent?.eventId
|
||||||
|
|
||||||
|
val currentTargetTimestamp = status.currentTimestampTargetBackwardMs
|
||||||
|
|
||||||
|
if (paginationState.hasMoreToLoad.not()) {
|
||||||
|
// start of the timeline is reached, there are no more events
|
||||||
|
status.isEndOfPollsBackward = true
|
||||||
|
|
||||||
|
if (oldestEventTimestamp != null && oldestEventTimestamp > 0) {
|
||||||
|
status.oldestTimestampTargetReachedMs = oldestEventTimestamp
|
||||||
|
}
|
||||||
|
} else if (oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) {
|
||||||
|
// target has been reached
|
||||||
|
status.oldestTimestampTargetReachedMs = oldestEventTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldestEventId != null) {
|
||||||
|
// save it for next backward pagination
|
||||||
|
status.oldestEventIdReached = oldestEventId
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a copy of the Realm object
|
||||||
|
status.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
object PollConstants {
|
||||||
|
const val MILLISECONDS_PER_DAY = 24 * 60 * 60_000
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SyncPollsTask : Task<SyncPollsTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val timeline: Timeline,
|
||||||
|
val roomId: String,
|
||||||
|
val currentTimestampMs: Long,
|
||||||
|
val eventsPageSize: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSyncPollsTask @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
) : SyncPollsTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: SyncPollsTask.Params) {
|
||||||
|
val currentPollHistoryStatus = getCurrentPollHistoryStatus(params.roomId)
|
||||||
|
|
||||||
|
params.timeline.restartWithEventId(currentPollHistoryStatus.mostRecentEventIdReached)
|
||||||
|
|
||||||
|
var loadStatus = LoadStatus(shouldLoadMore = true)
|
||||||
|
while (loadStatus.shouldLoadMore) {
|
||||||
|
loadStatus = fetchMorePollEventsForward(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.timeline.restartWithEventId(currentPollHistoryStatus.oldestEventIdReached)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getCurrentPollHistoryStatus(roomId: String): PollHistoryStatusEntity {
|
||||||
|
return monarchy.awaitTransaction { realm ->
|
||||||
|
PollHistoryStatusEntity
|
||||||
|
.getOrCreate(realm, roomId)
|
||||||
|
.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchMorePollEventsForward(params: SyncPollsTask.Params): LoadStatus {
|
||||||
|
val events = params.timeline.awaitPaginate(
|
||||||
|
direction = Timeline.Direction.FORWARDS,
|
||||||
|
count = params.eventsPageSize,
|
||||||
|
)
|
||||||
|
|
||||||
|
val paginationState = params.timeline.getPaginationState(direction = Timeline.Direction.FORWARDS)
|
||||||
|
|
||||||
|
return updatePollHistoryStatus(
|
||||||
|
roomId = params.roomId,
|
||||||
|
currentTimestampMs = params.currentTimestampMs,
|
||||||
|
events = events,
|
||||||
|
paginationState = paginationState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updatePollHistoryStatus(
|
||||||
|
roomId: String,
|
||||||
|
currentTimestampMs: Long,
|
||||||
|
events: List<TimelineEvent>,
|
||||||
|
paginationState: Timeline.PaginationState,
|
||||||
|
): LoadStatus {
|
||||||
|
return monarchy.awaitTransaction { realm ->
|
||||||
|
val status = PollHistoryStatusEntity.getOrCreate(realm, roomId)
|
||||||
|
val mostRecentEvent = events
|
||||||
|
.maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE }
|
||||||
|
?.root
|
||||||
|
val mostRecentEventIdReached = mostRecentEvent?.eventId
|
||||||
|
|
||||||
|
if (mostRecentEventIdReached != null) {
|
||||||
|
// save it for next forward pagination
|
||||||
|
status.mostRecentEventIdReached = mostRecentEventIdReached
|
||||||
|
}
|
||||||
|
|
||||||
|
val mostRecentTimestamp = mostRecentEvent?.originServerTs
|
||||||
|
|
||||||
|
val shouldLoadMore = paginationState.hasMoreToLoad &&
|
||||||
|
(mostRecentTimestamp == null || mostRecentTimestamp < currentTimestampMs)
|
||||||
|
|
||||||
|
LoadStatus(shouldLoadMore = shouldLoadMore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LoadStatus(
|
||||||
|
val shouldLoadMore: Boolean,
|
||||||
|
)
|
||||||
|
}
|
@ -17,25 +17,14 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room.relation.poll
|
package org.matrix.android.sdk.internal.session.room.relation.poll
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.isPollResponse
|
import org.matrix.android.sdk.api.session.events.model.isPollResponse
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
|
||||||
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
|
||||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
|
||||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@ -54,9 +43,8 @@ internal interface FetchPollResponseEventsTask : Task<FetchPollResponseEventsTas
|
|||||||
internal class DefaultFetchPollResponseEventsTask @Inject constructor(
|
internal class DefaultFetchPollResponseEventsTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
private val filterAndStoreEventsTask: FilterAndStoreEventsTask,
|
||||||
private val clock: Clock,
|
|
||||||
private val eventDecryptor: EventDecryptor,
|
|
||||||
) : FetchPollResponseEventsTask {
|
) : FetchPollResponseEventsTask {
|
||||||
|
|
||||||
override suspend fun execute(params: FetchPollResponseEventsTask.Params): Result<Unit> = runCatching {
|
override suspend fun execute(params: FetchPollResponseEventsTask.Params): Result<Unit> = runCatching {
|
||||||
@ -70,11 +58,12 @@ internal class DefaultFetchPollResponseEventsTask @Inject constructor(
|
|||||||
private suspend fun fetchAndProcessRelatedEventsFrom(params: FetchPollResponseEventsTask.Params, from: String? = null): String? {
|
private suspend fun fetchAndProcessRelatedEventsFrom(params: FetchPollResponseEventsTask.Params, from: String? = null): String? {
|
||||||
val response = getRelatedEvents(params, from)
|
val response = getRelatedEvents(params, from)
|
||||||
|
|
||||||
val filteredEvents = response.chunks
|
val filterTaskParams = FilterAndStoreEventsTask.Params(
|
||||||
.map { decryptEventIfNeeded(it) }
|
roomId = params.roomId,
|
||||||
.filter { it.isPollResponse() }
|
events = response.chunks,
|
||||||
|
filterPredicate = { it.isPollResponse() }
|
||||||
addMissingEventsInDB(params.roomId, filteredEvents)
|
)
|
||||||
|
filterAndStoreEventsTask.execute(filterTaskParams)
|
||||||
|
|
||||||
return response.nextBatch
|
return response.nextBatch
|
||||||
}
|
}
|
||||||
@ -90,29 +79,4 @@ internal class DefaultFetchPollResponseEventsTask @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun addMissingEventsInDB(roomId: String, events: List<Event>) {
|
|
||||||
monarchy.awaitTransaction { realm ->
|
|
||||||
val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() }
|
|
||||||
if (eventIdsToCheck.isNotEmpty()) {
|
|
||||||
val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId }
|
|
||||||
|
|
||||||
events.filterNot { it.eventId in existingIds }
|
|
||||||
.map { it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = computeLocalTs(it)) }
|
|
||||||
.forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun decryptEventIfNeeded(event: Event): Event {
|
|
||||||
if (event.isEncrypted()) {
|
|
||||||
eventDecryptor.decryptEventAndSaveResult(event, timeline = "")
|
|
||||||
}
|
|
||||||
|
|
||||||
event.ageLocalTs = computeLocalTs(event)
|
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeLocalTs(event: Event) = clock.epochMillis() - (event.unsignedData?.age ?: 0)
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.event
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
|
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeClock
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeEventDecryptor
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindAll
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenIn
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
internal class DefaultFilterAndStoreEventsTaskTest {
|
||||||
|
|
||||||
|
private val fakeMonarchy = FakeMonarchy()
|
||||||
|
private val fakeClock = FakeClock()
|
||||||
|
private val fakeEventDecryptor = FakeEventDecryptor()
|
||||||
|
|
||||||
|
private val defaultFilterAndStoreEventsTask = DefaultFilterAndStoreEventsTask(
|
||||||
|
monarchy = fakeMonarchy.instance,
|
||||||
|
clock = fakeClock,
|
||||||
|
eventDecryptor = fakeEventDecryptor.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt")
|
||||||
|
mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt")
|
||||||
|
mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room and list of events when execute then filter in using given predicate and store them in local if needed`() = runTest {
|
||||||
|
// Given
|
||||||
|
val aRoomId = "roomId"
|
||||||
|
val anEventId1 = "eventId1"
|
||||||
|
val anEventId2 = "eventId2"
|
||||||
|
val anEventId3 = "eventId3"
|
||||||
|
val anEventId4 = "eventId4"
|
||||||
|
val event1 = givenAnEvent(eventId = anEventId1, isEncrypted = true, clearType = EventType.ENCRYPTED)
|
||||||
|
val event2 = givenAnEvent(eventId = anEventId2, isEncrypted = true, clearType = EventType.MESSAGE)
|
||||||
|
val event3 = givenAnEvent(eventId = anEventId3, isEncrypted = false, clearType = EventType.MESSAGE)
|
||||||
|
val event4 = givenAnEvent(eventId = anEventId4, isEncrypted = false, clearType = EventType.MESSAGE)
|
||||||
|
val events = listOf(event1, event2, event3, event4)
|
||||||
|
val filterPredicate = { event: Event -> event == event2 }
|
||||||
|
val params = givenTaskParams(roomId = aRoomId, events = events, predicate = filterPredicate)
|
||||||
|
fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1)
|
||||||
|
fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2)
|
||||||
|
fakeClock.givenEpoch(123)
|
||||||
|
givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1))
|
||||||
|
val eventEntityToSave = EventEntity(eventId = anEventId2)
|
||||||
|
every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave
|
||||||
|
every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave
|
||||||
|
|
||||||
|
// When
|
||||||
|
defaultFilterAndStoreEventsTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "")
|
||||||
|
fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "")
|
||||||
|
// Check we save in DB the event2 which is a non stored poll response
|
||||||
|
verify {
|
||||||
|
event2.toEntity(aRoomId, SendState.SYNCED, any())
|
||||||
|
eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenTaskParams(roomId: String, events: List<Event>, predicate: (Event) -> Boolean) = FilterAndStoreEventsTask.Params(
|
||||||
|
roomId = roomId,
|
||||||
|
events = events,
|
||||||
|
filterPredicate = predicate,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun givenAnEvent(
|
||||||
|
eventId: String,
|
||||||
|
isEncrypted: Boolean,
|
||||||
|
clearType: String,
|
||||||
|
): Event {
|
||||||
|
val event = mockk<Event>(relaxed = true)
|
||||||
|
every { event.eventId } returns eventId
|
||||||
|
every { event.isEncrypted() } returns isEncrypted
|
||||||
|
every { event.getClearType() } returns clearType
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenExistingEventEntities(eventIdsToCheck: List<String>, existingIds: List<String>) {
|
||||||
|
val eventEntities = existingIds.map { EventEntity(eventId = it) }
|
||||||
|
fakeMonarchy.givenWhere<EventEntity>()
|
||||||
|
.givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck)
|
||||||
|
.givenFindAll(eventEntities)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp in milliseconds corresponding to 2023/01/26.
|
||||||
|
*/
|
||||||
|
private const val A_CURRENT_TIMESTAMP = 1674737619290L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp in milliseconds corresponding to 2023/01/20.
|
||||||
|
*/
|
||||||
|
private const val AN_EVENT_TIMESTAMP = 1674169200000L
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
internal class DefaultGetLoadedPollsStatusTaskTest {
|
||||||
|
|
||||||
|
private val fakeMonarchy = FakeMonarchy()
|
||||||
|
|
||||||
|
private val defaultGetLoadedPollsStatusTask = DefaultGetLoadedPollsStatusTask(
|
||||||
|
monarchy = fakeMonarchy.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history status exists in db with an oldestTimestamp reached when execute then the computed status is returned`() = runTest {
|
||||||
|
// Given
|
||||||
|
val params = givenTaskParams()
|
||||||
|
val pollHistoryStatus = aPollHistoryStatusEntity(
|
||||||
|
isEndOfPollsBackward = false,
|
||||||
|
oldestTimestampReached = AN_EVENT_TIMESTAMP,
|
||||||
|
)
|
||||||
|
fakeMonarchy.fakeRealm
|
||||||
|
.givenWhere<PollHistoryStatusEntity>()
|
||||||
|
.givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID)
|
||||||
|
.givenFindFirst(pollHistoryStatus)
|
||||||
|
val expectedStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 6,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = defaultGetLoadedPollsStatusTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history status exists in db and no oldestTimestamp reached when execute then the computed status is returned`() = runTest {
|
||||||
|
// Given
|
||||||
|
val params = givenTaskParams()
|
||||||
|
val pollHistoryStatus = aPollHistoryStatusEntity(
|
||||||
|
isEndOfPollsBackward = false,
|
||||||
|
oldestTimestampReached = null,
|
||||||
|
)
|
||||||
|
fakeMonarchy.fakeRealm
|
||||||
|
.givenWhere<PollHistoryStatusEntity>()
|
||||||
|
.givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID)
|
||||||
|
.givenFindFirst(pollHistoryStatus)
|
||||||
|
val expectedStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 0,
|
||||||
|
hasCompletedASyncBackward = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = defaultGetLoadedPollsStatusTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenTaskParams(): GetLoadedPollsStatusTask.Params {
|
||||||
|
return GetLoadedPollsStatusTask.Params(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
currentTimestampMs = A_CURRENT_TIMESTAMP,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aPollHistoryStatusEntity(
|
||||||
|
isEndOfPollsBackward: Boolean,
|
||||||
|
oldestTimestampReached: Long?,
|
||||||
|
): PollHistoryStatusEntity {
|
||||||
|
return PollHistoryStatusEntity(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
isEndOfPollsBackward = isEndOfPollsBackward,
|
||||||
|
oldestTimestampTargetReachedMs = oldestTimestampReached,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import io.mockk.coVerifyOrder
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeTimeline
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp in milliseconds corresponding to 2023/01/26.
|
||||||
|
*/
|
||||||
|
private const val A_CURRENT_TIMESTAMP = 1674737619290L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp in milliseconds corresponding to 2023/01/20.
|
||||||
|
*/
|
||||||
|
private const val AN_EVENT_TIMESTAMP = 1674169200000L
|
||||||
|
private const val A_PERIOD_IN_DAYS = 3
|
||||||
|
private const val A_PAGE_SIZE = 200
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
internal class DefaultLoadMorePollsTaskTest {
|
||||||
|
|
||||||
|
private val fakeMonarchy = FakeMonarchy()
|
||||||
|
private val fakeTimeline = FakeTimeline()
|
||||||
|
|
||||||
|
private val defaultLoadMorePollsTask = DefaultLoadMorePollsTask(
|
||||||
|
monarchy = fakeMonarchy.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given timeline when execute then more events are fetched in backward direction until has no more to load`() = runTest {
|
||||||
|
// Given
|
||||||
|
val params = givenTaskParams()
|
||||||
|
val oldestEventId = "oldest"
|
||||||
|
val pollHistoryStatus = aPollHistoryStatusEntity(
|
||||||
|
oldestEventIdReached = oldestEventId,
|
||||||
|
)
|
||||||
|
fakeMonarchy.fakeRealm
|
||||||
|
.givenWhere<PollHistoryStatusEntity>()
|
||||||
|
.givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID)
|
||||||
|
.givenFindFirst(pollHistoryStatus)
|
||||||
|
fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId)
|
||||||
|
val anEventId = "event-id"
|
||||||
|
val aTimelineEvent = aTimelineEvent(anEventId, AN_EVENT_TIMESTAMP)
|
||||||
|
fakeTimeline.givenAwaitPaginateReturns(
|
||||||
|
events = listOf(aTimelineEvent),
|
||||||
|
direction = Timeline.Direction.BACKWARDS,
|
||||||
|
count = params.eventsPageSize,
|
||||||
|
)
|
||||||
|
val aPaginationState = aPaginationState(hasMoreToLoad = false)
|
||||||
|
fakeTimeline.givenGetPaginationStateReturns(
|
||||||
|
paginationState = aPaginationState,
|
||||||
|
direction = Timeline.Direction.BACKWARDS,
|
||||||
|
)
|
||||||
|
val expectedLoadStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = false,
|
||||||
|
daysSynced = 6,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = defaultLoadMorePollsTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerifyOrder {
|
||||||
|
fakeTimeline.instance.restartWithEventId(oldestEventId)
|
||||||
|
fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.BACKWARDS, count = params.eventsPageSize)
|
||||||
|
fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.BACKWARDS)
|
||||||
|
}
|
||||||
|
pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId
|
||||||
|
pollHistoryStatus.oldestEventIdReached shouldBeEqualTo anEventId
|
||||||
|
pollHistoryStatus.isEndOfPollsBackward shouldBeEqualTo true
|
||||||
|
pollHistoryStatus.oldestTimestampTargetReachedMs shouldBeEqualTo AN_EVENT_TIMESTAMP
|
||||||
|
result shouldBeEqualTo expectedLoadStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given timeline when execute then more events are fetched in backward direction until current target is reached`() = runTest {
|
||||||
|
// Given
|
||||||
|
val params = givenTaskParams()
|
||||||
|
val oldestEventId = "oldest"
|
||||||
|
val pollHistoryStatus = aPollHistoryStatusEntity(
|
||||||
|
oldestEventIdReached = oldestEventId,
|
||||||
|
)
|
||||||
|
fakeMonarchy.fakeRealm
|
||||||
|
.givenWhere<PollHistoryStatusEntity>()
|
||||||
|
.givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID)
|
||||||
|
.givenFindFirst(pollHistoryStatus)
|
||||||
|
fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId)
|
||||||
|
val anEventId = "event-id"
|
||||||
|
val aTimelineEvent = aTimelineEvent(anEventId, AN_EVENT_TIMESTAMP)
|
||||||
|
fakeTimeline.givenAwaitPaginateReturns(
|
||||||
|
events = listOf(aTimelineEvent),
|
||||||
|
direction = Timeline.Direction.BACKWARDS,
|
||||||
|
count = params.eventsPageSize,
|
||||||
|
)
|
||||||
|
val aPaginationState = aPaginationState(hasMoreToLoad = true)
|
||||||
|
fakeTimeline.givenGetPaginationStateReturns(
|
||||||
|
paginationState = aPaginationState,
|
||||||
|
direction = Timeline.Direction.BACKWARDS,
|
||||||
|
)
|
||||||
|
val expectedLoadStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 6,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = defaultLoadMorePollsTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerifyOrder {
|
||||||
|
fakeTimeline.instance.restartWithEventId(oldestEventId)
|
||||||
|
fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.BACKWARDS, count = params.eventsPageSize)
|
||||||
|
fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.BACKWARDS)
|
||||||
|
}
|
||||||
|
pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId
|
||||||
|
pollHistoryStatus.oldestEventIdReached shouldBeEqualTo anEventId
|
||||||
|
pollHistoryStatus.isEndOfPollsBackward shouldBeEqualTo false
|
||||||
|
pollHistoryStatus.oldestTimestampTargetReachedMs shouldBeEqualTo AN_EVENT_TIMESTAMP
|
||||||
|
result shouldBeEqualTo expectedLoadStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenTaskParams(): LoadMorePollsTask.Params {
|
||||||
|
return LoadMorePollsTask.Params(
|
||||||
|
timeline = fakeTimeline.instance,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
currentTimestampMs = A_CURRENT_TIMESTAMP,
|
||||||
|
loadingPeriodInDays = A_PERIOD_IN_DAYS,
|
||||||
|
eventsPageSize = A_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aPollHistoryStatusEntity(
|
||||||
|
oldestEventIdReached: String,
|
||||||
|
): PollHistoryStatusEntity {
|
||||||
|
return PollHistoryStatusEntity(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
oldestEventIdReached = oldestEventIdReached,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aTimelineEvent(eventId: String, timestamp: Long): TimelineEvent {
|
||||||
|
val event = mockk<TimelineEvent>()
|
||||||
|
every { event.root.originServerTs } returns timestamp
|
||||||
|
every { event.root.eventId } returns eventId
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aPaginationState(hasMoreToLoad: Boolean): Timeline.PaginationState {
|
||||||
|
return Timeline.PaginationState(
|
||||||
|
hasMoreToLoad = hasMoreToLoad,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
|
import io.mockk.coVerifyOrder
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeTimeline
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val A_TIMESTAMP = 123L
|
||||||
|
private const val A_PAGE_SIZE = 200
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
internal class DefaultSyncPollsTaskTest {
|
||||||
|
|
||||||
|
private val fakeMonarchy = FakeMonarchy()
|
||||||
|
private val fakeTimeline = FakeTimeline()
|
||||||
|
|
||||||
|
private val defaultSyncPollsTask = DefaultSyncPollsTask(
|
||||||
|
monarchy = fakeMonarchy.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given timeline when execute then more events are fetched in forward direction after the most recent event id reached`() = runTest {
|
||||||
|
// Given
|
||||||
|
val params = givenTaskParams()
|
||||||
|
val mostRecentEventId = "most-recent"
|
||||||
|
val oldestEventId = "oldest"
|
||||||
|
val pollHistoryStatus = aPollHistoryStatusEntity(
|
||||||
|
mostRecentEventIdReached = mostRecentEventId,
|
||||||
|
oldestEventIdReached = oldestEventId,
|
||||||
|
)
|
||||||
|
fakeMonarchy.fakeRealm
|
||||||
|
.givenWhere<PollHistoryStatusEntity>()
|
||||||
|
.givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID)
|
||||||
|
.givenFindFirst(pollHistoryStatus)
|
||||||
|
fakeTimeline.givenRestartWithEventIdSuccess(mostRecentEventId)
|
||||||
|
fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId)
|
||||||
|
val anEventId = "event-id"
|
||||||
|
val aTimelineEvent = aTimelineEvent(anEventId)
|
||||||
|
fakeTimeline.givenAwaitPaginateReturns(
|
||||||
|
events = listOf(aTimelineEvent),
|
||||||
|
direction = Timeline.Direction.FORWARDS,
|
||||||
|
count = params.eventsPageSize,
|
||||||
|
)
|
||||||
|
fakeTimeline.givenGetPaginationStateReturns(
|
||||||
|
paginationState = aPaginationState(),
|
||||||
|
direction = Timeline.Direction.FORWARDS,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
defaultSyncPollsTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerifyOrder {
|
||||||
|
fakeTimeline.instance.restartWithEventId(mostRecentEventId)
|
||||||
|
fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.FORWARDS, count = params.eventsPageSize)
|
||||||
|
fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.FORWARDS)
|
||||||
|
fakeTimeline.instance.restartWithEventId(oldestEventId)
|
||||||
|
}
|
||||||
|
pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenTaskParams(): SyncPollsTask.Params {
|
||||||
|
return SyncPollsTask.Params(
|
||||||
|
timeline = fakeTimeline.instance,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
currentTimestampMs = A_TIMESTAMP,
|
||||||
|
eventsPageSize = A_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aPollHistoryStatusEntity(
|
||||||
|
mostRecentEventIdReached: String,
|
||||||
|
oldestEventIdReached: String,
|
||||||
|
): PollHistoryStatusEntity {
|
||||||
|
return PollHistoryStatusEntity(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
mostRecentEventIdReached = mostRecentEventIdReached,
|
||||||
|
oldestEventIdReached = oldestEventIdReached,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aTimelineEvent(eventId: String): TimelineEvent {
|
||||||
|
val event = mockk<TimelineEvent>()
|
||||||
|
every { event.root.originServerTs } returns 123L
|
||||||
|
every { event.root.eventId } returns eventId
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aPaginationState(): Timeline.PaginationState {
|
||||||
|
return Timeline.PaginationState(
|
||||||
|
hasMoreToLoad = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,12 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.relation.poll
|
package org.matrix.android.sdk.internal.session.room.relation.poll
|
||||||
|
|
||||||
|
import io.mockk.coJustRun
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -29,41 +30,28 @@ import org.junit.Test
|
|||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.isPollResponse
|
import org.matrix.android.sdk.api.session.events.model.isPollResponse
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask
|
||||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
|
||||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
||||||
import org.matrix.android.sdk.test.fakes.FakeClock
|
|
||||||
import org.matrix.android.sdk.test.fakes.FakeEventDecryptor
|
|
||||||
import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
|
import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.test.fakes.FakeMonarchy
|
|
||||||
import org.matrix.android.sdk.test.fakes.FakeRoomApi
|
import org.matrix.android.sdk.test.fakes.FakeRoomApi
|
||||||
import org.matrix.android.sdk.test.fakes.givenFindAll
|
|
||||||
import org.matrix.android.sdk.test.fakes.givenIn
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class DefaultFetchPollResponseEventsTaskTest {
|
internal class DefaultFetchPollResponseEventsTaskTest {
|
||||||
|
|
||||||
private val fakeRoomAPI = FakeRoomApi()
|
private val fakeRoomAPI = FakeRoomApi()
|
||||||
private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver()
|
private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver()
|
||||||
private val fakeMonarchy = FakeMonarchy()
|
private val filterAndStoreEventsTask = mockk<FilterAndStoreEventsTask>()
|
||||||
private val fakeClock = FakeClock()
|
|
||||||
private val fakeEventDecryptor = FakeEventDecryptor()
|
|
||||||
|
|
||||||
private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask(
|
private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask(
|
||||||
roomAPI = fakeRoomAPI.instance,
|
roomAPI = fakeRoomAPI.instance,
|
||||||
globalErrorReceiver = fakeGlobalErrorReceiver,
|
globalErrorReceiver = fakeGlobalErrorReceiver,
|
||||||
monarchy = fakeMonarchy.instance,
|
filterAndStoreEventsTask = filterAndStoreEventsTask,
|
||||||
clock = fakeClock,
|
|
||||||
eventDecryptor = fakeEventDecryptor.instance,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt")
|
mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt")
|
||||||
|
|
||||||
mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt")
|
mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt")
|
||||||
mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt")
|
mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt")
|
||||||
}
|
}
|
||||||
@ -74,7 +62,7 @@ internal class DefaultFetchPollResponseEventsTaskTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a room and a poll when execute then fetch related events and store them in local if needed`() = runTest {
|
fun `given a room and a poll when execute then fetch related events and store them in local`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val aRoomId = "roomId"
|
val aRoomId = "roomId"
|
||||||
val aPollEventId = "eventId"
|
val aPollEventId = "eventId"
|
||||||
@ -94,13 +82,7 @@ internal class DefaultFetchPollResponseEventsTaskTest {
|
|||||||
fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse)
|
fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse)
|
||||||
val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null)
|
val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null)
|
||||||
fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse)
|
fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse)
|
||||||
fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1)
|
coJustRun { filterAndStoreEventsTask.execute(any()) }
|
||||||
fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2)
|
|
||||||
fakeClock.givenEpoch(123)
|
|
||||||
givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1))
|
|
||||||
val eventEntityToSave = EventEntity(eventId = anEventId2)
|
|
||||||
every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave
|
|
||||||
every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave
|
|
||||||
|
|
||||||
// When
|
// When
|
||||||
defaultFetchPollResponseEventsTask.execute(params)
|
defaultFetchPollResponseEventsTask.execute(params)
|
||||||
@ -111,21 +93,22 @@ internal class DefaultFetchPollResponseEventsTaskTest {
|
|||||||
eventId = params.startPollEventId,
|
eventId = params.startPollEventId,
|
||||||
relationType = RelationType.REFERENCE,
|
relationType = RelationType.REFERENCE,
|
||||||
from = null,
|
from = null,
|
||||||
limit = FETCH_RELATED_EVENTS_LIMIT
|
limit = FETCH_RELATED_EVENTS_LIMIT,
|
||||||
)
|
)
|
||||||
fakeRoomAPI.verifyGetRelations(
|
fakeRoomAPI.verifyGetRelations(
|
||||||
roomId = params.roomId,
|
roomId = params.roomId,
|
||||||
eventId = params.startPollEventId,
|
eventId = params.startPollEventId,
|
||||||
relationType = RelationType.REFERENCE,
|
relationType = RelationType.REFERENCE,
|
||||||
from = aNextBatchToken,
|
from = aNextBatchToken,
|
||||||
limit = FETCH_RELATED_EVENTS_LIMIT
|
limit = FETCH_RELATED_EVENTS_LIMIT,
|
||||||
)
|
)
|
||||||
fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "")
|
coVerify {
|
||||||
fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "")
|
filterAndStoreEventsTask.execute(match {
|
||||||
// Check we save in DB the event2 which is a non stored poll response
|
it.roomId == aRoomId && it.events == firstEvents
|
||||||
verify {
|
})
|
||||||
event2.toEntity(aRoomId, SendState.SYNCED, any())
|
filterAndStoreEventsTask.execute(match {
|
||||||
eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION)
|
it.roomId == aRoomId && it.events == secondEvents
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,11 +136,4 @@ internal class DefaultFetchPollResponseEventsTaskTest {
|
|||||||
every { event.isEncrypted() } returns isEncrypted
|
every { event.isEncrypted() } returns isEncrypted
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenExistingEventEntities(eventIdsToCheck: List<String>, existingIds: List<String>) {
|
|
||||||
val eventEntities = existingIds.map { EventEntity(eventId = it) }
|
|
||||||
fakeMonarchy.givenWhere<EventEntity>()
|
|
||||||
.givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck)
|
|
||||||
.givenFindAll(eventEntities)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.test.fakes
|
||||||
|
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
class FakeTimeline {
|
||||||
|
val instance: Timeline = mockk()
|
||||||
|
|
||||||
|
fun givenRestartWithEventIdSuccess(eventId: String) {
|
||||||
|
justRun { instance.restartWithEventId(eventId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenAwaitPaginateReturns(events: List<TimelineEvent>, direction: Timeline.Direction, count: Int) {
|
||||||
|
coEvery { instance.awaitPaginate(direction, count) } returns events
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenGetPaginationStateReturns(paginationState: Timeline.PaginationState, direction: Timeline.Direction) {
|
||||||
|
every { instance.getPaginationState(direction) } returns paginationState
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.core.session
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.extensions.startSyncing
|
||||||
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class EnsureSessionSyncingUseCase @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
fun execute() {
|
||||||
|
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
||||||
|
if (session.syncService().getSyncState() == SyncState.Idle) {
|
||||||
|
Timber.w("EnsureSessionSyncingUseCase: start syncing")
|
||||||
|
session.startSyncing(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ enum class Command(
|
|||||||
val isDevCommand: Boolean,
|
val isDevCommand: Boolean,
|
||||||
val isThreadCommand: Boolean
|
val isThreadCommand: Boolean
|
||||||
) {
|
) {
|
||||||
|
CRASH_APP("/crash", null, "", R.string.command_description_crash_application, true, true),
|
||||||
EMOTE("/me", null, "<message>", R.string.command_description_emote, false, true),
|
EMOTE("/me", null, "<message>", R.string.command_description_emote, false, true),
|
||||||
BAN_USER("/ban", null, "<user-id> [reason]", R.string.command_description_ban_user, false, false),
|
BAN_USER("/ban", null, "<user-id> [reason]", R.string.command_description_ban_user, false, false),
|
||||||
UNBAN_USER("/unban", null, "<user-id> [reason]", R.string.command_description_unban_user, false, false),
|
UNBAN_USER("/unban", null, "<user-id> [reason]", R.string.command_description_unban_user, false, false),
|
||||||
|
@ -20,13 +20,16 @@ import im.vector.app.core.extensions.isEmail
|
|||||||
import im.vector.app.core.extensions.isMsisdn
|
import im.vector.app.core.extensions.isMsisdn
|
||||||
import im.vector.app.core.extensions.orEmpty
|
import im.vector.app.core.extensions.orEmpty
|
||||||
import im.vector.app.features.home.room.detail.ChatEffect
|
import im.vector.app.features.home.room.detail.ChatEffect
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CommandParser @Inject constructor() {
|
class CommandParser @Inject constructor(
|
||||||
|
private val vectorPreferences: VectorPreferences
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the text message into a Slash command.
|
* Convert the text message into a Slash command.
|
||||||
@ -404,6 +407,9 @@ class CommandParser @Inject constructor() {
|
|||||||
ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
|
ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Command.CRASH_APP.matches(slashCommand) && vectorPreferences.developerMode() -> {
|
||||||
|
throw RuntimeException("Application crashed from user demand")
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Unknown command
|
// Unknown command
|
||||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||||
|
@ -31,6 +31,7 @@ import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase
|
|||||||
import im.vector.app.core.pushers.PushersManager
|
import im.vector.app.core.pushers.PushersManager
|
||||||
import im.vector.app.core.pushers.RegisterUnifiedPushUseCase
|
import im.vector.app.core.pushers.RegisterUnifiedPushUseCase
|
||||||
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
||||||
|
import im.vector.app.core.session.EnsureSessionSyncingUseCase
|
||||||
import im.vector.app.features.analytics.AnalyticsConfig
|
import im.vector.app.features.analytics.AnalyticsConfig
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
import im.vector.app.features.analytics.extensions.toAnalyticsType
|
||||||
@ -93,7 +94,8 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
|
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
|
||||||
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||||
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
|
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers
|
private val ensureSessionSyncingUseCase: EnsureSessionSyncingUseCase,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@ -117,6 +119,8 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
if (isInitialized) return
|
if (isInitialized) return
|
||||||
isInitialized = true
|
isInitialized = true
|
||||||
|
// Ensure Session is syncing
|
||||||
|
ensureSessionSyncingUseCase.execute()
|
||||||
registerUnifiedPushIfNeeded()
|
registerUnifiedPushIfNeeded()
|
||||||
viewModelScope.launch(coroutineDispatchers.io) {
|
viewModelScope.launch(coroutineDispatchers.io) {
|
||||||
cleanupFiles()
|
cleanupFiles()
|
||||||
|
@ -38,8 +38,8 @@ data class HomeDetailViewState(
|
|||||||
val notificationCountRooms: Int = 0,
|
val notificationCountRooms: Int = 0,
|
||||||
val notificationHighlightRooms: Boolean = false,
|
val notificationHighlightRooms: Boolean = false,
|
||||||
val hasUnreadMessages: Boolean = false,
|
val hasUnreadMessages: Boolean = false,
|
||||||
val syncState: SyncState = SyncState.Idle,
|
val syncState: SyncState? = null,
|
||||||
val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState = SyncRequestState.IncrementalSyncIdle,
|
val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState? = null,
|
||||||
val pushCounter: Int = 0,
|
val pushCounter: Int = 0,
|
||||||
val pstnSupportFlag: Boolean = false,
|
val pstnSupportFlag: Boolean = false,
|
||||||
val forceDialPadTab: Boolean = false
|
val forceDialPadTab: Boolean = false
|
||||||
|
@ -60,8 +60,8 @@ data class RoomDetailViewState(
|
|||||||
val formattedTypingUsers: String? = null,
|
val formattedTypingUsers: String? = null,
|
||||||
val tombstoneEvent: Event? = null,
|
val tombstoneEvent: Event? = null,
|
||||||
val joinUpgradedRoomAsync: Async<String> = Uninitialized,
|
val joinUpgradedRoomAsync: Async<String> = Uninitialized,
|
||||||
val syncState: SyncState = SyncState.Idle,
|
val syncState: SyncState? = null,
|
||||||
val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState = SyncRequestState.IncrementalSyncIdle,
|
val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState? = null,
|
||||||
val pushCounter: Int = 0,
|
val pushCounter: Int = 0,
|
||||||
val highlightedEventId: String? = null,
|
val highlightedEventId: String? = null,
|
||||||
val unreadState: UnreadState = UnreadState.Unknown,
|
val unreadState: UnreadState = UnreadState.Unknown,
|
||||||
|
@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.factory
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
import im.vector.app.features.poll.PollViewState
|
import im.vector.app.features.poll.PollViewState
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
@ -29,6 +28,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class PollItemViewStateFactory @Inject constructor(
|
class PollItemViewStateFactory @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val pollOptionViewStateFactory: PollOptionViewStateFactory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
@ -40,7 +40,6 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
val question = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
val question = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
|
|
||||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
|
||||||
val totalVotes = pollResponseSummary?.totalVotes ?: 0
|
val totalVotes = pollResponseSummary?.totalVotes ?: 0
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
@ -48,7 +47,7 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
createSendingPollViewState(question, pollCreationInfo)
|
createSendingPollViewState(question, pollCreationInfo)
|
||||||
}
|
}
|
||||||
informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> {
|
informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> {
|
||||||
createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes, winnerVoteCount)
|
createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes)
|
||||||
}
|
}
|
||||||
pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> {
|
pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> {
|
||||||
createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary)
|
createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary)
|
||||||
@ -67,12 +66,7 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
question = question,
|
question = question,
|
||||||
votesStatus = stringProvider.getString(R.string.poll_no_votes_cast),
|
votesStatus = stringProvider.getString(R.string.poll_no_votes_cast),
|
||||||
canVote = false,
|
canVote = false,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo),
|
||||||
PollOptionViewState.PollSending(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: ""
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +75,6 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
pollCreationInfo: PollCreationInfo?,
|
pollCreationInfo: PollCreationInfo?,
|
||||||
pollResponseSummary: PollResponseData?,
|
pollResponseSummary: PollResponseData?,
|
||||||
totalVotes: Int,
|
totalVotes: Int,
|
||||||
winnerVoteCount: Int?,
|
|
||||||
): PollViewState {
|
): PollViewState {
|
||||||
val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) {
|
val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) {
|
||||||
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||||
@ -92,16 +85,7 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = false,
|
canVote = false,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseSummary),
|
||||||
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
|
||||||
PollOptionViewState.PollEnded(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: "",
|
|
||||||
voteCount = voteSummary?.total ?: 0,
|
|
||||||
votePercentage = voteSummary?.percentage ?: 0.0,
|
|
||||||
isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,14 +98,7 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
question = question,
|
question = question,
|
||||||
votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended),
|
votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseSummary),
|
||||||
val isMyVote = pollResponseSummary?.myVote == answer.id
|
|
||||||
PollOptionViewState.PollUndisclosed(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: "",
|
|
||||||
isSelected = isMyVote
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,17 +117,7 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseSummary),
|
||||||
val isMyVote = pollResponseSummary?.myVote == answer.id
|
|
||||||
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
|
||||||
PollOptionViewState.PollVoted(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: "",
|
|
||||||
voteCount = voteSummary?.total ?: 0,
|
|
||||||
votePercentage = voteSummary?.percentage ?: 0.0,
|
|
||||||
isSelected = isMyVote
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,12 +135,7 @@ class PollItemViewStateFactory @Inject constructor(
|
|||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollOptionViewStateFactory.createPollReadyOptions(pollCreationInfo),
|
||||||
PollOptionViewState.PollReady(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: ""
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.home.room.detail.timeline.factory
|
||||||
|
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PollOptionViewStateFactory @Inject constructor() {
|
||||||
|
|
||||||
|
fun createPollEndedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List<PollOptionViewState.PollEnded> {
|
||||||
|
val winnerVoteCount = pollResponseData?.winnerVoteCount
|
||||||
|
return pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val voteSummary = pollResponseData?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
|
PollOptionViewState.PollEnded(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
voteCount = voteSummary?.total ?: 0,
|
||||||
|
votePercentage = voteSummary?.percentage ?: 0.0,
|
||||||
|
isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPollSendingOptions(pollCreationInfo: PollCreationInfo?): List<PollOptionViewState.PollSending> {
|
||||||
|
return pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollSending(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPollUndisclosedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List<PollOptionViewState.PollUndisclosed> {
|
||||||
|
return pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val isMyVote = pollResponseData?.myVote == answer.id
|
||||||
|
PollOptionViewState.PollUndisclosed(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
isSelected = isMyVote
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPollVotedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List<PollOptionViewState.PollVoted> {
|
||||||
|
return pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val isMyVote = pollResponseData?.myVote == answer.id
|
||||||
|
val voteSummary = pollResponseData?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
|
PollOptionViewState.PollVoted(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
voteCount = voteSummary?.total ?: 0,
|
||||||
|
votePercentage = voteSummary?.percentage ?: 0.0,
|
||||||
|
isSelected = isMyVote
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPollReadyOptions(pollCreationInfo: PollCreationInfo?): List<PollOptionViewState.PollReady> {
|
||||||
|
return pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollReady(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: ""
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
@ -207,6 +207,7 @@ class RoomProfileFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
roomProfileController.callback = null
|
||||||
views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
|
views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
|
||||||
views.matrixProfileRecyclerView.cleanup()
|
views.matrixProfileRecyclerView.cleanup()
|
||||||
appBarStateChangeListener = null
|
appBarStateChangeListener = null
|
||||||
|
@ -23,20 +23,23 @@ import dagger.assisted.AssistedInject
|
|||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.DisposePollHistoryUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase
|
||||||
|
import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RoomPollsViewModel @AssistedInject constructor(
|
class RoomPollsViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: RoomPollsViewState,
|
@Assisted initialState: RoomPollsViewState,
|
||||||
private val getPollsUseCase: GetPollsUseCase,
|
private val getPollsUseCase: GetPollsUseCase,
|
||||||
private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase,
|
|
||||||
private val loadMorePollsUseCase: LoadMorePollsUseCase,
|
private val loadMorePollsUseCase: LoadMorePollsUseCase,
|
||||||
private val syncPollsUseCase: SyncPollsUseCase,
|
private val syncPollsUseCase: SyncPollsUseCase,
|
||||||
|
private val disposePollHistoryUseCase: DisposePollHistoryUseCase,
|
||||||
|
private val pollSummaryMapper: PollSummaryMapper,
|
||||||
) : VectorViewModel<RoomPollsViewState, RoomPollsAction, RoomPollsViewEvent>(initialState) {
|
) : VectorViewModel<RoomPollsViewState, RoomPollsAction, RoomPollsViewEvent>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@ -48,26 +51,26 @@ class RoomPollsViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val roomId = initialState.roomId
|
val roomId = initialState.roomId
|
||||||
updateLoadedPollStatus(roomId)
|
|
||||||
syncPolls(roomId)
|
syncPolls(roomId)
|
||||||
observePolls(roomId)
|
observePolls(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLoadedPollStatus(roomId: String) {
|
override fun onCleared() {
|
||||||
val loadedPollsStatus = getLoadedPollsStatusUseCase.execute(roomId)
|
withState { disposePollHistoryUseCase.execute(it.roomId) }
|
||||||
setState {
|
super.onCleared()
|
||||||
copy(
|
|
||||||
canLoadMore = loadedPollsStatus.canLoadMore,
|
|
||||||
nbLoadedDays = loadedPollsStatus.nbLoadedDays
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun syncPolls(roomId: String) {
|
private fun syncPolls(roomId: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
setState { copy(isSyncing = true) }
|
setState { copy(isSyncing = true) }
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
syncPollsUseCase.execute(roomId)
|
val loadedPollsStatus = syncPollsUseCase.execute(roomId)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
canLoadMore = loadedPollsStatus.canLoadMore,
|
||||||
|
nbSyncedDays = loadedPollsStatus.daysSynced,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result.isFailure) {
|
if (result.isFailure) {
|
||||||
_viewEvents.post(RoomPollsViewEvent.LoadingError)
|
_viewEvents.post(RoomPollsViewEvent.LoadingError)
|
||||||
@ -78,6 +81,7 @@ class RoomPollsViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
private fun observePolls(roomId: String) {
|
private fun observePolls(roomId: String) {
|
||||||
getPollsUseCase.execute(roomId)
|
getPollsUseCase.execute(roomId)
|
||||||
|
.map { it.mapNotNull { event -> pollSummaryMapper.map(event) } }
|
||||||
.onEach { setState { copy(polls = it) } }
|
.onEach { setState { copy(polls = it) } }
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
@ -96,7 +100,7 @@ class RoomPollsViewModel @AssistedInject constructor(
|
|||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
canLoadMore = status.canLoadMore,
|
canLoadMore = status.canLoadMore,
|
||||||
nbLoadedDays = status.nbLoadedDays,
|
nbSyncedDays = status.daysSynced,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ data class RoomPollsViewState(
|
|||||||
val polls: List<PollSummary> = emptyList(),
|
val polls: List<PollSummary> = emptyList(),
|
||||||
val isLoadingMore: Boolean = false,
|
val isLoadingMore: Boolean = false,
|
||||||
val canLoadMore: Boolean = true,
|
val canLoadMore: Boolean = true,
|
||||||
val nbLoadedDays: Int = 0,
|
val nbSyncedDays: Int = 0,
|
||||||
val isSyncing: Boolean = false,
|
val isSyncing: Boolean = false,
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.data
|
package im.vector.app.features.roomprofile.polls.list.data
|
||||||
|
|
||||||
data class LoadedPollsStatus(
|
sealed class PollHistoryError : Exception() {
|
||||||
val canLoadMore: Boolean,
|
object UnknownRoomError : PollHistoryError()
|
||||||
val nbLoadedDays: Int,
|
}
|
||||||
)
|
|
@ -16,159 +16,44 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.data
|
package im.vector.app.features.roomprofile.polls.list.data
|
||||||
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
import androidx.lifecycle.asFlow
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
import timber.log.Timber
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
class RoomPollDataSource @Inject constructor(
|
||||||
class RoomPollDataSource @Inject constructor() {
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
|
||||||
private val pollsFlow = MutableSharedFlow<List<PollSummary>>(replay = 1)
|
private fun getPollHistoryService(roomId: String): PollHistoryService {
|
||||||
private val polls = mutableListOf<PollSummary>()
|
return activeSessionHolder
|
||||||
private var fakeLoadCounter = 0
|
.getSafeActiveSession()
|
||||||
|
?.getRoom(roomId)
|
||||||
// TODO
|
?.pollHistoryService()
|
||||||
// unmock using SDK service + add unit tests
|
?: throw PollHistoryError.UnknownRoomError
|
||||||
// after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer
|
|
||||||
fun getPolls(roomId: String): Flow<List<PollSummary>> {
|
|
||||||
Timber.d("roomId=$roomId")
|
|
||||||
return pollsFlow.asSharedFlow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
|
fun dispose(roomId: String) {
|
||||||
Timber.d("roomId=$roomId")
|
getPollHistoryService(roomId).dispose()
|
||||||
return LoadedPollsStatus(
|
|
||||||
canLoadMore = canLoadMore(),
|
|
||||||
nbLoadedDays = fakeLoadCounter * 30,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canLoadMore(): Boolean {
|
fun getPolls(roomId: String): Flow<List<TimelineEvent>> {
|
||||||
return fakeLoadCounter < 2
|
return getPollHistoryService(roomId).getPollEvents().asFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
|
||||||
|
return getPollHistoryService(roomId).getLoadedPollsStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadMorePolls(roomId: String): LoadedPollsStatus {
|
suspend fun loadMorePolls(roomId: String): LoadedPollsStatus {
|
||||||
// TODO
|
return getPollHistoryService(roomId).loadMore()
|
||||||
// unmock using SDK service + add unit tests
|
|
||||||
delay(3000)
|
|
||||||
fakeLoadCounter++
|
|
||||||
when (fakeLoadCounter) {
|
|
||||||
1 -> polls.addAll(getActivePollsPart1() + getEndedPollsPart1())
|
|
||||||
2 -> polls.addAll(getActivePollsPart2() + getEndedPollsPart2())
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
pollsFlow.emit(polls)
|
|
||||||
return getLoadedPollsStatus(roomId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getActivePollsPart1(): List<PollSummary.ActivePoll> {
|
|
||||||
return listOf(
|
|
||||||
PollSummary.ActivePoll(
|
|
||||||
id = "id1",
|
|
||||||
// 2022/06/28 UTC+1
|
|
||||||
creationTimestamp = 1656367200000,
|
|
||||||
title = "Which charity would you like to support?"
|
|
||||||
),
|
|
||||||
PollSummary.ActivePoll(
|
|
||||||
id = "id2",
|
|
||||||
// 2022/06/26 UTC+1
|
|
||||||
creationTimestamp = 1656194400000,
|
|
||||||
title = "Which sport should the pupils do this year?"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getActivePollsPart2(): List<PollSummary.ActivePoll> {
|
|
||||||
return listOf(
|
|
||||||
PollSummary.ActivePoll(
|
|
||||||
id = "id3",
|
|
||||||
// 2022/06/24 UTC+1
|
|
||||||
creationTimestamp = 1656021600000,
|
|
||||||
title = "What type of food should we have at the party?"
|
|
||||||
),
|
|
||||||
PollSummary.ActivePoll(
|
|
||||||
id = "id4",
|
|
||||||
// 2022/06/22 UTC+1
|
|
||||||
creationTimestamp = 1655848800000,
|
|
||||||
title = "What film should we show at the end of the year party?"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getEndedPollsPart1(): List<PollSummary.EndedPoll> {
|
|
||||||
return listOf(
|
|
||||||
PollSummary.EndedPoll(
|
|
||||||
id = "id1-ended",
|
|
||||||
// 2022/06/28 UTC+1
|
|
||||||
creationTimestamp = 1656367200000,
|
|
||||||
title = "Which charity would you like to support?",
|
|
||||||
totalVotes = 22,
|
|
||||||
winnerOptions = listOf(
|
|
||||||
PollOptionViewState.PollEnded(
|
|
||||||
optionId = "id1",
|
|
||||||
optionAnswer = "Cancer research",
|
|
||||||
voteCount = 13,
|
|
||||||
votePercentage = 13 / 22.0,
|
|
||||||
isWinner = true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getEndedPollsPart2(): List<PollSummary.EndedPoll> {
|
|
||||||
return listOf(
|
|
||||||
PollSummary.EndedPoll(
|
|
||||||
id = "id2-ended",
|
|
||||||
// 2022/06/26 UTC+1
|
|
||||||
creationTimestamp = 1656194400000,
|
|
||||||
title = "Where should we do the offsite?",
|
|
||||||
totalVotes = 92,
|
|
||||||
winnerOptions = listOf(
|
|
||||||
PollOptionViewState.PollEnded(
|
|
||||||
optionId = "id1",
|
|
||||||
optionAnswer = "Hawaii",
|
|
||||||
voteCount = 43,
|
|
||||||
votePercentage = 43 / 92.0,
|
|
||||||
isWinner = true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PollSummary.EndedPoll(
|
|
||||||
id = "id3-ended",
|
|
||||||
// 2022/06/24 UTC+1
|
|
||||||
creationTimestamp = 1656021600000,
|
|
||||||
title = "What type of food should we have at the party?",
|
|
||||||
totalVotes = 22,
|
|
||||||
winnerOptions = listOf(
|
|
||||||
PollOptionViewState.PollEnded(
|
|
||||||
optionId = "id1",
|
|
||||||
optionAnswer = "Brazilian",
|
|
||||||
voteCount = 13,
|
|
||||||
votePercentage = 13 / 22.0,
|
|
||||||
isWinner = true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun syncPolls(roomId: String) {
|
suspend fun syncPolls(roomId: String) {
|
||||||
Timber.d("roomId=$roomId")
|
getPollHistoryService(roomId).syncPolls()
|
||||||
// TODO
|
|
||||||
// unmock using SDK service + add unit tests
|
|
||||||
if (fakeLoadCounter == 0) {
|
|
||||||
// fake first load
|
|
||||||
loadMorePolls(roomId)
|
|
||||||
} else {
|
|
||||||
// fake sync
|
|
||||||
delay(3000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,24 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.data
|
package im.vector.app.features.roomprofile.polls.list.data
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomPollRepository @Inject constructor(
|
class RoomPollRepository @Inject constructor(
|
||||||
private val roomPollDataSource: RoomPollDataSource,
|
private val roomPollDataSource: RoomPollDataSource,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer
|
fun dispose(roomId: String) {
|
||||||
fun getPolls(roomId: String): Flow<List<PollSummary>> {
|
roomPollDataSource.dispose(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPolls(roomId: String): Flow<List<TimelineEvent>> {
|
||||||
return roomPollDataSource.getPolls(roomId)
|
return roomPollDataSource.getPolls(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
|
suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
|
||||||
return roomPollDataSource.getLoadedPollsStatus(roomId)
|
return roomPollDataSource.getLoadedPollsStatus(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.roomprofile.polls.list.domain
|
||||||
|
|
||||||
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DisposePollHistoryUseCase @Inject constructor(
|
||||||
|
private val roomPollRepository: RoomPollRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(roomId: String) {
|
||||||
|
roomPollRepository.dispose(roomId)
|
||||||
|
}
|
||||||
|
}
|
@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetLoadedPollsStatusUseCase @Inject constructor(
|
class GetLoadedPollsStatusUseCase @Inject constructor(
|
||||||
private val roomPollRepository: RoomPollRepository,
|
private val roomPollRepository: RoomPollRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(roomId: String): LoadedPollsStatus {
|
suspend fun execute(roomId: String): LoadedPollsStatus {
|
||||||
return roomPollRepository.getLoadedPollsStatus(roomId)
|
return roomPollRepository.getLoadedPollsStatus(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,17 @@
|
|||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetPollsUseCase @Inject constructor(
|
class GetPollsUseCase @Inject constructor(
|
||||||
private val roomPollRepository: RoomPollRepository,
|
private val roomPollRepository: RoomPollRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(roomId: String): Flow<List<PollSummary>> {
|
fun execute(roomId: String): Flow<List<TimelineEvent>> {
|
||||||
return roomPollRepository.getPolls(roomId)
|
return roomPollRepository.getPolls(roomId)
|
||||||
.map { it.sortedByDescending { poll -> poll.creationTimestamp } }
|
.map { it.sortedByDescending { event -> event.root.originServerTs } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LoadMorePollsUseCase @Inject constructor(
|
class LoadMorePollsUseCase @Inject constructor(
|
||||||
|
@ -17,16 +17,26 @@
|
|||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync the polls of a given room from last manual loading (see LoadMorePollsUseCase) until now.
|
* Sync the polls of a given room from last manual loading if any (see LoadMorePollsUseCase) until now.
|
||||||
|
* Resume or start loading more to have at least a complete load.
|
||||||
*/
|
*/
|
||||||
class SyncPollsUseCase @Inject constructor(
|
class SyncPollsUseCase @Inject constructor(
|
||||||
private val roomPollRepository: RoomPollRepository,
|
private val roomPollRepository: RoomPollRepository,
|
||||||
|
private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase,
|
||||||
|
private val loadMorePollsUseCase: LoadMorePollsUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(roomId: String) {
|
suspend fun execute(roomId: String): LoadedPollsStatus {
|
||||||
roomPollRepository.syncPolls(roomId)
|
roomPollRepository.syncPolls(roomId)
|
||||||
|
val loadedStatus = getLoadedPollsStatusUseCase.execute(roomId)
|
||||||
|
return if (loadedStatus.hasCompletedASyncBackward) {
|
||||||
|
loadedStatus
|
||||||
|
} else {
|
||||||
|
loadMorePollsUseCase.execute(roomId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.roomprofile.polls.list.ui
|
||||||
|
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.factory.PollOptionViewStateFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PollSummaryMapper @Inject constructor(
|
||||||
|
private val pollResponseDataFactory: PollResponseDataFactory,
|
||||||
|
private val pollOptionViewStateFactory: PollOptionViewStateFactory,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun map(timelineEvent: TimelineEvent): PollSummary? {
|
||||||
|
val eventId = timelineEvent.root.eventId.orEmpty()
|
||||||
|
val result = runCatching {
|
||||||
|
val content = timelineEvent.getVectorLastMessageContent()
|
||||||
|
val pollResponseData = pollResponseDataFactory.create(timelineEvent)
|
||||||
|
val creationTimestamp = timelineEvent.root.originServerTs ?: 0
|
||||||
|
return if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent) {
|
||||||
|
convertToPollSummary(
|
||||||
|
eventId = eventId,
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
messagePollContent = content,
|
||||||
|
pollResponseData = pollResponseData
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Timber.w("missing mandatory info about poll event with id=$eventId")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
Timber.w("failed to map event with id $eventId")
|
||||||
|
}
|
||||||
|
return result.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToPollSummary(
|
||||||
|
eventId: String,
|
||||||
|
creationTimestamp: Long,
|
||||||
|
messagePollContent: MessagePollContent,
|
||||||
|
pollResponseData: PollResponseData?
|
||||||
|
): PollSummary {
|
||||||
|
val pollCreationInfo = messagePollContent.getBestPollCreationInfo()
|
||||||
|
val pollTitle = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
|
return if (pollResponseData?.isClosed == true) {
|
||||||
|
PollSummary.EndedPoll(
|
||||||
|
id = eventId,
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
title = pollTitle,
|
||||||
|
totalVotes = pollResponseData.totalVotes,
|
||||||
|
winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PollSummary.ActivePoll(
|
||||||
|
id = eventId,
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
title = pollTitle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,7 +78,7 @@ abstract class RoomPollsListFragment :
|
|||||||
views.roomPollsList.configureWith(roomPollsController)
|
views.roomPollsList.configureWith(roomPollsController)
|
||||||
views.roomPollsEmptyTitle.text = getEmptyListTitle(
|
views.roomPollsEmptyTitle.text = getEmptyListTitle(
|
||||||
canLoadMore = viewState.canLoadMore,
|
canLoadMore = viewState.canLoadMore,
|
||||||
nbLoadedDays = viewState.nbLoadedDays,
|
nbLoadedDays = viewState.nbSyncedDays,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ abstract class RoomPollsListFragment :
|
|||||||
roomPollsController.setData(viewState)
|
roomPollsController.setData(viewState)
|
||||||
views.roomPollsEmptyTitle.text = getEmptyListTitle(
|
views.roomPollsEmptyTitle.text = getEmptyListTitle(
|
||||||
canLoadMore = viewState.canLoadMore,
|
canLoadMore = viewState.canLoadMore,
|
||||||
nbLoadedDays = viewState.nbLoadedDays,
|
nbLoadedDays = viewState.nbSyncedDays,
|
||||||
)
|
)
|
||||||
views.roomPollsEmptyTitle.isVisible = !viewState.isSyncing && viewState.hasNoPolls()
|
views.roomPollsEmptyTitle.isVisible = !viewState.isSyncing && viewState.hasNoPolls()
|
||||||
views.roomPollsLoadMoreWhenEmpty.isVisible = viewState.hasNoPollsAndCanLoadMore()
|
views.roomPollsLoadMoreWhenEmpty.isVisible = viewState.hasNoPollsAndCanLoadMore()
|
||||||
|
@ -40,8 +40,8 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun render(
|
fun render(
|
||||||
newState: SyncState,
|
newState: SyncState?,
|
||||||
incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState,
|
incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState?,
|
||||||
pushCounter: Int,
|
pushCounter: Int,
|
||||||
showDebugInfo: Boolean
|
showDebugInfo: Boolean
|
||||||
) {
|
) {
|
||||||
@ -64,8 +64,9 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SyncState.toHumanReadable(): String {
|
private fun SyncState?.toHumanReadable(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
null -> "Unknown"
|
||||||
SyncState.Idle -> "Idle"
|
SyncState.Idle -> "Idle"
|
||||||
SyncState.InvalidToken -> "InvalidToken"
|
SyncState.InvalidToken -> "InvalidToken"
|
||||||
SyncState.Killed -> "Killed"
|
SyncState.Killed -> "Killed"
|
||||||
@ -76,8 +77,9 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SyncRequestState.IncrementalSyncRequestState.toHumanReadable(): String {
|
private fun SyncRequestState.IncrementalSyncRequestState?.toHumanReadable(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
null -> "Unknown"
|
||||||
SyncRequestState.IncrementalSyncIdle -> "Idle"
|
SyncRequestState.IncrementalSyncIdle -> "Idle"
|
||||||
is SyncRequestState.IncrementalSyncParsing -> "Parsing ${this.rooms} room(s) ${this.toDevice} toDevice(s)"
|
is SyncRequestState.IncrementalSyncParsing -> "Parsing ${this.rooms} room(s) ${this.toDevice} toDevice(s)"
|
||||||
SyncRequestState.IncrementalSyncError -> "Error"
|
SyncRequestState.IncrementalSyncError -> "Error"
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/roomToolbar"
|
android:id="@+id/roomToolbar"
|
||||||
@ -38,9 +39,9 @@
|
|||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/rootConstraintLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:id="@+id/rootConstraintLayout">
|
|
||||||
|
|
||||||
<im.vector.app.features.sync.widget.SyncStateView
|
<im.vector.app.features.sync.widget.SyncStateView
|
||||||
android:id="@+id/syncStateView"
|
android:id="@+id/syncStateView"
|
||||||
@ -75,7 +76,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:overScrollMode="always"
|
android:overScrollMode="always"
|
||||||
app:layout_constraintBottom_toTopOf="@id/notificationAreaView"
|
app:layout_constraintBottom_toTopOf="@id/failedMessagesWarningStub"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
||||||
@ -95,7 +96,19 @@
|
|||||||
app:closeIcon="@drawable/ic_close_24dp"
|
app:closeIcon="@drawable/ic_close_24dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" />
|
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/failedMessagesWarningStub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inflatedId="@+id/failedMessagesWarningStub"
|
||||||
|
android:layout="@layout/view_stub_failed_message_warning_layout"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/notificationAreaView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:layout_height="300dp" />
|
||||||
|
|
||||||
<im.vector.app.core.ui.views.NotificationAreaView
|
<im.vector.app.core.ui.views.NotificationAreaView
|
||||||
android:id="@+id/notificationAreaView"
|
android:id="@+id/notificationAreaView"
|
||||||
@ -107,17 +120,6 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ViewStub
|
|
||||||
android:id="@+id/failedMessagesWarningStub"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inflatedId="@+id/failedMessagesWarningStub"
|
|
||||||
android:layout="@layout/view_stub_failed_message_warning_layout"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:layout_height="300dp" />
|
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/inviteViewStub"
|
android:id="@+id/inviteViewStub"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -208,8 +210,8 @@
|
|||||||
android:id="@+id/composerContainer"
|
android:id="@+id/composerContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:translationZ="10dp"
|
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
|
android:translationZ="10dp"
|
||||||
app:layout_behavior="im.vector.app.core.utils.ExpandingBottomSheetBehavior" />
|
app:layout_behavior="im.vector.app.core.utils.ExpandingBottomSheetBehavior" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@ -217,7 +219,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:visibility="visible"
|
android:translationZ="10dp"
|
||||||
android:translationZ="10dp" />
|
android:visibility="visible" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
package im.vector.app.features.command
|
package im.vector.app.features.command
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeVectorPreferences
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
private const val A_SPACE_ID = "!my-space-id"
|
private const val A_SPACE_ID = "!my-space-id"
|
||||||
|
|
||||||
class CommandParserTest {
|
class CommandParserTest {
|
||||||
|
private val fakeVectorPreferences = FakeVectorPreferences()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseSlashCommandEmpty() {
|
fun parseSlashCommandEmpty() {
|
||||||
test("/", ParsedCommand.ErrorEmptySlashCommand)
|
test("/", ParsedCommand.ErrorEmptySlashCommand)
|
||||||
@ -70,7 +73,7 @@ class CommandParserTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun test(message: String, expectedResult: ParsedCommand) {
|
private fun test(message: String, expectedResult: ParsedCommand) {
|
||||||
val commandParser = CommandParser()
|
val commandParser = CommandParser(fakeVectorPreferences.instance)
|
||||||
val result = commandParser.parseSlashCommand(message, null, false)
|
val result = commandParser.parseSlashCommand(message, null, false)
|
||||||
result shouldBeEqualTo expectedResult
|
result shouldBeEqualTo expectedResult
|
||||||
}
|
}
|
||||||
|
@ -17,127 +17,71 @@
|
|||||||
package im.vector.app.features.home.room.detail.timeline.factory
|
package im.vector.app.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
|
||||||
import im.vector.app.features.poll.PollViewState
|
import im.vector.app.features.poll.PollViewState
|
||||||
import im.vector.app.test.fakes.FakeStringProvider
|
import im.vector.app.test.fakes.FakeStringProvider
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_MESSAGE_INFORMATION_DATA
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_OPTION_IDS
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_RESPONSE_DATA
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
|
||||||
private val A_MESSAGE_INFORMATION_DATA = MessageInformationData(
|
|
||||||
eventId = "eventId",
|
|
||||||
senderId = "senderId",
|
|
||||||
ageLocalTS = 0,
|
|
||||||
avatarUrl = "",
|
|
||||||
sendState = SendState.SENT,
|
|
||||||
messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true),
|
|
||||||
reactionsSummary = ReactionsSummaryData(),
|
|
||||||
sentByMe = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val A_POLL_RESPONSE_DATA = PollResponseData(
|
|
||||||
myVote = null,
|
|
||||||
votes = emptyMap(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val A_POLL_OPTION_IDS = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", "ec1a4db0-46d8-4d7a-9bb6-d80724715938", "3677ca8e-061b-40ab-bffe-b22e4e88fcad")
|
|
||||||
|
|
||||||
private val A_POLL_CONTENT = MessagePollContent(
|
|
||||||
unstablePollCreationInfo = PollCreationInfo(
|
|
||||||
question = PollQuestion(
|
|
||||||
unstableQuestion = "What is your favourite coffee?"
|
|
||||||
),
|
|
||||||
kind = PollType.UNDISCLOSED_UNSTABLE,
|
|
||||||
maxSelections = 1,
|
|
||||||
answers = listOf(
|
|
||||||
PollAnswer(
|
|
||||||
id = A_POLL_OPTION_IDS[0],
|
|
||||||
unstableAnswer = "Double Espresso"
|
|
||||||
),
|
|
||||||
PollAnswer(
|
|
||||||
id = A_POLL_OPTION_IDS[1],
|
|
||||||
unstableAnswer = "Macchiato"
|
|
||||||
),
|
|
||||||
PollAnswer(
|
|
||||||
id = A_POLL_OPTION_IDS[2],
|
|
||||||
unstableAnswer = "Iced Coffee"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
class PollItemViewStateFactoryTest {
|
class PollItemViewStateFactoryTest {
|
||||||
|
|
||||||
|
private val fakeStringProvider = FakeStringProvider()
|
||||||
|
private val fakePollOptionViewStateFactory = mockk<PollOptionViewStateFactory>()
|
||||||
|
|
||||||
|
private val pollItemViewStateFactory = PollItemViewStateFactory(
|
||||||
|
stringProvider = fakeStringProvider.instance,
|
||||||
|
pollOptionViewStateFactory = fakePollOptionViewStateFactory,
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a sending poll state then poll is not votable and option states are PollSending`() {
|
fun `given a sending poll state then poll is not votable and option states are PollSending`() {
|
||||||
val stringProvider = FakeStringProvider()
|
// Given
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
|
||||||
|
|
||||||
val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING)
|
val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING)
|
||||||
|
val optionViewStates = listOf(PollOptionViewState.PollSending(optionId = "", optionAnswer = ""))
|
||||||
|
every { fakePollOptionViewStateFactory.createPollSendingOptions(A_POLL_CONTENT.getBestPollCreationInfo()) } returns optionViewStates
|
||||||
|
|
||||||
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = A_POLL_CONTENT,
|
pollContent = A_POLL_CONTENT,
|
||||||
informationData = sendingPollInformationData,
|
informationData = sendingPollInformationData,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = stringProvider.instance.getString(R.string.poll_no_votes_cast),
|
votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast),
|
||||||
canVote = false,
|
canVote = false,
|
||||||
optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
|
optionViewStates = optionViewStates,
|
||||||
PollOptionViewState.PollSending(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: ""
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
verify { fakePollOptionViewStateFactory.createPollSendingOptions(A_POLL_CONTENT.getBestPollCreationInfo()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll state when poll is closed then poll is not votable and option states are Ended`() {
|
fun `given a sent poll state when poll is closed then poll is not votable and option states are Ended`() {
|
||||||
val stringProvider = FakeStringProvider()
|
// Given
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
|
||||||
|
|
||||||
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true)
|
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true)
|
||||||
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
||||||
|
val optionViewStates = listOf(
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
|
||||||
pollContent = A_POLL_CONTENT,
|
|
||||||
informationData = closedPollInformationData,
|
|
||||||
)
|
|
||||||
|
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
|
||||||
votesStatus = stringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0),
|
|
||||||
canVote = false,
|
|
||||||
optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
|
|
||||||
PollOptionViewState.PollEnded(
|
PollOptionViewState.PollEnded(
|
||||||
optionId = answer.id ?: "",
|
optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false
|
||||||
optionAnswer = answer.getBestAnswer() ?: "",
|
|
||||||
voteCount = 0,
|
|
||||||
votePercentage = 0.0,
|
|
||||||
isWinner = false
|
|
||||||
)
|
)
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
@Test
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() {
|
closedPollInformationData.pollResponseAggregatedSummary,
|
||||||
// Given
|
)
|
||||||
val stringProvider = FakeStringProvider()
|
} returns optionViewStates
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
|
||||||
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true)
|
|
||||||
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
@ -146,42 +90,90 @@ class PollItemViewStateFactoryTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
pollViewState shouldBeEqualTo PollViewState(
|
||||||
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
|
votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0),
|
||||||
|
canVote = false,
|
||||||
|
optionViewStates = optionViewStates,
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
|
closedPollInformationData.pollResponseAggregatedSummary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() {
|
||||||
|
// Given
|
||||||
|
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true)
|
||||||
|
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
||||||
|
val optionViewStates = listOf(
|
||||||
|
PollOptionViewState.PollEnded(
|
||||||
|
optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
|
closedPollInformationData.pollResponseAggregatedSummary,
|
||||||
|
)
|
||||||
|
} returns optionViewStates
|
||||||
|
|
||||||
|
// When
|
||||||
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
|
pollContent = A_POLL_CONTENT,
|
||||||
|
informationData = closedPollInformationData,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
pollViewState.votesStatus shouldBeEqualTo fakeStringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
||||||
val stringProvider = FakeStringProvider()
|
// Given
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
val optionViewStates = listOf(
|
||||||
|
PollOptionViewState.PollUndisclosed(
|
||||||
|
optionId = "",
|
||||||
|
optionAnswer = "",
|
||||||
|
isSelected = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollUndisclosedOptions(
|
||||||
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
|
A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary,
|
||||||
|
)
|
||||||
|
} returns optionViewStates
|
||||||
|
|
||||||
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = A_POLL_CONTENT,
|
pollContent = A_POLL_CONTENT,
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
informationData = A_MESSAGE_INFORMATION_DATA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = stringProvider.instance.getString(R.string.poll_undisclosed_not_ended),
|
votesStatus = fakeStringProvider.instance.getString(R.string.poll_undisclosed_not_ended),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
|
optionViewStates = optionViewStates,
|
||||||
PollOptionViewState.PollUndisclosed(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: "",
|
|
||||||
isSelected = false
|
|
||||||
)
|
)
|
||||||
},
|
verify {
|
||||||
|
fakePollOptionViewStateFactory.createPollUndisclosedOptions(
|
||||||
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
|
A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll when my vote exists then poll is still votable and options states are PollVoted`() {
|
fun `given a sent poll when my vote exists then poll is still votable and options states are PollVoted`() {
|
||||||
val stringProvider = FakeStringProvider()
|
// Given
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
|
||||||
|
|
||||||
val votedPollData = A_POLL_RESPONSE_DATA.copy(
|
val votedPollData = A_POLL_RESPONSE_DATA.copy(
|
||||||
totalVotes = 1,
|
totalVotes = 1, myVote = A_POLL_OPTION_IDS[0], votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0))
|
||||||
myVote = A_POLL_OPTION_IDS[0],
|
|
||||||
votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0))
|
|
||||||
)
|
)
|
||||||
val disclosedPollContent = A_POLL_CONTENT.copy(
|
val disclosedPollContent = A_POLL_CONTENT.copy(
|
||||||
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
||||||
@ -189,33 +181,46 @@ class PollItemViewStateFactoryTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
||||||
|
val optionViewStates = listOf(
|
||||||
|
PollOptionViewState.PollVoted(
|
||||||
|
optionId = "",
|
||||||
|
optionAnswer = "",
|
||||||
|
voteCount = 0,
|
||||||
|
votePercentage = 0.0,
|
||||||
|
isSelected = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollVotedOptions(
|
||||||
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
|
votedInformationData.pollResponseAggregatedSummary,
|
||||||
|
)
|
||||||
|
} returns optionViewStates
|
||||||
|
|
||||||
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = disclosedPollContent,
|
pollContent = disclosedPollContent,
|
||||||
informationData = votedInformationData,
|
informationData = votedInformationData,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = stringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1),
|
votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.mapIndexed { index, answer ->
|
optionViewStates = optionViewStates,
|
||||||
PollOptionViewState.PollVoted(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: "",
|
|
||||||
voteCount = if (index == 0) 1 else 0,
|
|
||||||
votePercentage = if (index == 0) 1.0 else 0.0,
|
|
||||||
isSelected = index == 0
|
|
||||||
)
|
)
|
||||||
},
|
verify {
|
||||||
|
fakePollOptionViewStateFactory.createPollVotedOptions(
|
||||||
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
|
votedInformationData.pollResponseAggregatedSummary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll with decryption failure when my vote exists then a warning message is displayed`() {
|
fun `given a sent poll with decryption failure when my vote exists then a warning message is displayed`() {
|
||||||
// Given
|
// Given
|
||||||
val stringProvider = FakeStringProvider()
|
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
|
||||||
val votedPollData = A_POLL_RESPONSE_DATA.copy(
|
val votedPollData = A_POLL_RESPONSE_DATA.copy(
|
||||||
totalVotes = 1,
|
totalVotes = 1,
|
||||||
myVote = A_POLL_OPTION_IDS[0],
|
myVote = A_POLL_OPTION_IDS[0],
|
||||||
@ -228,6 +233,21 @@ class PollItemViewStateFactoryTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
||||||
|
val optionViewStates = listOf(
|
||||||
|
PollOptionViewState.PollVoted(
|
||||||
|
optionId = "",
|
||||||
|
optionAnswer = "",
|
||||||
|
voteCount = 0,
|
||||||
|
votePercentage = 0.0,
|
||||||
|
isSelected = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollVotedOptions(
|
||||||
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
|
votedInformationData.pollResponseAggregatedSummary,
|
||||||
|
)
|
||||||
|
} returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
@ -236,34 +256,46 @@ class PollItemViewStateFactoryTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
pollViewState.votesStatus shouldBeEqualTo fakeStringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
||||||
val stringProvider = FakeStringProvider()
|
// Given
|
||||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
|
||||||
|
|
||||||
val disclosedPollContent = A_POLL_CONTENT.copy(
|
val disclosedPollContent = A_POLL_CONTENT.copy(
|
||||||
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
||||||
kind = PollType.DISCLOSED_UNSTABLE
|
kind = PollType.DISCLOSED_UNSTABLE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
val optionViewStates = listOf(
|
||||||
|
PollOptionViewState.PollReady(
|
||||||
|
optionId = "",
|
||||||
|
optionAnswer = "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollReadyOptions(
|
||||||
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
|
)
|
||||||
|
} returns optionViewStates
|
||||||
|
|
||||||
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = disclosedPollContent,
|
pollContent = disclosedPollContent,
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
informationData = A_MESSAGE_INFORMATION_DATA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = stringProvider.instance.getString(R.string.poll_no_votes_cast),
|
votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
|
optionViewStates = optionViewStates,
|
||||||
PollOptionViewState.PollReady(
|
|
||||||
optionId = answer.id ?: "",
|
|
||||||
optionAnswer = answer.getBestAnswer() ?: ""
|
|
||||||
)
|
)
|
||||||
},
|
verify {
|
||||||
|
fakePollOptionViewStateFactory.createPollReadyOptions(
|
||||||
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.home.room.detail.timeline.factory
|
||||||
|
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_OPTION_IDS
|
||||||
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_RESPONSE_DATA
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
||||||
|
internal class PollOptionViewStateFactoryTest {
|
||||||
|
|
||||||
|
private val pollOptionViewStateFactory = PollOptionViewStateFactory()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll data when creating ended poll options then correct options are returned`() {
|
||||||
|
// Given
|
||||||
|
val winnerVotesCount = 0
|
||||||
|
val pollResponseData = A_POLL_RESPONSE_DATA.copy(
|
||||||
|
isClosed = true,
|
||||||
|
winnerVoteCount = winnerVotesCount,
|
||||||
|
)
|
||||||
|
val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()
|
||||||
|
val expectedOptions = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollEnded(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
voteCount = 0,
|
||||||
|
votePercentage = 0.0,
|
||||||
|
isWinner = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollOptionViewStateFactory.createPollEndedOptions(
|
||||||
|
pollCreationInfo = pollCreationInfo,
|
||||||
|
pollResponseData = pollResponseData,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll data when creating sending poll options then correct options are returned`() {
|
||||||
|
// Given
|
||||||
|
val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()
|
||||||
|
val expectedOptions = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollSending(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollOptionViewStateFactory.createPollSendingOptions(
|
||||||
|
pollCreationInfo = pollCreationInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll data when creating undisclosed poll options then correct options are returned`() {
|
||||||
|
// Given
|
||||||
|
val pollResponseData = A_POLL_RESPONSE_DATA
|
||||||
|
val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()
|
||||||
|
val expectedOptions = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollUndisclosed(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
isSelected = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollOptionViewStateFactory.createPollUndisclosedOptions(
|
||||||
|
pollCreationInfo = pollCreationInfo,
|
||||||
|
pollResponseData = pollResponseData,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll data when creating voted poll options then correct options are returned`() {
|
||||||
|
// Given
|
||||||
|
val pollResponseData = A_POLL_RESPONSE_DATA.copy(
|
||||||
|
totalVotes = 1,
|
||||||
|
myVote = A_POLL_OPTION_IDS[0],
|
||||||
|
votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)),
|
||||||
|
)
|
||||||
|
val disclosedPollContent = A_POLL_CONTENT.copy(
|
||||||
|
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
||||||
|
kind = PollType.DISCLOSED_UNSTABLE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val pollCreationInfo = disclosedPollContent.getBestPollCreationInfo()
|
||||||
|
val expectedOptions = pollCreationInfo?.answers?.mapIndexed { index, answer ->
|
||||||
|
PollOptionViewState.PollVoted(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
voteCount = if (index == 0) 1 else 0,
|
||||||
|
votePercentage = if (index == 0) 1.0 else 0.0,
|
||||||
|
isSelected = index == 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollOptionViewStateFactory.createPollVotedOptions(
|
||||||
|
pollCreationInfo = pollCreationInfo,
|
||||||
|
pollResponseData = pollResponseData,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll data when creating ready poll options then correct options are returned`() {
|
||||||
|
// Given
|
||||||
|
val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()
|
||||||
|
val expectedOptions = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollReady(
|
||||||
|
optionId = answer.id.orEmpty(),
|
||||||
|
optionAnswer = answer.getBestAnswer().orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollOptionViewStateFactory.createPollReadyOptions(
|
||||||
|
pollCreationInfo = pollCreationInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedOptions
|
||||||
|
}
|
||||||
|
}
|
@ -17,23 +17,26 @@
|
|||||||
package im.vector.app.features.roomprofile.polls
|
package im.vector.app.features.roomprofile.polls
|
||||||
|
|
||||||
import com.airbnb.mvrx.test.MavericksTestRule
|
import com.airbnb.mvrx.test.MavericksTestRule
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus
|
import im.vector.app.features.roomprofile.polls.list.domain.DisposePollHistoryUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
||||||
|
import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper
|
||||||
import im.vector.app.test.test
|
import im.vector.app.test.test
|
||||||
import im.vector.app.test.testDispatcher
|
import im.vector.app.test.testDispatcher
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coJustRun
|
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
private const val A_ROOM_ID = "room-id"
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
|
||||||
@ -42,33 +45,37 @@ class RoomPollsViewModelTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
||||||
|
|
||||||
|
private val initialState = RoomPollsViewState(A_ROOM_ID)
|
||||||
private val fakeGetPollsUseCase = mockk<GetPollsUseCase>()
|
private val fakeGetPollsUseCase = mockk<GetPollsUseCase>()
|
||||||
private val fakeGetLoadedPollsStatusUseCase = mockk<GetLoadedPollsStatusUseCase>()
|
|
||||||
private val fakeLoadMorePollsUseCase = mockk<LoadMorePollsUseCase>()
|
private val fakeLoadMorePollsUseCase = mockk<LoadMorePollsUseCase>()
|
||||||
private val fakeSyncPollsUseCase = mockk<SyncPollsUseCase>()
|
private val fakeSyncPollsUseCase = mockk<SyncPollsUseCase>()
|
||||||
private val initialState = RoomPollsViewState(A_ROOM_ID)
|
private val fakeDisposePollHistoryUseCase = mockk<DisposePollHistoryUseCase>()
|
||||||
|
private val fakePollSummaryMapper = mockk<PollSummaryMapper>()
|
||||||
|
|
||||||
private fun createViewModel(): RoomPollsViewModel {
|
private fun createViewModel(): RoomPollsViewModel {
|
||||||
return RoomPollsViewModel(
|
return RoomPollsViewModel(
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
getPollsUseCase = fakeGetPollsUseCase,
|
getPollsUseCase = fakeGetPollsUseCase,
|
||||||
getLoadedPollsStatusUseCase = fakeGetLoadedPollsStatusUseCase,
|
|
||||||
loadMorePollsUseCase = fakeLoadMorePollsUseCase,
|
loadMorePollsUseCase = fakeLoadMorePollsUseCase,
|
||||||
syncPollsUseCase = fakeSyncPollsUseCase,
|
syncPollsUseCase = fakeSyncPollsUseCase,
|
||||||
|
disposePollHistoryUseCase = fakeDisposePollHistoryUseCase,
|
||||||
|
pollSummaryMapper = fakePollSummaryMapper,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given viewModel when created then polls list is observed, sync is launched and viewState is updated`() {
|
fun `given viewModel when created then polls list is observed, sync is launched and viewState is updated`() {
|
||||||
// Given
|
// Given
|
||||||
val loadedPollsStatus = givenGetLoadedPollsStatusSuccess()
|
val loadedPollsStatus = givenSyncPollsWithSuccess()
|
||||||
givenSyncPollsWithSuccess()
|
val aPollEvent = givenAPollEvent()
|
||||||
val polls = listOf(givenAPollSummary())
|
val aPollSummary = givenAPollSummary()
|
||||||
|
val polls = listOf(aPollEvent)
|
||||||
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls)
|
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls)
|
||||||
|
every { fakePollSummaryMapper.map(aPollEvent) } returns aPollSummary
|
||||||
val expectedViewState = initialState.copy(
|
val expectedViewState = initialState.copy(
|
||||||
polls = polls,
|
polls = listOf(aPollSummary),
|
||||||
canLoadMore = loadedPollsStatus.canLoadMore,
|
canLoadMore = loadedPollsStatus.canLoadMore,
|
||||||
nbLoadedDays = loadedPollsStatus.nbLoadedDays,
|
nbSyncedDays = loadedPollsStatus.daysSynced,
|
||||||
)
|
)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
@ -81,6 +88,7 @@ class RoomPollsViewModelTest {
|
|||||||
.finish()
|
.finish()
|
||||||
verify {
|
verify {
|
||||||
fakeGetPollsUseCase.execute(A_ROOM_ID)
|
fakeGetPollsUseCase.execute(A_ROOM_ID)
|
||||||
|
fakePollSummaryMapper.map(aPollEvent)
|
||||||
}
|
}
|
||||||
coVerify { fakeSyncPollsUseCase.execute(A_ROOM_ID) }
|
coVerify { fakeSyncPollsUseCase.execute(A_ROOM_ID) }
|
||||||
}
|
}
|
||||||
@ -88,10 +96,8 @@ class RoomPollsViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `given viewModel and error during sync process when created then error is raised in view event`() {
|
fun `given viewModel and error during sync process when created then error is raised in view event`() {
|
||||||
// Given
|
// Given
|
||||||
givenGetLoadedPollsStatusSuccess()
|
|
||||||
givenSyncPollsWithError(Exception())
|
givenSyncPollsWithError(Exception())
|
||||||
val polls = listOf(givenAPollSummary())
|
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns emptyFlow()
|
||||||
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls)
|
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
@ -104,19 +110,32 @@ class RoomPollsViewModelTest {
|
|||||||
coVerify { fakeSyncPollsUseCase.execute(A_ROOM_ID) }
|
coVerify { fakeSyncPollsUseCase.execute(A_ROOM_ID) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given viewModel when calling onCleared then poll history is disposed`() {
|
||||||
|
// Given
|
||||||
|
givenSyncPollsWithSuccess()
|
||||||
|
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns emptyFlow()
|
||||||
|
justRun { fakeDisposePollHistoryUseCase.execute(A_ROOM_ID) }
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
viewModel.onCleared()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify { fakeDisposePollHistoryUseCase.execute(A_ROOM_ID) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given viewModel when handle load more action then viewState is updated`() {
|
fun `given viewModel when handle load more action then viewState is updated`() {
|
||||||
// Given
|
// Given
|
||||||
val loadedPollsStatus = givenGetLoadedPollsStatusSuccess()
|
val loadedPollsStatus = givenSyncPollsWithSuccess()
|
||||||
givenSyncPollsWithSuccess()
|
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns emptyFlow()
|
||||||
val polls = listOf(givenAPollSummary())
|
|
||||||
every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls)
|
|
||||||
val newLoadedPollsStatus = givenLoadMoreWithSuccess()
|
val newLoadedPollsStatus = givenLoadMoreWithSuccess()
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val stateAfterInit = initialState.copy(
|
val stateAfterInit = initialState.copy(
|
||||||
polls = polls,
|
polls = emptyList(),
|
||||||
canLoadMore = loadedPollsStatus.canLoadMore,
|
canLoadMore = loadedPollsStatus.canLoadMore,
|
||||||
nbLoadedDays = loadedPollsStatus.nbLoadedDays,
|
nbSyncedDays = loadedPollsStatus.daysSynced,
|
||||||
)
|
)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
@ -128,7 +147,7 @@ class RoomPollsViewModelTest {
|
|||||||
.assertStatesChanges(
|
.assertStatesChanges(
|
||||||
stateAfterInit,
|
stateAfterInit,
|
||||||
{ copy(isLoadingMore = true) },
|
{ copy(isLoadingMore = true) },
|
||||||
{ copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbLoadedDays = newLoadedPollsStatus.nbLoadedDays) },
|
{ copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbSyncedDays = newLoadedPollsStatus.daysSynced) },
|
||||||
{ copy(isLoadingMore = false) },
|
{ copy(isLoadingMore = false) },
|
||||||
)
|
)
|
||||||
.finish()
|
.finish()
|
||||||
@ -139,8 +158,14 @@ class RoomPollsViewModelTest {
|
|||||||
return mockk()
|
return mockk()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenSyncPollsWithSuccess() {
|
private fun givenAPollEvent(): TimelineEvent {
|
||||||
coJustRun { fakeSyncPollsUseCase.execute(A_ROOM_ID) }
|
return mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenSyncPollsWithSuccess(): LoadedPollsStatus {
|
||||||
|
val loadedPollsStatus = givenALoadedPollsStatus()
|
||||||
|
coEvery { fakeSyncPollsUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus
|
||||||
|
return loadedPollsStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenSyncPollsWithError(error: Exception) {
|
private fun givenSyncPollsWithError(error: Exception) {
|
||||||
@ -148,20 +173,15 @@ class RoomPollsViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun givenLoadMoreWithSuccess(): LoadedPollsStatus {
|
private fun givenLoadMoreWithSuccess(): LoadedPollsStatus {
|
||||||
val loadedPollsStatus = givenALoadedPollsStatus(canLoadMore = false, nbLoadedDays = 20)
|
val loadedPollsStatus = givenALoadedPollsStatus(canLoadMore = false, nbSyncedDays = 20)
|
||||||
coEvery { fakeLoadMorePollsUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus
|
coEvery { fakeLoadMorePollsUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus
|
||||||
return loadedPollsStatus
|
return loadedPollsStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenGetLoadedPollsStatusSuccess(): LoadedPollsStatus {
|
private fun givenALoadedPollsStatus(canLoadMore: Boolean = true, nbSyncedDays: Int = 10) =
|
||||||
val loadedPollsStatus = givenALoadedPollsStatus()
|
|
||||||
every { fakeGetLoadedPollsStatusUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus
|
|
||||||
return loadedPollsStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun givenALoadedPollsStatus(canLoadMore: Boolean = true, nbLoadedDays: Int = 10) =
|
|
||||||
LoadedPollsStatus(
|
LoadedPollsStatus(
|
||||||
canLoadMore = canLoadMore,
|
canLoadMore = canLoadMore,
|
||||||
nbLoadedDays = nbLoadedDays,
|
daysSynced = nbSyncedDays,
|
||||||
|
hasCompletedASyncBackward = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.roomprofile.polls.list.data
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||||
|
import im.vector.app.test.fakes.FakePollHistoryService
|
||||||
|
import im.vector.app.test.fakes.givenAsFlow
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
|
||||||
|
internal class RoomPollDataSourceTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
|
||||||
|
private val roomPollDataSource = RoomPollDataSource(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history service when dispose then correct method of service is called`() {
|
||||||
|
// Given
|
||||||
|
val fakePollHistoryService = givenPollHistoryService()
|
||||||
|
fakePollHistoryService.givenDispose()
|
||||||
|
|
||||||
|
// When
|
||||||
|
roomPollDataSource.dispose(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fakePollHistoryService.verifyDispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history service when get polls then correct method of service is called and correct result is returned`() = runTest {
|
||||||
|
// Given
|
||||||
|
val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
|
||||||
|
fakeFlowLiveDataConversions.setup()
|
||||||
|
val fakePollHistoryService = givenPollHistoryService()
|
||||||
|
val pollEvents = listOf<TimelineEvent>()
|
||||||
|
fakePollHistoryService
|
||||||
|
.givenGetPollsReturns(pollEvents)
|
||||||
|
.givenAsFlow()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = roomPollDataSource.getPolls(A_ROOM_ID).firstOrNull()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo pollEvents
|
||||||
|
fakePollHistoryService.verifyGetPolls()
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history service when get loaded polls then correct method of service is called and correct result is returned`() = runTest {
|
||||||
|
// Given
|
||||||
|
val fakePollHistoryService = givenPollHistoryService()
|
||||||
|
val aLoadedPollsStatus = givenALoadedPollsStatus()
|
||||||
|
fakePollHistoryService.givenGetLoadedPollsStatusReturns(aLoadedPollsStatus)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = roomPollDataSource.getLoadedPollsStatus(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo aLoadedPollsStatus
|
||||||
|
fakePollHistoryService.verifyGetLoadedPollsStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history service when load more then correct method of service is called and correct result is returned`() = runTest {
|
||||||
|
// Given
|
||||||
|
val fakePollHistoryService = givenPollHistoryService()
|
||||||
|
val aLoadedPollsStatus = givenALoadedPollsStatus()
|
||||||
|
fakePollHistoryService.givenLoadMoreReturns(aLoadedPollsStatus)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = roomPollDataSource.loadMorePolls(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo aLoadedPollsStatus
|
||||||
|
fakePollHistoryService.verifyLoadMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given poll history service when sync polls then correct method of service is called`() = runTest {
|
||||||
|
// Given
|
||||||
|
val fakePollHistoryService = givenPollHistoryService()
|
||||||
|
fakePollHistoryService.givenSyncPollsSuccess()
|
||||||
|
|
||||||
|
// When
|
||||||
|
roomPollDataSource.syncPolls(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fakePollHistoryService.verifySyncPolls()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenPollHistoryService(): FakePollHistoryService {
|
||||||
|
return fakeActiveSessionHolder
|
||||||
|
.fakeSession
|
||||||
|
.fakeRoomService
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
.pollHistoryService()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenALoadedPollsStatus() = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
|
}
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.data
|
package im.vector.app.features.roomprofile.polls.list.data
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
import io.mockk.coEvery
|
||||||
import io.mockk.coJustRun
|
import io.mockk.coJustRun
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
@ -27,6 +28,8 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
private const val A_ROOM_ID = "room-id"
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
|
||||||
@ -38,10 +41,22 @@ class RoomPollRepositoryTest {
|
|||||||
roomPollDataSource = fakeRoomPollDataSource,
|
roomPollDataSource = fakeRoomPollDataSource,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given data source when dispose then correct method of data source is called`() {
|
||||||
|
// Given
|
||||||
|
justRun { fakeRoomPollDataSource.dispose(A_ROOM_ID) }
|
||||||
|
|
||||||
|
// When
|
||||||
|
roomPollRepository.dispose(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify { fakeRoomPollDataSource.dispose(A_ROOM_ID) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given data source when getting polls then correct method of data source is called`() = runTest {
|
fun `given data source when getting polls then correct method of data source is called`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val expectedPolls = listOf<PollSummary>()
|
val expectedPolls = listOf<TimelineEvent>()
|
||||||
every { fakeRoomPollDataSource.getPolls(A_ROOM_ID) } returns flowOf(expectedPolls)
|
every { fakeRoomPollDataSource.getPolls(A_ROOM_ID) } returns flowOf(expectedPolls)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
@ -53,20 +68,21 @@ class RoomPollRepositoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given data source when getting loaded polls status then correct method of data source is called`() {
|
fun `given data source when getting loaded polls status then correct method of data source is called`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val expectedStatus = LoadedPollsStatus(
|
val expectedStatus = LoadedPollsStatus(
|
||||||
canLoadMore = true,
|
canLoadMore = true,
|
||||||
nbLoadedDays = 10,
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = false,
|
||||||
)
|
)
|
||||||
every { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } returns expectedStatus
|
coEvery { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } returns expectedStatus
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val result = roomPollRepository.getLoadedPollsStatus(A_ROOM_ID)
|
val result = roomPollRepository.getLoadedPollsStatus(A_ROOM_ID)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result shouldBeEqualTo expectedStatus
|
result shouldBeEqualTo expectedStatus
|
||||||
verify { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) }
|
coVerify { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.roomprofile.polls.list.domain
|
||||||
|
|
||||||
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
internal class DisposePollHistoryUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeRoomPollRepository = mockk<RoomPollRepository>()
|
||||||
|
|
||||||
|
private val disposePollHistoryUseCase = DisposePollHistoryUseCase(
|
||||||
|
roomPollRepository = fakeRoomPollRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given repo when execute then correct method of repo is called`() {
|
||||||
|
// Given
|
||||||
|
val aRoomId = "roomId"
|
||||||
|
justRun { fakeRoomPollRepository.dispose(aRoomId) }
|
||||||
|
|
||||||
|
// When
|
||||||
|
disposePollHistoryUseCase.execute(aRoomId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify { fakeRoomPollRepository.dispose(aRoomId) }
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
import io.mockk.every
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
|
||||||
class GetLoadedPollsStatusUseCaseTest {
|
class GetLoadedPollsStatusUseCaseTest {
|
||||||
|
|
||||||
@ -33,20 +34,21 @@ class GetLoadedPollsStatusUseCaseTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given repo when execute then correct method of repo is called`() {
|
fun `given repo when execute then correct method of repo is called`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val aRoomId = "roomId"
|
val aRoomId = "roomId"
|
||||||
val expectedStatus = LoadedPollsStatus(
|
val expectedStatus = LoadedPollsStatus(
|
||||||
canLoadMore = true,
|
canLoadMore = true,
|
||||||
nbLoadedDays = 10,
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
)
|
)
|
||||||
every { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } returns expectedStatus
|
coEvery { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } returns expectedStatus
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val status = getLoadedPollsStatusUseCase.execute(aRoomId)
|
val status = getLoadedPollsStatusUseCase.execute(aRoomId)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
status shouldBeEqualTo expectedStatus
|
status shouldBeEqualTo expectedStatus
|
||||||
verify { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) }
|
coVerify { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
import im.vector.app.test.fixtures.RoomPollFixture
|
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
@ -27,6 +25,7 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
class GetPollsUseCaseTest {
|
class GetPollsUseCaseTest {
|
||||||
private val fakeRoomPollRepository = mockk<RoomPollRepository>()
|
private val fakeRoomPollRepository = mockk<RoomPollRepository>()
|
||||||
@ -39,16 +38,16 @@ class GetPollsUseCaseTest {
|
|||||||
fun `given repo when execute then correct method of repo is called and polls are sorted most recent first`() = runTest {
|
fun `given repo when execute then correct method of repo is called and polls are sorted most recent first`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val aRoomId = "roomId"
|
val aRoomId = "roomId"
|
||||||
val poll1 = RoomPollFixture.anActivePollSummary(timestamp = 1)
|
val poll1 = givenTimelineEvent(timestamp = 1)
|
||||||
val poll2 = RoomPollFixture.anActivePollSummary(timestamp = 2)
|
val poll2 = givenTimelineEvent(timestamp = 2)
|
||||||
val poll3 = RoomPollFixture.anActivePollSummary(timestamp = 3)
|
val poll3 = givenTimelineEvent(timestamp = 3)
|
||||||
val polls = listOf<PollSummary>(
|
val polls = listOf(
|
||||||
poll1,
|
poll1,
|
||||||
poll2,
|
poll2,
|
||||||
poll3,
|
poll3,
|
||||||
)
|
)
|
||||||
every { fakeRoomPollRepository.getPolls(aRoomId) } returns flowOf(polls)
|
every { fakeRoomPollRepository.getPolls(aRoomId) } returns flowOf(polls)
|
||||||
val expectedPolls = listOf<PollSummary>(
|
val expectedPolls = listOf(
|
||||||
poll3,
|
poll3,
|
||||||
poll2,
|
poll2,
|
||||||
poll1,
|
poll1,
|
||||||
@ -60,4 +59,10 @@ class GetPollsUseCaseTest {
|
|||||||
result shouldBeEqualTo expectedPolls
|
result shouldBeEqualTo expectedPolls
|
||||||
verify { fakeRoomPollRepository.getPolls(aRoomId) }
|
verify { fakeRoomPollRepository.getPolls(aRoomId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun givenTimelineEvent(timestamp: Long): TimelineEvent {
|
||||||
|
return mockk<TimelineEvent>().also {
|
||||||
|
every { it.root.originServerTs } returns timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,13 @@
|
|||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
import io.mockk.coJustRun
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
|
||||||
class LoadMorePollsUseCaseTest {
|
class LoadMorePollsUseCaseTest {
|
||||||
|
|
||||||
@ -35,12 +37,18 @@ class LoadMorePollsUseCaseTest {
|
|||||||
fun `given repo when execute then correct method of repo is called`() = runTest {
|
fun `given repo when execute then correct method of repo is called`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val aRoomId = "roomId"
|
val aRoomId = "roomId"
|
||||||
coJustRun { fakeRoomPollRepository.loadMorePolls(aRoomId) }
|
val loadedPollsStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
|
coEvery { fakeRoomPollRepository.loadMorePolls(aRoomId) } returns loadedPollsStatus
|
||||||
|
|
||||||
// When
|
// When
|
||||||
loadMorePollsUseCase.execute(aRoomId)
|
val result = loadMorePollsUseCase.execute(aRoomId)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
result shouldBeEqualTo loadedPollsStatus
|
||||||
coVerify { fakeRoomPollRepository.loadMorePolls(aRoomId) }
|
coVerify { fakeRoomPollRepository.loadMorePolls(aRoomId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,30 +17,81 @@
|
|||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
|
import io.mockk.coEvery
|
||||||
import io.mockk.coJustRun
|
import io.mockk.coJustRun
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.coVerifyOrder
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
|
||||||
class SyncPollsUseCaseTest {
|
class SyncPollsUseCaseTest {
|
||||||
|
|
||||||
private val fakeRoomPollRepository = mockk<RoomPollRepository>()
|
private val fakeRoomPollRepository = mockk<RoomPollRepository>()
|
||||||
|
private val fakeGetLoadedPollsStatusUseCase = mockk<GetLoadedPollsStatusUseCase>()
|
||||||
|
private val fakeLoadMorePollsUseCase = mockk<LoadMorePollsUseCase>()
|
||||||
|
|
||||||
private val syncPollsUseCase = SyncPollsUseCase(
|
private val syncPollsUseCase = SyncPollsUseCase(
|
||||||
roomPollRepository = fakeRoomPollRepository,
|
roomPollRepository = fakeRoomPollRepository,
|
||||||
|
getLoadedPollsStatusUseCase = fakeGetLoadedPollsStatusUseCase,
|
||||||
|
loadMorePollsUseCase = fakeLoadMorePollsUseCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given repo when execute then correct method of repo is called`() = runTest {
|
fun `given it has completed a sync backward when execute then only sync process is called`() = runTest {
|
||||||
// Given
|
// Given
|
||||||
val aRoomId = "roomId"
|
val aRoomId = "roomId"
|
||||||
|
val aLoadedStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) }
|
coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) }
|
||||||
|
coEvery { fakeGetLoadedPollsStatusUseCase.execute(aRoomId) } returns aLoadedStatus
|
||||||
|
|
||||||
// When
|
// When
|
||||||
syncPollsUseCase.execute(aRoomId)
|
val result = syncPollsUseCase.execute(aRoomId)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
coVerify { fakeRoomPollRepository.syncPolls(aRoomId) }
|
result shouldBeEqualTo aLoadedStatus
|
||||||
|
coVerifyOrder {
|
||||||
|
fakeRoomPollRepository.syncPolls(aRoomId)
|
||||||
|
fakeGetLoadedPollsStatusUseCase.execute(aRoomId)
|
||||||
|
}
|
||||||
|
coVerify(inverse = true) {
|
||||||
|
fakeLoadMorePollsUseCase.execute(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given it has not completed a sync backward when execute then sync process and load more is called`() = runTest {
|
||||||
|
// Given
|
||||||
|
val aRoomId = "roomId"
|
||||||
|
val aLoadedStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = false,
|
||||||
|
)
|
||||||
|
val anUpdatedLoadedStatus = LoadedPollsStatus(
|
||||||
|
canLoadMore = true,
|
||||||
|
daysSynced = 10,
|
||||||
|
hasCompletedASyncBackward = true,
|
||||||
|
)
|
||||||
|
coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) }
|
||||||
|
coEvery { fakeGetLoadedPollsStatusUseCase.execute(aRoomId) } returns aLoadedStatus
|
||||||
|
coEvery { fakeLoadMorePollsUseCase.execute(aRoomId) } returns anUpdatedLoadedStatus
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = syncPollsUseCase.execute(aRoomId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo anUpdatedLoadedStatus
|
||||||
|
coVerifyOrder {
|
||||||
|
fakeRoomPollRepository.syncPolls(aRoomId)
|
||||||
|
fakeGetLoadedPollsStatusUseCase.execute(aRoomId)
|
||||||
|
fakeLoadMorePollsUseCase.execute(aRoomId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.roomprofile.polls.list.ui
|
||||||
|
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.factory.PollOptionViewStateFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
private const val AN_EVENT_TIMESTAMP = 123L
|
||||||
|
private const val A_POLL_TITLE = "poll-title"
|
||||||
|
|
||||||
|
internal class PollSummaryMapperTest {
|
||||||
|
|
||||||
|
private val fakePollResponseDataFactory = mockk<PollResponseDataFactory>()
|
||||||
|
private val fakePollOptionViewStateFactory = mockk<PollOptionViewStateFactory>()
|
||||||
|
|
||||||
|
private val pollSummaryMapper = PollSummaryMapper(
|
||||||
|
pollResponseDataFactory = fakePollResponseDataFactory,
|
||||||
|
pollOptionViewStateFactory = fakePollOptionViewStateFactory,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mockkStatic("im.vector.app.core.extensions.TimelineEventKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a not ended poll event when mapping to model then result is active poll`() {
|
||||||
|
// Given
|
||||||
|
val pollStartedEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
pollTitle = A_POLL_TITLE,
|
||||||
|
isClosed = false,
|
||||||
|
)
|
||||||
|
val expectedResult = PollSummary.ActivePoll(
|
||||||
|
id = AN_EVENT_ID,
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
title = A_POLL_TITLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollSummaryMapper.map(pollStartedEvent)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an ended poll event when mapping to model then result is ended poll`() {
|
||||||
|
// Given
|
||||||
|
val totalVotes = 10
|
||||||
|
val winnerOptions = listOf<PollOptionViewState.PollEnded>()
|
||||||
|
val endedPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
pollTitle = A_POLL_TITLE,
|
||||||
|
isClosed = true,
|
||||||
|
totalVotes = totalVotes,
|
||||||
|
winnerOptions = winnerOptions,
|
||||||
|
)
|
||||||
|
val expectedResult = PollSummary.EndedPoll(
|
||||||
|
id = AN_EVENT_ID,
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
title = A_POLL_TITLE,
|
||||||
|
totalVotes = totalVotes,
|
||||||
|
winnerOptions = winnerOptions,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollSummaryMapper.map(endedPollEvent)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given missing data in event when mapping to model then result is null`() {
|
||||||
|
// Given
|
||||||
|
val noIdPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = "",
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
pollTitle = A_POLL_TITLE,
|
||||||
|
isClosed = false,
|
||||||
|
)
|
||||||
|
val noTimestampPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = 0,
|
||||||
|
pollTitle = A_POLL_TITLE,
|
||||||
|
isClosed = false,
|
||||||
|
)
|
||||||
|
val notAPollEvent = givenATimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = 0,
|
||||||
|
content = mockk<MessageTextContent>()
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result1 = pollSummaryMapper.map(noIdPollEvent)
|
||||||
|
val result2 = pollSummaryMapper.map(noTimestampPollEvent)
|
||||||
|
val result3 = pollSummaryMapper.map(notAPollEvent)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result1 shouldBe null
|
||||||
|
result2 shouldBe null
|
||||||
|
result3 shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenATimelineEvent(
|
||||||
|
eventId: String,
|
||||||
|
creationTimestamp: Long,
|
||||||
|
content: MessageContent,
|
||||||
|
): TimelineEvent {
|
||||||
|
val timelineEvent = mockk<TimelineEvent>()
|
||||||
|
every { timelineEvent.root.eventId } returns eventId
|
||||||
|
every { timelineEvent.root.originServerTs } returns creationTimestamp
|
||||||
|
every { timelineEvent.getVectorLastMessageContent() } returns content
|
||||||
|
return timelineEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollTimelineEvent(
|
||||||
|
eventId: String,
|
||||||
|
creationTimestamp: Long,
|
||||||
|
pollTitle: String,
|
||||||
|
isClosed: Boolean,
|
||||||
|
totalVotes: Int = 0,
|
||||||
|
winnerOptions: List<PollOptionViewState.PollEnded> = emptyList(),
|
||||||
|
): TimelineEvent {
|
||||||
|
val pollCreationInfo = givenPollCreationInfo(pollTitle)
|
||||||
|
val messageContent = givenAMessagePollContent(pollCreationInfo)
|
||||||
|
val timelineEvent = givenATimelineEvent(eventId, creationTimestamp, messageContent)
|
||||||
|
val pollResponseData = givenAPollResponseData(isClosed, totalVotes)
|
||||||
|
every { fakePollResponseDataFactory.create(timelineEvent) } returns pollResponseData
|
||||||
|
every {
|
||||||
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
|
pollCreationInfo,
|
||||||
|
pollResponseData
|
||||||
|
)
|
||||||
|
} returns winnerOptions
|
||||||
|
|
||||||
|
return timelineEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAMessagePollContent(pollCreationInfo: PollCreationInfo): MessagePollContent {
|
||||||
|
return MessagePollContent(
|
||||||
|
unstablePollCreationInfo = pollCreationInfo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenPollCreationInfo(pollTitle: String): PollCreationInfo {
|
||||||
|
return PollCreationInfo(
|
||||||
|
question = PollQuestion(unstableQuestion = pollTitle),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollResponseData(isClosed: Boolean, totalVotes: Int): PollResponseData {
|
||||||
|
return PollResponseData(
|
||||||
|
myVote = "",
|
||||||
|
votes = emptyMap(),
|
||||||
|
isClosed = isClosed,
|
||||||
|
totalVotes = totalVotes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coJustRun
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
class FakePollHistoryService : PollHistoryService by mockk() {
|
||||||
|
|
||||||
|
fun givenDispose() {
|
||||||
|
justRun { dispose() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyDispose() {
|
||||||
|
verify { dispose() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenGetPollsReturns(events: List<TimelineEvent>): LiveData<List<TimelineEvent>> {
|
||||||
|
return MutableLiveData(events).also {
|
||||||
|
every { getPollEvents() } returns it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyGetPolls() {
|
||||||
|
verify { getPollEvents() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenGetLoadedPollsStatusReturns(status: LoadedPollsStatus) {
|
||||||
|
coEvery { getLoadedPollsStatus() } returns status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyGetLoadedPollsStatus() {
|
||||||
|
coVerify { getLoadedPollsStatus() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenLoadMoreReturns(status: LoadedPollsStatus) {
|
||||||
|
coEvery { loadMore() } returns status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyLoadMore() {
|
||||||
|
coVerify { loadMore() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenSyncPollsSuccess() {
|
||||||
|
coJustRun { syncPolls() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySyncPolls() {
|
||||||
|
coVerify { syncPolls() }
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ class FakeRoom(
|
|||||||
private val fakeTimelineService: FakeTimelineService = FakeTimelineService(),
|
private val fakeTimelineService: FakeTimelineService = FakeTimelineService(),
|
||||||
private val fakeRelationService: FakeRelationService = FakeRelationService(),
|
private val fakeRelationService: FakeRelationService = FakeRelationService(),
|
||||||
private val fakeStateService: FakeStateService = FakeStateService(),
|
private val fakeStateService: FakeStateService = FakeStateService(),
|
||||||
|
private val fakePollHistoryService: FakePollHistoryService = FakePollHistoryService(),
|
||||||
) : Room by mockk() {
|
) : Room by mockk() {
|
||||||
|
|
||||||
override fun locationSharingService() = fakeLocationSharingService
|
override fun locationSharingService() = fakeLocationSharingService
|
||||||
@ -36,4 +37,6 @@ class FakeRoom(
|
|||||||
override fun relationService() = fakeRelationService
|
override fun relationService() = fakeRelationService
|
||||||
|
|
||||||
override fun stateService() = fakeStateService
|
override fun stateService() = fakeStateService
|
||||||
|
|
||||||
|
override fun pollHistoryService() = fakePollHistoryService
|
||||||
}
|
}
|
||||||
|
67
vector/src/test/java/im/vector/app/test/fixtures/PollFixture.kt
vendored
Normal file
67
vector/src/test/java/im/vector/app/test/fixtures/PollFixture.kt
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.test.fixtures
|
||||||
|
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
|
||||||
|
object PollFixture {
|
||||||
|
|
||||||
|
val A_MESSAGE_INFORMATION_DATA = MessageInformationData(
|
||||||
|
eventId = "eventId",
|
||||||
|
senderId = "senderId",
|
||||||
|
ageLocalTS = 0,
|
||||||
|
avatarUrl = "",
|
||||||
|
sendState = SendState.SENT,
|
||||||
|
messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true),
|
||||||
|
reactionsSummary = ReactionsSummaryData(),
|
||||||
|
sentByMe = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val A_POLL_RESPONSE_DATA = PollResponseData(
|
||||||
|
myVote = null,
|
||||||
|
votes = emptyMap(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val A_POLL_OPTION_IDS = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", "ec1a4db0-46d8-4d7a-9bb6-d80724715938", "3677ca8e-061b-40ab-bffe-b22e4e88fcad")
|
||||||
|
|
||||||
|
val A_POLL_CONTENT = MessagePollContent(
|
||||||
|
unstablePollCreationInfo = PollCreationInfo(
|
||||||
|
question = PollQuestion(
|
||||||
|
unstableQuestion = "What is your favourite coffee?"
|
||||||
|
), kind = PollType.UNDISCLOSED_UNSTABLE, maxSelections = 1, answers = listOf(
|
||||||
|
PollAnswer(
|
||||||
|
id = A_POLL_OPTION_IDS[0], unstableAnswer = "Double Espresso"
|
||||||
|
),
|
||||||
|
PollAnswer(
|
||||||
|
id = A_POLL_OPTION_IDS[1], unstableAnswer = "Macchiato"
|
||||||
|
),
|
||||||
|
PollAnswer(
|
||||||
|
id = A_POLL_OPTION_IDS[2], unstableAnswer = "Iced Coffee"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2023 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.test.fixtures
|
|
||||||
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
|
|
||||||
object RoomPollFixture {
|
|
||||||
|
|
||||||
fun anActivePollSummary(
|
|
||||||
id: String = "",
|
|
||||||
timestamp: Long,
|
|
||||||
title: String = "",
|
|
||||||
) = PollSummary.ActivePoll(
|
|
||||||
id = id,
|
|
||||||
creationTimestamp = timestamp,
|
|
||||||
title = title,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun anEndedPollSummary(
|
|
||||||
id: String = "",
|
|
||||||
timestamp: Long,
|
|
||||||
title: String = "",
|
|
||||||
totalVotes: Int,
|
|
||||||
winnerOptions: List<PollOptionViewState.PollEnded>
|
|
||||||
) = PollSummary.EndedPoll(
|
|
||||||
id = id,
|
|
||||||
creationTimestamp = timestamp,
|
|
||||||
title = title,
|
|
||||||
totalVotes = totalVotes,
|
|
||||||
winnerOptions = winnerOptions,
|
|
||||||
)
|
|
||||||
}
|
|
Reference in New Issue
Block a user