From 4964c3e372bcdfb827f3b2d00b600944e8616a35 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 21 Sep 2023 23:11:36 +0200 Subject: [PATCH 01/32] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index a609c9262d..f34cdfbdc0 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 2 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 2 +private const val versionPatch = 3 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From ea0103dccf5d680978d426cce33be99a7d00fe1a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:42:07 +0000 Subject: [PATCH 02/32] Update danger/danger-js action to v11.3.0 --- .github/workflows/danger.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index f05db3c791..772df369b8 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.2.8 + uses: danger/danger-js@11.3.0 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 417acc9341..34fc00ca1a 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -60,7 +60,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.2.8 + uses: danger/danger-js@11.3.0 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: From 47b6dc989b5fb0ba7259a3d97b656176c0d5c2ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 23 Sep 2023 19:34:48 +0000 Subject: [PATCH 03/32] Update dependency me.saket.telephoto:zoomable-image-coil to v0.6.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f648c1773..39565df9a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ dependencycheck = "8.4.0" dependencyanalysis = "1.22.0" stem = "2.3.0" sqldelight = "1.5.5" -telephoto = "0.6.1" +telephoto = "0.6.2" wysiwyg = "2.12.0" # DI From c72bb92ef29405c6653baa84bf87ed458bb380ef Mon Sep 17 00:00:00 2001 From: bmarty Date: Mon, 25 Sep 2023 00:09:17 +0000 Subject: [PATCH 04/32] Sync Strings from Localazy --- .../main/res/values-zh-rTW/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 6 ++++ .../main/res/values-zh-rTW/translations.xml | 5 +++ .../src/main/res/values-cs/translations.xml | 2 ++ .../src/main/res/values-ru/translations.xml | 4 +-- .../src/main/res/values-cs/translations.xml | 1 + .../main/res/values-zh-rTW/translations.xml | 6 +++- .../src/main/res/values-ru/translations.xml | 2 +- .../src/main/res/values-cs/translations.xml | 2 ++ .../main/res/values-zh-rTW/translations.xml | 12 +++++++ .../src/main/res/values-cs/translations.xml | 9 ++++++ .../main/res/values-zh-rTW/translations.xml | 8 +++++ .../main/res/values-zh-rTW/translations.xml | 1 + .../main/res/values-zh-rTW/translations.xml | 3 +- .../main/res/values-zh-rTW/translations.xml | 2 ++ .../main/res/values-zh-rTW/translations.xml | 14 +++++++-- .../src/main/res/values-cs/translations.xml | 7 +++++ .../src/main/res/values-de/translations.xml | 7 +++++ .../src/main/res/values-fr/translations.xml | 8 ++--- .../src/main/res/values-ru/translations.xml | 7 +++++ .../src/main/res/values-cs/translations.xml | 6 ++++ .../main/res/values-zh-rTW/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 17 ++++++++++ .../src/main/res/values-de/translations.xml | 3 ++ .../src/main/res/values-ru/translations.xml | 6 +++- .../main/res/values-zh-rTW/translations.xml | 31 ++++++++++++++++--- 26 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 features/call/src/main/res/values-cs/translations.xml create mode 100644 features/call/src/main/res/values-zh-rTW/translations.xml create mode 100644 features/poll/impl/src/main/res/values-zh-rTW/translations.xml create mode 100644 features/preferences/impl/src/main/res/values-cs/translations.xml create mode 100644 features/preferences/impl/src/main/res/values-zh-rTW/translations.xml create mode 100644 libraries/permissions/api/src/main/res/values-cs/translations.xml create mode 100644 libraries/permissions/api/src/main/res/values-de/translations.xml create mode 100644 libraries/permissions/api/src/main/res/values-ru/translations.xml diff --git a/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml b/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml index 81349674b7..996e5f5f3b 100644 --- a/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/analytics/impl/src/main/res/values-zh-rTW/translations.xml @@ -2,7 +2,7 @@ "我們不會紀錄或剖繪您的個人資料" "分享匿名的使用數據以協助我們釐清問題" - "您可以到 %1$s 閱讀我們的條款。" + "您可以到%1$s閱讀我們的條款。" "這裡" "您可以在任何時候關閉它" "我們不會和第三方分享您的資料" diff --git a/features/call/src/main/res/values-cs/translations.xml b/features/call/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..dfe6327828 --- /dev/null +++ b/features/call/src/main/res/values-cs/translations.xml @@ -0,0 +1,6 @@ + + + "Probíhající hovor" + "Klepněte pro návrat k hovoru" + "☎️ Probíhá hovor" + diff --git a/features/call/src/main/res/values-zh-rTW/translations.xml b/features/call/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..9a6be228ce --- /dev/null +++ b/features/call/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,5 @@ + + + "點擊以返回到通話頁面" + "☎️ 通話中" + diff --git a/features/ftue/impl/src/main/res/values-cs/translations.xml b/features/ftue/impl/src/main/res/values-cs/translations.xml index 724f3793cd..9363c704ad 100644 --- a/features/ftue/impl/src/main/res/values-cs/translations.xml +++ b/features/ftue/impl/src/main/res/values-cs/translations.xml @@ -2,6 +2,8 @@ "Jedná se o jednorázový proces, prosíme o strpení." "Nastavení vašeho účtu" + "Nastavení můžete později změnit." + "Povolte oznámení a nezmeškejte žádnou zprávu" "Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku." "Historie zpráv šifrovaných místností nebude v této aktualizaci k dispozici." "Rádi bychom se od vás dozvěděli, co si o tom myslíte, dejte nám vědět prostřednictvím stránky s nastavením." diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml index 3ecb8a52d0..641db1e0d4 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -14,9 +14,9 @@ "Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись." "Сменить поставщика учетной записи" "Нам не удалось связаться с этим домашним сервером. Убедитесь, что вы правильно ввели URL-адрес домашнего сервера. Если URL-адрес указан правильно, обратитесь к администратору домашнего сервера за дополнительной помощью." - "В настоящее время этот сервер не поддерживает скользящую синхронизацию." + "К сожалению данный сервер не поддерживает sliding sync." "URL-адрес домашнего сервера" - "Вы можете подключиться только к существующему серверу, поддерживающему скользящую синхронизацию. Администратору домашнего сервера потребуется настроить его. %1$s" + "Вы можете подключиться только к существующему серверу, поддерживающему sliding sync. Администратору домашнего сервера потребуется настроить его. %1$s" "Какой адрес у вашего сервера?" "Данная учетная запись была деактивирована." "Неверное имя пользователя и/или пароль" diff --git a/features/messages/impl/src/main/res/values-cs/translations.xml b/features/messages/impl/src/main/res/values-cs/translations.xml index 1543101f6d..906ef6b987 100644 --- a/features/messages/impl/src/main/res/values-cs/translations.xml +++ b/features/messages/impl/src/main/res/values-cs/translations.xml @@ -12,6 +12,7 @@ "Knihovna fotografií a videí" "Poloha" "Hlasování" + "Formátování textu" "Historie zpráv je momentálně v této místnosti nedostupná" "Nepodařilo se načíst údaje o uživateli" "Chtěli byste je pozvat zpět?" diff --git a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml index ac4725896e..1038508ac6 100644 --- a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml @@ -9,6 +9,7 @@ "附件" "位置" "投票" + "格式化文字" "此聊天室只有您一個人" "訊息已複製" "您沒有權限在此聊天室傳送訊息" @@ -17,8 +18,11 @@ "無法重設為預設模式,請再試一次。" "無法設定模式,請再試一次。" "所有訊息" - "只限提及與關鍵字" + "僅限提及與關鍵字" + "較少" + "更多" "重傳" "無法傳送您的訊息" + "較少" "移除" diff --git a/features/onboarding/impl/src/main/res/values-ru/translations.xml b/features/onboarding/impl/src/main/res/values-ru/translations.xml index 5c8c12c2b0..21bc36c78a 100644 --- a/features/onboarding/impl/src/main/res/values-ru/translations.xml +++ b/features/onboarding/impl/src/main/res/values-ru/translations.xml @@ -6,5 +6,5 @@ "Безопасное общение и совместная работа" "Добро пожаловать в самый быстрый Element. Преимущество в скорости и простоте." "Добро пожаловать в %1$s. Supercharged — это скорость и простота." - "Будь в своей стихии" + "Будь c element" diff --git a/features/poll/impl/src/main/res/values-cs/translations.xml b/features/poll/impl/src/main/res/values-cs/translations.xml index d199df993f..fa722e9616 100644 --- a/features/poll/impl/src/main/res/values-cs/translations.xml +++ b/features/poll/impl/src/main/res/values-cs/translations.xml @@ -4,6 +4,8 @@ "Zobrazit výsledky až po skončení hlasování" "Anonymní hlasování" "Volba %1$d" + "Opravdu chcete zrušit toto hlasování?" + "Zrušit hlasování" "Otázka nebo téma" "Čeho se hlasování týká?" "Vytvořit hlasování" diff --git a/features/poll/impl/src/main/res/values-zh-rTW/translations.xml b/features/poll/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..298224c8ad --- /dev/null +++ b/features/poll/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,12 @@ + + + "新增選項" + "只在投票結束後顯示結果" + "隱藏票數" + "選項 %1$d" + "您確定要捨棄這項投票嗎?" + "捨棄投票" + "問題或主題" + "投什麼?" + "建立投票" + diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..26b082e386 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,9 @@ + + + "Zobrazované jméno" + "Vaše zobrazované jméno" + "Došlo k neznámé chybě a informace nelze změnit." + "Nelze aktualizovat profil" + "Upravit profil" + "Aktualizace profilu…" + diff --git a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..f1cb358027 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,8 @@ + + + "顯示名稱" + "您的顯示名稱" + "無法更新個人檔案" + "編輯個人檔案" + "正在更新個人檔案…" + diff --git a/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml b/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml index 6e9eaabed3..bd4d1cb665 100644 --- a/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,6 +1,7 @@ "附上螢幕截圖" + "如果有其他問題,你可以聯絡我。" "聯絡我" "編輯螢幕截圖" "傳送螢幕截圖" diff --git a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml index 7b4eb895ea..14db6a6d91 100644 --- a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml @@ -23,12 +23,13 @@ "無法重設為預設模式,請再試一次。" "無法設定模式,請再試一次。" "所有訊息" - "只限提及與關鍵字" + "僅限提及與關鍵字" "封鎖" "封鎖使用者" "解除封鎖" "解除封鎖使用者" "離開聊天室" "夥伴" + "安全性" "主題" diff --git a/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml index 795a4e46d0..4ad446a0bf 100644 --- a/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomlist/impl/src/main/res/values-zh-rTW/translations.xml @@ -2,4 +2,6 @@ "建立新的對話或聊天室" "所有聊天室" + "您似乎正在使用新的裝置。請使用另一個裝置進行驗證,以存取您的加密訊息。" + "驗證這是您本人" diff --git a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml index fc59911a93..8b054ac408 100644 --- a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,9 +1,19 @@ + "似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。" + "確認顯示在其他工作階段上的表情符號是否和下方的相同。" + "比對表情符號" + "新的工作階段已完成驗證。它能夠存取您的加密訊息,而其他使用者會將它視為可信任的。" + "為了存取被加密的歷史訊息,請證明這是您本人。" + "開啟一個現存的工作階段" + "重新嘗試驗證" "我準備好了" "等待比對" - "不相符" - "相符" + "表情符號是唯一的,請相互比對,確認它們的排列順序是否相同。" + "不一樣" + "一樣" + "準備開始驗證,請到您的其他工作階段接受請求。" + "等待接受請求" "驗證已取消" "開始" diff --git a/libraries/permissions/api/src/main/res/values-cs/translations.xml b/libraries/permissions/api/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..967924280c --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-cs/translations.xml @@ -0,0 +1,7 @@ + + + "Aby mohla aplikace používat fotoaparát, udělte prosím oprávnění v nastavení systému." + "Udělte prosím oprávnění v nastavení systému." + "Aby aplikace mohla používat mikrofon, udělte prosím oprávnění v nastavení systému." + "Aby aplikace mohla zobrazovat upozornění, udělte prosím oprávnění v nastavení systému." + diff --git a/libraries/permissions/api/src/main/res/values-de/translations.xml b/libraries/permissions/api/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..5a16d89b4f --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ + + + "Damit die Anwendung die Kamera verwenden kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen." + "Bitte erteilen Sie die Erlaubnis in den Systemeinstellungen." + "Damit die Anwendung das Mikrofon verwenden kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen." + "Damit die Anwendung Benachrichtigungen anzeigen kann, erteilen Sie bitte die Erlaubnis in den Systemeinstellungen." + diff --git a/libraries/permissions/api/src/main/res/values-fr/translations.xml b/libraries/permissions/api/src/main/res/values-fr/translations.xml index 104425aca9..e0fac38135 100644 --- a/libraries/permissions/api/src/main/res/values-fr/translations.xml +++ b/libraries/permissions/api/src/main/res/values-fr/translations.xml @@ -1,7 +1,7 @@ - "Pour permettre à l\'application d\'utiliser l\'appareil photo, veuillez accorder l\'autorisation dans les paramètres du système." - "Veuillez accorder l\'autorisation dans les paramètres du système." - "Pour permettre à l\'application d\'utiliser le microphone, veuillez accorder l\'autorisation dans les paramètres du système." - "Pour permettre à l\'application d\'afficher les notifications, veuillez accorder l\'autorisation dans les paramètres du système." + "Pour permettre à l’application d’utiliser l’appareil photo, veuillez accorder l’autorisation dans les paramètres du système." + "Veuillez accorder l’autorisation dans les paramètres du système." + "Pour permettre à l\'application d’utiliser le microphone, veuillez accorder l’autorisation dans les paramètres du système." + "Pour permettre à l’application d’afficher les notifications, veuillez accorder l’autorisation dans les paramètres du système." diff --git a/libraries/permissions/api/src/main/res/values-ru/translations.xml b/libraries/permissions/api/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..5c7a1b25f1 --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-ru/translations.xml @@ -0,0 +1,7 @@ + + + "Чтобы приложение могло использовать камеру, предоставьте разрешение в системных настройках." + "Пожалуйста, предоставьте разрешение в системных настройках." + "Чтобы приложение могло использовать микрофон, предоставьте разрешение в системных настройках." + "Чтобы приложение отображало уведомления, предоставьте разрешение в системных настройках." + diff --git a/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml b/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml index cae45c8cd2..ea3bda3bae 100644 --- a/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-cs/translations.xml @@ -1,8 +1,11 @@ "Přepnout seznam s odrážkami" + "Zavřít možnosti formátování" "Přepnout blok kódu" "Zpráva…" + "Vytvořit odkaz" + "Upravit odkaz" "Použít tučný text" "Použít kurzívu" "Použít přeškrtnutí" @@ -12,7 +15,10 @@ "Použít formát inline kódu" "Nastavit odkaz" "Přepnout číslovaný seznam" + "Otevřít možnosti psaní" "Přepnout citaci" + "Odstranit odkaz" "Zrušit odsazení" + "Odkaz" "Přidat přílohu" diff --git a/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml b/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml index 4342c3478e..c299975f82 100644 --- a/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-zh-rTW/translations.xml @@ -14,6 +14,7 @@ "設定連結" "切換數字編號" "切換引用" + "移除連結" "減少縮排" "連結" "新增附件" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 683a1e6abd..75a35d3190 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -1,10 +1,15 @@ "Skrýt heslo" + "Pouze zmínky" + "Ztišeno" + "Hlasování" + "Hlasování ukončeno" "Odeslat soubory" "Zobrazit heslo" "Uživatelské menu" "Přijmout" + "Přidat na časovou osu" "Zpět" "Zrušit" "Vybrat fotku" @@ -34,16 +39,20 @@ "Zjistit více" "Odejít" "Opustit místnost" + "Spravovat účet" + "Spravovat zařízení" "Další" "Ne" "Teď ne" "OK" + "Otevřít nastavení" "Otevřít v aplikaci" "Rychlá odpověď" "Citovat" "Reagovat" "Odstranit" "Odpovědět" + "Odpovědět ve vlákně" "Nahlásit chybu" "Nahlásit obsah" "Zkusit znovu" @@ -64,6 +73,7 @@ "Ano" "O aplikaci" "Zásady používání" + "Pokročilá nastavení" "Analytika" "Zvuk" "Bubliny" @@ -82,6 +92,7 @@ "Přeposlat zprávu" "GIF" "Obrázek" + "V odpovědi na %1$s" "Tento Matrix identifikátor nelze najít, takže pozvánka nemusí být přijata." "Opuštění místnosti" "Odkaz zkopírován do schránky" @@ -96,14 +107,17 @@ "Heslo" "Lidé" "Trvalý odkaz" + "Oprávnění" "Celkový počet hlasů: %1$s" "Výsledky se zobrazí po skončení hlasování" "Zásady ochrany osobních údajů" + "Reakce" "Reakce" "Obnovování…" "Odpověď na %1$s" "Nahlásit chybu" "Zpráva odeslána" + "Editor formátovaného textu" "Název místnosti" "např. název vašeho projektu" "Hledat někoho" @@ -120,7 +134,9 @@ "Úspěch" "Návrhy" "Synchronizace" + "Text" "Oznámení třetích stran" + "Vlákno" "Téma" "O čem je tato místnost?" "Nelze dešifrovat" @@ -133,6 +149,7 @@ "Ověření dokončeno" "Video" "Čekání…" + "Hlasování: %1$s" "Potvrzení" "Upozornění" "Aktivity" diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 39a495c33f..afdcc0e79c 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -45,6 +45,7 @@ "Nein" "Nicht jetzt" "OK" + "Einstellungen öffnen" "Öffnen mit" "Schnelle Antwort" "Zitat" @@ -106,6 +107,7 @@ "Passwort" "Personen" "Permalink" + "Erlaubnis" "Stimmen insgesamt: %1$s" "Die Ergebnisse werden nach Ende der Umfrage angezeigt" "Datenschutz­erklärung" @@ -147,6 +149,7 @@ "Verifizierung abgeschlossen" "Video" "Warten…" + "Umfrage: %1$s" "Bestätigung" "Warnung" "Aktivitäten" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index a3b6a264df..ab7cdc7e85 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -9,6 +9,7 @@ "Показать пароль" "Меню пользователя" "Разрешить" + "Добавить в хронологию" "Назад" "Отмена" "Выбрать фото" @@ -44,6 +45,7 @@ "Нет" "Не сейчас" "Ок" + "Открыть настройки" "Открыть с помощью" "Быстрый ответ" "Цитата" @@ -105,6 +107,7 @@ "Пароль" "Пользователи" "Постоянная ссылка" + "Разрешение" "Всего голосов: %1$s" "Результаты будут показаны после завершения опроса" "Политика конфиденциальности" @@ -146,6 +149,7 @@ "Проверка завершена" "Видео" "Ожидание…" + "Опрос: %1$s" "Подтверждение" "Предупреждение" "Деятельность" @@ -223,7 +227,7 @@ "Поделиться местоположением" "Поделиться моим местоположением" "Открыть в Apple Maps" - "Открыть в Google Картах" + "Открыть в Google Maps" "Открыть в OpenStreetMap" "Поделиться этим местоположением" "Местоположение" diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index 0ace8beb22..2059c6f138 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -1,6 +1,10 @@ "隱藏密碼" + "僅限提及" + "已關閉通知" + "投票" + "投票已結束" "傳送檔案" "顯示密碼" "使用者選單" @@ -21,6 +25,7 @@ "完成" "編輯" "啟用" + "結束投票" "忘記密碼?" "轉寄" "邀請" @@ -33,16 +38,19 @@ "離開聊天室" "管理帳號" "管理裝置" - "下一個" + "下一步" "否" "以後再說" "OK" + "開啟設定" "用其他方式開啟" "快速回覆" "引用" "回應" "移除" "回覆" + "在討論串中回覆" + "回報程式錯誤" "檢舉內容" "再試一次" "再次嘗試解密" @@ -52,7 +60,7 @@ "傳送訊息" "分享" "分享連結" - "跳過" + "略過" "開始" "開始聊天" "開始驗證" @@ -61,6 +69,8 @@ "檢視原始碼" "是" "關於" + "可接受使用政策" + "進階設定" "分析" "音訊" "著作權" @@ -70,6 +80,7 @@ "開發者選項" "(已編輯)" "編輯中" + "* %1$s %2$s" "已啟用加密" "錯誤" "檔案" @@ -77,6 +88,7 @@ "訊息轉寄" "GIF" "圖片" + "回覆 %1$s" "找不到此 Matrix ID,因此可能沒有人會收到邀請。" "正在離開聊天室" "連結已複製到剪貼簿" @@ -91,14 +103,20 @@ "密碼" "夥伴" "永久連結" + "權限" + "總票數:%1$s" "結果將在投票結束後公佈" "隱私權政策" + "回應" "回應" - "重新整理…" + "重新整理中…" "正在回覆%1$s" + "回報程式錯誤" + "格式化文字編輯器" "聊天室名稱" "範例:您的計畫名稱" "搜尋結果" + "安全性" "選擇您的伺服器" "傳送中…" "伺服器 URL" @@ -107,6 +125,8 @@ "成功" "建議" "同步中" + "文字" + "討論串" "主題" "無法解密" "無法發送邀請給一或多個使用者。" @@ -117,6 +137,7 @@ "驗證完成" "影片" "等待中…" + "投票:%1$s" "確認" "警告" "活動" @@ -148,6 +169,7 @@ "無法上傳媒體檔案,請稍後再試。" "其他設定" "私訊" + "僅限提及與關鍵字" "在這個裝置上開啟通知" "群組聊天" "提及" @@ -155,6 +177,7 @@ "系統設定" "已關閉系統通知" "通知" + "帳號與裝置" "分享位置" "分享我的位置" "在 Apple Maps 中開啟" @@ -168,7 +191,7 @@ "錯誤" "成功" "分享匿名的使用數據以協助我們釐清問題" - "您可以到 %1$s 閱讀我們的條款。" + "您可以到%1$s閱讀我們的條款。" "這裡" "封鎖使用者" From 63e88971ce8b375ebf5aee521d1bb2908735956b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 08:13:54 +0200 Subject: [PATCH 05/32] Update dependency io.mockk:mockk to v1.13.8 (#1412) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f648c1773..90866c7adb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -128,7 +128,7 @@ test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.5.2" test_uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0" test_junitext = "androidx.test.ext:junit:1.1.5" -test_mockk = "io.mockk:mockk:1.13.7" +test_mockk = "io.mockk:mockk:1.13.8" test_barista = "com.adevinta.android:barista:4.3.0" test_hamcrest = "org.hamcrest:hamcrest:2.2" test_orchestrator = "androidx.test:orchestrator:1.4.2" From 870cd040d04cf76a8f250f01593f3cebf12f7310 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 08:03:26 +0000 Subject: [PATCH 06/32] Update rnkdsh/action-upload-diawi action to v1.5.2 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 724060aa40..37148c857c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: name: elementx-debug path: | app/build/outputs/apk/debug/*.apk - - uses: rnkdsh/action-upload-diawi@v1.5.1 + - uses: rnkdsh/action-upload-diawi@v1.5.2 id: diawi # Do not fail the whole build if Diawi upload fails continue-on-error: true From f4b46aa2bb4ab1bffa29ecb7d93dfc39d17c9626 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Mon, 25 Sep 2023 17:31:10 +0200 Subject: [PATCH 07/32] Push TimelineEvents further down the UI tree. (#1424) So that timeline items don't have to route their callback all the way down to TimelinePresenter. --- .../messages/impl/timeline/TimelineView.kt | 13 +++----- .../components/TimelineItemEventRow.kt | 33 ++++++++++--------- .../components/TimelineItemStateEventRow.kt | 7 ++-- .../event/TimelineItemContentView.kt | 6 ++-- .../components/event/TimelineItemPollView.kt | 13 +++++--- .../features/poll/api/PollContentView.kt | 2 +- 6 files changed, 40 insertions(+), 34 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 42f529a899..ba02b8eb95 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -103,10 +103,6 @@ fun TimelineView( // TODO implement this logic once we have support to 'jump to event X' in sliding sync } - fun onPollAnswerSelected(pollStartId: EventId, answerId: String) { - state.eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId)) - } - // Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms val alpha by alphaAnimation(label = "alpha for timeline") @@ -134,7 +130,7 @@ fun TimelineView( onReactionLongClick = onReactionLongClicked, onMoreReactionsClick = onMoreReactionsClicked, onTimestampClicked = onTimestampClicked, - onPollAnswerSelected = ::onPollAnswerSelected, + eventSink = state.eventSink, onSwipeToReply = onSwipeToReply, ) } @@ -172,7 +168,7 @@ fun TimelineItemRow( onMoreReactionsClick: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { when (timelineItem) { @@ -189,6 +185,7 @@ fun TimelineItemRow( isHighlighted = highlightedItem == timelineItem.identifier(), onClick = { onClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, + eventSink = eventSink, modifier = modifier, ) } else { @@ -205,7 +202,7 @@ fun TimelineItemRow( onMoreReactionsClick = onMoreReactionsClick, onTimestampClicked = onTimestampClicked, onSwipeToReply = { onSwipeToReply(timelineItem) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, modifier = modifier, ) } @@ -243,7 +240,7 @@ fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, onSwipeToReply = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 44c5d23600..1206ff9a45 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.constraintlayout.compose.ConstrainScope import androidx.constraintlayout.compose.ConstraintLayout +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView @@ -80,9 +81,9 @@ import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.components.EqualWidthColumn import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.swipe.SwipeableActionsState import io.element.android.libraries.designsystem.swipe.rememberSwipeableActionsState import io.element.android.libraries.designsystem.text.toPx @@ -123,7 +124,7 @@ fun TimelineItemEventRow( onReactionLongClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, onSwipeToReply: () -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { val coroutineScope = rememberCoroutineScope() @@ -181,7 +182,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, ) } } @@ -198,7 +199,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, ) } } @@ -240,7 +241,7 @@ private fun TimelineItemEventRowContent( onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier, ) { fun ConstrainScope.linkStartOrEnd(event: TimelineItem.Event) = if (event.isMine) { @@ -299,7 +300,7 @@ private fun TimelineItemEventRowContent( onTimestampClicked = { onTimestampClicked(event) }, - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, ) } @@ -371,7 +372,7 @@ private fun MessageEventBubbleContent( onMessageLongClick: () -> Unit, inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, @SuppressLint("ModifierParameter") bubbleModifier: Modifier = Modifier, // need to rename this modifier to distinguish it from the following ones ) { @@ -389,7 +390,7 @@ private fun MessageEventBubbleContent( onClick = onMessageClick, onLongClick = onMessageLongClick, extraPadding = event.toExtraPadding(), - onPollAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, modifier = modifier, ) } @@ -652,7 +653,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -673,7 +674,7 @@ internal fun TimelineItemEventRowPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -712,7 +713,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -735,7 +736,7 @@ internal fun TimelineItemEventRowWithReplyPreview() = ElementPreview { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -786,7 +787,7 @@ internal fun TimelineItemEventRowTimestampPreview( onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -818,7 +819,7 @@ internal fun TimelineItemEventRowWithManyReactionsPreview() = ElementPreview { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } } @@ -843,7 +844,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } @@ -864,6 +865,6 @@ internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, - onPollAnswerSelected = { _, _ -> }, + eventSink = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index d782a36dcf..3b2b5a8129 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.noExtraPadding @@ -35,8 +36,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.util.defaultTimelineContentPadding -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight @Composable fun TimelineItemStateEventRow( @@ -44,6 +45,7 @@ fun TimelineItemStateEventRow( isHighlighted: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } @@ -69,7 +71,7 @@ fun TimelineItemStateEventRow( onClick = onClick, onLongClick = onLongClick, extraPadding = noExtraPadding, - onPollAnswerSelected = { _, _ -> error("Polls are not supported in state events") }, + eventSink = eventSink, modifier = Modifier.defaultTimelineContentPadding() ) } @@ -88,5 +90,6 @@ internal fun TimelineItemStateEventRowPreview() = ElementPreview { isHighlighted = false, onClick = {}, onLongClick = {}, + eventSink = {} ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt index e882950d6c..833040074e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent @@ -31,7 +32,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent -import io.element.android.libraries.matrix.api.core.EventId @Composable fun TimelineItemEventContentView( @@ -40,7 +40,7 @@ fun TimelineItemEventContentView( extraPadding: ExtraPadding, onClick: () -> Unit, onLongClick: () -> Unit, - onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier ) { when (content) { @@ -95,7 +95,7 @@ fun TimelineItemEventContentView( ) is TimelineItemPollContent -> TimelineItemPollView( content = content, - onAnswerSelected = onPollAnswerSelected, + eventSink = eventSink, modifier = modifier, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index 2943bfc1d5..f68a15946e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -19,27 +19,32 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider import io.element.android.features.poll.api.PollContentView -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId import kotlinx.collections.immutable.toImmutableList @Composable fun TimelineItemPollView( content: TimelineItemPollContent, - onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier, ) { + fun onAnswerSelected(pollStartId: EventId, answerId: String) { + eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId)) + } + PollContentView( eventId = content.eventId, question = content.question, answerItems = content.answerItems.toImmutableList(), pollKind = content.pollKind, isPollEnded = content.isEnded, - onAnswerSelected = onAnswerSelected, + onAnswerSelected = ::onAnswerSelected, modifier = modifier, ) } @@ -50,6 +55,6 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte ElementPreview { TimelineItemPollView( content = content, - onAnswerSelected = { _, _ -> }, + eventSink = {}, ) } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index 9307c20e77..edb7776139 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -32,8 +32,8 @@ 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.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables From e285e016736b96bd5c9c529198d9d908170fe4c8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Sep 2023 20:45:29 +0200 Subject: [PATCH 08/32] NavigationStateService : do not throw error but just log and returns --- .../impl/DefaultAppNavigationStateService.kt | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt index 9360ce93ec..8aeadbede0 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt @@ -25,9 +25,9 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SpaceId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.services.appnavstate.api.AppForegroundStateService -import io.element.android.services.appnavstate.api.NavigationState -import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.AppNavigationState +import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.NavigationState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -50,16 +50,15 @@ class DefaultAppNavigationStateService @Inject constructor( private val state = MutableStateFlow( AppNavigationState( - navigationState = NavigationState.Root, - isInForeground = true, - ) + navigationState = NavigationState.Root, + isInForeground = true, + ) ) override val appNavigationState: StateFlow = state init { coroutineScope.launch { appForegroundStateService.start() - appForegroundStateService.isInForeground.collect { isInForeground -> state.getAndUpdate { it.copy(isInForeground = isInForeground) } } @@ -83,7 +82,7 @@ class DefaultAppNavigationStateService @Inject constructor( val currentValue = state.value.navigationState Timber.tag(loggerTag.value).d("Navigating to space $spaceId. Current state: $currentValue") val newValue: NavigationState.Space = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") is NavigationState.Session -> NavigationState.Space(owner, spaceId, currentValue) is NavigationState.Space -> NavigationState.Space(owner, spaceId, currentValue.parentSession) is NavigationState.Room -> NavigationState.Space(owner, spaceId, currentValue.parentSpace.parentSession) @@ -96,8 +95,8 @@ class DefaultAppNavigationStateService @Inject constructor( val currentValue = state.value.navigationState Timber.tag(loggerTag.value).d("Navigating to room $roomId. Current state: $currentValue") val newValue: NavigationState.Room = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") is NavigationState.Space -> NavigationState.Room(owner, roomId, currentValue) is NavigationState.Room -> NavigationState.Room(owner, roomId, currentValue.parentSpace) is NavigationState.Thread -> NavigationState.Room(owner, roomId, currentValue.parentRoom.parentSpace) @@ -109,9 +108,9 @@ class DefaultAppNavigationStateService @Inject constructor( val currentValue = state.value.navigationState Timber.tag(loggerTag.value).d("Navigating to thread $threadId. Current state: $currentValue") val newValue: NavigationState.Thread = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") - is NavigationState.Space -> error("onNavigateToRoom() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") + is NavigationState.Space -> return logError("onNavigateToRoom()") is NavigationState.Room -> NavigationState.Thread(owner, threadId, currentValue) is NavigationState.Thread -> NavigationState.Thread(owner, threadId, currentValue.parentRoom) } @@ -123,10 +122,10 @@ class DefaultAppNavigationStateService @Inject constructor( Timber.tag(loggerTag.value).d("Leaving thread. Current state: $currentValue") if (!currentValue.assertOwner(owner)) return val newValue: NavigationState.Room = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") - is NavigationState.Space -> error("onNavigateToRoom() must be called first") - is NavigationState.Room -> error("onNavigateToThread() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") + is NavigationState.Space -> return logError("onNavigateToRoom()") + is NavigationState.Room -> return logError("onNavigateToThread()") is NavigationState.Thread -> currentValue.parentRoom } state.getAndUpdate { it.copy(navigationState = newValue) } @@ -137,9 +136,9 @@ class DefaultAppNavigationStateService @Inject constructor( Timber.tag(loggerTag.value).d("Leaving room. Current state: $currentValue") if (!currentValue.assertOwner(owner)) return val newValue: NavigationState.Space = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") - is NavigationState.Space -> error("onNavigateToRoom() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") + is NavigationState.Space -> return logError("onNavigateToRoom()") is NavigationState.Room -> currentValue.parentSpace is NavigationState.Thread -> currentValue.parentRoom.parentSpace } @@ -151,8 +150,8 @@ class DefaultAppNavigationStateService @Inject constructor( Timber.tag(loggerTag.value).d("Leaving space. Current state: $currentValue") if (!currentValue.assertOwner(owner)) return val newValue: NavigationState.Session = when (currentValue) { - NavigationState.Root -> error("onNavigateToSession() must be called first") - is NavigationState.Session -> error("onNavigateToSpace() must be called first") + NavigationState.Root -> return logError("onNavigateToSession()") + is NavigationState.Session -> return logError("onNavigateToSpace()") is NavigationState.Space -> currentValue.parentSession is NavigationState.Room -> currentValue.parentSpace.parentSession is NavigationState.Thread -> currentValue.parentRoom.parentSpace.parentSession @@ -167,6 +166,10 @@ class DefaultAppNavigationStateService @Inject constructor( state.getAndUpdate { it.copy(navigationState = NavigationState.Root) } } + private fun logError(logPrefix: String) { + Timber.tag(loggerTag.value).w("$logPrefix must be call first, can't leave.") + } + private fun NavigationState.assertOwner(owner: String): Boolean { if (this.owner != owner) { Timber.tag(loggerTag.value).d("Can't leave current state as the owner is not the same (current = ${this.owner}, new = $owner)") From da21e8ba6381c297610653f72a8af2da9d79e881 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Sep 2023 20:56:04 +0200 Subject: [PATCH 09/32] Navigation: put RoomList SingleTop when navigating from permalink --- .../android/appnav/LoggedInFlowNode.kt | 20 ++++++++++--------- .../io/element/android/appnav/RootFlowNode.kt | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 164c2ae2e4..d1a953ba09 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -54,6 +54,7 @@ import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.deeplink.DeeplinkData import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.di.SessionScope @@ -68,6 +69,7 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import timber.log.Timber @@ -304,6 +306,15 @@ class LoggedInFlowNode @AssistedInject constructor( } } + internal suspend fun attachInviteList(deeplinkData: DeeplinkData.InviteList) = withContext(lifecycleScope.coroutineContext) { + notificationDrawerManager.clearMembershipNotificationForSession(deeplinkData.sessionId) + backstack.singleTop(NavTarget.RoomList) + backstack.push(NavTarget.InviteList) + waitForChildAttached { navTarget -> + navTarget is NavTarget.InviteList + } + } + @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { @@ -321,13 +332,4 @@ class LoggedInFlowNode @AssistedInject constructor( } } } - - internal suspend fun attachRoom(deeplinkData: DeeplinkData.Room) { - backstack.push(NavTarget.Room(deeplinkData.roomId)) - } - - internal suspend fun attachInviteList(deeplinkData: DeeplinkData.InviteList) { - notificationDrawerManager.clearMembershipNotificationForSession(deeplinkData.sessionId) - backstack.push(NavTarget.InviteList) - } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 60095c17d0..94f344be7e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -234,7 +234,7 @@ class RootFlowNode @AssistedInject constructor( .apply { when (deeplinkData) { is DeeplinkData.Root -> attachRoot() - is DeeplinkData.Room -> attachRoom(deeplinkData) + is DeeplinkData.Room -> attachRoom(deeplinkData.roomId) is DeeplinkData.InviteList -> attachInviteList(deeplinkData) } } From ee901c70a27657b12b3c3c78dafb507018677d1e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 21:10:42 +0200 Subject: [PATCH 10/32] Update gradle/gradle-build-action action to v2.8.1 (#1426) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/nightlyReports.yml | 2 +- .github/workflows/quality.yml | 2 +- .github/workflows/recordScreenshots.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/sonar.yml | 2 +- .github/workflows/tests.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37148c857c..b3408f138b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Assemble debug APK diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 124afbc98f..295cd9f1c6 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -62,7 +62,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Dependency analysis diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 34fc00ca1a..6e3efd2ee7 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -40,7 +40,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Run code quality check suite diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index e9ea93619c..8fadf6b352 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -24,7 +24,7 @@ jobs: java-version: '17' # Add gradle cache, this should speed up the process - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Record screenshots diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc8fc9055c..bce39b7962 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 - name: Create app bundle env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b511835a60..1619e8524b 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -32,7 +32,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: 🔊 Publish results to Sonar diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f662e5352d..c07ceedf00 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.8.0 + uses: gradle/gradle-build-action@v2.8.1 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} From 7464cbb7691de8fe5109217c3b5fcfd9642216f1 Mon Sep 17 00:00:00 2001 From: Marco Romano Date: Mon, 25 Sep 2023 21:56:41 +0200 Subject: [PATCH 11/32] Use eventSink method reference in TimelinePresenter.kt (#1428) Has been changed in https://github.com/vector-im/element-x-android/pull/1172 but in general method references should always be preferred to lambdas in composable functions (because they have higher stability guarantees). --- .../features/messages/impl/timeline/TimelinePresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index a7e86d341f..0fc18019c8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -123,7 +123,7 @@ class TimelinePresenter @Inject constructor( paginationState = paginationState, timelineItems = timelineItems, hasNewItems = hasNewItems.value, - eventSink = { handleEvents(it) } + eventSink = ::handleEvents ) } From 5ec6d7c23551cac02c83a08374e300fa9c552f0b Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Sep 2023 11:06:49 +0200 Subject: [PATCH 12/32] Navigation: fix test and update log. --- .../appnavstate/impl/DefaultAppNavigationStateService.kt | 2 +- .../appnavstate/impl/DefaultNavigationStateServiceTest.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt index 8aeadbede0..b39360c698 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt @@ -167,7 +167,7 @@ class DefaultAppNavigationStateService @Inject constructor( } private fun logError(logPrefix: String) { - Timber.tag(loggerTag.value).w("$logPrefix must be call first, can't leave.") + Timber.tag(loggerTag.value).w("$logPrefix must be call first.") } private fun NavigationState.assertOwner(owner: String): Boolean { diff --git a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt index dd0e576c79..ab272478cc 100644 --- a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt +++ b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt @@ -29,7 +29,6 @@ import io.element.android.services.appnavstate.test.A_THREAD_OWNER import io.element.android.tests.testutils.runCancellableScopeTest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first -import org.junit.Assert.assertThrows import org.junit.Test class DefaultNavigationStateServiceTest { @@ -63,8 +62,8 @@ class DefaultNavigationStateServiceTest { @Test fun testFailure() = runCancellableScopeTest { scope -> val service = createStateService(scope) - - assertThrows(IllegalStateException::class.java) { service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID) } + service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID) + assertThat(service.appNavigationState.value.navigationState).isEqualTo(NavigationState.Root) } private fun createStateService( From 9481360eb04da12d0b704ccd8e19f890994af5bd Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Tue, 26 Sep 2023 10:13:00 +0100 Subject: [PATCH 13/32] Delete story template We don't really use user stories in the platform repos, mostly use them in element-meta --- .github/ISSUE_TEMPLATE/story.yml | 35 -------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/story.yml diff --git a/.github/ISSUE_TEMPLATE/story.yml b/.github/ISSUE_TEMPLATE/story.yml deleted file mode 100644 index 436b92b507..0000000000 --- a/.github/ISSUE_TEMPLATE/story.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: User story issue -description: Second-level planning issue template. A story should take about a week or a sprint to finish. -title: "[Story] " -labels: [T-Story] - -body: -- type: textarea - attributes: - label: Story - description: A story should take roughly a week or a sprint to finish. Each story is usually made up of a number of tasks that take half to a full day. - value: | - As a user… - I want to… - so that I can… - - ## Scope - <!--These should be a list of technical tasks which take ½-1 day to complete--> - ```[tasklist] - ### Tasklist - - [ ] Task 1 - ``` - - - [ ] QA signoff on completion - - [ ] Design signoff on completion - - [ ] Product signoff on completion - - - ## Stretch goals - None at this time - <!--or add a tasklist--> - - ## Out of scope - - - validations: - required: false From 2e6581a5ada6b148c8e972cb5f7425e5f059cf7b Mon Sep 17 00:00:00 2001 From: Marco Romano <marcor@element.io> Date: Tue, 26 Sep 2023 11:19:24 +0200 Subject: [PATCH 14/32] Show poll creator view in timeline (#1429) - Shows edit/end poll buttons when the user is the creator of the poll. - Only the end poll button is wired right now as there is no "edit poll" screen yet. --- .../messages/impl/MessagesPresenter.kt | 20 +-- .../impl/actionlist/ActionListPresenter.kt | 2 +- .../messages/impl/timeline/TimelineEvents.kt | 4 + .../impl/timeline/TimelinePresenter.kt | 10 +- .../components/TimelineItemEventRow.kt | 1 + .../components/TimelineItemStateEventRow.kt | 1 + .../event/TimelineItemContentView.kt | 2 + .../components/event/TimelineItemPollView.kt | 20 +++ .../messages/MessagesPresenterTest.kt | 1 - .../timeline/TimelinePresenterTest.kt | 26 ++++ .../poll/api/PollAnswerViewProvider.kt | 10 +- .../features/poll/api/PollContentView.kt | 122 +++++++++++++++++- .../src/main/res/values/localazy.xml | 2 + ...torView-D-32_32_null_0,NEXUS_5,1.0,en].png | 3 + ...torView-D-32_32_null_1,NEXUS_5,1.0,en].png | 3 + ...torView-N-32_33_null_0,NEXUS_5,1.0,en].png | 3 + ...torView-N-32_33_null_1,NEXUS_5,1.0,en].png | 3 + ...ctedView-D-33_33_null,NEXUS_5,1.0,en].png} | 0 ...ctedView-N-33_34_null,NEXUS_5,1.0,en].png} | 0 ...tateView-D-34_34_null,NEXUS_5,1.0,en].png} | 0 ...tateView-N-34_35_null,NEXUS_5,1.0,en].png} | 0 ...xtView-D-35_35_null_0,NEXUS_5,1.0,en].png} | 0 ...xtView-D-35_35_null_1,NEXUS_5,1.0,en].png} | 0 ...xtView-D-35_35_null_2,NEXUS_5,1.0,en].png} | 0 ...xtView-D-35_35_null_3,NEXUS_5,1.0,en].png} | 0 ...xtView-D-35_35_null_4,NEXUS_5,1.0,en].png} | 0 ...xtView-D-35_35_null_5,NEXUS_5,1.0,en].png} | 0 ...xtView-N-35_36_null_0,NEXUS_5,1.0,en].png} | 0 ...xtView-N-35_36_null_1,NEXUS_5,1.0,en].png} | 0 ...xtView-N-35_36_null_2,NEXUS_5,1.0,en].png} | 0 ...xtView-N-35_36_null_3,NEXUS_5,1.0,en].png} | 0 ...xtView-N-35_36_null_4,NEXUS_5,1.0,en].png} | 0 ...xtView-N-35_36_null_5,NEXUS_5,1.0,en].png} | 0 ...nownView-D-36_36_null,NEXUS_5,1.0,en].png} | 0 ...nownView-N-36_37_null,NEXUS_5,1.0,en].png} | 0 ...eoView-D-37_37_null_0,NEXUS_5,1.0,en].png} | 0 ...eoView-D-37_37_null_1,NEXUS_5,1.0,en].png} | 0 ...eoView-D-37_37_null_2,NEXUS_5,1.0,en].png} | 0 ...eoView-N-37_38_null_0,NEXUS_5,1.0,en].png} | 0 ...eoView-N-37_38_null_1,NEXUS_5,1.0,en].png} | 0 ...eoView-N-37_38_null_2,NEXUS_5,1.0,en].png} | 0 ...aderView-D-38_38_null,NEXUS_5,1.0,en].png} | 0 ...aderView-N-38_39_null,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_0,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_1,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_10,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_11,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_12,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_13,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_14,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_15,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_16,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_17,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_18,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_19,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_2,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_20,NEXUS_5,1.0,en].png} | 0 ...ument-D-39_39_null_21,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_3,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_4,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_5,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_6,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_7,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_8,NEXUS_5,1.0,en].png} | 0 ...cument-D-39_39_null_9,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_0,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_1,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_10,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_11,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_12,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_13,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_14,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_15,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_16,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_17,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_18,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_19,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_2,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_20,NEXUS_5,1.0,en].png} | 0 ...ument-N-39_40_null_21,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_3,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_4,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_5,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_6,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_7,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_8,NEXUS_5,1.0,en].png} | 0 ...cument-N-39_40_null_9,NEXUS_5,1.0,en].png} | 0 ...ontent-D-40_40_null_0,NEXUS_5,1.0,en].png} | 0 ...ontent-N-40_41_null_0,NEXUS_5,1.0,en].png} | 0 ...geMenu-D-41_41_null_0,NEXUS_5,1.0,en].png} | 0 ...geMenu-D-41_41_null_1,NEXUS_5,1.0,en].png} | 0 ...geMenu-N-41_42_null_0,NEXUS_5,1.0,en].png} | 0 ...geMenu-N-41_42_null_1,NEXUS_5,1.0,en].png} | 0 ...nnerView-D-42_42_null,NEXUS_5,1.0,en].png} | 0 ...nnerView-N-42_43_null,NEXUS_5,1.0,en].png} | 0 ...orView-D-43_43_null_0,NEXUS_5,1.0,en].png} | 0 ...orView-D-43_43_null_1,NEXUS_5,1.0,en].png} | 0 ...orView-N-43_44_null_0,NEXUS_5,1.0,en].png} | 0 ...orView-N-43_44_null_1,NEXUS_5,1.0,en].png} | 0 ...ndicator-D-44_44_null,NEXUS_5,1.0,en].png} | 0 ...ndicator-N-44_45_null,NEXUS_5,1.0,en].png} | 0 ...InfoView-D-45_45_null,NEXUS_5,1.0,en].png} | 0 ...InfoView-N-45_46_null,NEXUS_5,1.0,en].png} | 0 ...ntCreator-D-11_11_null,NEXUS_5,1.0,en].png | 3 + ...ntCreator-N-11_12_null,NEXUS_5,1.0,en].png | 3 + ...atorEnded-D-12_12_null,NEXUS_5,1.0,en].png | 3 + ...atorEnded-N-12_13_null,NEXUS_5,1.0,en].png | 3 + ...orNoVotes-D-10_10_null,NEXUS_5,1.0,en].png | 3 + ...orNoVotes-N-10_11_null,NEXUS_5,1.0,en].png | 3 + 109 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-32_32_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-33_33_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-32_33_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-33_34_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-33_33_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-34_34_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-33_34_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-34_35_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-35_35_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-36_36_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-35_36_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-36_37_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-37_37_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-38_38_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-37_38_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-38_39_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_10,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_10,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_11,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_11,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_12,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_12,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_13,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_13,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_14,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_14,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_15,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_15,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_16,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_16,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_17,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_17,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_18,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_18,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_19,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_19,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_20,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_20,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_21,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_21,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_7,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_8,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_8,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_9,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_9,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_10,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_10,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_11,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_11,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_12,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_12,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_13,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_13,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_14,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_14,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_15,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_15,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_16,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_16,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_17,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_17,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_18,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_18,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_19,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_19,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_20,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_20,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_21,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_21,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_7,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_8,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_8,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_9,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_9,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-39_39_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-40_40_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-39_40_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-40_41_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-41_41_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-42_42_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-41_42_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-42_43_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-43_43_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-44_44_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-43_44_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-44_45_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-44_44_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-45_45_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-44_45_null,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-45_46_null,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 1dc46aa2bd..c99c0372a8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import im.vector.app.features.analytics.plan.PollEnd import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction @@ -39,6 +38,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter +import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter @@ -76,7 +76,6 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.room.canRedactAsState import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.textcomposer.MessageComposerMode -import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -95,7 +94,6 @@ class MessagesPresenter @AssistedInject constructor( private val messageSummaryFormatter: MessageSummaryFormatter, private val dispatchers: CoroutineDispatchers, private val clipboardHelper: ClipboardHelper, - private val analyticsService: AnalyticsService, private val preferencesStore: PreferencesStore, @Assisted private val navigator: MessagesNavigator, ) : Presenter<MessagesState> { @@ -155,6 +153,7 @@ class MessagesPresenter @AssistedInject constructor( targetEvent = event.event, composerState = composerState, enableTextFormatting = enableTextFormatting, + timelineState = timelineState, ) } is MessagesEvents.ToggleReaction -> { @@ -206,6 +205,7 @@ class MessagesPresenter @AssistedInject constructor( targetEvent: TimelineItem.Event, composerState: MessageComposerState, enableTextFormatting: Boolean, + timelineState: TimelineState, ) = launch { when (action) { TimelineItemAction.Copy -> handleCopyContents(targetEvent) @@ -216,7 +216,7 @@ class MessagesPresenter @AssistedInject constructor( TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent) TimelineItemAction.Forward -> handleForwardAction(targetEvent) TimelineItemAction.ReportContent -> handleReportAction(targetEvent) - TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent) + TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState) } } @@ -266,7 +266,7 @@ class MessagesPresenter @AssistedInject constructor( targetEvent: TimelineItem.Event, composerState: MessageComposerState, enableTextFormatting: Boolean, - ) { + ) { val composerMode = MessageComposerMode.Edit( targetEvent.eventId, (targetEvent.content as? TimelineItemTextBasedContent)?.let { @@ -344,11 +344,11 @@ class MessagesPresenter @AssistedInject constructor( navigator.onReportContentClicked(event.eventId, event.senderId) } - private suspend fun handleEndPollAction(event: TimelineItem.Event) { - event.eventId?.let { - room.endPoll(it, "The poll with event id: $it has ended.") - analyticsService.capture(PollEnd()) - } + private fun handleEndPollAction( + event: TimelineItem.Event, + timelineState: TimelineState, + ) { + event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) } } private suspend fun handleCopyContents(event: TimelineItem.Event) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 588adb1020..c9e485b88a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -108,7 +108,7 @@ class ActionListPresenter @Inject constructor( buildList { val isMineOrCanRedact = timelineItem.isMine || userCanRedact - // TODO Poll: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()` + // TODO Polls: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()` // when touching this // if (timelineItem.isRemote) { // // Can only reply or forward messages already uploaded to the server diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index e427646a71..ca23904583 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -26,4 +26,8 @@ sealed interface TimelineEvents { val pollStartId: EventId, val answerId: String ) : TimelineEvents + + data class PollEndClicked( + val pollStartId: EventId, + ) : TimelineEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 0fc18019c8..0d0fafd17d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import im.vector.app.features.analytics.plan.PollEnd import im.vector.app.features.analytics.plan.PollVote import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -98,11 +99,18 @@ class TimelinePresenter @Inject constructor( ) analyticsService.capture(PollVote()) } + is TimelineEvents.PollEndClicked -> appScope.launch { + room.endPoll( + pollStartId = event.pollStartId, + text = "The poll with event id: ${event.pollStartId} has ended." + ) + analyticsService.capture(PollEnd()) + } } } LaunchedEffect(timelineItems.size) { - computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems) + computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems) } LaunchedEffect(Unit) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 1206ff9a45..8a20007151 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -386,6 +386,7 @@ private fun MessageEventBubbleContent( ) { TimelineItemEventContentView( content = event.content, + isMine = event.isMine, interactionSource = interactionSource, onClick = onMessageClick, onLongClick = onMessageLongClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index 3b2b5a8129..ccffcc16ca 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -67,6 +67,7 @@ fun TimelineItemStateEventRow( ) { TimelineItemEventContentView( content = event.content, + isMine = event.isMine, interactionSource = interactionSource, onClick = onClick, onLongClick = onLongClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt index 833040074e..dccc020e49 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt @@ -36,6 +36,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt @Composable fun TimelineItemEventContentView( content: TimelineItemEventContent, + isMine: Boolean, interactionSource: MutableInteractionSource, extraPadding: ExtraPadding, onClick: () -> Unit, @@ -95,6 +96,7 @@ fun TimelineItemEventContentView( ) is TimelineItemPollContent -> TimelineItemPollView( content = content, + isMine = isMine, eventSink = eventSink, modifier = modifier, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index f68a15946e..958845f98c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -31,6 +31,7 @@ import kotlinx.collections.immutable.toImmutableList @Composable fun TimelineItemPollView( content: TimelineItemPollContent, + isMine: Boolean, eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier, ) { @@ -38,13 +39,20 @@ fun TimelineItemPollView( eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId)) } + fun onPollEnd(pollStartId: EventId) { + eventSink(TimelineEvents.PollEndClicked(pollStartId)) + } + PollContentView( eventId = content.eventId, question = content.question, answerItems = content.answerItems.toImmutableList(), pollKind = content.pollKind, isPollEnded = content.isEnded, + isMine = isMine, onAnswerSelected = ::onAnswerSelected, + onPollEdit = {}, // TODO Polls: Wire up this callback once poll edit screen is done. + onPollEnd = ::onPollEnd, modifier = modifier, ) } @@ -55,6 +63,18 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte ElementPreview { TimelineItemPollView( content = content, + isMine = false, + eventSink = {}, + ) + } + +@PreviewsDayNight +@Composable +internal fun TimelineItemPollCreatorViewPreview(@PreviewParameter(TimelineItemPollContentProvider::class) content: TimelineItemPollContent) = + ElementPreview { + TimelineItemPollView( + content = content, + isMine = true, eventSink = {}, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 07662e4bcc..9bbb514117 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -644,7 +644,6 @@ class MessagesPresenterTest { messageSummaryFormatter = FakeMessageSummaryFormatter(), navigator = navigator, clipboardHelper = clipboardHelper, - analyticsService = analyticsService, preferencesStore = preferencesStore, dispatchers = coroutineDispatchers, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index 0435be3cb1..ec83b86cd9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.PollEnd import im.vector.app.features.analytics.plan.PollVote +import io.element.android.features.messages.fixtures.aMessageEvent import io.element.android.features.messages.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter @@ -42,6 +44,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitWithLatch import io.element.android.tests.testutils.testCoroutineDispatchers +import io.element.android.tests.testutils.waitForPredicate import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -280,6 +283,29 @@ class TimelinePresenterTest { assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollVote()) } + @Test + fun `present - PollEndClicked event calls into rust room api and analytics`() = runTest { + val room = FakeMatrixRoom() + val analyticsService = FakeAnalyticsService() + val presenter = createTimelinePresenter( + room = room, + analyticsService = analyticsService, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(TimelineEvents.PollEndClicked(aMessageEvent().eventId!!)) + waitForPredicate { room.endPollInvocations.size == 1 } + cancelAndIgnoreRemainingEvents() + assertThat(room.endPollInvocations.size).isEqualTo(1) + assertThat(room.endPollInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID) + assertThat(room.endPollInvocations.first().text).isEqualTo("The poll with event id: \$anEventId has ended.") + assertThat(analyticsService.capturedEvents.size).isEqualTo(1) + assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollEnd()) + } + } + private fun TestScope.createTimelinePresenter( timeline: MatrixTimeline = FakeMatrixTimeline(), timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory() diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt index e94b5adeeb..42f0c4f527 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt @@ -19,13 +19,17 @@ package io.element.android.features.poll.api import io.element.android.libraries.matrix.api.poll.PollAnswer import kotlinx.collections.immutable.persistentListOf -fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = persistentListOf( +fun aPollAnswerItemList( + hasVotes: Boolean = true, + isEnded: Boolean = false, + isDisclosed: Boolean = true, +) = persistentListOf( aPollAnswerItem( answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"), isDisclosed = isDisclosed, isEnabled = !isEnded, isWinner = isEnded, - votesCount = 5, + votesCount = if (hasVotes) 5 else 0, percentage = 0.5f ), aPollAnswerItem( @@ -42,7 +46,7 @@ fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = isEnabled = !isEnded, isWinner = false, isSelected = true, - votesCount = 1, + votesCount = if (hasVotes) 1 else 0, percentage = 0.1f ), aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded), diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index edb7776139..bfcccf3b89 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -26,14 +26,19 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue 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.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables @@ -51,13 +56,37 @@ fun PollContentView( answerItems: ImmutableList<PollAnswerItem>, pollKind: PollKind, isPollEnded: Boolean, + isMine: Boolean, onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, + onPollEdit: (pollStartId: EventId) -> Unit, + onPollEnd: (pollStartId: EventId) -> Unit, modifier: Modifier = Modifier, ) { + val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } } + fun onAnswerSelected(pollAnswer: PollAnswer) { eventId?.let { onAnswerSelected(it, pollAnswer.id) } } + fun onPollEdit() { + eventId?.let { onPollEdit(it) } + } + + fun onPollEnd() { + eventId?.let { onPollEnd(it) } + } + + var showConfirmation: Boolean by remember { mutableStateOf(false) } + + if (showConfirmation) ConfirmationDialog( + content = stringResource(id = CommonStrings.common_poll_end_confirmation), + onSubmitClicked = { + onPollEnd() + showConfirmation = false + }, + onDismiss = { showConfirmation = false }, + ) + Column( modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp), @@ -67,11 +96,20 @@ fun PollContentView( PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected) if (isPollEnded || pollKind == PollKind.Disclosed) { - val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } } DisclosedPollBottomNotice(votesCount = votesCount) } else { UndisclosedPollBottomNotice() } + + if (isMine) { + CreatorView( + votesCount = 1, // TODO Polls: set to `votesCount` when edit poll screen is implemented. + isPollEnded = isPollEnded, + onPollEdit = ::onPollEdit, + onPollEnd = { showConfirmation = true }, + modifier = Modifier.fillMaxWidth(), + ) + } } } @@ -157,6 +195,31 @@ private fun ColumnScope.UndisclosedPollBottomNotice( ) } +@Composable +private fun CreatorView( + @Suppress("SameParameterValue") votesCount: Int, // TODO Polls: remove @Suppress when edit poll screen is implemented. + isPollEnded: Boolean, + onPollEdit: () -> Unit, + onPollEnd: () -> Unit, + modifier: Modifier = Modifier +) { + if (!isPollEnded) { + if (votesCount == 0) { + Button( + text = stringResource(id = CommonStrings.action_edit_poll), + onClick = onPollEdit, + modifier = modifier, + ) + } else { + Button( + text = stringResource(id = CommonStrings.action_end_poll), + onClick = onPollEnd, + modifier = modifier, + ) + } + } +} + @PreviewsDayNight @Composable internal fun PollContentUndisclosedPreview() = ElementPreview { @@ -166,7 +229,10 @@ internal fun PollContentUndisclosedPreview() = ElementPreview { answerItems = aPollAnswerItemList(isDisclosed = false), pollKind = PollKind.Undisclosed, isPollEnded = false, + isMine = false, onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, ) } @@ -179,7 +245,10 @@ internal fun PollContentDisclosedPreview() = ElementPreview { answerItems = aPollAnswerItemList(), pollKind = PollKind.Disclosed, isPollEnded = false, + isMine = false, onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, ) } @@ -192,6 +261,57 @@ internal fun PollContentEndedPreview() = ElementPreview { answerItems = aPollAnswerItemList(isEnded = true), pollKind = PollKind.Disclosed, isPollEnded = true, + isMine = false, onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun PollContentCreatorNoVotesPreview() = ElementPreview { + PollContentView( + eventId = EventId("\$anEventId"), + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(hasVotes = false, isEnded = false), + pollKind = PollKind.Disclosed, + isPollEnded = false, + isMine = true, + onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun PollContentCreatorPreview() = ElementPreview { + PollContentView( + eventId = EventId("\$anEventId"), + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(isEnded = false), + pollKind = PollKind.Disclosed, + isPollEnded = false, + isMine = true, + onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, + ) +} + +@PreviewsDayNight +@Composable +internal fun PollContentCreatorEndedPreview() = ElementPreview { + PollContentView( + eventId = EventId("\$anEventId"), + question = "What type of food should we have at the party?", + answerItems = aPollAnswerItemList(isEnded = true), + pollKind = PollKind.Disclosed, + isPollEnded = true, + isMine = true, + onAnswerSelected = { _, _ -> }, + onPollEdit = {}, + onPollEnd = {}, ) } diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 5d4d427af9..48c518deed 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -71,6 +71,7 @@ <string name="action_take_photo">"Take photo"</string> <string name="action_view_source">"View Source"</string> <string name="action_yes">"Yes"</string> + <string name="action_edit_poll">"Edit poll"</string> <string name="common_about">"About"</string> <string name="common_acceptable_use_policy">"Acceptable use policy"</string> <string name="common_advanced_settings">"Advanced settings"</string> @@ -149,6 +150,7 @@ <string name="common_verification_complete">"Verification complete"</string> <string name="common_video">"Video"</string> <string name="common_waiting">"Waiting…"</string> + <string name="common_poll_end_confirmation">"Are you sure you want to end this poll?"</string> <string name="common_poll_summary">"Poll: %1$s"</string> <string name="dialog_title_confirmation">"Confirmation"</string> <string name="dialog_title_warning">"Warning"</string> diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1ac340b172 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09440b685e219b4a5f5dc78d2694707d7eeb1905d6fdea6395e3dcc941482ac5 +size 51846 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..540d962b0c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-D-32_32_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae8d6b561d358c7ebccae2da4281f3ea1b4d433f52de3c3b2008c1208c0c8bd7 +size 54037 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7c14f911dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0cd0936ace9bac190f21680bf339cc23839e5f5d241522cb8f328b2db0887f6 +size 48293 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e14dee1d70 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemPollCreatorView-N-32_33_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc96f4b704396e0931a08a968139ee9d586cd1e14358b546361d11bc91e7bab5 +size 50373 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-32_32_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-33_33_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-32_32_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-D-33_33_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-32_33_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-33_34_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-32_33_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemRedactedView-N-33_34_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-33_33_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-34_34_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-33_33_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-D-34_34_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-33_34_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-34_35_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-33_34_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemStateView-N-34_35_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-34_34_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-D-35_35_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-34_35_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemTextView-N-35_36_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-35_35_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-36_36_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-35_35_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-D-36_36_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-35_36_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-36_37_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-35_36_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemUnknownView-N-36_37_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-36_36_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-D-37_37_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-36_37_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_null_TimelineItemVideoView-N-37_38_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-37_37_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-38_38_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-37_37_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-D-38_38_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-37_38_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-38_39_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-37_38_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.group_null_GroupHeaderView-N-38_39_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-38_38_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-D-39_39_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-38_39_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.html_null_HtmlDocument-N-39_40_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-39_39_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-40_40_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-39_39_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-D-40_40_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-39_40_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-40_41_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-39_40_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.reactionsummary_null_SheetContent-N-40_41_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-40_40_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-D-41_41_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-40_41_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_null_RetrySendMessageMenu-N-41_42_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-41_41_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-42_42_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-41_41_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-D-42_42_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-41_42_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-42_43_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-41_42_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineEncryptedHistoryBannerView-N-42_43_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-42_42_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-D-43_43_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-42_43_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineItemDaySeparatorView-N-43_44_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-43_43_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-44_44_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-43_43_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-D-44_44_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-43_44_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-44_45_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-43_44_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_null_TimelineLoadingMoreIndicator-N-44_45_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-44_44_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-45_45_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-44_44_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-D-45_45_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-44_45_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-45_46_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-44_45_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.debug_null_EventDebugInfoView-N-45_46_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1ac340b172 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-D-11_11_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09440b685e219b4a5f5dc78d2694707d7eeb1905d6fdea6395e3dcc941482ac5 +size 51846 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7c14f911dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreator-N-11_12_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0cd0936ace9bac190f21680bf339cc23839e5f5d241522cb8f328b2db0887f6 +size 48293 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c8e001c9af --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-D-12_12_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88230f28f7761d8f76bc5a50dbc4ba08a694eaf0d64207257f5196c741fb7b52 +size 49078 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..495cb4c484 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorEnded-N-12_13_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6e31f773b884608499081ab3c7a27297edfbf8af76f502d9076b4753956e2db +size 45929 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2d9643e989 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-D-10_10_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fbe23f6b00973c497f791c37f9d884e5d6baa6e6f5376f10633be0cb3043d49 +size 52067 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..48329ff374 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api_null_PollContentCreatorNoVotes-N-10_11_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a38081492746fdc6a4ae9f5408005456ab0f2143d4f937139d637aa39eabe77 +size 48435 From f0d5c46eaad9309788f31a47f5416e1c1256e23f Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 15:41:31 +0200 Subject: [PATCH 15/32] Element call URL must have https scheme. --- features/call/src/main/AndroidManifest.xml | 1 - .../features/call/CallIntentDataParser.kt | 2 +- .../call/CallIntentDataParserTests.kt | 36 ++++++++++++++----- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/features/call/src/main/AndroidManifest.xml b/features/call/src/main/AndroidManifest.xml index d106d4e7b8..877b7fb0a8 100644 --- a/features/call/src/main/AndroidManifest.xml +++ b/features/call/src/main/AndroidManifest.xml @@ -39,7 +39,6 @@ <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> - <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="call.element.io" /> diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt index 30e71d6201..f1c4291526 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt @@ -21,7 +21,7 @@ import javax.inject.Inject class CallIntentDataParser @Inject constructor() { - private val validHttpSchemes = sequenceOf("http", "https") + private val validHttpSchemes = sequenceOf("https") fun parse(data: String?): String? { val parsedUrl = data?.let { Uri.parse(data) } ?: return null diff --git a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt index 71290f15b7..4d5beef7e9 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt @@ -52,13 +52,17 @@ class CallIntentDataParserTests { } @Test - fun `Element Call urls will be returned as is`() { + fun `Element Call http urls returns null`() { val httpBaseUrl = "http://call.element.io" val httpCallUrl = "http://call.element.io/some-actual-call?with=parameters" + assertThat(callIntentDataParser.parse(httpBaseUrl)).isNull() + assertThat(callIntentDataParser.parse(httpCallUrl)).isNull() + } + + @Test + fun `Element Call urls will be returned as is`() { val httpsBaseUrl = "https://call.element.io" val httpsCallUrl = "https://call.element.io/some-actual-call?with=parameters" - assertThat(callIntentDataParser.parse(httpBaseUrl)).isEqualTo(httpBaseUrl) - assertThat(callIntentDataParser.parse(httpCallUrl)).isEqualTo(httpCallUrl) assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo(httpsBaseUrl) assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo(httpsCallUrl) } @@ -76,18 +80,34 @@ class CallIntentDataParserTests { } @Test - fun `element scheme with call host and url param gets url extracted`() { + fun `element scheme with call host and url with http will returns null`() { val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://call?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isNull() + } + + @Test + fun `element scheme with call host and url param gets url extracted`() { + val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "element://call?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) } @Test - fun `element scheme 2 with url param gets url extracted`() { + fun `element scheme 2 with url param with http returns null`() { val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isNull() + } + + @Test + fun `element scheme 2 with url param gets url extracted`() { + val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) } @@ -101,7 +121,7 @@ class CallIntentDataParserTests { @Test fun `element scheme 2 with no url returns null`() { - val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?no_url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -109,7 +129,7 @@ class CallIntentDataParserTests { @Test fun `element scheme with no call host returns null`() { - val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://no-call?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -129,7 +149,7 @@ class CallIntentDataParserTests { @Test fun `element invalid scheme returns null`() { - val embeddedUrl = "http://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "bad.scheme:/?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() From 8cb7c3211cd2cdb9c06af85a47872785962263dc Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 16:19:38 +0200 Subject: [PATCH 16/32] Element call: Pass in custom call URL query parameters --- .../features/call/CallIntentDataParser.kt | 33 +++++++++--- .../call/CallIntentDataParserTests.kt | 50 +++++++++++++++---- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt index f1c4291526..5992ccbc75 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt @@ -27,7 +27,7 @@ class CallIntentDataParser @Inject constructor() { val parsedUrl = data?.let { Uri.parse(data) } ?: return null val scheme = parsedUrl.scheme return when { - scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> data + scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> parsedUrl scheme == "element" && parsedUrl.host == "call" -> { // We use this custom scheme to load arbitrary URLs for other instances of Element Call, // so we can only verify it's an HTTP/HTTPs URL with a non-empty host @@ -40,14 +40,35 @@ class CallIntentDataParser @Inject constructor() { } // This should never be possible, but we still need to take into account the possibility else -> null - } + }?.withCustomParameters() } - private fun Uri.getUrlParameter(): String? { + private fun Uri.getUrlParameter(): Uri? { return getQueryParameter("url") - ?.takeIf { - val internalUri = Uri.parse(it) - internalUri.scheme in validHttpSchemes && !internalUri.host.isNullOrBlank() + ?.let { urlParameter -> + Uri.parse(urlParameter).takeIf { uri -> + uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank() + } } } } + +/** + * Ensure the uri has the following parameters and value: + * - appPrompt=false + * - confineToRoom=true + */ +private fun Uri.withCustomParameters(): String { + val builder = buildUpon() + builder.clearQuery() + queryParameterNames.forEach { + if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach + builder.appendQueryParameter(it, getQueryParameter(it)) + } + builder.appendQueryParameter(APP_PROMPT_PARAMETER, "false") + builder.appendQueryParameter(CONFINE_TO_ROOM_PARAMETER, "true") + return builder.build().toString() +} + +private const val APP_PROMPT_PARAMETER = "appPrompt" +private const val CONFINE_TO_ROOM_PARAMETER = "confineToRoom" diff --git a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt index 4d5beef7e9..aee97ed982 100644 --- a/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt +++ b/features/call/src/test/kotlin/io/element/android/features/call/CallIntentDataParserTests.kt @@ -62,9 +62,9 @@ class CallIntentDataParserTests { @Test fun `Element Call urls will be returned as is`() { val httpsBaseUrl = "https://call.element.io" - val httpsCallUrl = "https://call.element.io/some-actual-call?with=parameters" - assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo(httpsBaseUrl) - assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo(httpsCallUrl) + val httpsCallUrl = VALID_CALL_URL_WITH_PARAM + assertThat(callIntentDataParser.parse(httpsBaseUrl)).isEqualTo("$httpsBaseUrl?$EXTRA_PARAMS") + assertThat(callIntentDataParser.parse(httpsCallUrl)).isEqualTo("$httpsCallUrl&$EXTRA_PARAMS") } @Test @@ -89,10 +89,10 @@ class CallIntentDataParserTests { @Test fun `element scheme with call host and url param gets url extracted`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://call?url=$encodedUrl" - assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") } @Test @@ -105,10 +105,10 @@ class CallIntentDataParserTests { @Test fun `element scheme 2 with url param gets url extracted`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?url=$encodedUrl" - assertThat(callIntentDataParser.parse(url)).isEqualTo(embeddedUrl) + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") } @Test @@ -121,7 +121,7 @@ class CallIntentDataParserTests { @Test fun `element scheme 2 with no url returns null`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "io.element.call:/?no_url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -129,7 +129,7 @@ class CallIntentDataParserTests { @Test fun `element scheme with no call host returns null`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "element://no-call?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() @@ -149,9 +149,39 @@ class CallIntentDataParserTests { @Test fun `element invalid scheme returns null`() { - val embeddedUrl = "https://call.element.io/some-actual-call?with=parameters" + val embeddedUrl = VALID_CALL_URL_WITH_PARAM val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") val url = "bad.scheme:/?url=$encodedUrl" assertThat(callIntentDataParser.parse(url)).isNull() } + + @Test + fun `element scheme 2 with url extra param appPrompt gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&appPrompt=true" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url extra param confineToRoom gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}&confineToRoom=false" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS") + } + + @Test + fun `element scheme 2 with url fragment gets url extracted`() { + val embeddedUrl = "${VALID_CALL_URL_WITH_PARAM}#fragment" + val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8") + val url = "io.element.call:/?url=$encodedUrl" + assertThat(callIntentDataParser.parse(url)).isEqualTo("$VALID_CALL_URL_WITH_PARAM&$EXTRA_PARAMS#fragment") + } + + + companion object { + const val VALID_CALL_URL_WITH_PARAM = "https://call.element.io/some-actual-call?with=parameters" + const val EXTRA_PARAMS = "appPrompt=false&confineToRoom=true" + } } From 39a2e2a80937e26592f9cd812015366a751e38d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 16:22:41 +0200 Subject: [PATCH 17/32] Changelog --- changelog.d/1434.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1434.misc diff --git a/changelog.d/1434.misc b/changelog.d/1434.misc new file mode 100644 index 0000000000..8babe4771a --- /dev/null +++ b/changelog.d/1434.misc @@ -0,0 +1 @@ +Element call: add custom parameters to Element Call urls. From 5d0751de72b2892a9b419e4b6936bf77d2c6dcf7 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 16:48:13 +0200 Subject: [PATCH 18/32] Detekt: sentences must end with a period. --- .../io/element/android/features/call/CallIntentDataParser.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt index 5992ccbc75..b3e2f9227a 100644 --- a/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt +++ b/features/call/src/main/kotlin/io/element/android/features/call/CallIntentDataParser.kt @@ -57,6 +57,7 @@ class CallIntentDataParser @Inject constructor() { * Ensure the uri has the following parameters and value: * - appPrompt=false * - confineToRoom=true + * to ensure that the rendering will bo correct on the embedded Webview. */ private fun Uri.withCustomParameters(): String { val builder = buildUpon() From 34631f53b35dbd3f1377e53ee572cec0d8f32fc2 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 13:42:43 +0200 Subject: [PATCH 19/32] Avoid hard-coded string. --- .../android/libraries/matrix/impl/media/RustMediaLoader.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index c958810f5e..97148f7dd4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.media.MediaSource @@ -77,7 +78,7 @@ class RustMediaLoader( val mediaFile = innerClient.getMediaFile( mediaSource = mediaSource, body = body, - mimeType = mimeType ?: "application/octet-stream", + mimeType = mimeType ?: MimeTypes.OctetStream, tempDir = cacheDirectory.path, ) RustMediaFile(mediaFile) From e7ba973e7bfe3bb3cb2918a0fe600c127ed5fc67 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 13:53:25 +0200 Subject: [PATCH 20/32] Improve default mime type. --- .../TimelineItemContentMessageFactory.kt | 19 +++++++++++-------- .../libraries/core/mimetype/MimeTypes.kt | 8 ++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 040969e092..3d0dc4d80c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -109,14 +109,17 @@ class TimelineItemContentMessageFactory @Inject constructor( formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), fileExtension = fileExtensionExtractor.extractFromName(messageType.body) ) - is FileMessageType -> TimelineItemFileContent( - body = messageType.body, - thumbnailSource = messageType.info?.thumbnailSource, - fileSource = messageType.source, - mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, - formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = fileExtensionExtractor.extractFromName(messageType.body) - ) + is FileMessageType -> { + val fileExtension = fileExtensionExtractor.extractFromName(messageType.body) + TimelineItemFileContent( + body = messageType.body, + thumbnailSource = messageType.info?.thumbnailSource, + fileSource = messageType.source, + mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension), + formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), + fileExtension = fileExtension + ) + } is NoticeMessageType -> TimelineItemNoticeContent( body = messageType.body, htmlDocument = messageType.formatted?.toHtmlDocument(), diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt index c6373fbbf6..0506126568 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt @@ -51,4 +51,12 @@ object MimeTypes { fun String?.isMimeTypeFile() = this?.startsWith("file/").orFalse() fun String?.isMimeTypeText() = this?.startsWith("text/").orFalse() fun String?.isMimeTypeAny() = this?.startsWith("*/").orFalse() + + fun fromFileExtension(fileExtension: String): String { + return when (fileExtension.lowercase()) { + "apk" -> Apk + "pdf" -> Pdf + else -> OctetStream + } + } } From 3f73443ebbe8bff7e88a5e53f4d8708674a5a765 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 14:09:21 +0200 Subject: [PATCH 21/32] Be able to install APK from Element X. --- .../media/local/AndroidLocalMediaActions.kt | 55 +++++++++++++++++-- .../impl/media/viewer/MediaViewerView.kt | 18 ++++-- .../src/main/res/drawable/ic_apk_install.xml | 10 ++++ .../src/main/res/values/localazy.xml | 1 + 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 features/messages/impl/src/main/res/drawable/ic_apk_install.xml diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt index 44bff4f5ab..b060ea0bb5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.media.local +import android.app.Activity import android.content.ContentResolver import android.content.ContentValues import android.content.Context @@ -24,17 +25,25 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.core.content.FileProvider import androidx.core.net.toFile import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File @@ -50,10 +59,27 @@ class AndroidLocalMediaActions @Inject constructor( ) : LocalMediaActions { private var activityContext: Context? = null + private var apkInstallLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>? = null + private var pendingMedia: LocalMedia? = null @Composable override fun Configure() { val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + apkInstallLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + pendingMedia?.let { + coroutineScope.launch { + open(it) + } + } + } else { + // User cancelled + } + pendingMedia = null + } return DisposableEffect(Unit) { activityContext = context onDispose { @@ -99,11 +125,21 @@ class AndroidLocalMediaActions @Inject constructor( override suspend fun open(localMedia: LocalMedia): Result<Unit> = withContext(coroutineDispatchers.io) { require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE) runCatching { - val openMediaIntent = Intent(Intent.ACTION_VIEW) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setDataAndType(localMedia.toShareableUri(), localMedia.info.mimeType) - withContext(coroutineDispatchers.main) { - activityContext!!.startActivity(openMediaIntent) + when (localMedia.info.mimeType) { + MimeTypes.Apk -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (activityContext?.packageManager?.canRequestPackageInstalls() == false) { + pendingMedia = localMedia + activityContext?.startInstallFromSourceIntent(apkInstallLauncher!!) + Unit + } else { + openFile(localMedia) + } + } else { + openFile(localMedia) + } + } + else -> openFile(localMedia) } }.onSuccess { Timber.v("Open media succeed") @@ -112,6 +148,15 @@ class AndroidLocalMediaActions @Inject constructor( } } + private suspend fun openFile(localMedia: LocalMedia) { + val openMediaIntent = Intent(Intent.ACTION_VIEW) + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .setDataAndType(localMedia.toShareableUri(), localMedia.info.mimeType) + withContext(coroutineDispatchers.main) { + activityContext!!.startActivity(openMediaIntent) + } + } + private fun LocalMedia.toShareableUri(): Uri { val mediaAsFile = this.toFile() val authority = "${buildMeta.applicationId}.fileprovider" diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt index 7ab52216fe..b0570a6f2a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt @@ -47,11 +47,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import coil.compose.AsyncImage +import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMediaView import io.element.android.features.messages.impl.media.local.MediaInfo import io.element.android.features.messages.impl.media.local.rememberLocalMediaViewState import io.element.android.libraries.architecture.Async +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -92,6 +94,7 @@ fun MediaViewerView( topBar = { MediaViewerTopBar( actionsEnabled = state.downloadedMedia is Async.Success, + mimeType = state.mediaInfo.mimeType, onBackPressed = onBackPressed, eventSink = state.eventSink ) @@ -162,6 +165,7 @@ private fun rememberShowProgress(downloadedMedia: Async<LocalMedia>): Boolean { @Composable private fun MediaViewerTopBar( actionsEnabled: Boolean, + mimeType: String, onBackPressed: () -> Unit, eventSink: (MediaViewerEvents) -> Unit, ) { @@ -175,10 +179,16 @@ private fun MediaViewerTopBar( eventSink(MediaViewerEvents.OpenWith) }, ) { - Icon( - imageVector = Icons.Default.OpenInNew, - contentDescription = stringResource(id = CommonStrings.action_open_with) - ) + when (mimeType) { + MimeTypes.Apk -> Icon( + resourceId = R.drawable.ic_apk_install, + contentDescription = stringResource(id = CommonStrings.common_install_apk_android) + ) + else -> Icon( + imageVector = Icons.Default.OpenInNew, + contentDescription = stringResource(id = CommonStrings.action_open_with) + ) + } } IconButton( enabled = actionsEnabled, diff --git a/features/messages/impl/src/main/res/drawable/ic_apk_install.xml b/features/messages/impl/src/main/res/drawable/ic_apk_install.xml new file mode 100644 index 0000000000..b39fc4c5d5 --- /dev/null +++ b/features/messages/impl/src/main/res/drawable/ic_apk_install.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,160Q80,127 103.5,103.5Q127,80 160,80L480,80L720,320L720,490L640,490L640,360L440,360L440,160L160,160Q160,160 160,160Q160,160 160,160L160,800Q160,800 160,800Q160,800 160,800L600,800L600,880L160,880ZM160,800L160,490L160,490L160,360L160,160L160,160Q160,160 160,160Q160,160 160,160L160,800Q160,800 160,800Q160,800 160,800L160,800ZM200,760Q204,711 230,670Q256,629 298,605L260,537Q260,536 264,522Q269,520 273.5,520Q278,520 280,525L319,595Q339,587 359,582.5Q379,578 400,578Q421,578 441,582.5Q461,587 481,595L520,525Q520,525 535,521Q540,523 541,528Q542,533 540,537L502,605Q544,629 570,670Q596,711 600,760L200,760ZM310,700Q318,700 324,694Q330,688 330,680Q330,672 324,666Q318,660 310,660Q302,660 296,666Q290,672 290,680Q290,688 296,694Q302,700 310,700ZM490,700Q498,700 504,694Q510,688 510,680Q510,672 504,666Q498,660 490,660Q482,660 476,666Q470,672 470,680Q470,688 476,694Q482,700 490,700ZM800,880L640,720L696,663L760,726L760,560L840,560L840,726L904,663L960,720L800,880Z"/> +</vector> diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 5d4d427af9..b38e0e3796 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -93,6 +93,7 @@ <string name="common_gif">"GIF"</string> <string name="common_image">"Image"</string> <string name="common_in_reply_to">"In reply to %1$s"</string> + <string name="common_install_apk_android">"Install APK"</string> <string name="common_invite_unknown_profile">"This Matrix ID can\'t be found, so the invite might not be received."</string> <string name="common_leaving_room">"Leaving room"</string> <string name="common_link_copied_to_clipboard">"Link copied to clipboard"</string> From 29db17ae09d960547ea6167ff26b7b07e1659b01 Mon Sep 17 00:00:00 2001 From: ElementBot <benoitm+elementbot@element.io> Date: Tue, 26 Sep 2023 12:22:44 +0000 Subject: [PATCH 22/32] Update screenshots --- ...a.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...a.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png index 6715960e25..a322a9a364 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15ec8227e84c01b8e9acd5271e379d989c8e4ed72b57c87d252a992a0d3f1a1a -size 15702 +oid sha256:bd2a831abc63de6366f2a4fbac653d551e29fbbbb698f5dc3ae51de04dbf6138 +size 15941 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png index 2aca1f16a4..01188c15b3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.media.viewer_null_MediaViewerView_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08c11b272ebe9b4d8f9b76841b60a519d08a9e5f37970c66b78bd36319bcd53f -size 15782 +oid sha256:6b7dea2e1df15375ae07db4e6cf7b2a14a26c0ff1f2cbb11efdaea263d954550 +size 16067 From bf49078e40caf7f22269356f0b6c1582b941b0c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 14:20:43 +0200 Subject: [PATCH 23/32] Detekt: fix OptionalUnit issue. --- .../messages/impl/media/local/AndroidLocalMediaActions.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt index b060ea0bb5..3457ebc9ec 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt @@ -130,8 +130,7 @@ class AndroidLocalMediaActions @Inject constructor( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (activityContext?.packageManager?.canRequestPackageInstalls() == false) { pendingMedia = localMedia - activityContext?.startInstallFromSourceIntent(apkInstallLauncher!!) - Unit + activityContext?.startInstallFromSourceIntent(apkInstallLauncher!!).let { } } else { openFile(localMedia) } From 3eb112381d9d4f8fc1845f635c1c6f322200a00d Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 26 Sep 2023 15:01:30 +0200 Subject: [PATCH 24/32] Call `openFile` instead of `open` and avoid unwrapping the Activity context. --- .../messages/impl/media/local/AndroidLocalMediaActions.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt index 3457ebc9ec..ef93574134 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActions.kt @@ -72,7 +72,7 @@ class AndroidLocalMediaActions @Inject constructor( if (activityResult.resultCode == Activity.RESULT_OK) { pendingMedia?.let { coroutineScope.launch { - open(it) + openFile(it) } } } else { @@ -152,7 +152,7 @@ class AndroidLocalMediaActions @Inject constructor( .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setDataAndType(localMedia.toShareableUri(), localMedia.info.mimeType) withContext(coroutineDispatchers.main) { - activityContext!!.startActivity(openMediaIntent) + activityContext?.startActivity(openMediaIntent) } } From 702c137ead2a1134c093959be99e422c8bff2ecf Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoitm@matrix.org> Date: Tue, 26 Sep 2023 18:07:24 +0200 Subject: [PATCH 25/32] Replace `mutableStateOf` by `mutableIntStateOf` and `mutableFloatStateOf`. Use `intValue` and `floatValue` to avoid unboxing. --- .../features/location/api/StaticMapView.kt | 4 ++-- .../waitlistscreen/WaitListPresenter.kt | 7 ++++--- .../impl/media/viewer/MediaViewerPresenter.kt | 3 ++- .../components/TimelineItemEventRow.kt | 2 +- .../impl/bugreport/BugReportPresenter.kt | 18 ++++++++++-------- .../swipe/SwipeableActionsState.kt | 15 ++++++++------- .../designsystem/theme/components/Slider.kt | 4 ++-- .../matrix/ui/components/SelectedUsersList.kt | 4 ++-- 8 files changed, 31 insertions(+), 26 deletions(-) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index ead0b98228..f4443e92f4 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -21,7 +21,7 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -64,7 +64,7 @@ fun StaticMapView( contentAlignment = Alignment.Center ) { val context = LocalContext.current - var retryHash by remember { mutableStateOf(0) } + var retryHash by remember { mutableIntStateOf(0) } val builder = remember { StaticMapUrlBuilder(context) } val painter = rememberAsyncImagePainter( model = if (constraints.isZero) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt index 9c07204ab2..039986343b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.screens.waitlistscreen import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -58,14 +59,14 @@ class WaitListPresenter @AssistedInject constructor( mutableStateOf(Async.Uninitialized) } - val attemptNumber: MutableState<Int> = remember { mutableStateOf(0) } + val attemptNumber = remember { mutableIntStateOf(0) } fun handleEvents(event: WaitListEvents) { when (event) { WaitListEvents.AttemptLogin -> { // Do not attempt to login on first resume of the View. - attemptNumber.value++ - if (attemptNumber.value > 1) { + attemptNumber.intValue++ + if (attemptNumber.intValue > 1) { coroutineScope.loginAttempt(formState, loginAction) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt index 13a9ff3bee..b295f68e11 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -59,7 +60,7 @@ class MediaViewerPresenter @AssistedInject constructor( @Composable override fun present(): MediaViewerState { val coroutineScope = rememberCoroutineScope() - var loadMediaTrigger by remember { mutableStateOf(0) } + var loadMediaTrigger by remember { mutableIntStateOf(0) } val mediaFile: MutableState<MediaFile?> = remember { mutableStateOf(null) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 8a20007151..4ab98ce953 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -147,7 +147,7 @@ fun TimelineItemEventRow( } if (canReply) { val state: SwipeableActionsState = rememberSwipeableActionsState() - val offset = state.offset.value + val offset = state.offset.floatValue val swipeThresholdPx = 40.dp.toPx() val thresholdCrossed = abs(offset) > swipeThresholdPx SwipeSensitivity(3f) { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index 5fd95e79bd..9258909201 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -17,9 +17,11 @@ package io.element.android.features.rageshake.impl.bugreport import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableFloatState import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -43,27 +45,27 @@ class BugReportPresenter @Inject constructor( ) : Presenter<BugReportState> { private class BugReporterUploadListener( - private val sendingProgress: MutableState<Float>, + private val sendingProgress: MutableFloatState, private val sendingAction: MutableState<Async<Unit>> ) : BugReporterListener { override fun onUploadCancelled() { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Uninitialized } override fun onUploadFailed(reason: String?) { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Failure(Exception(reason)) } override fun onProgress(progress: Int) { - sendingProgress.value = progress.toFloat() / 100 + sendingProgress.floatValue = progress.toFloat() / 100 sendingAction.value = Async.Loading() } override fun onUploadSucceed(reportUrl: String?) { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Success(Unit) } } @@ -80,7 +82,7 @@ class BugReportPresenter @Inject constructor( .collectAsState(initial = "") val sendingProgress = remember { - mutableStateOf(0f) + mutableFloatStateOf(0f) } val sendingAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) @@ -107,7 +109,7 @@ class BugReportPresenter @Inject constructor( copy(sendScreenshot = event.sendScreenshot) } BugReportEvents.ClearError -> { - sendingProgress.value = 0f + sendingProgress.floatValue = 0f sendingAction.value = Async.Uninitialized } } @@ -115,7 +117,7 @@ class BugReportPresenter @Inject constructor( return BugReportState( hasCrashLogs = crashInfo.isNotEmpty(), - sendingProgress = sendingProgress.value, + sendingProgress = sendingProgress.floatValue, sending = sendingAction.value, formState = formState.value, screenshotUri = screenshotUri.value, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt index 77d80cdde5..542c46fae1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt @@ -21,9 +21,10 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.MutatePriority import androidx.compose.foundation.gestures.DraggableState import androidx.compose.runtime.Composable +import androidx.compose.runtime.FloatState import androidx.compose.runtime.Stable -import androidx.compose.runtime.State import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -41,8 +42,8 @@ class SwipeableActionsState { /** * The current position (in pixels) of the content. */ - val offset: State<Float> get() = offsetState - private var offsetState = mutableStateOf(0f) + val offset: FloatState get() = offsetState + private var offsetState = mutableFloatStateOf(0f) /** * Whether the content is currently animating to reset its offset after it was swiped. @@ -51,21 +52,21 @@ class SwipeableActionsState { private set val draggableState = DraggableState { delta -> - val targetOffset = offsetState.value + delta + val targetOffset = offsetState.floatValue + delta val isAllowed = isResettingOnRelease || targetOffset > 0f - offsetState.value += if (isAllowed) delta else 0f + offsetState.floatValue += if (isAllowed) delta else 0f } suspend fun resetOffset() { draggableState.drag(MutatePriority.PreventUserInput) { isResettingOnRelease = true try { - Animatable(offsetState.value).animateTo( + Animatable(offsetState.floatValue).animateTo( targetValue = 0f, animationSpec = tween(durationMillis = 300), ) { - dragBy(value - offsetState.value) + dragBy(value - offsetState.floatValue) } } finally { isResettingOnRelease = false diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt index 872376583d..2041d7998d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt @@ -22,7 +22,7 @@ import androidx.compose.material3.SliderColors import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -62,7 +62,7 @@ internal fun SlidersPreview() = ElementThemedPreview { ContentToPreview() } @Composable private fun ContentToPreview() { - var value by remember { mutableStateOf(0.33f) } + var value by remember { mutableFloatStateOf(0.33f) } Column { Slider(onValueChange = { value = it }, value = value, enabled = true) Slider(steps = 10, onValueChange = { value = it }, value = value, enabled = true) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt index 36eb445ae6..884f45de02 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt @@ -29,7 +29,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -55,7 +55,7 @@ fun SelectedUsersList( ) { val lazyListState = rememberLazyListState() if (autoScroll) { - var currentSize by rememberSaveable { mutableStateOf(selectedUsers.size) } + var currentSize by rememberSaveable { mutableIntStateOf(selectedUsers.size) } LaunchedEffect(selectedUsers.size) { val isItemAdded = selectedUsers.size > currentSize if (isItemAdded) { From 3c692e4c4812837e727ff42710bc657c514f4387 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 18:05:41 +0000 Subject: [PATCH 26/32] Update dependency org.matrix.rustcomponents:sdk-android to v0.1.58 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e25c9f360..9f8af860fb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -150,7 +150,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.57" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.58" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } From cf657df5fd722b46338aa1a3e85a1b9803c2b54a Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 27 Sep 2023 09:40:25 +0200 Subject: [PATCH 27/32] Fix API break with Matrix SDK 0.1.58. --- .../api/notification/NotificationData.kt | 1 + ...imelineEventToNotificationContentMapper.kt | 1 + .../matrix/impl/room/RoomContentForwarder.kt | 3 +-- .../matrix/impl/room/RustMatrixRoom.kt | 19 +++++++------------ 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 8565e4c747..290a54c502 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -61,6 +61,7 @@ sealed interface NotificationContent { ) : MessageLike data object RoomRedaction : MessageLike data object Sticker : MessageLike + data class Poll(val question: String) : MessageLike } sealed interface StateEvent : NotificationContent { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt index e30e57113d..b82716cc2d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt @@ -94,6 +94,7 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon } MessageLikeEventContent.RoomRedaction -> NotificationContent.MessageLike.RoomRedaction MessageLikeEventContent.Sticker -> NotificationContent.MessageLike.Sticker + is MessageLikeEventContent.Poll -> NotificationContent.MessageLike.Poll(question) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index 8ee0361ace..d539ec6a53 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -27,7 +27,6 @@ import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineListener -import org.matrix.rustcomponents.sdk.genTransactionId import kotlin.time.Duration.Companion.milliseconds /** @@ -61,7 +60,7 @@ class RoomContentForwarder( // Sending a message requires a registered timeline listener targetRoom.addTimelineListener(NoOpTimelineListener) withTimeout(timeoutMs.milliseconds) { - targetRoom.send(content, genTransactionId()) + targetRoom.send(content) } } // After sending, we remove the timeline diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 4a8a3f9bee..d879bcc53d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -67,7 +67,6 @@ import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle -import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.messageEventContentFromHtml import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import timber.log.Timber @@ -241,10 +240,9 @@ class RustMatrixRoom( } override suspend fun sendMessage(body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) { - val transactionId = genTransactionId() messageEventContentFromParts(body, htmlBody).use { content -> runCatching { - innerRoom.send(content, transactionId) + innerRoom.send(content) } } } @@ -253,26 +251,27 @@ class RustMatrixRoom( withContext(roomDispatcher) { if (originalEventId != null) { runCatching { - innerRoom.edit(messageEventContentFromParts(body, htmlBody), originalEventId.value, transactionId?.value) + innerRoom.edit(messageEventContentFromParts(body, htmlBody), originalEventId.value) } } else { runCatching { transactionId?.let { cancelSend(it) } - innerRoom.send(messageEventContentFromParts(body, htmlBody), genTransactionId()) + innerRoom.send(messageEventContentFromParts(body, htmlBody)) } } } override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) { runCatching { - innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventId.value, genTransactionId()) + innerRoom.getEventTimelineItemByEventId(eventId.value).use { eventTimelineItem -> + innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventTimelineItem) + } } } override suspend fun redactEvent(eventId: EventId, reason: String?) = withContext(roomDispatcher) { - val transactionId = genTransactionId() runCatching { - innerRoom.redact(eventId.value, reason, transactionId) + innerRoom.redact(eventId.value, reason) } } @@ -416,7 +415,6 @@ class RustMatrixRoom( description = description, zoomLevel = zoomLevel?.toUByte(), assetType = assetType?.toInner(), - txnId = genTransactionId(), ) } } @@ -433,7 +431,6 @@ class RustMatrixRoom( answers = answers, maxSelections = maxSelections.toUByte(), pollKind = pollKind.toInner(), - txnId = genTransactionId(), ) } } @@ -446,7 +443,6 @@ class RustMatrixRoom( innerRoom.sendPollResponse( pollStartId = pollStartId.value, answers = answers, - txnId = genTransactionId(), ) } } @@ -459,7 +455,6 @@ class RustMatrixRoom( innerRoom.endPoll( pollStartId = pollStartId.value, text = text, - txnId = genTransactionId(), ) } } From aefeb01cdd2b9f0aada6d2ad94211c1de2177be7 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 27 Sep 2023 10:12:04 +0200 Subject: [PATCH 28/32] Reduce the number of fallback notification. Only used now when message cannot be decrypted. Also avoid using `else` in this `when` block. --- .../notifications/NotifiableEventResolver.kt | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index c93d517e89..9951698b88 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -114,10 +114,63 @@ class NotifiableEventResolver @Inject constructor( title = null, // TODO check if title is needed anymore ) } else { - fallbackNotifiableEvent(userId, roomId, eventId) + Timber.tag(loggerTag.value).d("Ignoring notification state event for membership ${content.membershipState}") + null } } - else -> fallbackNotifiableEvent(userId, roomId, eventId) + NotificationContent.MessageLike.CallAnswer, + NotificationContent.MessageLike.CallCandidates, + NotificationContent.MessageLike.CallHangup, + NotificationContent.MessageLike.CallInvite -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}") + } + NotificationContent.MessageLike.KeyVerificationAccept, + NotificationContent.MessageLike.KeyVerificationCancel, + NotificationContent.MessageLike.KeyVerificationDone, + NotificationContent.MessageLike.KeyVerificationKey, + NotificationContent.MessageLike.KeyVerificationMac, + NotificationContent.MessageLike.KeyVerificationReady, + NotificationContent.MessageLike.KeyVerificationStart -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for verification ${content.javaClass.simpleName}") + } + is NotificationContent.MessageLike.Poll -> null.also { + // TODO Polls: handle notification rendering + Timber.tag(loggerTag.value).d("Ignoring notification for poll") + } + is NotificationContent.MessageLike.ReactionContent -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for reaction") + } + NotificationContent.MessageLike.RoomEncrypted -> fallbackNotifiableEvent(userId, roomId, eventId).also { + Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback") + } + NotificationContent.MessageLike.RoomRedaction -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for redaction") + } + NotificationContent.MessageLike.Sticker -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for sticker") + } + NotificationContent.StateEvent.PolicyRuleRoom, + NotificationContent.StateEvent.PolicyRuleServer, + NotificationContent.StateEvent.PolicyRuleUser, + NotificationContent.StateEvent.RoomAliases, + NotificationContent.StateEvent.RoomAvatar, + NotificationContent.StateEvent.RoomCanonicalAlias, + NotificationContent.StateEvent.RoomCreate, + NotificationContent.StateEvent.RoomEncryption, + NotificationContent.StateEvent.RoomGuestAccess, + NotificationContent.StateEvent.RoomHistoryVisibility, + NotificationContent.StateEvent.RoomJoinRules, + NotificationContent.StateEvent.RoomName, + NotificationContent.StateEvent.RoomPinnedEvents, + NotificationContent.StateEvent.RoomPowerLevels, + NotificationContent.StateEvent.RoomServerAcl, + NotificationContent.StateEvent.RoomThirdPartyInvite, + NotificationContent.StateEvent.RoomTombstone, + NotificationContent.StateEvent.RoomTopic, + NotificationContent.StateEvent.SpaceChild, + NotificationContent.StateEvent.SpaceParent -> null.also { + Timber.tag(loggerTag.value).d("Ignoring notification for state event ${content.javaClass.simpleName}") + } } } From a192a8823f19dae60399da28de57bfbb85d2f951 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 27 Sep 2023 10:48:31 +0200 Subject: [PATCH 29/32] Ensure `getEventTimelineItemByEventId` is called when we enter in reply mode. --- .../MessageComposerPresenter.kt | 21 ++++++++++++++++-- .../libraries/matrix/api/room/MatrixRoom.kt | 7 +++--- .../matrix/impl/room/RustMatrixRoom.kt | 22 ++++++++++++++++++- .../matrix/test/room/FakeMatrixRoom.kt | 10 ++++++++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 6898d7796f..9a6849023f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -20,6 +20,7 @@ import android.Manifest import android.annotation.SuppressLint import android.net.Uri import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -151,15 +152,24 @@ class MessageComposerPresenter @Inject constructor( } } + DisposableEffect(Unit) { + onDispose { + appCoroutineScope.launch { + room.exitReplyMode() + } + } + } + fun handleEvents(event: MessageComposerEvents) { when (event) { MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value - MessageComposerEvents.CloseSpecialMode -> { richTextEditorState.setHtml("") messageComposerContext.composerMode = MessageComposerMode.Normal("") + appCoroutineScope.launch { + room.exitReplyMode() + } } - is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage( message = event.message, updateComposerMode = { messageComposerContext.composerMode = it }, @@ -167,6 +177,13 @@ class MessageComposerPresenter @Inject constructor( ) is MessageComposerEvents.SetMode -> { messageComposerContext.composerMode = event.composerMode + appCoroutineScope.launch { + if (event.composerMode is MessageComposerMode.Reply) { + room.enterReplyMode(event.composerMode.eventId) + } else { + room.exitReplyMode() + } + } } MessageComposerEvents.AddAttachment -> localCoroutineScope.launch { showAttachmentSourcePicker = true diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 1dd6101354..465c0fe365 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -89,6 +89,10 @@ interface MatrixRoom : Closeable { suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?): Result<Unit> + suspend fun enterReplyMode(eventId: EventId): Result<Unit> + + suspend fun exitReplyMode(): Result<Unit> + suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit> @@ -184,7 +188,4 @@ interface MatrixRoom : Closeable { suspend fun endPoll(pollStartId: EventId, text: String): Result<Unit> override fun close() = destroy() - } - - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index d879bcc53d..a4cedfee91 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -60,6 +60,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem @@ -261,11 +262,30 @@ class RustMatrixRoom( } } + private var inReplyToEventTimelineItem: EventTimelineItem? = null + + override suspend fun enterReplyMode(eventId: EventId): Result<Unit> = withContext(roomDispatcher) { + runCatching { + inReplyToEventTimelineItem?.destroy() + inReplyToEventTimelineItem = null + inReplyToEventTimelineItem = innerRoom.getEventTimelineItemByEventId(eventId.value) + } + } + + override suspend fun exitReplyMode(): Result<Unit> = withContext(roomDispatcher) { + runCatching { + inReplyToEventTimelineItem?.destroy() + inReplyToEventTimelineItem = null + } + } + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) { runCatching { - innerRoom.getEventTimelineItemByEventId(eventId.value).use { eventTimelineItem -> + val inReplyTo = inReplyToEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(eventId.value) + inReplyTo.use { eventTimelineItem -> innerRoom.sendReply(messageEventContentFromParts(body, htmlBody), eventTimelineItem) } + inReplyToEventTimelineItem = null } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 0e8916e87e..7771548d01 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -31,8 +31,8 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState -import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState +import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimeline @@ -208,6 +208,14 @@ class FakeMatrixRoom( var replyMessageParameter: Pair<String, String?>? = null private set + override suspend fun enterReplyMode(eventId: EventId): Result<Unit> { + return Result.success(Unit) + } + + override suspend fun exitReplyMode(): Result<Unit> { + return Result.success(Unit) + } + override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> { replyMessageParameter = body to htmlBody return Result.success(Unit) From c9c7af6e065c0dade09000a571c102aff045a43f Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 27 Sep 2023 11:25:23 +0200 Subject: [PATCH 30/32] Simplify the management of `inReplyToEventTimelineItem` --- .../MessageComposerPresenter.kt | 18 ++---------------- .../libraries/matrix/api/room/MatrixRoom.kt | 2 -- .../matrix/impl/room/RustMatrixRoom.kt | 8 +------- .../matrix/test/room/FakeMatrixRoom.kt | 4 ---- 4 files changed, 3 insertions(+), 29 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 9a6849023f..d6e74a0df0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -20,7 +20,6 @@ import android.Manifest import android.annotation.SuppressLint import android.net.Uri import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -152,23 +151,12 @@ class MessageComposerPresenter @Inject constructor( } } - DisposableEffect(Unit) { - onDispose { - appCoroutineScope.launch { - room.exitReplyMode() - } - } - } - fun handleEvents(event: MessageComposerEvents) { when (event) { MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value MessageComposerEvents.CloseSpecialMode -> { richTextEditorState.setHtml("") messageComposerContext.composerMode = MessageComposerMode.Normal("") - appCoroutineScope.launch { - room.exitReplyMode() - } } is MessageComposerEvents.SendMessage -> appCoroutineScope.sendMessage( message = event.message, @@ -177,11 +165,9 @@ class MessageComposerPresenter @Inject constructor( ) is MessageComposerEvents.SetMode -> { messageComposerContext.composerMode = event.composerMode - appCoroutineScope.launch { - if (event.composerMode is MessageComposerMode.Reply) { + if (event.composerMode is MessageComposerMode.Reply) { + appCoroutineScope.launch { room.enterReplyMode(event.composerMode.eventId) - } else { - room.exitReplyMode() } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 465c0fe365..17cb637d80 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -91,8 +91,6 @@ interface MatrixRoom : Closeable { suspend fun enterReplyMode(eventId: EventId): Result<Unit> - suspend fun exitReplyMode(): Result<Unit> - suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit> diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index a4cedfee91..4c20dd4d0c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -139,6 +139,7 @@ class RustMatrixRoom( roomCoroutineScope.cancel() innerRoom.destroy() roomListItem.destroy() + inReplyToEventTimelineItem?.destroy() } override val name: String? @@ -272,13 +273,6 @@ class RustMatrixRoom( } } - override suspend fun exitReplyMode(): Result<Unit> = withContext(roomDispatcher) { - runCatching { - inReplyToEventTimelineItem?.destroy() - inReplyToEventTimelineItem = null - } - } - override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> = withContext(roomDispatcher) { runCatching { val inReplyTo = inReplyToEventTimelineItem ?: innerRoom.getEventTimelineItemByEventId(eventId.value) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 7771548d01..e8abdb62df 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -212,10 +212,6 @@ class FakeMatrixRoom( return Result.success(Unit) } - override suspend fun exitReplyMode(): Result<Unit> { - return Result.success(Unit) - } - override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?): Result<Unit> { replyMessageParameter = body to htmlBody return Result.success(Unit) From 411bbb230ac30dbc9502aa484bf0d5d120a77aa5 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 27 Sep 2023 14:21:55 +0200 Subject: [PATCH 31/32] Changelog for version 0.2.3 --- CHANGES.md | 13 +++++++++++++ changelog.d/1434.misc | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) delete mode 100644 changelog.d/1434.misc diff --git a/CHANGES.md b/CHANGES.md index beb480dd45..96fc743ee3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,16 @@ +Changes in Element X v0.2.3 (2023-09-27) +======================================== + +Features ✨ +---------- + - Handle installation of Apks from the media viewer. ([#1432](https://github.com/vector-im/element-x-android/pull/1432)) + - Integrate SDK 0.1.58 ([#1437](https://github.com/vector-im/element-x-android/pull/1437)) + +Other changes +------------- + - Element call: add custom parameters to Element Call urls. ([#1434](https://github.com/vector-im/element-x-android/issues/1434)) + + Changes in Element X v0.2.2 (2023-09-21) ======================================== diff --git a/changelog.d/1434.misc b/changelog.d/1434.misc deleted file mode 100644 index 8babe4771a..0000000000 --- a/changelog.d/1434.misc +++ /dev/null @@ -1 +0,0 @@ -Element call: add custom parameters to Element Call urls. From 84784b2b7ea90572cda0411b9fdeb4c8a634f1c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 27 Sep 2023 14:22:09 +0200 Subject: [PATCH 32/32] Adding fastlane file for version 0.2.3 --- fastlane/metadata/android/en-US/changelogs/40002030.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40002030.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40002030.txt b/fastlane/metadata/android/en-US/changelogs/40002030.txt new file mode 100644 index 0000000000..ff4f86ce7e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40002030.txt @@ -0,0 +1,2 @@ +Main changes in this version: bugfixes. +Full changelog: https://github.com/vector-im/element-x-android/releases