Merge branch 'develop' into feature/fga/timeline_thread_decoration
This commit is contained in:
@@ -15,7 +15,7 @@ appId: ${APP_ID}
|
||||
- tapOn: "gnuradio.org"
|
||||
- extendedWaitUntil:
|
||||
visible: "This server currently doesn’t support sliding sync."
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
- tapOn: "Cancel"
|
||||
- back
|
||||
- back
|
||||
|
||||
@@ -2,4 +2,4 @@ appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Help improve Element X dbg"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
||||
@@ -2,4 +2,4 @@ appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "All Chats"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
||||
@@ -2,4 +2,4 @@ appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Be in your element"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
||||
@@ -2,4 +2,4 @@ appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Change account provider"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
||||
@@ -2,4 +2,4 @@ appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: ${ROOM_NAME}
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
||||
@@ -3,4 +3,4 @@ appId: ${APP_ID}
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
id: "welcome_screen-title"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- takeScreenshot: build/maestro/510-Timeline
|
||||
- tapOn: "Message"
|
||||
- tapOn:
|
||||
id: "rich_text_editor"
|
||||
- inputText: "Hello world!"
|
||||
- tapOn: "Send"
|
||||
- hideKeyboard
|
||||
|
||||
@@ -37,8 +37,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class RootPresenterTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,8 +37,7 @@ import org.junit.Test
|
||||
|
||||
class LoggedInPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
1
changelog.d/1232.bugfix
Normal file
1
changelog.d/1232.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix long click on simple formatted messages
|
||||
1
changelog.d/1297.bugfix
Normal file
1
changelog.d/1297.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix top padding in room list when app is opened in offline mode.
|
||||
1
changelog.d/510.misc
Normal file
1
changelog.d/510.misc
Normal file
@@ -0,0 +1 @@
|
||||
Add a sub-screen "Notifications" in the existing application Settings
|
||||
@@ -1,10 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Wir werden keine personenbezogenen Daten aufzeichnen oder auswerten"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Du kannst alle unsere Nutzerbedingungen %1$s lesen."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Du kannst dies jederzeit deaktivieren"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben deine Daten nicht an Dritte weiter"</string>
|
||||
<string name="screen_analytics_prompt_title">"Hilf uns, %1$s zu verbessern"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Nous n\'enregistrerons ni ne traiterons aucune donnée personnelle"</string>
|
||||
<string name="screen_analytics_prompt_data_usage">"Nous n\'enregistrerons ni ne profilerons aucune donnée personnelle"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Partagez des données d\'utilisation anonymes pour nous aider à identifier les problèmes."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Consultez nos conditions d\'utilisation %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Vous pouvez lire toutes nos conditions %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"ici"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Vous pouvez désactiver cette fonction à tout moment"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Vous pouvez le désactiver à tout moment"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Nous ne partagerons pas vos données avec des tiers"</string>
|
||||
<string name="screen_analytics_prompt_title">"Aidez-nous à améliorer %1$s"</string>
|
||||
<string name="screen_analytics_prompt_title">"Aidez à améliorer %1$s"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"分享匿名的使用數據以協助我們釐清問題"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"您可以到 %1$s 閱讀我們的條款。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string>
|
||||
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"我們不會和第三方分享您的資料"</string>
|
||||
<string name="screen_analytics_prompt_title">"讓 %1$s 變得更好"</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,8 +31,7 @@ import org.junit.Test
|
||||
|
||||
class AnalyticsOptInPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -30,8 +30,7 @@ import org.junit.Test
|
||||
|
||||
class AnalyticsPreferencesPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"Neuer Raum"</string>
|
||||
<string name="screen_create_room_action_invite_people">"Freunde zu Element einladen"</string>
|
||||
<string name="screen_create_room_add_people_title">"Personen hinzufügen"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Beim Erstellen des Raums ist ein Fehler aufgetreten"</string>
|
||||
<string name="screen_create_room_private_option_description">"Die Nachrichten in diesem Raum sind verschlüsselt. Die Verschlüsselung kann nicht nachträglich deaktiviert werden."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privater Raum (nur auf Einladung)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Nachrichten sind nicht verschlüsselt und jeder kann sie lesen. Du kannst die Verschlüsselung zu einem späteren Zeitpunkt aktivieren."</string>
|
||||
<string name="screen_create_room_public_option_title">"Öffentlicher Raum (jeder)"</string>
|
||||
<string name="screen_create_room_room_name_label">"Raumname"</string>
|
||||
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten"</string>
|
||||
<string name="screen_create_room_title">"Raum erstellen"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"Nouveau salon"</string>
|
||||
<string name="screen_create_room_action_create_room">"Nouvelle salle"</string>
|
||||
<string name="screen_create_room_action_invite_people">"Inviter des amis sur Element"</string>
|
||||
<string name="screen_create_room_add_people_title">"Inviter des personnes"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Une erreur s\'est produite lors de la création du salon"</string>
|
||||
<string name="screen_create_room_private_option_description">"Les messages dans ce salon sont chiffrés. Une fois activé, le chiffrement ne peut pas être désactivé."</string>
|
||||
<string name="screen_create_room_private_option_title">"Salon privé (sur invitation uniquement)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Les messages ne sont pas chiffrés et n\'importe qui peut les lire. Vous pouvez activer le chiffrement ultérieurement."</string>
|
||||
<string name="screen_create_room_public_option_title">"Salon public (n’importe qui)"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nom du salon"</string>
|
||||
<string name="screen_create_room_topic_label">"Sujet (optionnel)"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Une erreur s\'est produite lors de la création de la salle"</string>
|
||||
<string name="screen_create_room_private_option_description">"Les messages dans cette pièce sont cryptés. Le cryptage ne peut pas être désactivé par la suite."</string>
|
||||
<string name="screen_create_room_private_option_title">"Salle privée (sur invitation seulement)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Les messages ne sont pas cryptés et n\'importe qui peut les lire. Vous pouvez activer le chiffrement ultérieurement."</string>
|
||||
<string name="screen_create_room_public_option_title">"Salle publique (tout le monde)"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nom de la salle"</string>
|
||||
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Une erreur s\'est produite lors de la tentative de démarrage d\'une discussion"</string>
|
||||
<string name="screen_create_room_title">"Créer un salon"</string>
|
||||
<string name="screen_create_room_title">"Créer une salle"</string>
|
||||
</resources>
|
||||
|
||||
@@ -32,8 +32,7 @@ import org.junit.Test
|
||||
|
||||
class AddPeoplePresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private lateinit var presenter: AddPeoplePresenter
|
||||
|
||||
@@ -60,8 +60,7 @@ private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery"
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ConfigureRoomPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private lateinit var presenter: ConfigureRoomPresenter
|
||||
|
||||
@@ -44,8 +44,7 @@ import org.junit.Test
|
||||
|
||||
class CreateRoomRootPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private lateinit var userRepository: FakeUserRepository
|
||||
|
||||
@@ -33,8 +33,7 @@ import org.junit.Test
|
||||
|
||||
class DefaultUserListPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val userRepository = FakeUserRepository()
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Dies ist ein einmaliger Vorgang, danke fürs Warten."</string>
|
||||
<string name="screen_migration_title">"Dein Konto einrichten"</string>
|
||||
<string name="screen_welcome_bullet_1">"Anrufe, Umfragen, Suche und mehr werden später in diesem Jahr hinzugefügt."</string>
|
||||
<string name="screen_welcome_bullet_2">"Der Nachrichtenverlauf für verschlüsselte Räume wird in diesem Update nicht verfügbar sein."</string>
|
||||
<string name="screen_welcome_bullet_3">"Wir würden uns freuen, wenn du uns über die Einstellungsseite deine Meinung mitteilst."</string>
|
||||
<string name="screen_welcome_button">"Los geht\'s!"</string>
|
||||
<string name="screen_welcome_subtitle">"Folgendes musst du wissen:"</string>
|
||||
<string name="screen_welcome_title">"Willkommen bei %1$s!"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kannst deine Einstellungen später ändern."</string>
|
||||
<string name="screen_notification_optin_title">"Erlaube Benachrichtigungen und verpasse keine Nachricht"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Ce processus n’a besoin d’être fait qu’une seule fois, merci de patienter."</string>
|
||||
<string name="screen_migration_message">"Il s\'agit d\'un processus unique, merci d\'avoir attendu."</string>
|
||||
<string name="screen_migration_title">"Configuration de votre compte."</string>
|
||||
<string name="screen_welcome_bullet_2">"L’historique des messages pour les salons chiffrés ne sera pas disponible dans cette mise à jour."</string>
|
||||
<string name="screen_welcome_bullet_3">"Nous serions ravis d’avoir votre avis, n’hésitez pas à nous le partager via la page des paramètres."</string>
|
||||
<string name="screen_welcome_button">"C’est parti !"</string>
|
||||
<string name="screen_welcome_subtitle">"Voici ce qu’il faut savoir :"</string>
|
||||
<string name="screen_welcome_title">"Bienvenue sur %1$s !"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Vous pourrez modifier vos paramètres ultérieurement."</string>
|
||||
<string name="screen_notification_optin_title">"Autorisez les notifications et ne manquez aucun message"</string>
|
||||
<string name="screen_welcome_bullet_1">"Les appels, les sondages, les recherches et plus encore seront ajoutés plus tard cette année."</string>
|
||||
<string name="screen_welcome_bullet_2">"L\'historique des messages pour les salles cryptées ne sera pas disponible dans cette mise à jour."</string>
|
||||
<string name="screen_welcome_bullet_3">"N\'hésitez pas à nous faire part de vos commentaires via la page des paramètres."</string>
|
||||
<string name="screen_welcome_button">"C\'est parti !"</string>
|
||||
<string name="screen_welcome_subtitle">"Voici ce que vous devez savoir :"</string>
|
||||
<string name="screen_welcome_title">"Bienvenue à %1$s !"</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_migration_message">"Ide o jednorazový proces, ďakujeme za trpezlivosť."</string>
|
||||
<string name="screen_migration_title">"Nastavenie vášho účtu."</string>
|
||||
<string name="screen_notification_optin_subtitle">"Svoje nastavenia môžete neskôr zmeniť."</string>
|
||||
<string name="screen_notification_optin_title">"Povoľte oznámenia a nikdy nezmeškajte žiadnu správu"</string>
|
||||
<string name="screen_welcome_bullet_1">"Hovory, ankety, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku."</string>
|
||||
<string name="screen_welcome_bullet_2">"História správ pre zašifrované miestnosti nebude v tejto aktualizácii k dispozícii."</string>
|
||||
<string name="screen_welcome_bullet_3">"Radi by sme od vás počuli, dajte nám vedieť, čo si myslíte, prostredníctvom stránky nastavení."</string>
|
||||
|
||||
@@ -32,8 +32,7 @@ import org.junit.Test
|
||||
|
||||
class MigrationScreenPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -39,8 +39,7 @@ import org.junit.Test
|
||||
|
||||
class NotificationsOptInPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private var isFinished = false
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_invites_decline_chat_message">"Möchtest du den Beitritt zu %1$s wirklich ablehnen?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Möchtest du den privaten Chat mit %1$s wirklich ablehnen?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Chat ablehnen"</string>
|
||||
<string name="screen_invites_empty_list">"Keine Einladungen"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) hat dich eingeladen"</string>
|
||||
</resources>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_invites_decline_chat_message">"Voulez-vous vraiment refuser l‘invitation à rejoindre %1$s ?"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Êtes-vous sûr de vouloir décliner l\'invitation à participer %1$s ?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Refuser l\'invitation"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Voulez-vous vraiment refuser ce chat privé avec %1$s ?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Êtes-vous sûr de vouloir refuser cette discussion privée avec %1$s ?"</string>
|
||||
<string name="screen_invites_decline_direct_chat_title">"Refuser le chat"</string>
|
||||
<string name="screen_invites_empty_list">"Aucune invitation"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) vous a invité"</string>
|
||||
|
||||
@@ -50,8 +50,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class InviteListPresenterTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -39,8 +39,7 @@ import org.junit.Test
|
||||
|
||||
class LeaveRoomPresenterImplTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -42,8 +42,7 @@ import org.junit.Test
|
||||
|
||||
class SendLocationPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val permissionsPresenterFake = PermissionsPresenterFake()
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.junit.Test
|
||||
|
||||
class ShowLocationPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val permissionsPresenterFake = PermissionsPresenterFake()
|
||||
|
||||
@@ -1,47 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_account_provider_change">"Kontoanbieter wechseln"</string>
|
||||
<string name="screen_account_provider_form_hint">"Adresse des Homeservers"</string>
|
||||
<string name="screen_account_provider_form_notice">"Gib einen Suchbegriff oder eine Domainadresse ein."</string>
|
||||
<string name="screen_account_provider_form_subtitle">"Suche nach einem Unternehmen, einer Community oder einem privaten Server."</string>
|
||||
<string name="screen_account_provider_form_title">"Finde einen Kontoanbieter"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Konversationen stattfinden — genauso wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren."</string>
|
||||
<string name="screen_account_provider_signin_title">"Du bist dabei dich bei %s anzumelden"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Konversationen stattfinden — genauso wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren."</string>
|
||||
<string name="screen_account_provider_signup_title">"Du bist dabei ein Konto auf %s zu erstellen"</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org ist ein großer, kostenloser Server im öffentlichen Matrix-Netzwerk für sichere, dezentrale Kommunikation, der von der Matrix.org Foundation betrieben wird."</string>
|
||||
<string name="screen_change_account_provider_other">"Andere"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Verwende einen anderen Kontoanbieter, z. B. deinen eigenen privaten Server oder ein Arbeitskonto."</string>
|
||||
<string name="screen_change_account_provider_title">"Kontoanbieter ändern"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Wir konnten diesen Homeserver nicht erreichen. Bitte überprüfe, dass du die Homeserver-URL korrekt eingegeben hast. Wenn die URL korrekt ist, wende dich an deinen Homeserver-Administrator für weitere Hilfe."</string>
|
||||
<string name="screen_change_server_error_no_sliding_sync_message">"Dieser Server unterstützt derzeit keine Sliding Sync."</string>
|
||||
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
|
||||
<string name="screen_change_server_form_notice">"Du kannst dich nur mit einem existierenden Server verbinden, der Sliding Sync unterstützt. Dein Homeserver-Administrator muss es konfigurieren. %1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
|
||||
<string name="screen_login_error_deactivated_account">"Dieses Konto wurde deaktiviert."</string>
|
||||
<string name="screen_login_error_invalid_credentials">"Falscher Benutzername und/oder Passwort"</string>
|
||||
<string name="screen_login_error_invalid_user_id">"Dies ist kein gültiger Benutzeridentifikator. Erwartetes Format: \'@user:homeserver.org\'"</string>
|
||||
<string name="screen_login_error_unsupported_authentication">"Der ausgewählte Homeserver unterstützt kein Passwort- oder OIDC-Login. Bitte kontaktiere deinen Admin oder wähle einen anderen Homeserver."</string>
|
||||
<string name="screen_login_form_header">"Gib deine Daten ein"</string>
|
||||
<string name="screen_login_title">"Willkommen zurück!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Bei %1$s anmelden"</string>
|
||||
<string name="screen_server_confirmation_change_server">"Kontoanbieter wechseln"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Ein privater Server für Element-Mitarbeiter."</string>
|
||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix ist ein offenes Netzwerk für sichere, dezentrale Kommunikation"</string>
|
||||
<string name="screen_server_confirmation_message_register">"Hier werden deine Konversationen stattfinden — genau so wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren."</string>
|
||||
<string name="screen_server_confirmation_title_login">"Du bist dabei dich bei %1$s anzumelden"</string>
|
||||
<string name="screen_server_confirmation_title_register">"Du bist dabei ein Konto auf %1$s zu erstellen"</string>
|
||||
<string name="screen_waitlist_message">"Im Moment besteht eine hohe Nachfrage nach %1$s auf %2$s. Besuche die App in ein paar Tagen wieder und versuche es erneut.
|
||||
|
||||
Vielen Dank für deine Geduld!"</string>
|
||||
<string name="screen_waitlist_message_success">"Willkommen bei %1$s!"</string>
|
||||
<string name="screen_waitlist_title">"Du hast es fast geschafft!"</string>
|
||||
<string name="screen_waitlist_title_success">"Du bist dabei."</string>
|
||||
<string name="screen_account_provider_continue">"Weiter"</string>
|
||||
<string name="screen_change_server_submit">"Weiter"</string>
|
||||
<string name="screen_change_server_title">"Wählen deinen Server"</string>
|
||||
<string name="screen_login_password_hint">"Passwort"</string>
|
||||
<string name="screen_login_submit">"Weiter"</string>
|
||||
<string name="screen_login_subtitle">"Matrix ist ein offenes Netzwerk für sichere, dezentrale Kommunikation"</string>
|
||||
<string name="screen_login_username_hint">"Benutzername"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_account_provider_change">"Changer de fournisseur"</string>
|
||||
<string name="screen_account_provider_change">"Changer de fournisseur de compte"</string>
|
||||
<string name="screen_account_provider_form_hint">"Adresse du serveur d\'accueil"</string>
|
||||
<string name="screen_account_provider_form_notice">"Entrez un mot clé de recherche ou un nom de domaine."</string>
|
||||
<string name="screen_account_provider_form_subtitle">"Rechercher une entreprise, une communauté ou un serveur privé."</string>
|
||||
<string name="screen_account_provider_form_title">"Trouver un fournisseur de services"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"C\'est ici que vos conversations seront stockées - tout comme vous utiliseriez un fournisseur de messagerie pour conserver vos e-mails."</string>
|
||||
<string name="screen_account_provider_form_notice">"Entrez un terme de recherche ou une adresse de domaine."</string>
|
||||
<string name="screen_account_provider_form_subtitle">"Recherchez une entreprise, une communauté ou un serveur privé."</string>
|
||||
<string name="screen_account_provider_form_title">"Trouver un fournisseur de comptes"</string>
|
||||
<string name="screen_account_provider_signin_subtitle">"C\'est ici que vos conversations seront enregistrées, comme vous le feriez avec un fournisseur de messagerie pour conserver vos e-mails."</string>
|
||||
<string name="screen_account_provider_signin_title">"Vous êtes sur le point de vous connecter à %s"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"C\'est ici que vos conversations seront stockées - tout comme vous utiliseriez un fournisseur de messagerie pour conserver vos e-mails."</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"C\'est ici que vos conversations seront enregistrées, comme vous le feriez avec un fournisseur de messagerie pour conserver vos e-mails."</string>
|
||||
<string name="screen_account_provider_signup_title">"Vous êtes sur le point de créer un compte sur %s"</string>
|
||||
<string name="screen_change_account_provider_other">"Autre"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Utilisez un autre fournisseur de compte, tel que votre propre serveur ou un compte professionnel."</string>
|
||||
<string name="screen_change_account_provider_title">"Changer de fournisseur"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nous n\'avons pas pu atteindre ce serveur domestique. Vérifiez que vous avez correctement saisi l\'URL du serveur d\'accueil. Si l\'URL est correcte, contactez l\'administrateur de votre serveur domestique pour obtenir de l\'aide."</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org est un grand serveur gratuit sur le réseau public Matrix pour une communication sécurisée et décentralisée, géré par la Fondation Matrix.org."</string>
|
||||
<string name="screen_change_account_provider_other">"Autres"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Utilisez un autre fournisseur de compte, tel que votre propre serveur privé ou un compte professionnel."</string>
|
||||
<string name="screen_change_account_provider_title">"Changer de fournisseur de compte"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nous n\'avons pas pu atteindre ce serveur d\'accueil. Vérifiez que vous avez correctement saisi l\'URL du serveur d\'accueil. Si l\'URL est correcte, contactez l\'administrateur de votre serveur d\'accueil pour obtenir de l\'aide."</string>
|
||||
<string name="screen_change_server_error_no_sliding_sync_message">"Ce serveur ne prend actuellement pas en charge la synchronisation glissante."</string>
|
||||
<string name="screen_change_server_form_header">"URL du serveur d\'accueil"</string>
|
||||
<string name="screen_change_server_form_notice">"Vous ne pouvez vous connecter qu\'à un serveur existant qui prend en charge la synchronisation glissante. L\'administrateur de votre serveur domestique devra la configurer. %1$s"</string>
|
||||
<string name="screen_change_server_form_notice">"Vous ne pouvez vous connecter qu\'à un serveur existant qui prend en charge la synchronisation par glissement. L\'administrateur de votre serveur d\'accueil devra le configurer. %1$s"</string>
|
||||
<string name="screen_change_server_subtitle">"Quelle est l\'adresse de votre serveur ?"</string>
|
||||
<string name="screen_login_error_deactivated_account">"Ce compte a été désactivé."</string>
|
||||
<string name="screen_login_error_invalid_credentials">"Nom d\'utilisateur et/ou mot de passe incorrect"</string>
|
||||
<string name="screen_login_error_invalid_credentials">"Nom d\'utilisateur et/ou mot de passe incorrects"</string>
|
||||
<string name="screen_login_error_invalid_user_id">"Il ne s\'agit pas d\'un identifiant utilisateur valide. Format attendu : « @user:homeserver.org »"</string>
|
||||
<string name="screen_login_error_unsupported_authentication">"Le serveur domestique sélectionné ne prend pas en charge le mot de passe ou la connexion OIDC. Contactez votre administrateur ou choisissez un autre serveur domestique."</string>
|
||||
<string name="screen_login_error_unsupported_authentication">"Le serveur d\'accueil sélectionné ne prend pas en charge le mot de passe ou la connexion OIDC. Contactez votre administrateur ou choisissez un autre serveur d\'accueil."</string>
|
||||
<string name="screen_login_form_header">"Saisir vos informations personnelles"</string>
|
||||
<string name="screen_login_title">"Heureux de vous revoir!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Se connecter à %1$s"</string>
|
||||
<string name="screen_login_title">"Bienvenue !"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Connectez-vous à %1$s"</string>
|
||||
<string name="screen_server_confirmation_change_server">"Changer de fournisseur de compte"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Un serveur privé pour les employés d’Element."</string>
|
||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix est un réseau ouvert de communication sécurisée et décentralisée."</string>
|
||||
<string name="screen_server_confirmation_message_register">"C\'est là que vos conversations seront conservées — de la même manière que votre service d’e-mail habituel conserverait vos e-mails."</string>
|
||||
<string name="screen_server_confirmation_title_login">"Vous allez vous connecter à %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_register">"Vous allez créer un compte sur %1$s"</string>
|
||||
<string name="screen_waitlist_message">"Il y a une forte demande pour %1$s sur %2$s en ce moment. Rouvrez l’app dans quelques jours et réessayez.
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Un serveur privé pour les employés d\'Element."</string>
|
||||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
|
||||
<string name="screen_server_confirmation_message_register">"C\'est ici que vos conversations seront enregistrées, comme vous le feriez avec un fournisseur de messagerie pour conserver vos e-mails."</string>
|
||||
<string name="screen_server_confirmation_title_login">"Vous êtes sur le point de vous connecter à %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_register">"Vous êtes sur le point de créer un compte sur %1$s"</string>
|
||||
<string name="screen_waitlist_message">"Il y a une forte demande pour %1$s sur %2$s à l\'heure actuelle. Revenez sur l\'application dans quelques jours et réessayez.
|
||||
|
||||
Merci de votre patience !"</string>
|
||||
<string name="screen_waitlist_message_success">"Bienvenue sur %1$s !"</string>
|
||||
Merci pour votre patience !"</string>
|
||||
<string name="screen_waitlist_message_success">"Bienvenue à %1$s !"</string>
|
||||
<string name="screen_waitlist_title">"Vous y êtes presque."</string>
|
||||
<string name="screen_waitlist_title_success">"Vous y êtes."</string>
|
||||
<string name="screen_account_provider_continue">"Continuer"</string>
|
||||
@@ -41,6 +42,6 @@ Merci de votre patience !"</string>
|
||||
<string name="screen_change_server_title">"Sélectionnez votre serveur"</string>
|
||||
<string name="screen_login_password_hint">"Mot de passe"</string>
|
||||
<string name="screen_login_submit">"Continuer"</string>
|
||||
<string name="screen_login_subtitle">"Matrix est un réseau ouvert de communication sécurisée et décentralisée."</string>
|
||||
<string name="screen_login_subtitle">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
|
||||
<string name="screen_login_username_hint">"Nom d\'utilisateur"</string>
|
||||
</resources>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<string name="screen_account_provider_signin_title">"Chystáte sa prihlásiť do %s"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"Tu budú žiť vaše konverzácie — podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov."</string>
|
||||
<string name="screen_account_provider_signup_title">"Chystáte sa vytvoriť účet na %s"</string>
|
||||
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org je veľký bezplatný server vo verejnej sieti Matrix na bezpečnú, decentralizovanú komunikáciu, ktorý prevádzkuje nadácia Matrix.org."</string>
|
||||
<string name="screen_change_account_provider_other">"Iný"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Použite iného poskytovateľa účtu, ako napríklad vlastný súkromný server alebo pracovný účet."</string>
|
||||
<string name="screen_change_account_provider_title">"Zmeniť poskytovateľa účtu"</string>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_account_provider_signin_title">"您即將登入%s"</string>
|
||||
<string name="screen_account_provider_signin_title">"您即將登入 %s"</string>
|
||||
<string name="screen_account_provider_signup_title">"您即將在 %s 建立帳號"</string>
|
||||
<string name="screen_change_account_provider_other">"其他"</string>
|
||||
<string name="screen_change_server_error_no_sliding_sync_message">"此伺服器目前不支援 sliding sync。"</string>
|
||||
<string name="screen_change_server_form_header">"家伺服器 URL"</string>
|
||||
<string name="screen_login_form_header">"輸入您的詳細資料"</string>
|
||||
<string name="screen_login_title">"歡迎回來!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"登入 %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_login">"您即將登入 %1$s"</string>
|
||||
|
||||
@@ -33,8 +33,7 @@ import org.junit.Test
|
||||
|
||||
class ChangeServerPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.junit.Test
|
||||
|
||||
class OidcPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -31,8 +31,7 @@ import org.junit.Test
|
||||
|
||||
class ChangeAccountProviderPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -39,8 +39,7 @@ import org.junit.Test
|
||||
|
||||
class ConfirmAccountProviderPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -38,8 +38,7 @@ import org.junit.Test
|
||||
|
||||
class LoginPasswordPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -38,8 +38,7 @@ import org.junit.Test
|
||||
|
||||
class SearchAccountProviderPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,8 +37,7 @@ import org.junit.Test
|
||||
|
||||
class WaitListPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_signout_confirmation_dialog_content">"Möchtest du Dich wirklich abmelden?"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Abmelden"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Abmeldung läuft…"</string>
|
||||
<string name="screen_signout_confirmation_dialog_submit">"Abmelden"</string>
|
||||
<string name="screen_signout_preference_item">"Abmelden"</string>
|
||||
</resources>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_signout_confirmation_dialog_content">"Êtes-vous sûr de vouloir vous déconnecter?"</string>
|
||||
<string name="screen_signout_confirmation_dialog_content">"Êtes-vous sûr de vouloir vous déconnecter ?"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Se déconnecter"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Déconnexion en cours…"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Déconnexion…"</string>
|
||||
<string name="screen_signout_confirmation_dialog_submit">"Se déconnecter"</string>
|
||||
<string name="screen_signout_preference_item">"Se déconnecter"</string>
|
||||
</resources>
|
||||
|
||||
@@ -32,8 +32,7 @@ import org.junit.Test
|
||||
|
||||
class LogoutPreferencePresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -126,7 +126,12 @@ private fun HtmlBody(
|
||||
when (val node = nodes.next()) {
|
||||
is TextNode -> {
|
||||
if (!node.isBlank) {
|
||||
ClickableLinkText(text = node.text(), interactionSource = interactionSource)
|
||||
ClickableLinkText(
|
||||
text = node.text(),
|
||||
interactionSource = interactionSource,
|
||||
onClick = onTextClicked,
|
||||
onLongClick = onTextLongClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
is Element -> {
|
||||
|
||||
@@ -1,41 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<plurals name="room_timeline_state_changes">
|
||||
<item quantity="one">"%1$d Raumänderung"</item>
|
||||
<item quantity="other">"%1$d Raumänderungen"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_attachment_source_camera">"Kamera"</string>
|
||||
<string name="screen_room_attachment_source_camera_photo">"Foto aufnehmen"</string>
|
||||
<string name="screen_room_attachment_source_camera_video">"Video aufnehmen"</string>
|
||||
<string name="screen_room_attachment_source_files">"Anhang"</string>
|
||||
<string name="screen_room_attachment_source_gallery">"Foto- & Video-Bibliothek"</string>
|
||||
<string name="screen_room_attachment_source_location">"Standort"</string>
|
||||
<string name="screen_room_attachment_source_poll">"Umfrage"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"Der Nachrichtenverlauf ist in diesem Raum derzeit nicht verfügbar"</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Benutzerdetails konnten nicht abgerufen werden"</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Möchtest du sie wieder einladen?"</string>
|
||||
<string name="screen_room_invite_again_alert_title">"Du bist allein in diesem Chat"</string>
|
||||
<string name="screen_room_message_copied">"Nachricht kopiert"</string>
|
||||
<string name="screen_room_no_permission_to_post">"Du bist keine Berechtigung, um in diesem Raum zu posten"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom">"Benutzerdefinierte Einstellung zulassen"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom_footnote">"Das Aktivieren dieser Option wird die Standardeinstellungen überschreiben."</string>
|
||||
<string name="screen_room_notification_settings_custom_settings_title">"Benachrichtige mich in diesem Chat für"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote">"Du kannst es in deinem %1$s ändern."</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"Globale Einstellungen"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_title">"Standardeinstellung"</string>
|
||||
<string name="screen_room_notification_settings_edit_remove_setting">"Benutzerdefinierte Einstellung entfernen"</string>
|
||||
<string name="screen_room_notification_settings_error_loading_settings">"Beim Laden der Benachrichtigungseinstellungen ist ein Fehler aufgetreten."</string>
|
||||
<string name="screen_room_notification_settings_error_restoring_default">"Wiederherstellung des Standardmodus fehlgeschlagen. Bitte versuche es erneut."</string>
|
||||
<string name="screen_room_notification_settings_error_setting_mode">"Fehler beim Einstellen des Modus. Bitte versuche es erneut."</string>
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"Alle Nachrichten"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Nur Erwähnungen und Schlüsselwörter"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"In diesem Raum, benachrichtige mich für"</string>
|
||||
<string name="screen_room_reactions_show_less">"Weniger anzeigen"</string>
|
||||
<string name="screen_room_reactions_show_more">"Mehr anzeigen"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Erneut senden"</string>
|
||||
<string name="screen_room_retry_send_menu_title">"Ihre Nachricht konnte nicht gesendet werden"</string>
|
||||
<string name="screen_room_timeline_add_reaction">"Emoji hinzufügen"</string>
|
||||
<string name="screen_room_timeline_less_reactions">"Weniger anzeigen"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Fehler bei der Verarbeitung von Medien zum Hochladen, bitte versuche es erneut."</string>
|
||||
<string name="screen_room_attachment_text_formatting">"Textformatierung"</string>
|
||||
<string name="screen_room_retry_send_menu_remove_action">"Entfernen"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,36 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<plurals name="room_timeline_state_changes">
|
||||
<item quantity="one">"%1$d changement dans la conversation"</item>
|
||||
<item quantity="other">"%1$d changements dans la conversation"</item>
|
||||
<item quantity="one">"%1$d changement de salle"</item>
|
||||
<item quantity="other">"%1$d changements de salle"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_attachment_source_camera">"Appareil photo"</string>
|
||||
<string name="screen_room_attachment_source_camera_photo">"Prendre une photo"</string>
|
||||
<string name="screen_room_attachment_source_camera_video">"Enregistrer une vidéo"</string>
|
||||
<string name="screen_room_attachment_source_files">"Pièce-jointe"</string>
|
||||
<string name="screen_room_attachment_source_gallery">"Gallerie photo et vidéo"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"L’historique des messages n’est pas disponible actuellement dans ce salon"</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Impossible de récupérer les détails de l’utilisateur"</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Souhaitez-vous les inviter à revenir ?"</string>
|
||||
<string name="screen_room_attachment_source_files">"Pièce jointe"</string>
|
||||
<string name="screen_room_attachment_source_gallery">"Photothèque et vidéothèque"</string>
|
||||
<string name="screen_room_attachment_source_location">"Emplacement"</string>
|
||||
<string name="screen_room_attachment_source_poll">"Sondage"</string>
|
||||
<string name="screen_room_attachment_text_formatting">"Formatage du texte"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"L\'historique des messages n\'est actuellement pas disponible dans cette salle"</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Impossible de récupérer les détails de l\'utilisateur"</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Aimeriez-vous les inviter à revenir ?"</string>
|
||||
<string name="screen_room_invite_again_alert_title">"Vous êtes seul dans ce chat"</string>
|
||||
<string name="screen_room_message_copied">"Message copié"</string>
|
||||
<string name="screen_room_no_permission_to_post">"Vous n‘avez pas le droit de poster dans ce salon"</string>
|
||||
<string name="screen_room_no_permission_to_post">"Vous n\'êtes pas autorisé à publier dans cette salle"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom">"Autoriser les paramètres personnalisés"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom_footnote">"Activer cette option remplacera votre paramètre par défaut"</string>
|
||||
<string name="screen_room_notification_settings_custom_settings_title">"Me notifier dans ce chat pour"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"paramètres généraux"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom_footnote">"L\'activation de cette option annulera votre paramètre par défaut"</string>
|
||||
<string name="screen_room_notification_settings_custom_settings_title">"Prévenez-moi dans ce chat pour"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote">"Vous pouvez le modifier dans votre %1$s."</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"paramètres globaux"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_title">"Paramètre par défaut"</string>
|
||||
<string name="screen_room_notification_settings_error_loading_settings">"Une erreur s’est produite lors du chargement des paramètres de notification."</string>
|
||||
<string name="screen_room_notification_settings_error_restoring_default">"Impossible de restaurer le mode par défaut, veuillez réessayer."</string>
|
||||
<string name="screen_room_notification_settings_error_setting_mode">"Impossible de régler le mode, veuillez réessayer."</string>
|
||||
<string name="screen_room_notification_settings_edit_remove_setting">"Supprimer le paramètre personnalisé"</string>
|
||||
<string name="screen_room_notification_settings_error_loading_settings">"Une erreur s\'est produite lors du chargement des paramètres de notification."</string>
|
||||
<string name="screen_room_notification_settings_error_restoring_default">"Échec de la restauration du mode par défaut, veuillez réessayer."</string>
|
||||
<string name="screen_room_notification_settings_error_setting_mode">"Échec de la configuration du mode, veuillez réessayer."</string>
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"Tous les messages"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Mentions et mots-clés uniquement"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Mentions et mots clés uniquement"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"Dans cette salle, prévenez-moi pour"</string>
|
||||
<string name="screen_room_reactions_show_less">"Afficher moins"</string>
|
||||
<string name="screen_room_reactions_show_more">"Afficher plus"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Renvoyer"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Envoyer à nouveau"</string>
|
||||
<string name="screen_room_retry_send_menu_title">"Votre message n\'a pas pu être envoyé"</string>
|
||||
<string name="screen_room_timeline_add_reaction">"Ajouter un emoji"</string>
|
||||
<string name="screen_room_timeline_less_reactions">"Montrer moins"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Échec du traitement du média avant son envoi, veuillez réessayer."</string>
|
||||
<string name="screen_room_retry_send_menu_remove_action">"Supprimer"</string>
|
||||
<string name="screen_room_timeline_add_reaction">"Ajouter un émoji"</string>
|
||||
<string name="screen_room_timeline_less_reactions">"Afficher moins"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Échec du traitement des médias à télécharger, veuillez réessayer."</string>
|
||||
<string name="screen_room_retry_send_menu_remove_action">"Enlever"</string>
|
||||
</resources>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<string name="screen_room_attachment_source_gallery">"Knižnica fotografií a videí"</string>
|
||||
<string name="screen_room_attachment_source_location">"Poloha"</string>
|
||||
<string name="screen_room_attachment_source_poll">"Anketa"</string>
|
||||
<string name="screen_room_attachment_text_formatting">"Formátovanie textu"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"História správ v tejto miestnosti nie je momentálne k dispozícii"</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Nepodarilo sa získať údaje o používateľovi"</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Chceli by ste ich pozvať späť?"</string>
|
||||
|
||||
@@ -86,8 +86,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class MessagesPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val mockMediaUrl: Uri = mockk("localMediaUri")
|
||||
|
||||
@@ -41,8 +41,7 @@ import org.junit.Test
|
||||
|
||||
class ActionListPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -43,8 +43,7 @@ import org.junit.Test
|
||||
|
||||
class AttachmentsPreviewPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val mediaPreProcessor = FakeMediaPreProcessor()
|
||||
|
||||
@@ -39,8 +39,7 @@ import org.junit.Test
|
||||
|
||||
class ForwardMessagesPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
|
||||
|
||||
@@ -44,8 +44,7 @@ private val TESTED_MEDIA_INFO = aFileInfo()
|
||||
|
||||
class MediaViewerPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.junit.Test
|
||||
|
||||
class ReportMessagePresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,6 +24,7 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Composer
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerContextImpl
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
@@ -57,6 +58,7 @@ import io.element.android.libraries.textcomposer.Message
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.waitForPredicate
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -67,8 +69,7 @@ import java.io.File
|
||||
|
||||
class MessageComposerPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val pickerProvider = FakePickerProvider().apply {
|
||||
@@ -208,6 +209,15 @@ class MessageComposerPresenterTest {
|
||||
val messageSentState = awaitItem()
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
waitForPredicate { analyticsService.capturedEvents.size == 1 }
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = false,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +250,14 @@ class MessageComposerPresenterTest {
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = true,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +290,14 @@ class MessageComposerPresenterTest {
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE to ANOTHER_MESSAGE)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = true,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +330,14 @@ class MessageComposerPresenterTest {
|
||||
assertThat(messageSentState.richTextEditorState.messageHtml).isEqualTo("")
|
||||
assertThat(messageSentState.canSendMessage).isFalse()
|
||||
assertThat(fakeMatrixRoom.replyMessageParameter).isEqualTo(A_REPLY to A_REPLY)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = false,
|
||||
isReply = true,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@ import java.util.Date
|
||||
|
||||
class TimelinePresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,8 +33,7 @@ import org.junit.Test
|
||||
|
||||
class CustomReactionPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val presenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||
|
||||
@@ -37,8 +37,7 @@ import org.junit.Test
|
||||
|
||||
class ReactionSummaryPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val aggregatedReaction = anAggregatedReaction(userId = A_USER_ID, key = "👍", isHighlighted = true)
|
||||
|
||||
@@ -32,8 +32,7 @@ import org.junit.Test
|
||||
|
||||
class RetrySendMenuPresenterTests {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val room = FakeMatrixRoom()
|
||||
|
||||
@@ -109,7 +109,9 @@ fun ConnectivityIndicatorContainer(
|
||||
} else {
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 6.dp
|
||||
}
|
||||
val target = remember(isOnline) { if (isOnline) 0.dp else statusBarTopPadding }
|
||||
val target = remember(isIndicatorVisible.targetState, statusBarTopPadding) {
|
||||
if (!isIndicatorVisible.targetState) 0.dp else statusBarTopPadding
|
||||
}
|
||||
val animationStateOffset by animateDpAsState(
|
||||
targetValue = target,
|
||||
animationSpec = spring(
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_onboarding_sign_in_manually">"Manuell anmelden"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Mit QR-Code anmelden"</string>
|
||||
<string name="screen_onboarding_sign_up">"Konto erstellen"</string>
|
||||
<string name="screen_onboarding_subtitle">"Sicher kommunizieren und zusammenarbeiten"</string>
|
||||
<string name="screen_onboarding_welcome_message">"Willkommen beim schnellsten Element aller Zeiten. Optimiert für Geschwindigkeit und Einfachheit."</string>
|
||||
<string name="screen_onboarding_welcome_subtitle">"Willkommen zur %1$s. Verbessert, für Geschwindigkeit und Einfachheit."</string>
|
||||
<string name="screen_onboarding_welcome_title">"Sei in deinem Element"</string>
|
||||
</resources>
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_onboarding_sign_in_manually">"Se connecter manuellement"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Se connecter avec un code QR"</string>
|
||||
<string name="screen_onboarding_sign_in_manually">"Connectez-vous manuellement"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Connectez-vous avec le code QR"</string>
|
||||
<string name="screen_onboarding_sign_up">"Créer un compte"</string>
|
||||
<string name="screen_onboarding_subtitle">"Communiquer et collaborer en toute sécurité"</string>
|
||||
<string name="screen_onboarding_welcome_message">"Bienvenue dans l’Element le plus rapide de tous les temps. Surpuissant pour plus de vitesse et de simplicité."</string>
|
||||
<string name="screen_onboarding_welcome_subtitle">"Bienvenue dans %1$s. Affiné pour plus de rapidité et de simplicité."</string>
|
||||
<string name="screen_onboarding_subtitle">"Communiquez et collaborez en toute sécurité"</string>
|
||||
<string name="screen_onboarding_welcome_message">"Bienvenue dans l\'Element le plus rapide de tous les temps. Boosté pour plus de rapidité et de simplicité."</string>
|
||||
<string name="screen_onboarding_welcome_subtitle">"Bienvenue sur %1$s. Boosté, pour rapidité et simplicité."</string>
|
||||
<string name="screen_onboarding_welcome_title">"Soyez dans votre Element"</string>
|
||||
</resources>
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"使用 QR code 登入"</string>
|
||||
<string name="screen_onboarding_sign_up">"建立帳號"</string>
|
||||
<string name="screen_onboarding_welcome_message">"歡迎使用有史以來最快的 Element。速度超快,操作簡便。"</string>
|
||||
<string name="screen_onboarding_welcome_title">"得心應手"</string>
|
||||
<string name="screen_onboarding_welcome_title">"Be in your element"</string>
|
||||
</resources>
|
||||
|
||||
@@ -29,8 +29,7 @@ import org.junit.Test
|
||||
|
||||
class OnBoardingPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -41,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconToggleButton
|
||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||
@@ -111,6 +112,7 @@ fun PollAnswerView(
|
||||
answerItem.isSelected -> 1f
|
||||
else -> 0f
|
||||
},
|
||||
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.features.poll.impl.create
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -25,6 +26,7 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
@@ -32,6 +34,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
@@ -61,6 +64,8 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -68,6 +73,8 @@ fun CreatePollView(
|
||||
state: CreatePollState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) }
|
||||
BackHandler(onBack = navBack)
|
||||
if (state.showConfirmation) ConfirmationDialog(
|
||||
@@ -76,6 +83,7 @@ fun CreatePollView(
|
||||
onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) }
|
||||
)
|
||||
val questionFocusRequester = remember { FocusRequester() }
|
||||
val answerFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
questionFocusRequester.requestFocus()
|
||||
}
|
||||
@@ -102,40 +110,43 @@ fun CreatePollView(
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
val lazyListState = rememberLazyListState()
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.consumeWindowInsets(paddingValues)
|
||||
.imePadding()
|
||||
.fillMaxSize(),
|
||||
state = lazyListState,
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(id = R.string.screen_create_poll_question_desc),
|
||||
modifier = Modifier.padding(start = 32.dp),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
}
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
OutlinedTextField(
|
||||
value = state.question,
|
||||
onValueChange = {
|
||||
state.eventSink(CreatePollEvents.SetQuestion(it))
|
||||
},
|
||||
modifier = Modifier
|
||||
.focusRequester(questionFocusRequester)
|
||||
.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(text = stringResource(id = R.string.screen_create_poll_question_hint))
|
||||
},
|
||||
keyboardOptions = keyboardOptions,
|
||||
)
|
||||
}
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.screen_create_poll_question_desc),
|
||||
modifier = Modifier.padding(start = 32.dp),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
OutlinedTextField(
|
||||
value = state.question,
|
||||
onValueChange = {
|
||||
state.eventSink(CreatePollEvents.SetQuestion(it))
|
||||
},
|
||||
modifier = Modifier
|
||||
.focusRequester(questionFocusRequester)
|
||||
.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(text = stringResource(id = R.string.screen_create_poll_question_hint))
|
||||
},
|
||||
keyboardOptions = keyboardOptions,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
itemsIndexed(state.answers) { index, answer ->
|
||||
val isLastItem = index == state.answers.size - 1
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
OutlinedTextField(
|
||||
@@ -143,7 +154,9 @@ fun CreatePollView(
|
||||
onValueChange = {
|
||||
state.eventSink(CreatePollEvents.SetAnswer(index, it))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.then(if (isLastItem) Modifier.focusRequester(answerFocusRequester) else Modifier)
|
||||
.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(text = stringResource(id = R.string.screen_create_poll_answer_hint, index + 1))
|
||||
},
|
||||
@@ -170,22 +183,28 @@ fun CreatePollView(
|
||||
iconSource = IconSource.Vector(Icons.Default.Add),
|
||||
),
|
||||
style = ListItemStyle.Primary,
|
||||
onClick = { state.eventSink(CreatePollEvents.AddAnswer) },
|
||||
onClick = {
|
||||
state.eventSink(CreatePollEvents.AddAnswer)
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
lazyListState.animateScrollToItem(state.answers.size + 1)
|
||||
answerFocusRequester.requestFocus()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
}
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(id = R.string.screen_create_poll_anonymous_headline)) },
|
||||
supportingContent = { Text(text = stringResource(id = R.string.screen_create_poll_anonymous_desc)) },
|
||||
trailingContent = ListItemContent.Switch(
|
||||
checked = state.pollKind == PollKind.Undisclosed,
|
||||
onChange = { state.eventSink(CreatePollEvents.SetPollKind(if (it) PollKind.Undisclosed else PollKind.Disclosed)) },
|
||||
),
|
||||
)
|
||||
Column {
|
||||
HorizontalDivider()
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(id = R.string.screen_create_poll_anonymous_headline)) },
|
||||
supportingContent = { Text(text = stringResource(id = R.string.screen_create_poll_anonymous_desc)) },
|
||||
trailingContent = ListItemContent.Switch(
|
||||
checked = state.pollKind == PollKind.Undisclosed,
|
||||
onChange = { state.eventSink(CreatePollEvents.SetPollKind(if (it) PollKind.Undisclosed else PollKind.Disclosed)) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
features/poll/impl/src/main/res/values-de/translations.xml
Normal file
11
features/poll/impl/src/main/res/values-de/translations.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_poll_add_option_btn">"Option hinzufügen"</string>
|
||||
<string name="screen_create_poll_anonymous_desc">"Ergebnisse erst nach Ende der Umfrage anzeigen"</string>
|
||||
<string name="screen_create_poll_anonymous_headline">"Anonyme Umfrage"</string>
|
||||
<string name="screen_create_poll_answer_hint">"Option %1$d"</string>
|
||||
<string name="screen_create_poll_discard_confirmation">"Bist du sicher, dass du diese Umfrage verwerfen willst?"</string>
|
||||
<string name="screen_create_poll_discard_confirmation_title">"Umfrage verwerfen"</string>
|
||||
<string name="screen_create_poll_question_desc">"Frage oder Thema"</string>
|
||||
<string name="screen_create_poll_title">"Umfrage erstellen"</string>
|
||||
</resources>
|
||||
12
features/poll/impl/src/main/res/values-fr/translations.xml
Normal file
12
features/poll/impl/src/main/res/values-fr/translations.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_poll_add_option_btn">"Ajouter une option"</string>
|
||||
<string name="screen_create_poll_anonymous_desc">"Afficher les résultats uniquement après la fin du sondage"</string>
|
||||
<string name="screen_create_poll_anonymous_headline">"Masquer les votes"</string>
|
||||
<string name="screen_create_poll_answer_hint">"Option %1$d"</string>
|
||||
<string name="screen_create_poll_discard_confirmation">"Êtes-vous sûr de vouloir supprimer ce sondage ?"</string>
|
||||
<string name="screen_create_poll_discard_confirmation_title">"Supprimer le sondage"</string>
|
||||
<string name="screen_create_poll_question_desc">"Question ou sujet"</string>
|
||||
<string name="screen_create_poll_question_hint">"Quel est le sujet du sondage ?"</string>
|
||||
<string name="screen_create_poll_title">"Créer un sondage"</string>
|
||||
</resources>
|
||||
12
features/poll/impl/src/main/res/values-sk/translations.xml
Normal file
12
features/poll/impl/src/main/res/values-sk/translations.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_poll_add_option_btn">"Pridať možnosť"</string>
|
||||
<string name="screen_create_poll_anonymous_desc">"Zobraziť výsledky až po skončení ankety"</string>
|
||||
<string name="screen_create_poll_anonymous_headline">"Anonymná anketa"</string>
|
||||
<string name="screen_create_poll_answer_hint">"Možnosť %1$d"</string>
|
||||
<string name="screen_create_poll_discard_confirmation">"Ste si istí, že chcete túto anketu zahodiť?"</string>
|
||||
<string name="screen_create_poll_discard_confirmation_title">"Odstrániť anketu"</string>
|
||||
<string name="screen_create_poll_question_desc">"Otázka alebo téma"</string>
|
||||
<string name="screen_create_poll_question_hint">"O čom je anketa?"</string>
|
||||
<string name="screen_create_poll_title">"Vytvoriť anketu"</string>
|
||||
</resources>
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_poll_add_option_btn">"Add option"</string>
|
||||
<string name="screen_create_poll_anonymous_desc">"Show results only after poll ends"</string>
|
||||
<string name="screen_create_poll_anonymous_headline">"Anonymous Poll"</string>
|
||||
<string name="screen_create_poll_anonymous_headline">"Hide votes"</string>
|
||||
<string name="screen_create_poll_answer_hint">"Option %1$d"</string>
|
||||
<string name="screen_create_poll_discard_confirmation">"Are you sure you want to discard this poll?"</string>
|
||||
<string name="screen_create_poll_discard_confirmation_title">"Discard Poll"</string>
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.junit.Test
|
||||
|
||||
class CreatePollPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private var navUpInvocationsCount = 0
|
||||
|
||||
@@ -40,6 +40,8 @@ dependencies {
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.featureflag.ui)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.pushstore.api)
|
||||
implementation(projects.libraries.pushstore.test)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.features.rageshake.api)
|
||||
|
||||
@@ -33,6 +33,8 @@ import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.features.preferences.impl.about.AboutNode
|
||||
import io.element.android.features.preferences.impl.analytics.AnalyticsSettingsNode
|
||||
import io.element.android.features.preferences.impl.developer.DeveloperSettingsNode
|
||||
import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode
|
||||
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode
|
||||
import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode
|
||||
import io.element.android.features.preferences.impl.root.PreferencesRootNode
|
||||
import io.element.android.libraries.architecture.BackstackNode
|
||||
@@ -69,6 +71,12 @@ class PreferencesFlowNode @AssistedInject constructor(
|
||||
|
||||
@Parcelize
|
||||
data object About : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object NotificationSettings : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class EditDefaultNotificationSetting(val isOneToOne: Boolean) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
@@ -94,6 +102,10 @@ class PreferencesFlowNode @AssistedInject constructor(
|
||||
override fun onOpenDeveloperSettings() {
|
||||
backstack.push(NavTarget.DeveloperSettings)
|
||||
}
|
||||
|
||||
override fun onOpenNotificationSettings() {
|
||||
backstack.push(NavTarget.NotificationSettings)
|
||||
}
|
||||
}
|
||||
createNode<PreferencesRootNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
@@ -114,6 +126,18 @@ class PreferencesFlowNode @AssistedInject constructor(
|
||||
NavTarget.AnalyticsSettings -> {
|
||||
createNode<AnalyticsSettingsNode>(buildContext)
|
||||
}
|
||||
NavTarget.NotificationSettings -> {
|
||||
val notificationSettingsCallback = object : NotificationSettingsNode.Callback {
|
||||
override fun editDefaultNotificationMode(isOneToOne: Boolean) {
|
||||
backstack.push(NavTarget.EditDefaultNotificationSetting(isOneToOne))
|
||||
}
|
||||
}
|
||||
createNode<NotificationSettingsNode>(buildContext, listOf(notificationSettingsCallback))
|
||||
}
|
||||
is NavTarget.EditDefaultNotificationSetting -> {
|
||||
val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne)
|
||||
createNode<EditDefaultNotificationSettingNode>(buildContext, plugins = listOf(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
sealed interface NotificationSettingsEvents {
|
||||
|
||||
data object RefreshSystemNotificationsEnabled : NotificationSettingsEvents
|
||||
data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
||||
data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
||||
data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
||||
data object FixConfigurationMismatch : NotificationSettingsEvents
|
||||
data object ClearConfigurationMismatchError : NotificationSettingsEvents
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class NotificationSettingsNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: NotificationSettingsPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun editDefaultNotificationMode(isOneToOne: Boolean)
|
||||
}
|
||||
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
||||
private fun openEditDefault(isOneToOne: Boolean) {
|
||||
callbacks.forEach { it.editDefaultNotificationMode(isOneToOne) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
NotificationSettingsView(
|
||||
state = state,
|
||||
onOpenEditDefault = { openEditDefault(isOneToOne = it) },
|
||||
onBackPressed = ::navigateUp,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class NotificationSettingsPresenter @Inject constructor(
|
||||
private val notificationSettingsService: NotificationSettingsService,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val systemNotificationsEnabledProvider: SystemNotificationsEnabledProvider
|
||||
) : Presenter<NotificationSettingsState> {
|
||||
@Composable
|
||||
override fun present(): NotificationSettingsState {
|
||||
val userPushStore = remember { userPushStoreFactory.create(matrixClient.sessionId) }
|
||||
val systemNotificationsEnabled: MutableState<Boolean> = remember {
|
||||
mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled())
|
||||
}
|
||||
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val appNotificationsEnabled = userPushStore
|
||||
.getNotificationEnabledForDevice()
|
||||
.collectAsState(initial = false)
|
||||
|
||||
val matrixSettings: MutableState<NotificationSettingsState.MatrixSettings> = remember {
|
||||
mutableStateOf(NotificationSettingsState.MatrixSettings.Uninitialized)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
fetchSettings(matrixSettings)
|
||||
observeNotificationSettings(matrixSettings)
|
||||
}
|
||||
|
||||
fun handleEvents(event: NotificationSettingsEvents) {
|
||||
when (event) {
|
||||
is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled)
|
||||
is NotificationSettingsEvents.SetCallNotificationsEnabled -> localCoroutineScope.setCallNotificationsEnabled(event.enabled)
|
||||
is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled)
|
||||
NotificationSettingsEvents.ClearConfigurationMismatchError -> {
|
||||
matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
|
||||
}
|
||||
NotificationSettingsEvents.FixConfigurationMismatch -> localCoroutineScope.fixConfigurationMismatch(matrixSettings)
|
||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
|
||||
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NotificationSettingsState(
|
||||
matrixSettings = matrixSettings.value,
|
||||
appSettings = NotificationSettingsState.AppSettings(
|
||||
systemNotificationsEnabled = systemNotificationsEnabled.value,
|
||||
appNotificationsEnabled = appNotificationsEnabled.value
|
||||
),
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun CoroutineScope.observeNotificationSettings(target: MutableState<NotificationSettingsState.MatrixSettings>) {
|
||||
notificationSettingsService.notificationSettingsChangeFlow
|
||||
.debounce(0.5.seconds)
|
||||
.onEach {
|
||||
fetchSettings(target)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.fetchSettings(target: MutableState<NotificationSettingsState.MatrixSettings>) = launch {
|
||||
val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow()
|
||||
val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow()
|
||||
|
||||
val oneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = true).getOrThrow()
|
||||
val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow()
|
||||
|
||||
if(groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) {
|
||||
target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val callNotificationsEnabled = notificationSettingsService.isCallEnabled().getOrThrow()
|
||||
val atRoomNotificationsEnabled = notificationSettingsService.isRoomMentionEnabled().getOrThrow()
|
||||
|
||||
target.value = NotificationSettingsState.MatrixSettings.Valid(
|
||||
atRoomNotificationsEnabled = atRoomNotificationsEnabled,
|
||||
callNotificationsEnabled = callNotificationsEnabled,
|
||||
defaultGroupNotificationMode = encryptedGroupDefaultMode,
|
||||
defaultOneToOneNotificationMode = encryptedOneToOneDefaultMode,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.fixConfigurationMismatch(target: MutableState<NotificationSettingsState.MatrixSettings>) = launch {
|
||||
runCatching {
|
||||
val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow()
|
||||
val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow()
|
||||
|
||||
if (groupDefaultMode != encryptedGroupDefaultMode) {
|
||||
notificationSettingsService.setDefaultRoomNotificationMode(
|
||||
isEncrypted = encryptedGroupDefaultMode != RoomNotificationMode.ALL_MESSAGES,
|
||||
mode = RoomNotificationMode.ALL_MESSAGES,
|
||||
isOneToOne = false,
|
||||
)
|
||||
}
|
||||
|
||||
val oneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = true).getOrThrow()
|
||||
val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow()
|
||||
|
||||
if (oneToOneDefaultMode != encryptedOneToOneDefaultMode) {
|
||||
notificationSettingsService.setDefaultRoomNotificationMode(
|
||||
isEncrypted = encryptedOneToOneDefaultMode != RoomNotificationMode.ALL_MESSAGES,
|
||||
mode = RoomNotificationMode.ALL_MESSAGES,
|
||||
isOneToOne = true,
|
||||
)
|
||||
}
|
||||
}.fold(
|
||||
onSuccess = {},
|
||||
onFailure = {
|
||||
target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean) = launch {
|
||||
notificationSettingsService.setRoomMentionEnabled(enabled)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean) = launch {
|
||||
notificationSettingsService.setCallEnabled(enabled)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch {
|
||||
userPushStore.setNotificationEnabledForDevice(enabled)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
||||
@Immutable
|
||||
data class NotificationSettingsState(
|
||||
val matrixSettings: MatrixSettings,
|
||||
val appSettings: AppSettings,
|
||||
val eventSink: (NotificationSettingsEvents) -> Unit,
|
||||
) {
|
||||
sealed interface MatrixSettings {
|
||||
data object Uninitialized : MatrixSettings
|
||||
data class Valid(
|
||||
val atRoomNotificationsEnabled: Boolean,
|
||||
val callNotificationsEnabled: Boolean,
|
||||
val defaultGroupNotificationMode: RoomNotificationMode?,
|
||||
val defaultOneToOneNotificationMode: RoomNotificationMode?,
|
||||
) : MatrixSettings
|
||||
|
||||
data class Invalid(
|
||||
val fixFailed: Boolean
|
||||
) : MatrixSettings
|
||||
}
|
||||
|
||||
data class AppSettings(
|
||||
val systemNotificationsEnabled: Boolean,
|
||||
val appNotificationsEnabled: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
||||
open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> {
|
||||
override val values: Sequence<NotificationSettingsState>
|
||||
get() = sequenceOf(
|
||||
aNotificationSettingsState(),
|
||||
)
|
||||
}
|
||||
|
||||
fun aNotificationSettingsState() = NotificationSettingsState(
|
||||
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
|
||||
atRoomNotificationsEnabled = true,
|
||||
callNotificationsEnabled = true,
|
||||
defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||
defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||
),
|
||||
appSettings = NotificationSettingsState.AppSettings(
|
||||
systemNotificationsEnabled = false,
|
||||
appNotificationsEnabled = true,
|
||||
),
|
||||
eventSink = {}
|
||||
)
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.NotificationsOff
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceView
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
/**
|
||||
* A view that allows a user edit their global notification settings.
|
||||
*/
|
||||
@Composable
|
||||
fun NotificationSettingsView(
|
||||
state: NotificationSettingsState,
|
||||
onOpenEditDefault: (isOneToOne: Boolean) -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
OnLifecycleEvent { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> state.eventSink.invoke(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
PreferenceView(
|
||||
modifier = modifier,
|
||||
onBackPressed = onBackPressed,
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_title)
|
||||
) {
|
||||
|
||||
when (state.matrixSettings) {
|
||||
is NotificationSettingsState.MatrixSettings.Invalid -> InvalidNotificationSettingsView(
|
||||
showError = state.matrixSettings.fixFailed,
|
||||
onContinueClicked = { state.eventSink(NotificationSettingsEvents.FixConfigurationMismatch) },
|
||||
onDismissError = { state.eventSink(NotificationSettingsEvents.ClearConfigurationMismatchError) },
|
||||
)
|
||||
NotificationSettingsState.MatrixSettings.Uninitialized -> return@PreferenceView
|
||||
is NotificationSettingsState.MatrixSettings.Valid -> NotificationSettingsContentView(
|
||||
matrixSettings = state.matrixSettings,
|
||||
systemSettings = state.appSettings,
|
||||
onNotificationsEnabledChanged = { state.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(it))},
|
||||
onGroupChatsClicked = { onOpenEditDefault(false) },
|
||||
onDirectChatsClicked = { onOpenEditDefault(true) },
|
||||
onMentionNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) },
|
||||
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationSettingsContentView(
|
||||
matrixSettings: NotificationSettingsState.MatrixSettings.Valid,
|
||||
systemSettings: NotificationSettingsState.AppSettings,
|
||||
onNotificationsEnabledChanged: (Boolean) -> Unit,
|
||||
onGroupChatsClicked: () -> Unit,
|
||||
onDirectChatsClicked: () -> Unit,
|
||||
onMentionNotificationsChanged: (Boolean) -> Unit,
|
||||
// onCallsNotificationsChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) {
|
||||
PreferenceText(
|
||||
icon = Icons.Filled.NotificationsOff,
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_system_notifications_turned_off),
|
||||
subtitle = stringResource(id = CommonStrings.screen_notification_settings_system_notifications_action_required,
|
||||
stringResource(id = CommonStrings.screen_notification_settings_system_notifications_action_required_content_link)),
|
||||
onClick = {
|
||||
context.startNotificationSettingsIntent()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceSwitch(
|
||||
modifier = modifier,
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_enable_notifications),
|
||||
isChecked = systemSettings.appNotificationsEnabled,
|
||||
switchAlignment = Alignment.Top,
|
||||
onCheckedChange = onNotificationsEnabledChanged
|
||||
)
|
||||
|
||||
if (systemSettings.appNotificationsEnabled) {
|
||||
PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_notification_section_title)) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_group_chats),
|
||||
subtitle = getTitleForRoomNotificationMode(mode = matrixSettings.defaultGroupNotificationMode),
|
||||
onClick = onGroupChatsClicked
|
||||
)
|
||||
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_direct_chats),
|
||||
subtitle = getTitleForRoomNotificationMode(mode = matrixSettings.defaultOneToOneNotificationMode),
|
||||
onClick = onDirectChatsClicked
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_mode_mentions)) {
|
||||
PreferenceSwitch(
|
||||
modifier = Modifier,
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_room_mention_label),
|
||||
isChecked = matrixSettings.atRoomNotificationsEnabled,
|
||||
switchAlignment = Alignment.Top,
|
||||
onCheckedChange = onMentionNotificationsChanged
|
||||
)
|
||||
}
|
||||
// We are removing the call notification toggle until call support has been added
|
||||
// PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_additional_settings_section_title)) {
|
||||
// PreferenceSwitch(
|
||||
// modifier = Modifier,
|
||||
// title = stringResource(id = CommonStrings.screen_notification_settings_calls_label),
|
||||
// isChecked = matrixSettings.callNotificationsEnabled,
|
||||
// switchAlignment = Alignment.Top,
|
||||
// onCheckedChange = onCallsNotificationsChanged
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getTitleForRoomNotificationMode(mode: RoomNotificationMode?) =
|
||||
when(mode) {
|
||||
RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages)
|
||||
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords)
|
||||
RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute)
|
||||
null -> ""
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvalidNotificationSettingsView(
|
||||
showError: Boolean,
|
||||
onContinueClicked: () -> Unit,
|
||||
onDismissError: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
Surface(
|
||||
Modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
stringResource(CommonStrings.screen_notification_settings_configuration_mismatch),
|
||||
modifier = Modifier.weight(1f),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
stringResource(CommonStrings.screen_notification_settings_configuration_mismatch_description),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_continue),
|
||||
size = ButtonSize.Medium,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onContinueClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if(showError) {
|
||||
ErrorDialog(
|
||||
title = stringResource(id = CommonStrings.dialog_title_error),
|
||||
content = stringResource(id = CommonStrings.screen_notification_settings_failed_fixing_configuration),
|
||||
onDismiss = onDismissError
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun NotificationSettingsViewLightPreview(@PreviewParameter(NotificationSettingsStateProvider::class) state: NotificationSettingsState) =
|
||||
ElementPreviewLight { ContentToPreview(state) }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun NotificationSettingsViewDarkPreview(@PreviewParameter(NotificationSettingsStateProvider::class) state: NotificationSettingsState) =
|
||||
ElementPreviewDark { ContentToPreview(state) }
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview(state: NotificationSettingsState) {
|
||||
NotificationSettingsView(
|
||||
state = state,
|
||||
onBackPressed = {},
|
||||
onOpenEditDefault = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun InvalidNotificationSettingsViewightPreview() =
|
||||
ElementPreviewLight { InvalidNotificationSettingsContentToPreview() }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun InvalidNotificationSettingsViewDarkPreview() =
|
||||
ElementPreviewDark { InvalidNotificationSettingsContentToPreview() }
|
||||
|
||||
@Composable
|
||||
private fun InvalidNotificationSettingsContentToPreview() {
|
||||
InvalidNotificationSettingsView(
|
||||
showError = false,
|
||||
onContinueClicked = {},
|
||||
onDismissError = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import javax.inject.Inject
|
||||
|
||||
interface SystemNotificationsEnabledProvider {
|
||||
fun notificationsEnabled(): Boolean
|
||||
}
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class, boundType = SystemNotificationsEnabledProvider::class)
|
||||
class DefaultSystemNotificationsEnabledProvider @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
): SystemNotificationsEnabledProvider {
|
||||
override fun notificationsEnabled(): Boolean {
|
||||
return NotificationManagerCompat.from(context).areNotificationsEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications.edit
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.RadioButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun DefaultNotificationSettingOption(
|
||||
mode: RoomNotificationMode,
|
||||
modifier: Modifier = Modifier,
|
||||
isSelected: Boolean = false,
|
||||
onOptionSelected: (RoomNotificationMode) -> Unit = {},
|
||||
) {
|
||||
val subtitle = when(mode) {
|
||||
RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages)
|
||||
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords)
|
||||
else -> ""
|
||||
}
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { onOptionSelected(mode) },
|
||||
role = Role.RadioButton,
|
||||
)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 8.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
}
|
||||
|
||||
RadioButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.size(48.dp),
|
||||
selected = isSelected,
|
||||
onClick = null // null recommended for accessibility with screenreaders
|
||||
)
|
||||
}
|
||||
}
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun DefaultNotificationSettingOptionPreview() = ElementPreview { ContentToPreview() }
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
Column {
|
||||
DefaultNotificationSettingOption(
|
||||
mode = RoomNotificationMode.ALL_MESSAGES,
|
||||
isSelected = true,
|
||||
)
|
||||
DefaultNotificationSettingOption(
|
||||
mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||
isSelected = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications.edit
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class EditDefaultNotificationSettingNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: EditDefaultNotificationSettingPresenter.Factory
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
data class Inputs(
|
||||
val isOneToOne: Boolean
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs = inputs<Inputs>()
|
||||
private val presenter = presenterFactory.create(inputs.isOneToOne)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
EditDefaultNotificationSettingView(
|
||||
state = state,
|
||||
onBackPressed = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications.edit
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||
private val notificationSettingsService: NotificationSettingsService,
|
||||
@Assisted private val isOneToOne: Boolean,
|
||||
) : Presenter<EditDefaultNotificationSettingState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(oneToOne: Boolean): EditDefaultNotificationSettingPresenter
|
||||
}
|
||||
@Composable
|
||||
override fun present(): EditDefaultNotificationSettingState {
|
||||
|
||||
val mode: MutableState<RoomNotificationMode?> = remember {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
LaunchedEffect(Unit) {
|
||||
fetchSettings(mode)
|
||||
observeNotificationSettings(mode)
|
||||
}
|
||||
|
||||
fun handleEvents(event: EditDefaultNotificationSettingStateEvents) {
|
||||
when (event) {
|
||||
is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> localCoroutineScope.setDefaultNotificationMode(event.mode)
|
||||
}
|
||||
}
|
||||
|
||||
return EditDefaultNotificationSettingState(
|
||||
isOneToOne = isOneToOne,
|
||||
mode = mode.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.fetchSettings(mode: MutableState<RoomNotificationMode?>) = launch {
|
||||
mode.value = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = isOneToOne).getOrThrow()
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun CoroutineScope.observeNotificationSettings(mode: MutableState<RoomNotificationMode?>) {
|
||||
notificationSettingsService.notificationSettingsChangeFlow
|
||||
.debounce(0.5.seconds)
|
||||
.onEach {
|
||||
fetchSettings(mode)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode) = launch {
|
||||
// On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did).
|
||||
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne)
|
||||
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications.edit
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
||||
data class EditDefaultNotificationSettingState(
|
||||
val isOneToOne: Boolean,
|
||||
val mode: RoomNotificationMode?,
|
||||
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 io.element.android.features.preferences.impl.notifications.edit
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
||||
sealed interface EditDefaultNotificationSettingStateEvents {
|
||||
data class SetNotificationMode(val mode: RoomNotificationMode): EditDefaultNotificationSettingStateEvents
|
||||
}
|
||||
@@ -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 io.element.android.features.preferences.impl.notifications.edit
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceView
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
/**
|
||||
* A view that allows a user to edit the default notification setting for rooms. This can be set separately
|
||||
* for one-to-one and group rooms, indicated by [EditDefaultNotificationSettingState.isOneToOne].
|
||||
*/
|
||||
@Composable
|
||||
fun EditDefaultNotificationSettingView(
|
||||
state: EditDefaultNotificationSettingState,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
val title = if(state.isOneToOne) {
|
||||
CommonStrings.screen_notification_settings_direct_chats
|
||||
} else {
|
||||
CommonStrings.screen_notification_settings_group_chats
|
||||
}
|
||||
PreferenceView(
|
||||
modifier = modifier,
|
||||
onBackPressed = onBackPressed,
|
||||
title = stringResource(id = title)
|
||||
) {
|
||||
|
||||
// Only ALL_MESSAGES and MENTIONS_AND_KEYWORDS_ONLY are valid global defaults.
|
||||
val validModes = listOf(RoomNotificationMode.ALL_MESSAGES, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||
|
||||
val categoryTitle = if(state.isOneToOne) {
|
||||
CommonStrings.screen_notification_settings_edit_screen_direct_section_header
|
||||
} else {
|
||||
CommonStrings.screen_notification_settings_edit_screen_group_section_header
|
||||
}
|
||||
PreferenceCategory(title = stringResource(id = categoryTitle)) {
|
||||
|
||||
if (state.mode != null) {
|
||||
Column(modifier = Modifier.selectableGroup()) {
|
||||
validModes.forEach { item ->
|
||||
DefaultNotificationSettingOption(
|
||||
mode = item,
|
||||
isSelected = state.mode == item,
|
||||
onOptionSelected = { state.eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ class PreferencesRootNode @AssistedInject constructor(
|
||||
fun onOpenAnalytics()
|
||||
fun onOpenAbout()
|
||||
fun onOpenDeveloperSettings()
|
||||
fun onOpenNotificationSettings()
|
||||
}
|
||||
|
||||
private fun onOpenBugReport() {
|
||||
@@ -72,6 +73,10 @@ class PreferencesRootNode @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onOpenNotificationSettings() {
|
||||
plugins<Callback>().forEach { it.onOpenNotificationSettings() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
@@ -87,6 +92,7 @@ class PreferencesRootNode @AssistedInject constructor(
|
||||
onOpenDeveloperSettings = this::onOpenDeveloperSettings,
|
||||
onSuccessLogout = { onSuccessLogout(activity, it) },
|
||||
onManageAccountClicked = { onManageAccountClicked(activity, state.accountManagementUrl) },
|
||||
onOpenNotificationSettings = this::onOpenNotificationSettings
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
@@ -46,6 +48,7 @@ class PreferencesRootPresenter @Inject constructor(
|
||||
private val buildType: BuildType,
|
||||
private val versionFormatter: VersionFormatter,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : Presenter<PreferencesRootState> {
|
||||
|
||||
@Composable
|
||||
@@ -60,6 +63,11 @@ class PreferencesRootPresenter @Inject constructor(
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() }
|
||||
|
||||
val showNotificationSettings = remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
showNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
|
||||
}
|
||||
|
||||
// We should display the 'complete verification' option if the current session can be verified
|
||||
val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false)
|
||||
|
||||
@@ -81,6 +89,7 @@ class PreferencesRootPresenter @Inject constructor(
|
||||
accountManagementUrl = accountManagementUrl.value,
|
||||
showAnalyticsSettings = hasAnalyticsProviders,
|
||||
showDeveloperSettings = showDeveloperSettings,
|
||||
showNotificationSettings = showNotificationSettings.value,
|
||||
snackbarMessage = snackbarMessage,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@ data class PreferencesRootState(
|
||||
val accountManagementUrl: String?,
|
||||
val showAnalyticsSettings: Boolean,
|
||||
val showDeveloperSettings: Boolean,
|
||||
val showNotificationSettings: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
)
|
||||
|
||||
@@ -28,5 +28,6 @@ fun aPreferencesRootState() = PreferencesRootState(
|
||||
accountManagementUrl = "aUrl",
|
||||
showAnalyticsSettings = true,
|
||||
showDeveloperSettings = true,
|
||||
showNotificationSettings = true,
|
||||
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.material.icons.outlined.DeveloperMode
|
||||
import androidx.compose.material.icons.outlined.Help
|
||||
import androidx.compose.material.icons.outlined.InsertChart
|
||||
import androidx.compose.material.icons.outlined.ManageAccounts
|
||||
import androidx.compose.material.icons.outlined.Notifications
|
||||
import androidx.compose.material.icons.outlined.VerifiedUser
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -58,6 +59,7 @@ fun PreferencesRootView(
|
||||
onOpenAbout: () -> Unit,
|
||||
onOpenDeveloperSettings: () -> Unit,
|
||||
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
|
||||
onOpenNotificationSettings: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
@@ -92,6 +94,13 @@ fun PreferencesRootView(
|
||||
onClick = onOpenAnalytics,
|
||||
)
|
||||
}
|
||||
if(state.showNotificationSettings) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.screen_notification_settings_title),
|
||||
icon = Icons.Outlined.Notifications,
|
||||
onClick = onOpenNotificationSettings,
|
||||
)
|
||||
}
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.action_report_bug),
|
||||
icon = Icons.Outlined.BugReport,
|
||||
@@ -153,5 +162,6 @@ private fun ContentToPreview(matrixUser: MatrixUser) {
|
||||
onVerifyClicked = {},
|
||||
onSuccessLogout = {},
|
||||
onManageAccountClicked = {},
|
||||
onOpenNotificationSettings = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ package io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||
import io.element.android.libraries.androidutils.file.getSizeOfFiles
|
||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@@ -27,8 +27,7 @@ import org.junit.Test
|
||||
|
||||
class AboutPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -30,8 +30,7 @@ import org.junit.Test
|
||||
|
||||
class AnalyticsSettingsPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.junit.Test
|
||||
|
||||
class DeveloperSettingsPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user