From 217c88f34236614d20cb416201744f838cc999f7 Mon Sep 17 00:00:00 2001 From: zeritti Date: Sat, 28 Nov 2020 09:45:41 +0000 Subject: [PATCH 001/218] Translated using Weblate (Czech) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 61 ++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index dd6a401164..ea8cff2da2 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -1673,7 +1673,7 @@ Odešle daný emote zabarvený jako duha Časová osa Editor zpráv - Zapnout šifrování end-to-end + Zapnout šifrování end-to-end… Jakmile zapnuto, šifrování nelze vypnout. Zapnout šifrování\? Jakmile zapnuto, šifrování místnosti nelze vypnout. Zprávy odeslané v zašifrované místnosti nemohou být čteny serverem, ale pouze účastníky místnosti. Zapnutím šifrování mohou boty a můstky přestat správně pracovat. @@ -2225,4 +2225,63 @@ Nemáte oprávnění k zahájení hovoru Nemáte oprávnění k zahájení konferenčního hovoru Resetovat + Odkaz na Matrix + QR kód nebyl oskenován! + Neplatný QR kód (neplatné URI)! + Sobě nelze zaslat přimou zprávu! + Sdílet textem + Hledat kontakty v Matrixu + Nastavit avatara + Souhlas uživatele nebyl udělen. + Sdělte tento kód lidem, aby s Vámi mohli po skenování zahájit konverzaci. + Můj kód + Sdílet můj kód + Skenovat QR kód + To není platný matrixový QR kód + 🔐️ Přidej se ke mně v elementu + Ahoj, mluv se mnou v Elementu: %s + Pozvat přátele + Přidat lidi + "Téma: " + Přidat téma + %s, abyste dali lidem vědět, o čem tato místnost je. + Toto je počátek Vaší historie přímých zpráv s %s. + Toto je počátek této konverzace. + Toto je počátek %s. + Exportovat audit + K zapnutí šifrování v této místnosti nemáte oprávnění. + Přímá zpráva + Zakládám místnost… + Některé znaky nejsou dovoleny + Prosím, zadejte adresu místnosti + Tato adresa je již obsazena + Adresa místnosti + Můžete zapnout, pokud bude tato místnost využita pro spolupráci interních týmů na Vašem homeserveru. Později nelze změnit. + Trvale blokovat vstup do této místnosti všem, kdo nejsou členy %s + Skrýt pokročilé + Ukázat pokročilé + %1$d z %2$d + Založit novou přímou konverzaci pomocí Matrix ID + Založit novou přímou konverzaci pomocí skenu QR kódu + Abyste nalezli existující kontakty, jež znáte, souhlasíte s odesláním svých kontaktních údajů (telefonní čísla a/nebo emailové adresy) na nastavený server pro identity (%1$s)\? +\n +\nZa účelem soukromí budou data před odesláním hašována. + Poslat emailové adresy a telefonní čísla + Udělit souhlas + Zrušit můj souhlas + Neudělili jste souhlas pro odeslání emailových adres a telefonních čísel na tento server pro identity za účelem nalezení dalších uživatelů podle svých kontaktů. + Udělili jste souhlas pro odeslání emailových adres a telefonních čísel na tento server pro identity za účelem nalezení dalších uživatelů podle svých kontaktů. + Poslat emailové adresy a telefonní čísla + Doporučení + Kontakty + Známí uživatelé + Poslední + QR kód + Přidat pomocí QR kódu + Hledat podle jména nebo ID + Udělte právo přístupu ke kontaktům. + Pro sken QR kódu je nutné povolit přístup k fotoaparátu. + Poslat historii požadavků na sdílení klíčů + Žádné další výsledky + Zahájit chat \ No newline at end of file From f7f7e808f2eaf12341e796d364875582090ab68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 28 Nov 2020 08:43:44 +0000 Subject: [PATCH 002/218] Translated using Weblate (Estonian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 57 ++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 152f5f03e2..6bc5b3cd8c 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -863,7 +863,7 @@ Saadab antud emote vikerkaarevärvides Ajajoon Sõnumite kirjutamine - Võta läbiv krüptimine kasutusele + Võta läbiv krüptimine kasutusele… Kui krüptimine on juba kasutusele võetud, siis ei saa seda enam eemaldada. Kas võtame krüptimise kasutusele\? Kui kord juba kasutusele võetud, siis krüptimist enam hiljem ära lõpetada ei saa. Krüptitud sõnumeid ei saa lugeda ei vaheapealses veebiliikluses ega serveris ja vaid jututoa liikmed saavad neid lugeda. Krüptimise kasutusele võtmine võib takistada nii robotite kui sõnumisildade tööd. @@ -2191,4 +2191,59 @@ Otsevestlus Lisa kaasa võtmevahetusega seotud päringute ajalugu Rohkem otsingutulemusi pole + Kas selleks, et leida tuttavaid, oled sa nõus saatma oma kontaktteavet (telefoninumbreid ja/või e-posti aadresse) siin rakenduses seadistatud isikutuvastusserverile (%1$s)\? +\n +\nParema turvalisuse nimel me ei saada teavet mitte loetava tekstina, vaid räsina. + 🔐️ Liitu minuga vestlusrakenduses Element + Hei, palun suhtle minuga vestlusrakenduses Element: %s + Kutsu sõpru + Lisa inimesi + "Teema: " + lisa jututoa teema + Selleks et kõik teaks, millega siin jututoas tegeletakse, palun %s. + See on otsesõnumite algus kasutajaga %s. + See on vestluse algus. + Siit maalt algab %s jututuba. + Sul puuduvad õigused siin jututoas läbiva krüptimise kasutuselevõtmiseks. + Loon jututuba… + Mõned tähemärgid ei ole siin lubatud + Palun kirjuta jututoa aadress + See aadress on juba kasutusel + Jututoa aadress + Sa võid sellise võimaluse kasutusele võtta, kui seda jututuba kasutatakse vaid organisatsioonisiseste tiimide ühistööks oma koduserveri piires. Seda ei saa hiljem muuta. + Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %s koduserveris + Peida lisaseadistused + Näita lisaseadistusi + %1$d / %2$d + Alusta QR-koodi lugemise abil uut vestlust + Alusta Matrix\'i kasutajatunnuse alusel uut vestlust + Matrix\'i link + QR-kood on lugemata! + Vigane QR-kood (vigane URI)! + Sa ei ole Muhv ega saa iseendale sõnumeid saata! + Jaga tekstina + Otsi Matrix\'i võrgust tuttavaid + Seadista tunnuspilt + Kasutaja nõusolek on puudu. + Selleks, et teised kasutajad saaks sind lisada oma kontaktiks ja alustada vestlust, jaga seda QR-koodi nendega. + Minu QR-kood + Jaga minu koodi + Loe QR-koodi + See ei ole korralik Matrix\'i QR-kood + Saada e-posti aadresse ja telefoninumbreid + Nõustu + Tühista minu nõusolek + Selleks, et leida Matrixikasutajaid oma kontaktide hulgast, sa ei ole andnud nõusolekut saata e-posti aadresse ja telefoninumbreid sellele isikutuvastusserverile. + Selleks, et leida Matrixikasutajaid oma kontaktide hulgast, oled sa andnud nõusoleku saata e-posti aadresse ja telefoninumbreid sellele isikutuvastusserverile. + Saada e-posti aadresse ja telefoninumbreid + Soovitused + Kontaktid + Tuttavad kasutajad + Hiljutised + QR-kood + Lisa QR-koodi abil + Otsi nime või Matrix\'i tunnuse alusel + Anna õigused oma kontakte lugeda. + QR-koodi lugemiseks pead selleks kaamerale õigused andma. + Alusta vestlust \ No newline at end of file From 493cd2a0e3f23c30ccad85a1ee81f305dcfc870e Mon Sep 17 00:00:00 2001 From: sblondon Date: Fri, 27 Nov 2020 14:49:03 +0000 Subject: [PATCH 003/218] Translated using Weblate (French) Currently translated at 98.8% (1963 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index d6258d45bd..41cc30447d 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1654,7 +1654,7 @@ Envoie la réaction fournie colorée comme un arc-en-ciel Discussions Éditeur de messages - Activer le chiffrement de bout en bout + Active le chiffrement de bout en bout… Une fois qu’il est activé, le chiffrement ne peut pas être désactivé. Activer le chiffrement \? Une fois qu’il est activé, le chiffrement ne peut pas être désactivé. Les messages envoyés dans les salons chiffrés ne peuvent pas être vus par le serveur, uniquement par les participants du salon. Activer le chiffrement empêchera peut-être les robots et les passerelles de fonctionner correctement. @@ -2199,4 +2199,36 @@ Inclure l\'historique d\'échange de clés Plus aucun résultat Exporter le rapport d\'audit + %s pour permettre aux gens de connaître le sujet de ce salon. + Ceci est le début de l\'historique de vos message direct avec %s. + Ceci est le début de cette conversation. + Ceci est le début de %s. + Vous n\'avez pas le droit d\'activer le chiffrement dans ce salon. + Création du salon… + Certains caractères sont interdits + Veuillez fournir une adresse de salon + Cette adresse est déjà utilisée + Adresse du salon + Créer une nouvelle conversation directe en scannant un QR Code + %1$d de %2$d + Créer une nouvelle conversation directe avec un identifiant Matrix + Dans le but de découvrir des contacts que vous connaîtriez, acceptez-vous d\'envoyer vos données de contact (numéros de téléphone et/ou e-mails) au serveur d\'identité configuré (%1$s) \? +\n +\nPour une meilleure protection de la vie privée, les données seront condensées (hash) avant l\'envoi. + Envoyer des e-mails et des numéros de téléphone + Autoriser + Révoquer mon autorisation + Vous n\'avez pas donné votre autorisation pour envoyer des e-mails et des numéros de téléphone à ce serveur d\'identité pour découvrir d\'autres utilisateurs à partir de vos contacts. + Vous avez donné votre autorisation pour envoyer des e-mails et des numéros de téléphone à ce serveur d\'identité pour découvrir d\'autres utilisateurs à partir de vos contacts. + Envoyer des e-mails et des numéros de téléphone + Suggestions + Contacts + Utilisateurs connus + Récent + QR code + Ajouter avec un QR Code + Chercher par nom ou identifiant + Autoriser l\'accès à vos contacts. + Pour scanner un QR code, vous devez autoriser l\'accès à votre appareil photo. + Débuter la discussion \ No newline at end of file From 9531a384867bf09165af219fd004537d5a449240 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Fri, 27 Nov 2020 17:31:38 +0000 Subject: [PATCH 004/218] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index c040882e80..65db55c162 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1808,7 +1808,7 @@ Envia o emoji colorido como um arco-íris Conversas Campo de texto - Ativar a criptografia de ponta a ponta + Ativar a criptografia de ponta a ponta… Uma vez ativada, a criptografia não poderá ser desativada. Ativar criptografia\? Uma vez ativada, a criptografia de uma sala não pode ser desativada. As mensagens enviadas em uma sala criptografada não podem ser lidas pelo servidor, apenas pelos participantes desta sala. A ativação da criptografia pode impedir que muitos bots e integrações funcionem corretamente. @@ -2254,4 +2254,59 @@ Não há mais resultados Exportar auditoria Enviar mensagem + Mostrar mais + Esconder mais + Link na Matrix + %s para que as pessoas saibam do que se trata esta sala. + Digite o endereço da sala + Recentes + Código QR não escaneado! + Código QR inválido (URL inválido)! + Não é possível enviar mensagens para si mesmo! + Compartilhar por texto + Pesquisar contatos na Matrix + Definir foto + A autorização do usuário não foi fornecida. + Compartilhe este código com as pessoas, para que possam escaneá-lo, de modo a adicionar seu contato e começar a conversar. + Meu código + Compartilhar meu código + Escanear um código QR + Não é um código QR da Matrix válido + 🔐️ Junte-se a mim no Element + Ei, fale comigo no Element: %s + Convidar amigos + Adicionar pessoas + "Descrição:· " + Adicionar uma descrição + Este é o começo do seu histórico de mensagens com %s. + Este é o começo desta conversa. + Este é o início de %s. + Você não tem permissão para ativar a criptografia nesta sala. + Criando sala… + Alguns caracteres não são permitidos + Este endereço já está em uso + Endereço da sala + Você pode habilitar essa opção se a sala for usada apenas para colaboração com equipes internas em seu servidor local. Essa opção não poderá ser alterada mais tarde. + Impedir que qualquer pessoa que não faça parte de %s jamais entre nesta sala + %1$d de %2$d + Começar uma nova conversa escaneando um código QR + Começar uma nova conversa com um ID na Matrix + De modo a descobrir contatos a partir de pessoas que você conhece, você aceita enviar seus dados de contato (números de telefone e/ou e-mails) para o servidor de identidade configurado (%1$s)\? +\n +\nPara obter mais privacidade, os dados enviados serão criptografados antes de serem enviados. + Enviar e-mails e números de telefone + Autorizar + Revogar minha autorização + Você não autorizou o envio de e-mails e números de telefone para este servidor de identidade, de modo a descobrir outras pessoas a partir dos seus contatos. + Você autorizou o envio de e-mails e números de telefone para este servidor de identidade, de modo a descobrir outras pessoas a partir dos seus contatos. + Enviar e-mails e números de telefone + Sugestões + Contatos + Pessoas conhecidas + Código QR + Adicionar por código QR + Pesquise por nome ou ID + Aceite a permissão para acessar seus contatos. + Para escanear um código QR, você precisa permitir o acesso à câmera. + Começar a conversar \ No newline at end of file From 422c681e55504d9e3315de8306f8b064c3208a89 Mon Sep 17 00:00:00 2001 From: krikra01 Date: Fri, 27 Nov 2020 15:05:39 +0000 Subject: [PATCH 005/218] Translated using Weblate (Russian) Currently translated at 97.1% (1930 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 5b04407645..1064748828 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -2301,4 +2301,9 @@ Добавить изображение из Тема Название Комнаты + Вы дали свое согласие на отправку электронных писем и телефонных номеров на этот сервер идентификации для обнаружения других пользователей из ваших контактов. + Добавить по QR-коду + Поиск по имени или идентификатору + Разрешить доступ к вашим контактам. + Чтобы отсканировать QR-код, вам нужно разрешить доступ к камере. \ No newline at end of file From a5079f524344cbe09528d9be03762d7f4f7aab88 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 27 Nov 2020 16:34:29 +0000 Subject: [PATCH 006/218] Translated using Weblate (Albanian) Currently translated at 99.3% (1973 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- vector/src/main/res/values-sq/strings.xml | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index ce76f7c387..93e82a7cde 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2181,4 +2181,57 @@ Mesazh i drejtpërdrejtë Dërgo historik kërkesash për dhënie kyçesh S’ka më përfundime + Lidhje Matrix + Kod QR jo i skanuar! + Kod QR i pavlefshëm (URI e pavlefshme)! + S’mund t’i dërgoni mesazh të drejtpërdrejtë vetes! + Jepuani si tekst + Kërkoni për kontakte në Matrix + Caktoni avatar + S’është dhënë pranimi nga përdoruesi. + Jepuani këtë kod njerëzve, që të mund ta skanojnë për t’ju shtuar dhe për të filluar të bisedoni. + Kodi im + Ndaje kodin tim me të tjerët + Skanoni një kod QR + S’është kod QR Matrix i vlefshëm + Takohuni me mua në Element + Hej, bisedoni me mua në Element: %s + Ftoni shokë + Shtoni persona + "Temë: " + Shtoni një temë + %s, që t’u bëni me dije njerëzve se për çfarë është kjo dhomë. + Ky është fillimi i historikut të mesazheve tuaj të drejtpërdrejtë me %s. + Ky është fillimi i kësaj bisede. + Ky është fillimi i %s. + S’keni leje të aktivizoni fshehtëzim në këtë dhomë. + Po krijohet dhoma… + Disa nga shenja nuk lejohen + Ju lutemi, jepni një adresë dhome + Kjo adresë është e përdorur tashmë + Adresë dhome + Mund ta aktivizoni këtë, nëse dhoma do të përdoret vetëm për bashkëpunim mes ekipesh të brendshëm në shërbyesin tuaj Home. Kjo s’mund të ndryshohet më vonë. + Blloko përgjithnjë pjesëmarrjen në këtë dhomë të kujtdo që s’është pjesë e %s + Fshihi të mëtejshmet + Shfaq të mëtejshme + %1$d nga %2$d + Krijoni një bisedë të re të drejtpërdrejtë duke skanuar një kod QR + Krijoni një bisedë të re të drejtpërdrejtë përmes ID-je Matrix + Që të mund të zbulohet kontakte ekzistuese që njihni, pranoni të dërgohen të dhënat tuaja të kontaktit (numra telefonash dhe/ose email-e) te Shërbyesi i formësuar për Identitete (%1$s)\? +\n +\nPër më tepër privatësi, të dhënat e dërguara do të kodohen, para se të dërgohen. + Dërgoni email-e dhe numra telefonash + Jepe pranimin + Shfuqizoje pranimin tim + S’keni dhënë pranimin tuaj për të dërguar email-e dhe numra telefonash te ky shërbyes identitetesh që të zbulojë përdorues të tjerë prej kontakteve tuaj. + Keni dhënë pranimin tuaj për të dërguar email-e dhe numra telefonash te ky shërbyes identitetesh që të zbulojë përdorues të tjerë prej kontakteve tuaj. + Dërgo email-e dhe numra telefonash + Sugjerime + Kontakte + Përdorues të Ditur + Kod QR + Shtoni përmes kodi QR + Kërkoni sipas emri ose ID-je + Që të skanoni një kod QR, lypset të lejoni përdorim kamere. + Filloni të Llafoseni \ No newline at end of file From 88e05ffd05611eeb58f9011951f15e4c3429f7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 27 Nov 2020 22:25:31 +0000 Subject: [PATCH 007/218] Translated using Weblate (Estonian) Currently translated at 100.0% (210 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/et/ --- .../src/main/res/values-et/strings.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-et/strings.xml b/matrix-sdk-android/src/main/res/values-et/strings.xml index 71ee50f075..957c0b9955 100644 --- a/matrix-sdk-android/src/main/res/values-et/strings.xml +++ b/matrix-sdk-android/src/main/res/values-et/strings.xml @@ -213,4 +213,27 @@ %1$s liitus Sina alustasid vestlust %1$s alustas vestlust + Tühi jututuba (oli %s) + + %1$s, %2$s, %3$s ja %4$d muu + %1$s, %2$s, %3$s ja %4$d muud + + %1$s, %2$s, %3$s ja %4$s + %1$s, %2$s ja %3$s + 🎉 Kõikide serverite osalemine on keelatud! Seda jututuba ei saa enam kasutada. + Muudatusi ei ole. + • Nüüd on keelatud serverid, mille ip-aadress vastab mustrile. + • Nüüd on lubatud serverid, mille ip-aadress vastab mustrile. + • Server, mille nimes leidub %s, eemaldati lubatud serverite loendist. + • Nüüd on lubatud serverid, mille nimes leidub %s. + • Server, mille nimes leidub %s eemaldati keeluloendist. + • Keelatud on server, mille nimes leidub %s. + Sina muutsid selle jututoa jaoks serverite pääsuloendit. + %s muutis selle jututoa jaoks serverite pääsuloendit. + Sina kirjeldasid selle jututoa jaoks serverite pääsuloendi. + %s kirjeldas selle jututoa jaoks serverite pääsuloendi. + • Keelatud on serverid, mille ip-aadress vastab mustrile. + • Lubatud on serverid, mille ip-aadress vastab mustrile. + • Lubatud on serverid, mille nimes leidub %s. + • Keelatud on serverid, mille nimes leidub %s. \ No newline at end of file From eb17463b68599e6834404f6308133f6ce44fcd1f Mon Sep 17 00:00:00 2001 From: sblondon Date: Fri, 27 Nov 2020 14:28:40 +0000 Subject: [PATCH 008/218] Translated using Weblate (French) Currently translated at 100.0% (210 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/fr/ --- .../src/main/res/values-fr/strings.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-fr/strings.xml b/matrix-sdk-android/src/main/res/values-fr/strings.xml index 9c5fa7d3b1..f49c54a8ba 100644 --- a/matrix-sdk-android/src/main/res/values-fr/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fr/strings.xml @@ -213,4 +213,27 @@ Vous avez exclus %1$s Vous avez révoqué l\'exclusion de %1$s Vous avez expulsé %1$s. Raison : %2$s + Salon vide (était %s) + + %1$s, %2$s, %3$s et %4$d autre + %1$s, %2$s, %3$s et %4$d autres + + %1$s, %2$s, %3$s et %4$s + %1$s, %2$s et %3$s + 🎉 Tous les serveurs sont interdits de participer ! Ce salon ne peut plus être utilisé. + Aucun changement. + • Les serveurs correspondant à des IP littérales sont maintenant interdits. + • Les serveurs correspondant à %s sont interdits. + • Les serveurs correspondants à des IP littérales sont interdites. + • Les serveurs correspondants à des IP littérales sont autorisés. + • Les serveurs correspondants à des IP littérales sont maintenant autorisées. + • Les serveurs correspondant à %s sont supprimés de la liste autorisée. + • les serveur correspondant à %s sont maintenant autorisés. + • Les serveurs correspondant à %s étaient supprimés de la liste des interdits. + • Les serveurs correspondant à %s sont maintenant interdits. + Vous avez changé les droits ACL du serveur pour ce salon. + %s a changé les droits ACL du serveur pour ce salon. + • Les serveurs correspondant à %s sont autorisés. + Vous avez paramétré les ACL pour ce salon. + %s paramètre les autorisations étendues (ACL) du serveur pour ce salon. \ No newline at end of file From dd50399a212255ab31106305219c2c583083f22a Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Fri, 27 Nov 2020 16:57:28 +0000 Subject: [PATCH 009/218] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (210 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/pt_BR/ --- .../src/main/res/values-pt-rBR/strings.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml index ed9f91cdb3..e6c93cb55c 100644 --- a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml +++ b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml @@ -221,4 +221,27 @@ %1$s entrou Você criou a sala %1$s criou a sala + Sala vazia (era %s) + + %1$s, %2$s, %3$s e %4$d outro + %1$s, %2$s, %3$s e %4$d outros + + %1$s, %2$s, %3$s e %4$s + %1$s, %2$s e %3$s + 🎉 Todos os servidores estão proibidos de participar! Esta sala não pode mais ser usada. + Nenhuma alteração. + • Servidores correspondentes aos IP literais agora estão banidos. + • Servidores correspondentes aos IP literais agora estão permitidos. + • Servidores correspondentes à %s foram removidos da lista de permitidos. + • Servidores correspondentes à %s agora são permitidos. + • Servidores correspondente à %s foram removidos da lista de banidos. + • Servidores correspondentes à %s foram banidos. + Você alterou a lista de controle de acesso (ACL) do servidor para esta sala. + %s alterou a lista de controle de acesso (ACL) do servidor para esta sala. + • Servidores correspondentes aos IP literais estão banidos. + • Servidores correspondentes aos IP literais estão permitidos. + • Servidores correspondentes à %s estão permitidos. + • Servidores correspondentes à %s estão banidos. + Você definiu a lista de controle de acesso (ACL) do servidor para esta sala. + %s definiu a lista de controle de acesso (ACL) do servidor para esta sala. \ No newline at end of file From 431f5d76ce67e203af6d000b80cd8393ea25b233 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 27 Nov 2020 16:12:40 +0000 Subject: [PATCH 010/218] Translated using Weblate (Albanian) Currently translated at 95.2% (200 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/sq/ --- .../src/main/res/values-sq/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-sq/strings.xml b/matrix-sdk-android/src/main/res/values-sq/strings.xml index 4055b35025..6387ebb3bd 100644 --- a/matrix-sdk-android/src/main/res/values-sq/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml @@ -213,4 +213,17 @@ %1$s erdhi Krijuat diskutimin %1$s krijoi diskutimin + Dhomë e zbrazët (was %s) + + %1$s, %2$s, %3$s dhe %4$d tjetër + %1$s, %2$s, %3$s dhe %4$d të tjerë + + %1$s, %2$s, %3$s dhe %4$s + %1$s, %2$s dhe %3$s + 🎉 U është penguar pjesëmarrja krejt shërbyesve! Kjo dhomë s’mund të përdoret më. + Pa ndryshim. + Ndryshuat ACL-ra shërbyesi për këtë dhomë. + %s ndryshoi ACL-ra shërbyesi për këtë dhomë. + Ujdisët ACL-ra shërbyesi për këtë dhomë. + %s ujdisi ACL-ra shërbyesi për këtë dhomë. \ No newline at end of file From f14b3908498366ad7532f0cb8568d888ab621b2f Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Fri, 27 Nov 2020 16:30:27 +0000 Subject: [PATCH 011/218] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/ --- fastlane/metadata/android/pt_BR/changelogs/40100100.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/pt_BR/changelogs/40100100.txt b/fastlane/metadata/android/pt_BR/changelogs/40100100.txt index 02cfd45a87..4884d7f62a 100644 --- a/fastlane/metadata/android/pt_BR/changelogs/40100100.txt +++ b/fastlane/metadata/android/pt_BR/changelogs/40100100.txt @@ -1 +1,2 @@ -// A FAZER +Esta nova versão contém principalmente correções de erros e melhorias. Enviar mensagens agora é muito mais rápido. +Registro de todas as alterações: https://github.com/vector-im/element-android/releases/tag/v1.0.10 From f6cc05634ff3852be95b20a35c783bb1c0e59660 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 16:38:46 +0100 Subject: [PATCH 012/218] Send task: small rework and cleanup --- .../room/send/queue/EventSenderProcessor.kt | 6 +---- .../session/room/send/queue/QueueMemento.kt | 1 - .../session/room/send/queue/QueuedTask.kt | 12 +++++++--- .../room/send/queue/RedactQueuedTask.kt | 22 +++++++------------ .../room/send/queue/SendEventQueuedTask.kt | 10 ++------- 5 files changed, 20 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt index 62e225c624..b8f6e52674 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt @@ -106,11 +106,7 @@ internal class EventSenderProcessor @Inject constructor( // non blocking add to queue sendingQueue.add(task) markAsManaged(task) - return object : Cancelable { - override fun cancel() { - task.cancel() - } - } + return task } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt index e69c65ec4c..dfbac347d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.room.send.queue import android.content.Context -import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.send.SendState diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt index bccbc97ff4..e5c1cf7435 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt @@ -16,14 +16,20 @@ package org.matrix.android.sdk.internal.session.room.send.queue -abstract class QueuedTask { +import org.matrix.android.sdk.api.util.Cancelable + +abstract class QueuedTask : Cancelable { var retryCount = 0 + private var hasBeenCancelled: Boolean = false + abstract suspend fun execute() abstract fun onTaskFailed() - abstract fun isCancelled() : Boolean + open fun isCancelled() = hasBeenCancelled - abstract fun cancel() + final override fun cancel() { + hasBeenCancelled = true + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt index a3c19a1f7c..e2fb978cd0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt @@ -22,18 +22,16 @@ import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository internal class RedactQueuedTask( - val toRedactEventId: String, + private val toRedactEventId: String, val redactionLocalEchoId: String, - val roomId: String, - val reason: String?, - val redactEventTask: RedactEventTask, - val localEchoRepository: LocalEchoRepository, - val cancelSendTracker: CancelSendTracker + private val roomId: String, + private val reason: String?, + private val redactEventTask: RedactEventTask, + private val localEchoRepository: LocalEchoRepository, + private val cancelSendTracker: CancelSendTracker ) : QueuedTask() { - private var _isCancelled: Boolean = false - - override fun toString() = "[RedactEventRunnableTask $redactionLocalEchoId]" + override fun toString() = "[RedactQueuedTask $redactionLocalEchoId]" override suspend fun execute() { redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason)) @@ -44,10 +42,6 @@ internal class RedactQueuedTask( } override fun isCancelled(): Boolean { - return _isCancelled || cancelSendTracker.isCancelRequestedFor(redactionLocalEchoId, roomId) - } - - override fun cancel() { - _isCancelled = true + return super.isCancelled() || cancelSendTracker.isCancelRequestedFor(redactionLocalEchoId, roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt index 21a4145a9d..f934aad67b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt @@ -33,9 +33,7 @@ internal class SendEventQueuedTask( val cancelSendTracker: CancelSendTracker ) : QueuedTask() { - private var _isCancelled: Boolean = false - - override fun toString() = "[SendEventRunnableTask ${event.eventId}]" + override fun toString() = "[SendEventQueuedTask ${event.eventId}]" override suspend fun execute() { sendEventTask.execute(SendEventTask.Params(event, encrypt)) @@ -56,10 +54,6 @@ internal class SendEventQueuedTask( } override fun isCancelled(): Boolean { - return _isCancelled || cancelSendTracker.isCancelRequestedFor(event.eventId, event.roomId) - } - - override fun cancel() { - _isCancelled = true + return super.isCancelled() || cancelSendTracker.isCancelRequestedFor(event.eventId, event.roomId) } } From cd983de058557ae0bd9902138d80f863dfa78323 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Nov 2020 10:08:31 +0100 Subject: [PATCH 013/218] Fix cancellation of sending event (#2438) --- CHANGES.md | 2 +- .../session/room/send/DefaultSendService.kt | 2 ++ .../room/send/queue/EventSenderProcessor.kt | 14 ++++++++++++++ .../internal/session/room/send/queue/QueuedTask.kt | 8 +++++++- .../session/room/send/queue/RedactQueuedTask.kt | 2 +- .../session/room/send/queue/SendEventQueuedTask.kt | 2 +- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e48281081b..7f626da497 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Fix cancellation of sending event (#2438) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index b13ce15da6..5a71ff7b76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -210,6 +210,8 @@ internal class DefaultSendService @AssistedInject constructor( override fun cancelSend(eventId: String) { cancelSendTracker.markLocalEchoForCancel(eventId, roomId) + // This is maybe the current task, so cancel it too + eventSenderProcessor.cancel(eventId, roomId) taskExecutor.executorScope.launch { localEchoRepository.deleteFailedEcho(roomId, eventId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt index b8f6e52674..5014d94558 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room.send.queue +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.auth.data.SessionParams @@ -109,10 +110,18 @@ internal class EventSenderProcessor @Inject constructor( return task } + fun cancel(eventId: String, roomId: String) { + (currentTask as? SendEventQueuedTask) + ?.takeIf { it -> it.event.eventId == eventId && it.event.roomId == roomId } + ?.cancel() + } + companion object { private const val RETRY_WAIT_TIME_MS = 10_000L } + private var currentTask: QueuedTask? = null + private var sendingQueue = LinkedBlockingQueue() private var networkAvailableLock = Object() @@ -125,6 +134,7 @@ internal class EventSenderProcessor @Inject constructor( while (!isInterrupted) { Timber.v("## SendThread wait for task to process") val task = sendingQueue.take() + .also { currentTask = it } Timber.v("## SendThread Found task to process $task") if (task.isCancelled()) { @@ -179,6 +189,10 @@ internal class EventSenderProcessor @Inject constructor( task.onTaskFailed() throw InterruptedException() } + exception is CancellationException -> { + Timber.v("## SendThread task has been cancelled") + break@retryLoop + } else -> { Timber.v("## SendThread retryLoop Un-Retryable error, try next task") // this task is in error, check next one? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt index e5c1cf7435..9a7fcd8d91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt @@ -23,7 +23,13 @@ abstract class QueuedTask : Cancelable { private var hasBeenCancelled: Boolean = false - abstract suspend fun execute() + suspend fun execute() { + if (!isCancelled()) { + doExecute() + } + } + + abstract suspend fun doExecute() abstract fun onTaskFailed() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt index e2fb978cd0..8e7ba2f155 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt @@ -33,7 +33,7 @@ internal class RedactQueuedTask( override fun toString() = "[RedactQueuedTask $redactionLocalEchoId]" - override suspend fun execute() { + override suspend fun doExecute() { redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason)) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt index f934aad67b..ea097082c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/SendEventQueuedTask.kt @@ -35,7 +35,7 @@ internal class SendEventQueuedTask( override fun toString() = "[SendEventQueuedTask ${event.eventId}]" - override suspend fun execute() { + override suspend fun doExecute() { sendEventTask.execute(SendEventTask.Params(event, encrypt)) } From 7bd8d54d5c1aec326c6b411e0efa4f67451e2192 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Mon, 30 Nov 2020 03:08:55 +0000 Subject: [PATCH 014/218] Translated using Weblate (Persian) Currently translated at 99.4% (1976 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 74 ++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index ae3292bb2c..99b86327d9 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -1699,7 +1699,7 @@ برای امنیت بیشتر، %s را با بررسی یک کد یکبارمصرف تایید کنید. پس از فعال‌شدن ، نمی‌توان رمزگذاری برای یک اتاق را غیرفعال کرد. پیام های ارسالی در یک اتاق رمزگذاری شده توسط سرور قابل مشاهده نیست و فقط توسط اعضای اتاق قابل مشاهده است. فعال کردن رمزنگاری ممکن است از کارکرد بسیاری از ربات ها و پل ها جلوگیری کند. رمزگذاری فعال شود؟ - فعال‌سازی رمزنگاری سرتاسر + به کار انداختن رمزنگاری سرتاسری… شکلک را با رنگ‌بندی رنگین گمان ارسال می کند پیام را با رنگ‌بندی رنگین کمان ارسال می کند این نشست نمی‌تواند تائید را با نشست‌های دیگر شما به اشتراک بگذارد. @@ -2159,4 +2159,76 @@ پیام مستقیم ارسال تاریخچهٔ درخواست‌های هم‌رسانی کلید نتایج بیش‌تری نیست + کلید امنیتیتان را در جایی امن، مانند مدیر گذرواژه یا گاوصندوق نگه دارید. + عبارت امنیتی‌ای را که تنها خودتان می‌دانید، برای امن کردن رمزها روی کارسازتان وارد کنید. + کلید امنیتیتان را در جایی امن، مانند مدیر گذرواژه یا گاوصندوق نگه دارید. + عبارت رمزی که فقط خودتان می‌دانید را وارد کرده و کلیدی برای پشتیبان تولید کنید. + به هنوتم جایگزین، می‌توانید نشامی هر کارساز هویت دیگری را وارد کنید + کارساز خانگیتان (%1$s) پیشنهاد استفاده از %2$s برای کارساز هویتتان را می‌دهد + برای محرمانگیتان، المنت تنها از فرستادن شماره تلفن و رایانامه‌های کاربری در هم ریخته پشتیبانی می‌کند. + این عملیات ممکن نیست. کارساز خانگی منقضی شده است. + نتوانستیم کاربران را دعوت کنیم. لطفاً کاربرانی که می‌خواهید دعوت کنید را بررسی کرده و دوباره تلاش کنید. + نتوانستیم پیامتان را ایجاد کنیم. لطفاً کاربرانی که می‌خواهید دعوت کنید را بررسی کرده و دوباره تلاش کنید. + انسداد هرکسی خارج از %s از پیوستن به این اتاق + پیوند ماتریکس + دور ریختن تغییرات + تغییرات ذخیره‌نشده‌ای وجود دارد. دور ریختن تغییرات؟ + اتاق هنوز ایجاد نشده است. لغو ایجاد اتاق؟ + رمز QR پویش نشد! + رمز QR نامعتبر (نشانی نامعتبر)! + نمی‌توانید به خودتان پیام دهید! + هم‌رسانی با متن + جست‌وجوی مخاطبان در ماتریکس + تنظیم آواتار + ذخیرهٔ کلید امنیتیتان + برای تأیید عبارت امنیتیتان، دوباره واردش کنید. + عبارت امنیتی + تنظیم یک عبارت امنیتی + ذخیرهٔ کلید امنیتیتان + استفاده از یک عبارت امنیتی + نشانی کارساز هویتی را وارد کنید + رضایت کاربر فراهم نشده است. + لطفاً نخست شرایط کارساز هویت را در تنظیمات بپذیرید. + لطفاً نخست کارساز هویتی را پیکربندی کنید. + کارساز هویت منقضی شده اسن. المنت تنها نگارش ۲ از API را پشتیبانی می‌کند. + این رمز را افراد هم‌رسانی کرده تا بتوانند برای افزودنتان و شروع گپ، بپویندش. + رمزم + هم‌رسانی رمزم + پویش یک رمز QR + این یک رمز QR ماتریکس معتبر نیست + 🔐️ پیوستن به من روی المنت + سلام. روی المنت باهام حرف بزن: %s + دعوت دوستان + افزودن افراد + "موضوع: " + افزودن موضوع + %s برای دادن اجازهٔ دانستن موضوع این اتاق به افراد. + این، آغاز تاریخچهٔ پیام مستقیمتان با %s است. + این، آغاز %s است. + این، آغاز گفت‌وگوست. + اجازهٔ به کار انداختن رمزنگاری در این اتاق را ندارید. + ایجاد کردن اتاق… + برخی نویسه‌ها مجاز نیستند + لطفاً نام اتاقی را وارد کنید + این نشانی در حال استفاده است + نشانی اتاق + نهفتن پیشرفته + نمایش پیشرفته + %1$d از %2$d + ایجاد گفت‌وگوی مسنقیمی جدید با پویش یک رمز QR + ایجاد گفت‌وگوی مستقیمی جدید با شناسهٔ ماتریکس + فرستادن رایانامه‌ها و شماره تلفن‌ها + رضایت دادن + ابطال رضایتم + فرستادن رایانامه‌ها و شماره تلفن‌ها + پیشنهادها + مخاطبان + کاربران شناخته + اخیر + رمز QR + افزودن با رمز QR + جست‌وجو با نام یا شناسه + دسترسی به مخاطبانتان را مجاز کنید. + برای پویش یک رمز QR نیاز است دسترسی به دوربین را مجاز کنید. + آغاز به گپ \ No newline at end of file From b53ba2b98e7913c004b66a9d8d72f6e43d381936 Mon Sep 17 00:00:00 2001 From: Kaede Date: Mon, 30 Nov 2020 01:35:50 +0000 Subject: [PATCH 015/218] Translated using Weblate (Japanese) Currently translated at 49.8% (991 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index e764f5001b..45bb33c807 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1098,4 +1098,18 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ グループ通話を開始する権限がありません リセット なし + トピック + 部屋名 + この部屋内のメッセージはエンドツーエンド暗号化されていません。 + ここでのメッセージはエンドツーエンド暗号化されていません。 + 設定 + あなたにはこの部屋の暗号化を有効にする権限がありません。 + 未読メッセージ + タイムラインでスワイプして返信を有効にする + タイムラインで非表示のイベントを表示する + QR コードをスキャン + QR コード画像 + QR コード + QR コードによる追加 + コードを共有 \ No newline at end of file From e42906f08ad3171ed6eadd1227520c688a2019dc Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Mon, 30 Nov 2020 12:41:04 +0000 Subject: [PATCH 016/218] Translated using Weblate (Russian) Currently translated at 99.3% (1973 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 1064748828..2974a3374b 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -2306,4 +2306,49 @@ Поиск по имени или идентификатору Разрешить доступ к вашим контактам. Чтобы отсканировать QR-код, вам нужно разрешить доступ к камере. + Ссылка Matrix + QR-код не отсканирован! + Недействительный QR-код (недопустимый URI)! + Нельзя отправлять сообщения самому себе! + Поделиться по тексту + Поиск контактов в Matrix + Установить аватар + Согласие пользователя не предоставлено. + Поделитесь этим кодом с людьми, чтобы они могли отсканировать его, добавить вас и начать общение. + Мой код + Поделиться моим кодом + Сканировать QR-код + Это недействительный QR-код matrix + 🔐️ Присоединяйтесь ко мне в Element + Привет, поговори со мной в Element: %s + Пригласить друзей + Добавить людей + "Тема: " + Добавить тему + Это начало разговора. + Это начало %s. + У вас нет разрешения на включение шифрования в этой комнате. + Создание комнаты… + Некоторые символы не разрешены + Укажите адрес комнаты + Этот адрес уже используется + Адрес комнаты + Вы можете включить это, если комната будет использоваться только для совместной работы с внутренними командами на вашем домашнем сервере. Это не может быть изменено позже. + Запретить кому-либо, не входящему в %s, когда-либо присоединяться к этой комнате + Скрыть дополнительные настройки + Показать дополнительные настройки + %1$d из %2$d + Согласны ли вы на отправку своих контактных данных (номера телефонов и/или электронную почту) на настроенный сервер идентификации (%1$s) для обнаружения контактов\? +\n +\nДля большей конфиденциальности отправленные данные перед отправкой будут хешированы. + Дать согласие + Отозвать моё согласие + Вы не дали свое согласие на отправку электронной почты и номеров телефонов на этот сервер идентификации для обнаружения других пользователей из ваших контактов. + Больше никаких результатов + Предложения + Контакты + Известные пользователи + QR-код + Отправить историю запросов на обмен ключами + Начать беседу \ No newline at end of file From 6236b01189aaf28cad8dea44dc1fafb9ec0ab609 Mon Sep 17 00:00:00 2001 From: HolgerHuo Date: Mon, 30 Nov 2020 12:00:52 +0000 Subject: [PATCH 017/218] Translated using Weblate (Chinese (Simplified)) Currently translated at 96.7% (1921 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index bcb449cabb..db7a1c4b89 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -702,7 +702,7 @@ 请允许资料分析以帮助我们改进 Element。 是的,我愿意帮助! 停用账户 - + 请在我停用账户的同时忘记我发送的所有消息(警告:这将导致未来的用户看到残缺的对话) 请输入您的密码以继续: 停用账户 @@ -2148,4 +2148,8 @@ 您没有权限发起通话 您没有权限发起会议通话 重置 + 允许访问您的联系人。 + 如需扫描二维码,您须允许相机访问权限。 + 没有更多结果 + 开始畅聊 \ No newline at end of file From 1c3a279b8acee7d5cb7372635de4059adc3611fd Mon Sep 17 00:00:00 2001 From: sr093906 Date: Mon, 30 Nov 2020 09:14:04 +0000 Subject: [PATCH 018/218] Translated using Weblate (Chinese (Simplified)) Currently translated at 96.7% (1921 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index db7a1c4b89..4c89c5a26f 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -1723,7 +1723,7 @@ 发送彩虹色给定表情 时间线 消息编辑器 - 启用端到端加密 + 启用端到端加密… 一旦启用,加密无法禁用。 是否启用加密? 启用后,将无法禁用聊天室加密。服务器无法看到加密聊天室中发送的消息,只有聊天室的参与者才能看到。启用加密可能会阻止许多机器人和桥接正常工作。 From 3b21400bb85306bfafd98bebdb99c1c4a5231af5 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 30 Nov 2020 02:32:10 +0000 Subject: [PATCH 019/218] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 8d019a7635..d39254d506 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1623,7 +1623,7 @@ 將指定的表情符號以彩虹的方式上色後傳送 時間軸 訊息編輯器 - 啟用端到端加密 + 啟用端到端加密…… 一旦啟用加密就無法停用。 啟用加密? 一旦啟用對聊天室的加密就無法停用。傳送到已加密聊天室的訊息無法被伺服器看見,僅有聊天室的參與者可見。啟用加密可能會讓許多機器人與橋接無法運作。 @@ -2153,4 +2153,59 @@ 直接訊息 傳送金鑰共享請求歷史 沒有更多結果 + Matrix 連結 + 未掃描 QR code! + 無效的 QR code(無效的 URI)! + 無法對您自己直接訊息! + 透過文字分享 + 在 Matrix 上搜尋聯絡人 + 設定大頭照 + 使用者尚未提供其同意。 + 與夥伴們分享此條碼,這樣他們就可以掃描它來加入您並開始聊天。 + 我的條碼 + 分享我的條碼 + 掃描 QR code + 不是有效的 Matrix QR code + 🔐️ 在 Element 上加入我 + 嗨,和我在 Element 上聊天吧:%s + 邀請朋友 + "主題: " + 新增夥伴 + 新增主題 + %s 讓人們知道這個聊天室是做什麼用的。 + 這是您與 %s 直接訊息歷史紀錄的開頭。 + 這是此對話的開頭。 + 這是 %s 的開頭。 + 您沒有在此聊天室中啟用加密的權限。 + 正在建立聊天室…… + 不允許部份字元 + 請提供聊天室地址 + 此地址已被使用 + 聊天室地址 + 如果聊天室僅用於與您的家伺服氣上的內部團隊協作的話,可以啟用此功能。但無法在稍後變更。 + 封鎖任何不是 %s 一部分的人加入此聊天室 + 隱藏進階 + 顯示進階 + %2$d 中的 %1$d + 透過掃描 QR code 建立新的直接對話 + 透過 Matrix ID 建立新的直接對話 + 為了探索您已知的既有聯絡人,您是否接受將您的聯絡人資料(電話號碼與/或電子郵件)傳送到設定好的身份伺服器 (%1$s)? +\n +\n為了更好的隱私,要傳送的資料將會先雜湊過再傳送。 + 傳送電子郵件與電話號碼 + 給予同意 + 撤銷我的同意 + 您尚未同意傳送電子郵件與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。 + 您已同意傳送電子郵件與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。 + 傳送電子郵件或電話號碼 + 建議 + 聯絡人 + 已知的使用者 + 最近 + QR code + 透過 QR code 新增 + 使用名稱或 ID 搜尋 + 允許存取您聯絡人的權限。 + 要掃描 QR code,您必須允許存取攝影機。 + 開始聊天 \ No newline at end of file From 6d0f9baba4907bd166b4654f555dd50fef05f2ee Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Mon, 30 Nov 2020 12:49:10 +0000 Subject: [PATCH 020/218] Translated using Weblate (Russian) Currently translated at 92.8% (195 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/ru/ --- .../src/main/res/values-ru/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-ru/strings.xml b/matrix-sdk-android/src/main/res/values-ru/strings.xml index ef9cda1dc2..84a3ac3ec3 100644 --- a/matrix-sdk-android/src/main/res/values-ru/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml @@ -227,4 +227,16 @@ %1$s создал(а) обсуждение Вы обновили. %s обновлена. + + %1$s, %2$s, %3$s и %4$d другой + %1$s, %2$s, %3$s и %4$d других + %1$s, %2$s, %3$s и %4$d другие + %1$s, %2$s, %3$s и %4$d другие + + %1$s, %2$s, %3$s и %4$s + %1$s, %2$s и %3$s + 🎉 Всем серверам запрещено участвовать! Эта комната больше не может быть использована. + Без изменений. + Пустая комната (была %s) + • Соответствующий сервер %s заблокирован. \ No newline at end of file From 088608011b0f1e77773d40eeb2b246c8e6778480 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 30 Nov 2020 02:47:25 +0000 Subject: [PATCH 021/218] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (210 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/zh_Hant/ --- .../src/main/res/values-zh-rTW/strings.xml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml index b3de5910a5..08050b400d 100644 --- a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml +++ b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml @@ -208,4 +208,26 @@ %1$s 已加入 您已建立此討論 %1$s 已建立此討論 + 空的聊天室(曾為 %s) + + %1$s, %2$s, %3$s 與 %4$d 個其他 + + %1$s, %2$s, %3$s 與 %4$s + %1$s, %2$s 與 %3$s + 🎉 禁止所有伺服器參與!無法再使用此聊天室。 + 無變更。 + • 禁止伺服器符合 IP 文字。 + • 允許伺服器符合 IP 文字。 + • 伺服器符合 %s 已從允許清單中移除。 + • 允許伺服器符合 %s。 + • 伺服器符合 %s 已從禁止清單中移除。 + • 現在禁止伺服器符合 %s。 + 您為此聊天室變更了伺服器 ACL。 + %s 為此聊天是變更了伺服器 ACL。 + • 禁止伺服器符合 IP 文字。 + • 允許伺服器符合 IP 文字。 + • 已允許伺服器符合 %s。 + • 已禁止伺服器符合 %s。 + 您為此聊天是設定了伺服器 ACL。 + %s 為此聊天是設定了伺服器 ACL。 \ No newline at end of file From 313393506378b362e953f95a0817240cd7bc0c50 Mon Sep 17 00:00:00 2001 From: strix aluco Date: Mon, 30 Nov 2020 05:34:25 +0000 Subject: [PATCH 022/218] Translated using Weblate (Ukrainian) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40100100.txt | 2 ++ fastlane/metadata/android/uk/full_description.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/uk/changelogs/40100100.txt diff --git a/fastlane/metadata/android/uk/changelogs/40100100.txt b/fastlane/metadata/android/uk/changelogs/40100100.txt new file mode 100644 index 0000000000..e5333ae561 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40100100.txt @@ -0,0 +1,2 @@ +Ця версія містить переважно виправлення помилок та деякі покращення. Відправлення повідомлень стало тепер ще швидшим. +Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt index 64247581d2..026ae4162a 100644 --- a/fastlane/metadata/android/uk/full_description.txt +++ b/fastlane/metadata/android/uk/full_description.txt @@ -7,7 +7,7 @@ Element — це застосунок для спілкування та спі Element ґрунтовно відрізняється від інших застосунків для спілкування та співпраці тому що він є децентралізованим та відкритоджерельним. -Element дозволяє вам розміщувати сервер в себе або обирати будь-якого з надавачів послуг, таким чином забезпечуючи вам конфіденційність і можливість володіти власними даними й бесідами та контролювати їх. Він надає вам доступ до відкритої мережі, тож ви не є обмеженими спілкуванням виключно з користувачами Element. І він є дуже надійним та безпечним. +Element дозволяє вам розміщувати сервер в себе або обирати будь-якого з надавачів послуг, таким чином забезпечуючи вам конфіденційність і можливість володіти власними даними й бесідами та контролювати їх. Він надає вам доступ до відкритої мережі, тож ви не є обмеженими спілкуванням виключно з користувачами Element. І він є дуже надійним та безпечним. Element здатен забезпечити усе це завдяки тому, що він заснований на протоколі Matrix — стандарті для відкритого та децентралізованого спілкування. From 04914be442845798712b97dd4a2ef7d6ee0a147a Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 30 Nov 2020 02:12:17 +0000 Subject: [PATCH 023/218] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh_Hant/changelogs/40100100.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt b/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt index 3c21bcbeb6..0ea092ba9a 100644 --- a/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt +++ b/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt @@ -1 +1,2 @@ -// 待辦事項 +這個新版本主要包含錯誤修復與改善。傳送訊息更快了。 +完整的變更紀錄請見:https://github.com/vector-im/element-android/releases/tag/v1.0.10 From cafe86e67508ae5264985afecf357fe2cb921a32 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 09:56:26 +0100 Subject: [PATCH 024/218] Rework: create a MediaModule --- .../sdk/internal/session/SessionComponent.kt | 2 + .../session/homeserver/CapabilitiesAPI.kt | 7 ---- .../DefaultGetHomeServerCapabilitiesTask.kt | 17 ++++---- .../GetMediaConfigResult.kt} | 6 +-- .../sdk/internal/session/media/MediaAPI.kt | 30 ++++++++++++++ .../sdk/internal/session/media/MediaModule.kt | 39 +++++++++++++++++++ 6 files changed, 84 insertions(+), 17 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/{homeserver/GetUploadCapabilitiesResult.kt => media/GetMediaConfigResult.kt} (86%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index e6fd5a7a0c..659fcc8f5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.session.group.GroupModule import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesModule import org.matrix.android.sdk.internal.session.identity.IdentityModule import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule +import org.matrix.android.sdk.internal.session.media.MediaModule import org.matrix.android.sdk.internal.session.openid.OpenIdModule import org.matrix.android.sdk.internal.session.profile.ProfileModule import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker @@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers GroupModule::class, ContentModule::class, CacheModule::class, + MediaModule::class, CryptoModule::class, PushersModule::class, OpenIdModule::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt index 39b6608de3..8242edac84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt @@ -22,19 +22,12 @@ import retrofit2.Call import retrofit2.http.GET internal interface CapabilitiesAPI { - /** * Request the homeserver capabilities */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities") fun getCapabilities(): Call - /** - * Request the upload capabilities - */ - @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config") - fun getUploadCapabilities(): Call - /** * Request the versions */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index 8d289dfda5..f3686b02d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -29,6 +29,8 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerConfigExtractor +import org.matrix.android.sdk.internal.session.media.GetMediaConfigResult +import org.matrix.android.sdk.internal.session.media.MediaAPI import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.wellknown.GetWellknownTask @@ -40,6 +42,7 @@ internal interface GetHomeServerCapabilitiesTask : Task internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( private val capabilitiesAPI: CapabilitiesAPI, + private val mediaAPI: MediaAPI, @SessionDatabase private val monarchy: Monarchy, private val eventBus: EventBus, private val getWellknownTask: GetWellknownTask, @@ -67,9 +70,9 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } }.getOrNull() - val uploadCapabilities = runCatching { - executeRequest(eventBus) { - apiCall = capabilitiesAPI.getUploadCapabilities() + val mediaConfig = runCatching { + executeRequest(eventBus) { + apiCall = mediaAPI.getMediaConfig() } }.getOrNull() @@ -83,11 +86,11 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig)) }.getOrNull() - insertInDb(capabilities, uploadCapabilities, versions, wellknownResult) + insertInDb(capabilities, mediaConfig, versions, wellknownResult) } private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?, - getUploadCapabilitiesResult: GetUploadCapabilitiesResult?, + getMediaConfigResult: GetMediaConfigResult?, getVersionResult: Versions?, getWellknownResult: WellknownResult?) { monarchy.awaitTransaction { realm -> @@ -97,8 +100,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword() } - if (getUploadCapabilitiesResult != null) { - homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize + if (getMediaConfigResult != null) { + homeServerCapabilitiesEntity.maxUploadFileSize = getMediaConfigResult.maxUploadSize ?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetUploadCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetMediaConfigResult.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetUploadCapabilitiesResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetMediaConfigResult.kt index 92903bf96e..fece6c06c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetUploadCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetMediaConfigResult.kt @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.homeserver +package org.matrix.android.sdk.internal.session.media import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal data class GetUploadCapabilitiesResult( +internal data class GetMediaConfigResult( /** * The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content. * If not listed or null, the size limit should be treated as unknown. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt new file mode 100644 index 0000000000..dba693c724 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import org.matrix.android.sdk.internal.network.NetworkConstants +import retrofit2.Call +import retrofit2.http.GET + +internal interface MediaAPI { + /** + * Retrieve the configuration of the content repository + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config + */ + @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config") + fun getMediaConfig(): Call +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt new file mode 100644 index 0000000000..84a82dc75a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.internal.session.SessionScope +import retrofit2.Retrofit + +@Module +internal abstract class MediaModule { + + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesMediaAPI(retrofit: Retrofit): MediaAPI { + return retrofit.create(MediaAPI::class.java) + } + } + +// @Binds +// abstract fun bindGetHomeServerCapabilitiesTask(task: DefaultGetHomeServerCapabilitiesTask): GetHomeServerCapabilitiesTask +} From e5cbf9e3a3b5e18910fea328f6d97a9a8d4074ff Mon Sep 17 00:00:00 2001 From: Hivaa Date: Thu, 3 Dec 2020 11:01:04 +0000 Subject: [PATCH 025/218] Translated using Weblate (Persian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 99b86327d9..a3d377b3bc 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -299,7 +299,7 @@ رایانامه‌ای برای بازیابی تنظیم کرده تا بتوانید در صورت نیاز، از طریقش به دست افرادی که می‌شناسید، قابل کشف باشید. ثبت شماره تلفن (بعدا در صورت دلخواه می توانید از آن برای شناسایی دوستان خود استفاده کنید). نشانی رایانامهٔ پیوسته به حسابتان را برای بازنشانی گذواژه‌تان وارد کنید: - Latn + Arab شناسهٔ کاربری، نام یا رایانامه فرستادن پاسخی رمزشده… فرستادن یک پاسخ (رمزنشده)… @@ -2231,4 +2231,19 @@ دسترسی به مخاطبانتان را مجاز کنید. برای پویش یک رمز QR نیاز است دسترسی به دوربین را مجاز کنید. آغاز به گپ + خروجی گرفتن + اگر اتاق فقط برای تعامل با افراد داخل سرور خانه شما می‌باشد، این قابلیت را فعال کنید. این تنظیم را بعدا نمی‌توانید تغییر دهید. + آیا می‌خواهید جهت کشف مخاطبینی که می شناسید، داده های مخاطب خود را (شماره تلفن و ایمیل) به سرور هویت‌سنجی(%1$s) ارسال کنید؟ +\n +\nبرای حفظ حریم خصوصی بیشتر، داده‌ها قبل از ارسال هش می‌شوند. + یک کلید امنیتی ایجاد کنید تا در مکانی امن مانند سامانه مدیریت رمز عبور یا گاوصندوق آن را ذخیره کنید. + در حال حاضر هیچ ارتباطی با این شناسه وجود ندارد. + هویت خود را تأیید کنید تا به پیام‌های رمز شده دسترسی پیدا کنید. + هویت خود را با تأیید این ورود به سیستم از یکی دیگر از نشست‌های خود، تأیید کنید تا به پیام‌های رمز شده دسترسی پیدا کنید. + + دستگاهی را که می‌توانید با استفاده از آن‌ها خود را تایید کنید نشان بده + %d دستگاهی را که می‌توانید با استفاده از آن‌ها خود را تایید کنید نشان بده + + شما عدم رضایت خود را برای ارسال ایمیل و شماره تلفن به این سرور اعلام کرده‌اید در نتیجه نمی‌توانید کاربران دیگر را با استفاده از مخاطبین خود پیدا کنید. + شما رضایت خود را برای ارسال ایمیل و شماره تلفن به این سرور اعلام کرده‌اید تا بتوانید کاربران دیگر را مخاطبین خود پیدا کنید. \ No newline at end of file From 3e563a37a22de883b32877bed61abc5c0cf1d3c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 13:43:04 +0100 Subject: [PATCH 026/218] Rework: Make RawCacheStrategy class more generic, to use it for other SDK API --- CHANGES.md | 2 +- .../CacheStrategy.kt} | 10 +++++----- .../org/matrix/android/sdk/api/raw/RawService.kt | 4 +++- .../sdk/internal/raw/DefaultGetUrlTask.kt | 16 ++++++++-------- .../sdk/internal/raw/DefaultRawService.kt | 8 ++++---- 5 files changed, 21 insertions(+), 19 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{raw/RawCacheStrategy.kt => cache/CacheStrategy.kt} (83%) diff --git a/CHANGES.md b/CHANGES.md index f6f63db534..06af610ecb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - RawCacheStrategy has been moved and renamed to CacheStrategy Build 🧱: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawCacheStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt similarity index 83% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawCacheStrategy.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt index f4eada559e..2880d851d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawCacheStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/cache/CacheStrategy.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.raw +package org.matrix.android.sdk.api.cache -sealed class RawCacheStrategy { +sealed class CacheStrategy { // Data is always fetched from the server - object NoCache : RawCacheStrategy() + object NoCache : CacheStrategy() // Once data is retrieved, it is stored for the provided amount of time. // In case of error, and if strict is set to false, the cache can be returned if available - data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean) : RawCacheStrategy() + data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean) : CacheStrategy() // Once retrieved, the data is stored in cache and will be always get from the cache - object InfiniteCache : RawCacheStrategy() + object InfiniteCache : CacheStrategy() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt index 19549a338e..f1722b2189 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.raw +import org.matrix.android.sdk.api.cache.CacheStrategy + /** * Useful methods to fetch raw data from the server. The access token will not be used to fetched the data */ @@ -23,7 +25,7 @@ interface RawService { /** * Get a URL, either from cache or from the remote server, depending on the cache strategy */ - suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String + suspend fun getUrl(url: String, cacheStrategy: CacheStrategy): String /** * Specific case for the well-known file. Cache validity is 8 hours diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt index 1f4ca6d627..16633d90ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.raw import com.zhuinden.monarchy.Monarchy import okhttp3.ResponseBody -import org.matrix.android.sdk.api.raw.RawCacheStrategy +import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.internal.database.model.RawCacheEntity import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.getOrCreate @@ -32,7 +32,7 @@ import javax.inject.Inject internal interface GetUrlTask : Task { data class Params( val url: String, - val rawCacheStrategy: RawCacheStrategy + val cacheStrategy: CacheStrategy ) } @@ -42,14 +42,14 @@ internal class DefaultGetUrlTask @Inject constructor( ) : GetUrlTask { override suspend fun execute(params: GetUrlTask.Params): String { - return when (params.rawCacheStrategy) { - RawCacheStrategy.NoCache -> doRequest(params.url) - is RawCacheStrategy.TtlCache -> doRequestWithCache( + return when (params.cacheStrategy) { + CacheStrategy.NoCache -> doRequest(params.url) + is CacheStrategy.TtlCache -> doRequestWithCache( params.url, - params.rawCacheStrategy.validityDurationInMillis, - params.rawCacheStrategy.strict + params.cacheStrategy.validityDurationInMillis, + params.cacheStrategy.strict ) - RawCacheStrategy.InfiniteCache -> doRequestWithCache( + CacheStrategy.InfiniteCache -> doRequestWithCache( params.url, Long.MAX_VALUE, true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt index 3b0d7546e5..42b826de16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.raw -import org.matrix.android.sdk.api.raw.RawCacheStrategy +import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.raw.RawService import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -25,15 +25,15 @@ internal class DefaultRawService @Inject constructor( private val getUrlTask: GetUrlTask, private val cleanRawCacheTask: CleanRawCacheTask ) : RawService { - override suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String { - return getUrlTask.execute(GetUrlTask.Params(url, rawCacheStrategy)) + override suspend fun getUrl(url: String, cacheStrategy: CacheStrategy): String { + return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy)) } override suspend fun getWellknown(userId: String): String { val homeServerDomain = userId.substringAfter(":") return getUrl( "https://$homeServerDomain/.well-known/matrix/client", - RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) + CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) ) } From 0c037184f857fc67a38c91022d5b5d6e971c4ce5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 13:46:25 +0100 Subject: [PATCH 027/218] Create a MediaService to handle UrlPreview request - WIP --- .idea/dictionaries/bmarty.xml | 1 + .../sdk/api/session/media/MediaService.kt | 45 +++++++++++++++++ .../sdk/api/session/media/PreviewUrlData.kt | 49 +++++++++++++++++++ .../sdk/internal/session/media/MediaAPI.kt | 13 +++++ 4 files changed, 108 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index 5ad39614b7..8bf33df0ac 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -24,6 +24,7 @@ pbkdf pids pkcs + previewable riotx signin signout diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt new file mode 100644 index 0000000000..6594b2d0b5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.media + +import org.matrix.android.sdk.api.cache.CacheStrategy +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.util.JsonDict + +interface MediaService { + /** + * Extract URLs from an Event. + * @return the list of URLs contains in the body of the Event. It does not mean that URLs in this list have UrlPreview data + */ + fun extractUrls(event: Event): List + + /** + * Get Raw Url Preview data from the homeserver. There is no cache management for this request + */ + suspend fun getRawPreviewUrl(url: String): JsonDict + + /** + * Get Url Preview data from the homeserver, or from cache, depending on the cache strategy + * @param url + */ + suspend fun getPreviewUrl(url: String, cacheStrategy: CacheStrategy): PreviewUrlData + + /** + * Clear the cache of all retrieved UrlPreview data + */ + suspend fun clearCache() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt new file mode 100644 index 0000000000..1b32aed7d8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.media + +/** + * Facility data class to get the common field of a PreviewUrl response form the server + * + * Example of return data for the url `https://matrix.org`: + *
+ * {
+ *     "matrix:image:size": 112805,
+ *     "og:description": "Matrix is an open standard for interoperable, decentralised, real-time communication",
+ *     "og:image": "mxc://matrix.org/2020-12-03_uFqjagCCTJbaaJxb",
+ *     "og:image:alt": "Matrix is an open standard for interoperable, decentralised, real-time communication",
+ *     "og:image:height": 467,
+ *     "og:image:type": "image/jpeg",
+ *     "og:image:width": 911,
+ *     "og:locale": "en_US",
+ *     "og:site_name": "Matrix.org",
+ *     "og:title": "Matrix.org",
+ *     "og:type": "website",
+ *     "og:url": "https://matrix.org"
+ * }
+ * 
+ */ +data class PreviewUrlData( + // Value of field "og:url". If not provided, this is the value passed in parameter + val url: String, + // Value of field "og:title" + val title: String?, + // Value of field "og:description" + val description: String?, + // Value of field "og:image" + val mxcUrl: String? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt index dba693c724..821d9b9875 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt @@ -16,9 +16,11 @@ package org.matrix.android.sdk.internal.session.media +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants import retrofit2.Call import retrofit2.http.GET +import retrofit2.http.Query internal interface MediaAPI { /** @@ -27,4 +29,15 @@ internal interface MediaAPI { */ @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config") fun getMediaConfig(): Call + + /** + * Get information about a URL for the client. Typically this is called when a client + * sees a URL in a message and wants to render a preview for the user. + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-preview-url + * @param url Required. The URL to get a preview of. + * @param ts The preferred point in time to return a preview for. The server may return a newer version + * if it does not have the requested version available. + */ + @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url") + fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Int?): Call } From 8a35bfcc31e4d3b92878c346a154ddaa835cc3c7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 13:57:47 +0100 Subject: [PATCH 028/218] Rework: rename files using the interface name and not the implementation name --- .../raw/{DefaultCleanRawCacheTask.kt => CleanRawCacheTask.kt} | 0 .../sdk/internal/raw/{DefaultGetUrlTask.kt => GetUrlTask.kt} | 0 .../filter/{DefaultSaveFilterTask.kt => SaveFilterTask.kt} | 0 .../group/{DefaultGetGroupDataTask.kt => GetGroupDataTask.kt} | 0 ...ServerCapabilitiesTask.kt => GetHomeServerCapabilitiesTask.kt} | 0 .../{DefaultGetContextOfEventTask.kt => GetContextOfEventTask.kt} | 0 .../room/timeline/{DefaultPaginationTask.kt => PaginationTask.kt} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/{DefaultCleanRawCacheTask.kt => CleanRawCacheTask.kt} (100%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/{DefaultGetUrlTask.kt => GetUrlTask.kt} (100%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/{DefaultSaveFilterTask.kt => SaveFilterTask.kt} (100%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/{DefaultGetGroupDataTask.kt => GetGroupDataTask.kt} (100%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/{DefaultGetHomeServerCapabilitiesTask.kt => GetHomeServerCapabilitiesTask.kt} (100%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/{DefaultGetContextOfEventTask.kt => GetContextOfEventTask.kt} (100%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/{DefaultPaginationTask.kt => PaginationTask.kt} (100%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultCleanRawCacheTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/CleanRawCacheTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultCleanRawCacheTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/CleanRawCacheTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultSaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultSaveFilterTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGetGroupDataTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultGetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultGetContextOfEventTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultPaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt similarity index 100% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultPaginationTask.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt From a6724b5f75df3aa078e2f5bbc42d901a3febb76d Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 3 Dec 2020 16:10:50 +0300 Subject: [PATCH 029/218] EventTypeFilter implementation to allow hiding member events. --- .../session/room/timeline/EventTypeFilter.kt | 29 +++++++++++++++++++ .../room/timeline/TimelineEventFilters.kt | 2 +- .../query/TimelineEventEntityQueries.kt | 19 ++++++++++-- .../room/summary/RoomSummaryEventsHelper.kt | 3 +- .../session/room/timeline/DefaultTimeline.kt | 2 +- .../timeline/TimelineHiddenReadReceipts.kt | 2 +- .../helper/TimelineSettingsFactory.kt | 19 +++++++----- 7 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt new file mode 100644 index 0000000000..f2f6929fdd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.timeline + +data class EventTypeFilter( + /** + * Allowed event type. + */ + val eventType: String, + /** + * Allowed state key. Set null if you want to allow all events, + * otherwise allowed events will be filtered according to the given stateKey. + */ + val stateKey: String? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt index c751632286..4415c8e4b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt @@ -36,5 +36,5 @@ data class TimelineEventFilters( /** * If [filterTypes] is true, the list of types allowed by the list. */ - val allowedTypes: List = emptyList() + val allowedTypes: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 41a13c785d..148232cf94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -71,8 +71,23 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, } internal fun RealmQuery.filterEvents(filters: TimelineEventFilters): RealmQuery { - if (filters.filterTypes) { - `in`(TimelineEventEntityFields.ROOT.TYPE, filters.allowedTypes.toTypedArray()) + if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) { + beginGroup() + filters.allowedTypes.forEachIndexed { index, filter -> + if (filter.stateKey == null) { + equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType) + } else { + beginGroup() + equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType) + and() + equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey) + endGroup() + } + if (index != filters.allowedTypes.size - 1) { + or() + } + } + endGroup() } if (filters.filterUseless) { not() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt index a3862b001b..7437a686da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.summary import io.realm.Realm import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants +import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.latestEvent @@ -26,7 +27,7 @@ internal object RoomSummaryEventsHelper { private val previewFilters = TimelineEventFilters( filterTypes = true, - allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES, + allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES.map { EventTypeFilter(eventType = it, stateKey = null) }, filterUseless = true, filterRedacted = false, filterEdits = true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 995a21aa23..0dc0d33d38 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -785,7 +785,7 @@ internal class DefaultTimeline( private fun List.filterEventsWithSettings(): List { return filter { - val filterType = !settings.filters.filterTypes || settings.filters.allowedTypes.contains(it.root.type) + val filterType = !settings.filters.filterTypes || settings.filters.allowedTypes.map { it.eventType }.contains(it.root.type) if (!filterType) return@filter false val filterEdits = if (settings.filters.filterEdits && it.root.getClearType() == EventType.MESSAGE) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt index 3dcc5e21b1..6fe6013108 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt @@ -151,7 +151,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu beginGroup() var needOr = false if (settings.filters.filterTypes) { - val allowedTypes = settings.filters.allowedTypes.toTypedArray() + val allowedTypes = settings.filters.allowedTypes.map { it.eventType }.toTypedArray() not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", allowedTypes) needOr = true } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt index 3317612a6c..ff396646c0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt @@ -17,12 +17,17 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.resources.UserPreferencesProvider +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import javax.inject.Inject -class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { +class TimelineSettingsFactory @Inject constructor( + private val userPreferencesProvider: UserPreferencesProvider, + private val session: Session +) { fun create(): TimelineSettings { return if (userPreferencesProvider.shouldShowHiddenEvents()) { @@ -48,12 +53,12 @@ class TimelineSettingsFactory @Inject constructor(private val userPreferencesPro } } - private fun List.filterDisplayableTypes(): List { - return filter { type -> - when (type) { - EventType.STATE_ROOM_MEMBER -> userPreferencesProvider.shouldShowRoomMemberStateEvents() - else -> true - } + private fun List.filterDisplayableTypes(): List { + return map { + EventTypeFilter( + eventType = it, + stateKey = if (it == EventType.STATE_ROOM_MEMBER && userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null + ) } } } From 32b7cc64fb1c6915d645fba6d22d3d663494110f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 3 Dec 2020 16:19:37 +0300 Subject: [PATCH 030/218] Changelog added. Fixes #2486 --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index f6f63db534..321bfd9bef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: Bugfix 🐛: - Double bottomsheet effect after verify with passphrase - EditText cursor jumps to the start while typing fast (#2469) + - UTD for events before invitation if member state events are hidden (#2486) Translations 🗣: - From 1109d9f88a1bc906941df6f1a8bbeb998719e9bc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 14:45:20 +0100 Subject: [PATCH 031/218] PreviewUrl create DB object and handle migration --- .../sdk/api/session/media/PreviewUrlData.kt | 2 + .../database/RealmSessionStoreMigration.kt | 18 ++++++++- .../database/model/PreviewUrlCacheEntity.kt | 36 +++++++++++++++++ .../database/model/SessionRealmModule.kt | 1 + .../query/PreviewUrlCacheEntityQueries.kt | 39 +++++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt index 1b32aed7d8..33fc8b052b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/PreviewUrlData.kt @@ -40,6 +40,8 @@ package org.matrix.android.sdk.api.session.media data class PreviewUrlData( // Value of field "og:url". If not provided, this is the value passed in parameter val url: String, + // Value of field "og:site_name" + val siteName: String?, // Value of field "og:title" val title: String?, // Value of field "og:description" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 973388da49..b970ec60e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -20,6 +20,7 @@ import io.realm.DynamicRealm import io.realm.RealmMigration import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields +import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -27,7 +28,7 @@ import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 5L + const val SESSION_STORE_SCHEMA_VERSION = 6L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -38,6 +39,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 2) migrateTo3(realm) if (oldVersion <= 3) migrateTo4(realm) if (oldVersion <= 4) migrateTo5(realm) + if (oldVersion <= 5) migrateTo6(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -89,4 +91,18 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { ?.removeField("adminE2EByDefault") ?.removeField("preferredJitsiDomain") } + + private fun migrateTo6(realm: DynamicRealm) { + Timber.d("Step 5 -> 6") + realm.schema.create("PreviewUrlCacheEntity") + .addField(PreviewUrlCacheEntityFields.URL, String::class.java) + .setRequired(PreviewUrlCacheEntityFields.URL, true) + .addPrimaryKey(PreviewUrlCacheEntityFields.URL) + .addField(PreviewUrlCacheEntityFields.URL_FROM_SERVER, String::class.java) + .addField(PreviewUrlCacheEntityFields.SITE_NAME, String::class.java) + .addField(PreviewUrlCacheEntityFields.TITLE, String::class.java) + .addField(PreviewUrlCacheEntityFields.DESCRIPTION, String::class.java) + .addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java) + .addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt new file mode 100644 index 0000000000..b1e0b64405 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PreviewUrlCacheEntity.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +internal open class PreviewUrlCacheEntity( + @PrimaryKey + var url: String = "", + + var urlFromServer: String? = null, + var siteName: String? = null, + var title: String? = null, + var description: String? = null, + var mxcUrl: String? = null, + + var lastUpdatedTimestamp: Long = 0L +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index f62312f8fc..bca2c42c9e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -48,6 +48,7 @@ import io.realm.annotations.RealmModule PushRulesEntity::class, PushRuleEntity::class, PushConditionEntity::class, + PreviewUrlCacheEntity::class, PusherEntity::class, PusherDataEntity::class, ReadReceiptsSummaryEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt new file mode 100644 index 0000000000..a139c17439 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PreviewUrlCacheEntityQueries.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity +import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields + +/** + * Get the current PreviewUrlCacheEntity, return null if it does not exist + */ +internal fun PreviewUrlCacheEntity.Companion.get(realm: Realm, url: String): PreviewUrlCacheEntity? { + return realm.where() + .equalTo(PreviewUrlCacheEntityFields.URL, url) + .findFirst() +} + +/** + * Get the current PreviewUrlCacheEntity, create one if it does not exist + */ +internal fun PreviewUrlCacheEntity.Companion.getOrCreate(realm: Realm, url: String): PreviewUrlCacheEntity { + return get(realm, url) ?: realm.createObject(url) +} From f9ccb0e8de8e78ebe4126c00ed9e81599d2431ae Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 2 Dec 2020 15:51:37 +0000 Subject: [PATCH 032/218] Translated using Weblate (Spanish) Currently translated at 97.2% (1932 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ --- vector/src/main/res/values-es/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index a26c4c91d6..b5356f3c68 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -1577,8 +1577,8 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Conferencia en progreso! Iniciar Videoconferencia Iniciar Audioconferencia - No puedes hacer llamarte a tí mismo - No puedes hacer llamarte a tí mismo, espera a que los participantes acepten la invitación + No puedes hacer llamarte a ti mismo + No puedes hacer llamarte a ti mismo, espera a que los participantes acepten la invitación Fallo al añadir Widget Fallo al eliminar Widget Confirmar llamada @@ -2211,7 +2211,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Sólo mostrar el número de mensajes no leídos en una notificación sencilla. Mostrar detalles, como nombres de salas y contenido de mensajes. Mostrar contenido de notificaciones - Element sólo puede ser desbloqueado via Código PIN. + Element sólo puede ser desbloqueado vía Código PIN. Activar biometría de este dispositivo en particular, como huellas dactilares o reconocimiento facial. Activar biometría Configurar protecciones From ec60d7d0b62054aa5c68021a08fb47a41d065e5c Mon Sep 17 00:00:00 2001 From: Hivaa Date: Thu, 3 Dec 2020 12:43:58 +0000 Subject: [PATCH 033/218] Translated using Weblate (Persian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index a3d377b3bc..fc25126f48 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -1062,7 +1062,7 @@ مخاطب دوربین صدا - جُنگ + گالری برچسب رسانه هیچ رسانه‌ای در این اتاق نیست @@ -1525,7 +1525,7 @@ [%1$s] \nاین خطا از کنترل المنت خارج است و به چند دلیل ممکن است رخ داده باشد. امکان دارد در صورت تلاش مجدد مشکل رفع شود، همچنین می‌توانید بررسی کنید که سرویس Google Play در استفاده از اینترنت در تنظیمات گوشی محدودیتی نداشته باشد یا ساعت دستگاه شما درست باشد. همچنین ممکن است به علت استفاده از ROM سفارشی‌شده این خطا رخ داده باشد. [%1$s] -\nاین خطا از کنترل هیوا خارج است و طبق گفته گوگل ، این خطا نشان می دهد که دستگاه بیش از حد مجاز، برنامه های ثبت شده در FCM دارد. این خطا فقط در مواردی رخ می دهد که تعداد زیادی برنامه وجود دارد، بنابراین نباید بر کاربر عادی رخ دهد. +\nاین خطا از کنترل المنت خارج است و طبق گفته گوگل ، این خطا نشان می دهد که دستگاه بیش از حد مجاز، برنامه های ثبت شده در FCM دارد. این خطا فقط در مواردی رخ می دهد که تعداد زیادی برنامه وجود دارد، بنابراین نباید بر کاربر عادی رخ دهد. بازیابی توکن FCM با مشکل مواجه شد: \n%1$s توکن FCM با موفقیت بازیابی شد: @@ -1588,21 +1588,21 @@ این دعوت به %s ارسال شده که ارتباطی با این حساب ندارد. \nممکن است بخواهید با حسابی دیگر وارد شده یا این رایانامه را به حسابتان بیفزایید. متاسفانه به دلیل عدم دسترسی، درخواست شما امکان پذیر نمی باشد - هیوا می‌تواند با دیدن دفترچه تلفن شما کاربرهای دیگر هیوا را بر اساس ایمبل و شماره تلفنشان پیدا کند. + المنت می‌تواند با دیدن دفترچه تلفن شما کاربرهای دیگر ماتریکس را بر اساس ایمبل و شماره تلفنشان پیدا کند. \n \nآیا موافق با اشتراک‌گذاری دفترچه تلفنتان به این منظور هستید؟ - هیوا می‌تواند با دیدن دفترچه تلفن شما کاربرهای دیگر هیوا را بر اساس ایمبل و شماره تلفنشان پیدا کند. اگر مایل به اشتراک گذاری دفترچه تلفنتان به این منظور هستید لطفا در پنجره‌ی بعد اجازه‌ی این کار را بدهید. - هیوا برای برقراری تماس تصویری نیازمند دسترسی به میکروفون و دوربین است. + المنت می‌تواند با دیدن دفترچه تلفن شما کاربرهای دیگر ماتریکس را بر اساس ایمبل و شماره تلفنشان پیدا کند. اگر مایل به اشتراک گذاری دفترچه تلفنتان به این منظور هستید لطفا در پنجره‌ی بعد اجازه‌ی این کار را بدهید. + المنت برای برقراری تماس تصویری نیازمند دسترسی به میکروفون و دوربین است. \n \nلطفا در پنجره های بعدی دسترسی های لازم را بدهید. " \n \nلطفا برای برقراری تماس در پنجره بعدی دسترسی لازم را بدهید." - هیوا برای برقراری تماس صوتی نیازمند دسترسی به میکروفون است. + المنت برای برقراری تماس صوتی نیازمند دسترسی به میکروفون است. " \n \nلطفا برای برقراری تماس در پنجره بعدی دسترسی لازم را بدهید." - هیوا برای ارسال و ذخیره‌ی فایل‌ها نیاز به دسترسی به گالری شما را دارد. + المنت برای ارسال و ذخیره‌ی فایل‌ها نیاز به دسترسی به گالری شما را دارد. \n \nلطفا در پنجره‌ای که باز می‌شود این دسترسی را بدهید. هنگامی که سرور شما قابلیت تماس ندارد، از سرور %s استفاده کن @@ -2044,7 +2044,7 @@ برای ادامه استفاده از سرور %1$s باید شرایط و ضوابط را خوانده و موافقت کنید. Markdown غیرفعال شده است. Markdown فعال شده است. - جهت رفع مشکلات مدیریت برنامه‌های هیوا + جهت رفع مشکلات مدیریت برنامه‌های ماتریکس فعال و غیرفعال کردن markdown نام مستعار شما را تغییر می‌دهد اخراج کاربر با شناسه داده شده From 83df430d172da0791a05c77a684b182c75441682 Mon Sep 17 00:00:00 2001 From: quidje Date: Wed, 2 Dec 2020 16:28:56 +0000 Subject: [PATCH 034/218] Translated using Weblate (Dutch) Currently translated at 55.6% (1106 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nl/ --- vector/src/main/res/values-nl/strings.xml | 283 +--------------------- 1 file changed, 4 insertions(+), 279 deletions(-) diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index f10458970e..b735c56452 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -1,17 +1,15 @@ - + nl NL - Berichten Gesprek Instellingen Info over deelnemer Historisch - Oké Annuleren @@ -44,7 +42,6 @@ Toch sturen of Uitnodigen - Afmelden Spraakoproep @@ -57,27 +54,22 @@ Sluiten Gekopieerd naar klembord Uitschakelen - Bevestiging Waarschuwing - Thuis Favorieten Personen Gesprekken - Gespreksnamen filteren Favorieten filteren Personen filteren Gespreksnamen filteren - Uitnodigingen Lage prioriteit - Gesprekken Lokale contactenlijst @@ -85,7 +77,6 @@ Geen gesprekken U heeft Element geen toegang tot uw lokale contacten gegeven Geen resultaten - Gesprekken Gesprekscatalogus @@ -95,7 +86,6 @@ 1 gebruiker %d gebruikers - Logboek versturen Crash-logboek versturen Schermafdruk versturen @@ -108,10 +98,8 @@ Verzenden van foutmelding is mislukt (%s) Voortgang (%s%%) De toepassing is de vorige keer gecrasht. Wilt u dit melden\? - Versturen naar Gelezen - Gesprek toetreden Gebruikersnaam Account aanmaken @@ -120,14 +108,11 @@ Thuisserver-URL Identiteitsserver-URL Zoeken - Nieuw gesprek beginnen Spraakoproep beginnen Video-oproep beginnen - Bestanden versturen Foto of video maken - Aanmelden Account aanmaken @@ -176,7 +161,6 @@ Uw wachtwoord is opnieuw ingesteld. \n \nU bent op alle sessies afgemeld en zult niet langer pushmeldingen ontvangen. Om meldingen opnieuw in te schakelen, meldt u zich op elk apparaat opnieuw aan. - URL moet met http[s]:// beginnen Aanmelden mislukt: netwerkfout @@ -185,7 +169,6 @@ Registreren mislukt Registreren mislukt: e-mail-eigendomsfout Voer een geldige URL in - Ongeldige gebruikersnaam/wachtwoord Het opgegeven toegangsbewijs werd niet herkend Ongeldige JSON @@ -193,35 +176,27 @@ Er zijn te veel verzoeken verstuurd Deze gebruikersnaam is al in gebruik Er is nog niet op de koppeling in de e-mail geklikt - - Leesbevestigingslijst - - Versturen als Origineel Groot Medium Klein - "Download annuleren? Upload annuleren? %d s %1$dm %2$ds - Gisteren Vandaag - Gespreksnaam Gespreksonderwerp - Oproep verbonden Oproep is aan het verbinden… @@ -231,16 +206,13 @@ Inkomende video-oproep Inkomende spraakoproep Oproep gaande… - De andere kant heeft niet opgenomen. Mediaverbinding is mislukt Kan de camera niet initialiseren oproep elders opgenomen - Een afbeelding of video maken" Kan geen video opnemen" - Informatie Element heeft toegang nodig tot uw mediabestanden om bijlagen te verzenden en op te slaan. @@ -262,25 +234,20 @@ Element kan uw adresboek gebruiken om andere Matrix-gebruikers te vinden aan de hand van hun e-mailadressen en telefoonnummers. \n \nWilt u uw adresboek hiervoor delen\? - Sorry. De actie is niet toegepast vanwege ontbrekende rechten - Opgeslagen In downloads opslaan? JA NEE Verdergaan - Verwijderen Toetreden Voorvertoning Afwijzen - Ga naar het eerste ongelezen bericht. - %s heeft u uitgenodigd in dit gesprek Deze uitnodiging is naar %s verstuurd, maar die is niet geassocieerd met deze account. @@ -288,27 +255,22 @@ U probeert toegang te verkrijgen tot %s. Zou u het gesprek willen toetreden om eraan deel te nemen\? een gesprek Dit is een voorvertoning van dit gesprek. Gespreksinteracties zijn uitgeschakeld. - Nieuw gesprek Deelnemer toevoegen 1 deelnemer - Gesprek verlaten Weet u zeker dat u het gesprek wilt verlaten\? Weet u zeker dat u %s uit dit gesprek wilt verwijderen\? Aanmaken - Online Offline Afwezig - BEHEERDERSGEREEDSCHAPPEN BELLEN TWEEGESPREKKEN SESSIES - Uitnodigen Dit gesprek verlaten Verwijderen uit dit gesprek @@ -324,18 +286,14 @@ Sessielijst weergeven U kunt deze veranderingen niet ongedaan maken aangezien u de gebruiker tot hetzelfde niveau als uzelf promoveert. \nWeet u het zeker\? - Weet u zeker dat u %s in dit gesprek wilt uitnodigen\? - Uitnodigen met ID LOKALE CONTACTEN (%d) Enkel Matrix-gebruikers - Gebruiker uitnodigen met ID Voer één of meer e-mailadressen of Matrix-ID’s in E-mailadres of Matrix-ID - Zoeken %s is aan het typen… @@ -352,7 +310,6 @@ Onverstuurde berichten verwijderen Bestand niet gevonden U heeft geen toestemming om dit naar dit gesprek te sturen - Vertrouwen Niet vertrouwen @@ -365,7 +322,6 @@ Het certificaat is veranderd van één dat door uw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN. Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Contacteer de serverbeheerder voor de verwachte vingerafdruk. Aanvaard het certificaat alleen als de serverbeheerder een vingerafdruk heeft gepubliceerd die overeenkomt met degene hierboven. - Info over gesprek Personen @@ -374,7 +330,6 @@ Ongeldige ID. Het zou een e-mailadres of een Matrix-ID zoals ‘@gebruikersnaam:domein’ moeten zijn UITGENODIGD TOEGETREDEN - Reden voor het melden van deze inhoud Wilt u alle berichten van deze gebruiker verbergen\? @@ -382,7 +337,6 @@ \nLet op: deze actie zal de app opnieuw opstarten; dit kan even duren. Upload annuleren Download annuleren - Zoeken Gespreksleden filteren @@ -391,7 +345,6 @@ BERICHTEN PERSONEN BESTANDEN - TOETREDEN CATALOGUS @@ -404,18 +357,15 @@ Gesprek toetreden Treed een gesprek toe Voer een gespreks(bij)naam in - Catalogus doorbladeren Catalogus wordt doorzocht… - Favoriet Lage prioriteit Tweegesprek Gesprek verlaten Vergeten - Berichten Instellingen @@ -424,9 +374,7 @@ Derdepartijvermeldingen Copyright Privacybeleid - - Profielfoto Naam E-mailadres @@ -435,22 +383,18 @@ Telefoonnummer toevoegen Toon informatie over de app in de systeeminstellingen. App-informatie - Meldingen voor deze account inschakelen Meldingen voor deze sessie inschakelen Het scherm voor 3 seconden aanzetten - Berichten in één-op-één-gesprekken Berichten in groepsgesprekken Wanneer ik in een gesprek word uitgenodigd Oproepuitnodigingen Door een robot verstuurde berichten - Synchronisatie in de achtergrond Achtergrondssynchronisatie inschakelen Synchronisatieverzoek is verlopen Pauze tussen elk synchronisatie - Versie olm-versie Algemene voorwaarden @@ -459,8 +403,6 @@ Privacybeleid Cache wissen - - Gebruikersinstellingen Meldingen Genegeerde gebruikers @@ -479,7 +421,6 @@ ID Publieke naam Publieke naam bijwerken - Laatst gezien %1$s @ %2$s Deze actie vereist bijkomende authenticatie. @@ -487,19 +428,15 @@ Authenticatie Wachtwoord: Indienen - Aangemeld als Thuisserver Identiteitsserver - Verificatie in afwachting Bekijk uw e-mail en tik op de koppeling erin. Tik zodra dit gedaan is op Verdergaan. Het verifiëren van het e-mailadres is mislukt. Bekijk uw e-mail en tik op de koppeling erin. Tik zodra dit gedaan is op Verdergaan. - Dit e-mailadres is al in gebruik. Dit e-mailadres is niet gevonden. Dit telefoonnummer is al in gebruik. - Wachtwoord veranderen Huidig wachtwoord Nieuw wachtwoord @@ -509,13 +446,9 @@ Alle berichten van %s tonen\? \n \nLet op: deze actie zal de app herstarten; dit kan even duren. - Weet u zeker dat u dit meldingsdoel wilt verwijderen\? - Weet u zeker dat u de %1$s %2$s wilt verwijderen\? - Kies een land - Land Kies een land Telefoonnummer @@ -525,21 +458,17 @@ Voer een activeringscode in Er is een fout opgetreden bij het valideren van uw telefoonnummer Code - - Gespreksafbeelding Gespreksnaam Onderwerp Gesprekslabel Gelabeld als: - Favoriet Lage prioriteit Geen - Toegankelijk- en zichtbaarheid Dit gesprek vermelden in de gesprekscatalogus @@ -547,22 +476,18 @@ Toegang tot de gespreksgeschiedenis Wie kan er de geschiedenis lezen\? Wie heeft er toegang tot dit gesprek\? - Iedereen Alleen deelnemers (vanaf het moment dat deze optie wordt geselecteerd) Alleen deelnemers (vanaf het moment dat ze worden uitgenodigd) Alleen deelnemers (vanaf het moment dat ze toetreden) - Om naar een gesprek te verwijzen moet dit een adres hebben. Alleen personen die uitgenodigd zijn Iedereen die de koppeling van het gesprek kent, met uitzondering van gasten Iedereen die de koppeling van het gesprek kent, inclusief gasten - Verbannen gebruikers - Geavanceerd Interne ID van dit gesprek @@ -574,35 +499,27 @@ Eind-tot-eind-versleuteling is actief Alleen naar geverifieerde sessies versleutelen Ongeverifieerde sessies in dit gesprek nooit berichten sturen vanaf deze sessie. - Dit gesprek heeft geen lokale adressen Nieuw adres (bv. #foo:matrix.org) - Ongeldig bijnaamformaat ‘%s’ is geen geldig bijnaamformaat U zult geen hoofdadres voor dit gesprek opgegeven hebben. Hoofdadreswaarschuwingen - Instellen als hoofdadres Niet instellen als hoofdadres Gespreks-ID kopiëren Gespreksadres kopiëren - Versleuteling is ingeschakeld in dit gesprek. Versleuteling is uitgeschakeld in dit gesprek. Versleuteling inschakelen \n(let op: dit kan niet meer uitgeschakeld worden!) - Catalogus - %s heeft geprobeerd een specifiek punt in de geschiedenis van dit gesprek te laden, maar kon het niet vinden. - Informatie over eind-tot-eind-versleuteling - Gebeurtenisinformatie Gebruikers-ID Curve25519-identiteitssleutel @@ -610,7 +527,6 @@ Algoritme Sessie-ID Ontsleutelingsfout - Informatie over sessie van afzender Publieke naam Publieke naam @@ -618,7 +534,6 @@ Sessiesleutel Verificatie Ed25519-vingerafdruk - E2E-gesprekssleutels exporteren Gesprekssleutels exporteren Exporteer de sleutels naar een lokaal bestand @@ -628,31 +543,25 @@ De E2E-gesprekssleutels zijn in ‘%s’ opgeslagen. \n \nLet op: dit bestand kan verwijderd worden als de app verwijderd is. - E2E-gesprekssleutels importeren Gesprekssleutels importeren Importeer de sleutels uit een lokaal bestand Importeren Enkel naar geverifieerde sessies versleutelen Versleutelde berichten nooit naar ongeverifieerde sessies sturen vanaf deze sessie. - NIET geverifieerd Geverifieerd Geblokkeerd - onbekende sessie geen - Verifiëren Ontverifiëren Blokkeringslijst Deblokkeringslijst - Sessie verifiëren Om te verifiëren dat deze sessie vertrouwd kan worden, contacteert u de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraagt u hem/haar of de sleutel die hij/zij ziet in zijn/haar Gebruikersinstellingen van deze sessie overeenkomt met de sleutel hieronder: Als het overeenkomt, drukt u op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en zou u het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. Ik verifieer dat de sleutels overeenkomen - Dit gesprek bevat onbekende sessies Dit gesprek bevat onbekende sessies die niet geverifieerd zijn. @@ -660,7 +569,6 @@ \nWe raden u aan om bij elke sessie door het verificatieprocces heen te gaan voordat u verdergaat, maar u kunt het bericht ook zonder te verifiëren opnieuw versturen. \n \nOnbekende sessies: - Kies een gesprekscatalogus Het kan zijn dat de server niet beschikbaar of overbelast is @@ -668,30 +576,23 @@ Thuisserver-URL Alle gesprekken op server %s Alle lokale gesprekken op %s - Zoeken in de historiek Offline - Gebruikerscatalogus GEBRUIKERSCATALOGUS (%s) Starten bij opstarten Mediacache wissen Media bewaren - Tijdsaanduidingen weergeven voor alle berichten Databesparingsmodus - Gebruikersinterface Taal Taal kiezen - 1 week 1 maand Altijd - Thema - Tekstgrootte Klein Normaal @@ -704,18 +605,14 @@ Licht thema Donker thema Zwart thema - Bezig met synchroniseren… Luisteren voor evenementen - Meldingsgeluid Tijdsaanduidingen in 12-uursformaat weergeven - U heeft toestemming nodig om widgets in dit gesprek te beheren Aanmaken van widget is mislukt Vergadergesprekken maken met jitsi Weet u zeker dat u deze widget uit dit gesprek wilt verwijderen\? - Kan widget niet aanmaken. Versturen van verzoek mislukt. @@ -728,67 +625,48 @@ Matrix-apps toevoegen Geluidsmeldingen Stille meldingen - Foutmelding - Foto maken Video maken - Bellen Berichten die mijn weergavenaam bevatten Berichten die mijn gebruikersnaam bevatten Statistische gegevens - Systeemcamera gebruiken - U heeft een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. Uw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. Verificatie starten Delen zonder te verifiëren Verzoek negeren - Let op! Vergadergesprekken zijn in ontwikkeling en kunnen dus nog kuren vertonen. - Opdrachtfout Onbekende opdracht: %s - Uit Lawaaierig - Versleuteld bericht - Info over gemeenschap - Laden… - Afsluiten Acties Gemeenschappen - Gemeenschapsnamen filteren - Uitnodigen Gemeenschappen Geen groepen - Schudden om een probleem te melden - Weet u zeker dat u een nieuw gesprek met %s wilt beginnen\? Weet u zeker dat u een spraakoproep wilt beginnen\? Weet u zeker dat u een video-oproep wilt beginnen\? - Groepenlijst - 1 verandering in lidmaatschap %d veranderingen in lidmaatschap - Ledenlijst Opschrift openen Synchroniseren… @@ -801,12 +679,10 @@ %d deelnemers Weet u zeker dat u deze gebruiker uit dit gesprek wilt verbannen\? - 1 nieuw bericht %d nieuwe berichten - 1 gesprek %d gesprekken @@ -820,19 +696,14 @@ Alleen vermeldingen Dempen Snelkoppeling aan thuisscherm toevoegen - Inline URL-voorvertoning Trillen bij vermelden van een gebruiker - Badge - Meldingen Dit gesprek geeft geen badges voor gemeenschappen weer Nieuwe gemeenschaps-ID (bv. +foo:matrix.org) Ongeldige gemeenschaps-ID ‘%s’ is geen geldige gemeenschaps-ID - - 1 ongelezen bericht waarin u vermeld staat %d ongelezen berichten waarin u vermeld staat @@ -842,12 +713,10 @@ %d gesprekken %1$s in %2$s - 1 actieve widget %d actieve widgets - Aanmaken Gemeenschap aanmaken @@ -855,42 +724,34 @@ Voorbeeld Gemeenschaps-ID voorbeeld - Thuis Personen Gesprekken Geen gebruikers - Gesprekken Toegetreden Uitgenodigd Groepsleden filteren Groepsgesprekken filteren - De gemeenschapsbeheerder heeft geen lange beschrijving gegeven voor deze gemeenschap. - %2$s heeft u uit %1$s gezet %2$s heeft u uit %1$s verbannen Reden: %1$s Opnieuw toetreden Gesprek vergeten - Ontvangst-avatar Avatar - 1 ongelezen bericht waarin u vermeld bent %d ongelezen berichten waarin u vermeld bent Vermeldingsavatar Verstuur een sticker - Sticker versturen U heeft momenteel geen stickerpakketten ingeschakeld. \n \nWilt u er nu een paar toevoegen\? - Meldingsprivacy Normaal Gereduceerde privacy @@ -900,25 +761,20 @@ • De inhoud van de berichten in de melding is veilig overgebracht, rechtstreeks vanaf de Matrix-thuisserver • Meldingen bevatten meta- en berichtdata • Meldingen zullen berichtinhoud niet weergeven - Account deactiveren Mijn account deactiveren - Meldingsprivacy Element kan op de achtergrond werken om uw meldingen veilig en privé te beheren. Dit beïnvloedt mogelijk het accuverbruik. Toestemming verlenen Kies een andere optie - Statistische gegevens (analytics) versturen Element verzamelt anonieme statistische gegevens (analytics) om het voor ons mogelijk te maken om de app te verbeteren. Schakel statistische gegevens in om ons te helpen bij het verbeteren van Element. Ja, ik wil helpen! - Er ontbreekt een vereiste parameter. Er is een parameter ongeldig. Om de %1$s-thuisserver verder te blijven gebruiken, dient u de voorwaarden te lezen en ermee akkoord te gaan. Nu doorlezen - Account deactiveren Dit zal uw account voorgoed onbruikbaar maken. U zult zich niet meer kunnen aanmelden, en niemand anders zal met dezelfde gebruikers-ID kunnen registreren. Dit zal er voor zorgen dat uw account alle gesprekken verlaat waar deze momenteel lid van is, en het verwijdert de accountgegevens van de identiteitsserver. Deze actie is onomkeerbaar. \n @@ -928,37 +784,25 @@ Vergeet alle berichten die ik heb verstuurd wanneer mijn account gedeactiveerd is (Let op: dit zal er voor zorgen dat toekomstige gebruikers een onvolledig beeld krijgen van gesprekken) Voer uw wachtwoord in om verder te gaan: Account deactiveren - Licenties van derde partijen - Downloaden Inspreken Beveiligingssleutels van uw sessies opnieuw aanvragen. - Sleutelaanvraag verstuurd. - Aanvraag verstuurd Start Element op een ander apparaat dat het bericht kan ontsleutelen, zodat het de sleutels naar deze sessie kan sturen. - Typ hier… - Wissen Spraakbericht versturen - doorgaan met… Sorry, er is geen externe toepassing gevonden om deze actie te voltooien. - Stemberichten versturen - Voer uw wachtwoord in. - Beschrijf het probleem in het Engels, indien mogelijk. Verstuur een versleuteld antwoord… Verstuur een antwoord (onversleuteld)… Media bekijken vóór het versturen - U bent momenteel geen lid van een gemeenschap. - Enter-knop van toetsenbord gebruiken om berichten te versturen Toont een actie Verbant gebruiker met gegeven ID @@ -976,10 +820,8 @@ Het gesprek wordt hier voortgezet Dit gesprek is een voortzetting van een ander gesprek Klik hier om oudere berichten te zien - Deze actie is niet mogelijk wegens ontbrekende rechten. Systeemmeldingen - 1s %ds @@ -996,60 +838,44 @@ 1d %dd - nu %1$s %2$s geleden %1$s - "%1$s, " %1$s en %2$s %1$s %2$s - 1 geselecteerd %d geselecteerd Om Matrix-appbeheer te herstellen - 1 deelnemer %d deelnemers - 1 gesprek %d gesprekken Bronlimiet overschreden Beheerder contacteren - contact op te nemen met uw dienstbeheerder - Deze thuisserver heeft een van zijn bronlimieten overschreden, dus sommige gebruikers zullen zich niet kunnen aanmelden. Deze thuisserver heeft een van zijn bronlimieten overschreden. - Deze thuisserver heeft zijn limiet voor maandelijks actieve gebruikers overschreden, dus sommige gebruikers zullen zich niet kunnen aanmelden. Deze thuisserver heeft zijn limiet voor maandelijks actieve gebruikers overschreden. - Gelieve %s om deze limiet te verhogen. Gelieve %s om deze dienst te blijven gebruiken. - Foutmelding - Status.im-thema - Toch bellen Aanvaarden - Gelieve het beleid van deze thuisserver te lezen en aanvaarden: - Oproepen Gebruik de standaardbeltoon van Element voor inkomende oproepen Beltoon voor inkomende oproepen Selecteer beltoon voor oproepen: - Eruit sturen Reden - Versie %s Voorvertoning van koppelingen in het gesprek tonen (als uw thuisserver deze functie ondersteunt). Typmeldingen versturen @@ -1065,7 +891,6 @@ Dienst wordt geïnitialiseerd Sleutelback-up Sleutelback-up gebruiken - Sleutelback-up is nog niet klaar, even geduld… Indien u zich nu afmeldt, zult u uw versleutelde berichten verliezen Sleutelback-up is bezig. Indien u zich nu afmeldt, zult u de toegang tot uw versleutelde berichten verliezen. @@ -1076,23 +901,19 @@ Weet u het zeker\? Back-up maken U zult de toegang tot uw versleutelde berichten verliezen, tenzij u eerst een back-up van uw sleutels maakt vooraleer u zich afmeldt. - Blijven Overslaan Klaar Afbreken Negeren - Weet u zeker dat u zich wilt afmelden\? Markeren als gelezen Aanmelden met unieke aanmelding Deze URL kan niet bereikt worden, gelieve deze na te kijken Uw apparaat gebruikt een verouderd TLS-beveiligingsprotocol, dat kwetsbaar is voor aanvallen. Uit veiligheidsoverwegingen zult u geen verbinding kunnen maken Video-oproep gaande… - Geavanceerde meldingsinstellingen Meldingsbelang op gebeurtenis - Problemen met meldingen oplossen Diagnostische probleemoplossingsinformatie Testen uitvoeren @@ -1100,37 +921,31 @@ Basisdiagnose is oké. Als u nog steeds geen meldingen ontvangt, gelieve dan een bugmelding in te dienen om ons te helpen onderzoeken. Er zijn één of meer tests mislukt, probeer de aanbevolen oplossing(en). Er zijn één of meer tests mislukt, gelieve een bugmelding in te dienen om ons te helpen onderzoeken. - Systeeminstellingen. Meldingen zijn ingeschakeld in de systeeminstellingen. Meldingen zijn uitgeschakeld in de systeeminstellingen. \nGelieve deze te controleren. Instellingen openen - Accountinstellingen. Meldingen zijn ingeschakeld voor uw account. Meldingen zijn uitgeschakeld voor uw account. \nGelieve de accountinstellingen te controleren. Inschakelen - Sessie-instellingen. Meldingen zijn ingeschakeld voor deze sessie. Meldingen zijn niet ingeschakeld voor deze sessie. \nGelieve de Element-instellingen te controleren. Inschakelen - Aangepaste instellingen. Sommige soorten berichten zijn stil (ze geven een geluidsloze melding). Sommige meldingen zijn uitgeschakeld in uw aangepaste instellingen. Laden van aangepaste regels is mislukt, probeer het opnieuw. Instellingen controleren - Play-diensten controleren De APK van Google Play Services is beschikbaar en up-to-date. Element maakt gebruikt van Google Play Services om pushberichten af te leveren, maar dit lijkt niet juist geconfigureerd te zijn: \n%1$s Play-diensten herstellen - Firebase-bewijs Het FCM-bewijs is opgehaald: \n%1$s @@ -1143,27 +958,22 @@ [%1$s] \nDeze fout is onafhankelijk van Element. Er is geen Google-account verbonden met de telefoon. Open het accountbeheer en voeg er een Google-account toe. Account toevoegen - Bewijsregistratie FCM-bewijs geregistreerd bij thuisserver. FCM-bewijs niet geregistreerd bij thuisserver: \n%1$s - Meldingsdienst Meldingsdienst is actief. Meldingsdienst is niet actief. \nProbeer de app te herstarten. Dienst starten - Meldingsdienst automatisch herstarten Dienst is afgesloten en automatisch herstart. Dienst is niet herstart - Starten bij opstarten van apparaat De dienst zal starten wanneer het apparaat wordt herstart. De dienst zal niet starten wanneer het apparaat wordt herstart en u zult geen meldingen ontvangen tot u Element hebt geopend. Starten bij opstarten inschakelen - Achtergrondbeperkingen controleren Achtergrondbeperkingen zijn uitgeschakeld voor Element. Deze test dient uitgevoerd te worden met een mobiele verbinding (geen wifi). \n%1$s @@ -1171,51 +981,39 @@ \nAl wat de app probeert te doen zal in de achtergrond hevig beperkt worden; dit kan het correct functioneren van meldingen beïnvloeden. \n%1$s Beperkingen uitschakelen - Accuoptimalisatie Element wordt niet beperkt door accuoptimalisatie. Als een gebruiker een apparaat los van de oplader een tijd laat stilliggen, met het scherm uitgeschakeld, gaat het apparaat in slaapmodus. Dit verhindert apps de toegang tot het netwerk, en stelt hun taken, synchronisaties en standaardalarmen uit. Optimalisatie negeren - De app heeft geen verbinding met de homeserver nodig in de achtergrond, dit zou het gebruik van de batterij moeten verlagen Lawaaiierige meldingen configureren Oproepmeldingen configureren Stille meldingen configureren Bepaal de LED-kleur, vibratie, geluid, … - - Beheer van cryptografische sleutels Berichten versturen met Enter De Enter-knop van het toetsenbord zal berichten versturen in plaats van een regeleinde in te voegen - Achtergrondverbinding Element heeft een achtergrondverbinding met lage impact nodig om betrouwbare meldingen te kunnen hebben. \nOp het volgende scherm zult u gevraagd worden om Element toestemming te verlenen om altijd in de achtergrond te kunnen draaien, gelieve deze toestemming te verlenen. Toestemming verlenen - Databesparingsmodus past een specifieke filter toe zodat aanwezigheidsupdates en typmeldingen weggefilterd worden. - Er is een fout opgetreden bij het verifiëren van uw e-mailadres. - Wachtwoord Wachtwoord bijwerken Het wachtwoord is ongeldig Wachtwoorden komen niet overeen - Er is een fout opgetreden bij het verifiëren van uw telefoonnummer. Bijkomende info: %s - Media Standaardcompressie Kiezen Standaardmediabron Kiezen Sluitergeluid afspelen - Maak een wachtwoord aan om de geëxporteerde sleutels mee te versleutelen. U heeft dit wachtwoord nodig om de sleutels te kunnen importeren. Herstel van versleutelde berichten Sleutelback-up beheren - %1$s: 1 bericht %1$s: %2$d berichten @@ -1224,51 +1022,40 @@ %d melding %d meldingen - Nieuwe gebeurtenis Gesprek Nieuwe berichten Nieuwe uitnodiging Ik ** Versturen mislukt - open het gesprek - Start de systeemcamera in plaats van het aangepaste camerascherm. Deze optie vereist een externe app om de berichten mee op te nemen. - De opdracht ‘%s’ heeft meer parameters nodig, of sommige parameters zijn onjuist. Markdown is ingeschakeld. Markdown is uitgeschakeld. - Stil Voer een gebruikersnaam in. Gespreksleden lui laden Verbeter de prestaties door gespreksleden enkel bij de eerste weergave te laden. Uw thuisserver ondersteunt het lui laden van gespreksleden nog niet. Probeer het later opnieuw. - Sorry, er is een fout opgetreden - uitvouwen invouwen - Infogebied weergeven Altijd Voor berichten en fouten Enkel voor fouten - %1$s: %1$s: %2$s +%d %d+ Er is geen geldige APK van Google Play Services gevonden. Meldingen zullen mogelijk niet correct functioneren. - Wachtwoord aanmaken Wachtwoorden komen niet overeen Voer een wachtwoord in Wachtwoord is te zwak - Verwijder het wachtwoord als u wilt dat Element een herstelsleutel genereert. Geen Matrix-sessie beschikbaar - Verlies nooit uw versleutelde berichten Berichten in versleutelde gesprekken worden beveiligd met eind-tot-eind-versleuteling. Enkel de ontvanger(s) en u hebben de sleutels om deze berichten te lezen. \n @@ -1276,7 +1063,6 @@ Begin sleutelback-up te gebruiken (Geavanceerd) Sleutels handmatig exporteren - Beveilig uw back-up met een wachtwoord. We bewaren een versleutelde kopie van uw sleutels op onze thuisserver. Bescherm uw back-up met een wachtwoord om deze veilig te houden. \n @@ -1298,7 +1084,6 @@ De herstelsleutel is opgeslagen naar ‘%s’. \n \nLet op: dit bestand kan verwijderd worden als de app wordt verwijderd. - Gelieve er een kopie van te maken Herstelsleutel delen met… Herstelsleutel wordt gegenereerd met wachtwoord, dit proces kan enkele seconden duren. @@ -1306,25 +1091,18 @@ Onverwachte fout Back-up begonnen Uw versleutelingssleutels worden nu in de achtergrond naar uw thuisserver geback-upt. De initiële back-up kan enkele minuten duren. - - Weet u het zeker\? U kunt de toegang tot uw berichten verliezen indien u zich afmeldt of dit apparaat verliest. - Back-upversie wordt opgehaald… Gebruik uw herstelwachtwoord om uw versleutelde berichtgeschiedenis te ontgrendelen uw herstelsleutel gebruiken Als u uw herstelwachtwoord niet meer weet, kunt u %s. - Gebruik uw herstelsleutel om uw versleutelde berichtgeschiedenis te ontgrendelen Voer de herstelsleutel in - Berichtherstel - Herstelsleutel verloren\? U kunt er een nieuwe instellen in de instellingen. De back-up kan met dit wachtwoord niet ontsleuteld worden: controleer of u het juiste herstelwachtwoord heeft ingevoerd. Netwerkfout: controleer uw verbinding en probeer het opnieuw. - Back-up wordt hersteld: Herstelsleutel wordt berekend… Sleutels worden gedownload… @@ -1332,7 +1110,6 @@ Geschiedenis ontgrendelen Voer een herstelsleutel in De back-up kan met deze herstelsleutel niet ontsleuteld worden: controleer of u de juiste herstelsleutel heeft ingevoerd. - Back-up hersteld %s! Back-up met %d sleutel hersteld. @@ -1342,18 +1119,13 @@ Er is %d nieuwe sleutel toegevoegd aan deze sessie. Er zijn %d nieuwe sleutels toegevoegd aan deze sessie. - Verkrijgen van laatste herstelsleutelversie (%s) mislukt. Sessieversleuteling is niet actief - - Herstellen uit back-up Back-up verwijderen - Sleutelback-up is correct ingesteld voor deze sessie. Sleutelback-up is niet actief op deze sessie. Uw sleutels worden niet geback-upt vanaf deze sessie. - De back-up heeft een ondertekening van een onbekende sessie met ID %s. De back-up heeft een geldige ondertekening van deze sessie. De back-up heeft een geldige ondertekening van de geverifieerde sessie %s. @@ -1361,14 +1133,11 @@ De back-up heeft een ongeldige ondertekening van de geverifieerde sessie %s De back-up heeft een ongeldige ondertekening van de ongeverifieerde sessie %s Verkrijgen van vertrouwensinformatie voor back-up mislukt (%s). - Herstel nu met uw wachtwoord of herstelsleutel om sleutelback-up op deze sessie te gebruiken. Back-up wordt verwijderd… Verwijderen van back-up is mislukt (%s) - Back-up verwijderen Uw geback-upte versleutelingssleutels verwijderen van de server\? U zult uw herstelsleutel niet meer kunnen gebruiken om de versleutelde berichtgeschiedenis te lezen. - Nieuwe sleutelback-up Er is een nieuwe sleutelback-up voor versleutelde berichten gedetecteerd. \n @@ -1376,35 +1145,26 @@ Ik was het Verlies nooit uw versleutelde berichten Begin sleutelback-up te gebruiken - Verlies nooit uw versleutelde berichten Sleutelback-up gebruiken - Nieuwe sleutels voor versleutelde berichten Beheren in sleutelback-up - Back-up van sleutels wordt gemaakt… - Alle sleutels zijn geback-upt Back-up van %d sleutel wordt gemaakt… Back-up van %d sleutels wordt gemaakt… - Versie Algoritme Ondertekening - Ongeldig thuisserverontdekkingsantwoord Serveropties automatisch aanvullen Element heeft een aangepaste serverconfiguratie gedetecteerd voor uw gebruikers-ID-domein ‘%1$s’: \n%2$s Configuratie gebruiken - Sorry, vergadergesprekken met Jitsi worden nog niet ondersteund op oudere apparaten (met een Android-versie lager dan 5.0) - Sessie verifiëren - onbekend IP-adres Een nieuwe sessie vraagt versleutelingssleutels aan. \nSessienaam: %1$s @@ -1414,45 +1174,36 @@ \nSessienaam: %1$s \nLaatst gezien: %2$s \nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. - Verifiëren Delen Sleuteldeelverzoek Negeren - Verifieer door een korte tekenreeks te vergelijken. Voor een maximale beveiliging bevelen we aan om dit onder vier ogen te doen, of via een ander vertrouwd communicatiekanaal. Verificatie beginnen Inkomend verificatieverzoek Verifieer de sessie door deze als vertrouwd te markeren. Door de sessie van uw gesprekspartners te vertrouwen, hoeft u zich nog minder zorgen te maken over het gebruik van eind-tot-eind-versleutelde berichten. De sessie verifiëren zal deze als vertrouwd markeren, en deze ook aan uw gesprekspartner als vertrouwd markeren. - Verifieer deze sessie door te bevestigen dat de volgende emoticons op het scherm van uw gesprekspartner verschijnen Verifieer deze sessie door te bevestigen dat de volgende cijfers op het scherm van uw gesprekspartner verschijnen - U heeft een inkomend verificatieverzoek ontvangen. Verzoek bekijken Wachten op bevestiging van gesprekspartner… - Geverifieerd! U heeft de sessie geverifieerd. Beveiligde berichten met deze gebruiker worden eind-tot-eind-versleuteld en kunnen niet door derde partijen gelezen worden. Ik snap het - Verschijnt er niets\? Nog niet alle cliënten bieden ondersteuning voor interactieve verificatie. Gebruik de traditionele verificatiemethode. Traditionele verificatie gebruiken. - Sleutelverificatie Verzoek geannuleerd De andere partij heeft de verificatie geannuleerd. \n%s De verificatie is geannuleerd. \nReden: %s - Interactieve sessieverificatie Verificatieverzoek %s wil uw sessie verifiëren - De gebruiker heeft de verificatie geannuleerd Het verificatieproces is verlopen De sessie heeft geen weet van die transactie @@ -1464,22 +1215,18 @@ Sleutels komen niet overeen Gebruikers komen niet overeen Onbekende fout - Geen Intrekken Verbinding verbreken Nakijken Weigeren - Geen identiteitsserver geconfigureerd. - Oproep mislukt door verkeerd geconfigureerde server Vraag de beheerder van uw thuisserver (%1$s) om een TURN-server te configureren om oproepen betrouwbaar te doen werken. \n \nAls alternatief kunt u de publieke server op %2$s gebruiken. Dit is minder betrouwbaar en zal tevens uw IP-addres delen met die server. U kunt dit ook configureren in de Instellingen. Probeer %s te gebruiken Vraag het niet opnieuw - Kies een e-mailadres om te gebruiken voor accountherstel. Later kunt u ervoor kiezen om mensen u te laten vinden via uw e-mailadres. Kies een telefoonnummer. Later kunt u ervoor kiezen om mensen u te laten vinden via dit nummer. Kies een e-mailadres om te gebruiken voor accountherstel. Later kunt u ervoor kiezen om mensen u te laten vinden via uw e-mailadres of telefoonnummer. @@ -1500,25 +1247,19 @@ Geen achtergrondssynchronisatie U zal geen melding van berichten ontvangen als de app zich in de achtergrond bevindt. Kon de instellingen niet bijwerken. - - Voorkeur voor synchronisatie-interval %s \nDe synchronisatie is mogelijk uitgesteld als gevolg van de batterij of staat van uw apparaat (slaapmodus). Integraties Latn - Terugvaloproepassistentieserver toestaan Authenticatie vereist - - Gebruik een integratiebeheerder om bots, bruggen, widgets en stickerpakketten te beheren. \nIntegratiebeheerders ontvangen configuratiedata en kunnen widgets aanpassen, gespreksuitnodigingen versturen en bestuursniveaus instellen namens u. Ontdekken Beheer uw ontdekinstellingen. Integraties toestaan Integratiebeheerder - Publieke naam (zichtbaar voor mensen met wie u communiceert) De publieke naam van een sessie is zichtbaar voor mensen met wie u communiceert Widget @@ -1531,46 +1272,35 @@ Widget herladen Openen in browser Toegang intrekken voor mij - Uw weergavenaam Uw profielfoto-URL Uw gebruikers-ID Uw thema Widget-ID Gespreks-ID - - Deze widget wil gebruik maken van de volgende bronnen: Toestaan Alles blokkeren Camera gebruiken Microfoon gebruiken DRM-beschermde media lezen - Geen integratiebeheerder ingesteld. Om verder te gaan dient u de dienstvoorwaarden te aanvaarden. - Er bestaat al een back-up op uw thuisserver Het lijkt erop dat u al een back-up van uw herstelsleutel heeft uit een andere sessie. Wilt u deze vervangen door degene die u nu aanmaakt\? Vervangen Stoppen - Back-upstatus wordt gecontroleerd U bent afgemeld vanwege onjuiste of verlopen gebruikersreferenties. - U gebruikt geen identiteitsserver Er is geen identiteitsserver geconfigureerd. Dit is vereist om uw wachtwoord opnieuw in te stellen. - Het lijkt er op dat je probeert verbinding te maken met een andere thuisserver. Wil je uitloggen\? - Bewerken Beantwoorden - Opnieuw proberen Betreed een kamer om de applicatie te gebruiken. Heeft je een uitnodiging gestuurd Uitgenodigd door %s - Je bent helemaal bij! Je hebt geen ongelezen berichten meer Welkom thuis! @@ -1579,19 +1309,15 @@ Je directe gesprekken zullen hier worden weergegeven Kamers Je kamers zullen hier worden weergegeven - Reacties Bevestigen Leuk vinden Reactie Toevoegen Reacties Bekijken Reacties - Gebeurtenis verwijderd door gebruiker Gebeurtenis gemodereerd door gesprek beheerder Laatst bewerkt door %1$s op %2$s - - Niet correcte gebeurtenis, kan niet weergeven Maak een nieuw gesprek aan Geen netwerk. Controleer uw internet verbinding. @@ -1599,16 +1325,15 @@ Wijzig netwerk Even wachten… Alle Gemeenschappen - Dit gesprek kan niet worden voorvertoond De voorvertoning van wereld-leesbare gesprekken zijn nog niet ondersteund in Element - Gesprekken Directe Berichten - Nieuw Gesprek AANMAKEN Gespreksnaam Publiek Iedereen zal dit gesprek kunnen toetreden - + Pauzeren + Afspelen + \ No newline at end of file From 1933fc948c733d5f812d2c3e7a7e850a7a804cc1 Mon Sep 17 00:00:00 2001 From: Keij0 Date: Wed, 2 Dec 2020 13:20:59 +0000 Subject: [PATCH 035/218] Translated using Weblate (Polish) Currently translated at 76.8% (1526 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- vector/src/main/res/values-pl/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 6501b49aee..a7d29516b2 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -29,7 +29,7 @@ Wideo Nie można rozpocząć połączenia, spróbuj ponownie później Ze względu na brak pewnych uprawnień, niektóre funkcje mogą nie działać… - Musisz posiadać uprawninenia, aby rozpocząć połączenie grupowe + Musisz posiadać uprawnienia, aby rozpocząć połączenie grupowe Nie można rozpocząć połączenia Informacje o sesji Połączenia grupowe nie są obsługiwane w szyfrowanych pokojach @@ -1777,7 +1777,7 @@ Spróbuj uruchomić ponownie aplikację. Zamknij Wstrzymaj Odtwórz - Nie posiadasz wymaganych uprawnień do rozpoczęcia połaczenia w tym pokoju + Nie posiadasz wymaganych uprawnień do rozpoczęcia połączenia w tym pokoju Odbierz Usuwanie widżetu nie powiodło się Dodawanie widżetu nie powiodło się @@ -1787,6 +1787,6 @@ Spróbuj uruchomić ponownie aplikację. Rozpocznij połączenie wideo Połączenie grupowe już trwa! Nie posiadasz wymaganych uprawnień aby rozpocząć połączenie grupowe w tym pokoju - Nie posiadasz wymaganych uprawnień do rozpoczęcia połaczenia + Nie posiadasz wymaganych uprawnień do rozpoczęcia połączenia Nie posiadasz wymaganych uprawnień aby rozpocząć połączenie grupowe \ No newline at end of file From 5faaabf2f4740d014fee608044dded41a83384c5 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 2 Dec 2020 13:58:07 +0000 Subject: [PATCH 036/218] Translated using Weblate (Swedish) Currently translated at 97.3% (1933 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 4f71783b20..f5a848f819 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -749,7 +749,7 @@ Moderatorer Den här sessionen kan inte dela den här verifikationen med dina andra sessioner. \nVerifikationen kommer att sparas lokalt och delas i en framtida version av appen. - Aktivera totalsträckskryptering + Aktivera totalsträckskryptering… Efter aktivering så kan kryptering inte avaktiveras. Efter aktivering så kan kryptering inte avaktiveras. Meddelanden som skickas i ett krypterat rum kan inte läsas av servern, bara av deltagarna i rummet. Att aktivera kryptering kan göra att många bottar och bryggor inte funkar ordentligt. Meddelanden mellan dig och den här användaren är totalsträckskrypterade och kan inte läsas av tredje parter. From 167144b5049e8a03e04cbb10e24be4c808e2b661 Mon Sep 17 00:00:00 2001 From: Hivaa Date: Thu, 3 Dec 2020 10:33:52 +0000 Subject: [PATCH 037/218] Translated using Weblate (Persian) Currently translated at 91.9% (193 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/fa/ --- matrix-sdk-android/src/main/res/values-fa/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-fa/strings.xml b/matrix-sdk-android/src/main/res/values-fa/strings.xml index 11a786f5ac..37aa2629a0 100644 --- a/matrix-sdk-android/src/main/res/values-fa/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fa/strings.xml @@ -213,4 +213,10 @@ %1$s پیوست گفت‌وگو را ایجاد کردید %1$s گفت‌وگو را ایجاد کرد + + %1$s، %2$s، %3$s و %4$d نفر دیگر + %1$s، %2$s، %3$s و %4$d نفر دیگر + + %1$s، %2$s و %3$s + %1$s، %2$s و %3$s \ No newline at end of file From bd926fa74b0da64ace478e4e13f416d3c85a8ae5 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 2 Dec 2020 13:52:11 +0000 Subject: [PATCH 038/218] Translated using Weblate (Swedish) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv/changelogs/40100100.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/sv/changelogs/40100100.txt b/fastlane/metadata/android/sv/changelogs/40100100.txt index 6da756aca9..afdef3583b 100644 --- a/fastlane/metadata/android/sv/changelogs/40100100.txt +++ b/fastlane/metadata/android/sv/changelogs/40100100.txt @@ -1 +1,2 @@ -// ATT GÖRA +Den här nya versionen innehåller mest buggfixar och förbättringar. Det går nu mycket snabbare att skicka meddelanden. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.10 From bd5ac514efca81d8883182ae5de20466381c9dcf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 15:33:42 +0100 Subject: [PATCH 039/218] PreviewUrl: create the task and the service --- .../matrix/android/sdk/api/session/Session.kt | 6 + .../sdk/api/session/media/MediaService.kt | 11 +- .../sdk/internal/session/DefaultSession.kt | 4 + .../session/media/ClearPreviewUrlCacheTask.kt | 40 ++++++ .../session/media/DefaultMediaService.kt | 46 +++++++ .../session/media/GetPreviewUrlTask.kt | 122 ++++++++++++++++++ .../session/media/GetRawPreviewUrlTask.kt | 42 ++++++ .../sdk/internal/session/media/MediaAPI.kt | 2 +- .../sdk/internal/session/media/MediaModule.kt | 15 ++- .../session/media/PreviewUrlMapper.kt | 31 +++++ 10 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/ClearPreviewUrlCacheTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 56609610f1..8a95baf3cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService +import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService @@ -181,6 +182,11 @@ interface Session : */ fun widgetService(): WidgetService + /** + * Returns the media service associated with the session + */ + fun mediaService(): MediaService + /** * Returns the integration manager service associated with the session */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt index 6594b2d0b5..9040ec7d5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt @@ -29,14 +29,19 @@ interface MediaService { /** * Get Raw Url Preview data from the homeserver. There is no cache management for this request + * @param url The url to get the preview data from + * @param timestamp The optional timestamp */ - suspend fun getRawPreviewUrl(url: String): JsonDict + suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict /** * Get Url Preview data from the homeserver, or from cache, depending on the cache strategy - * @param url + * @param url The url to get the preview data from + * @param timestamp The optional timestamp. Note that this parameter is not taken into account + * if the data is already in cache and the cache strategy allow to use it + * @param cacheStrategy the cache strategy, see the type for more details */ - suspend fun getPreviewUrl(url: String, cacheStrategy: CacheStrategy): PreviewUrlData + suspend fun getPreviewUrl(url: String, timestamp: Long?, cacheStrategy: CacheStrategy): PreviewUrlData /** * Clear the cache of all retrieved UrlPreview data diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 25345e953c..c5f3f65a34 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService +import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService @@ -102,6 +103,7 @@ internal class DefaultSession @Inject constructor( private val permalinkService: Lazy, private val secureStorageService: Lazy, private val profileService: Lazy, + private val mediaService: Lazy, private val widgetService: Lazy, private val syncThreadProvider: Provider, private val contentUrlResolver: ContentUrlResolver, @@ -263,6 +265,8 @@ internal class DefaultSession @Inject constructor( override fun widgetService(): WidgetService = widgetService.get() + override fun mediaService(): MediaService = mediaService.get() + override fun integrationManagerService() = integrationManagerService override fun callSignalingService(): CallSignalingService = callSignalingService.get() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/ClearPreviewUrlCacheTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/ClearPreviewUrlCacheTask.kt new file mode 100644 index 0000000000..004b622c64 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/ClearPreviewUrlCacheTask.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import com.zhuinden.monarchy.Monarchy +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface ClearPreviewUrlCacheTask : Task + +internal class DefaultClearPreviewUrlCacheTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy +) : ClearPreviewUrlCacheTask { + + override suspend fun execute(params: Unit) { + monarchy.awaitTransaction { realm -> + realm.where() + .findAll() + .deleteAllFromRealm() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt new file mode 100644 index 0000000000..846b81ae72 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import org.matrix.android.sdk.api.cache.CacheStrategy +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.media.MediaService +import org.matrix.android.sdk.api.session.media.PreviewUrlData +import org.matrix.android.sdk.api.util.JsonDict +import javax.inject.Inject + +internal class DefaultMediaService @Inject constructor( + private val clearPreviewUrlCacheTask: ClearPreviewUrlCacheTask, + private val getPreviewUrlTask: GetPreviewUrlTask, + private val getRawPreviewUrlTask: GetRawPreviewUrlTask +) : MediaService { + override fun extractUrls(event: Event): List { + TODO("Not yet implemented") + } + + override suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict { + return getRawPreviewUrlTask.execute(GetRawPreviewUrlTask.Params(url, timestamp)) + } + + override suspend fun getPreviewUrl(url: String, timestamp: Long?, cacheStrategy: CacheStrategy): PreviewUrlData { + return getPreviewUrlTask.execute(GetPreviewUrlTask.Params(url, timestamp, cacheStrategy)) + } + + override suspend fun clearCache() { + clearPreviewUrlCacheTask.execute(Unit) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt new file mode 100644 index 0000000000..69cdfa8faa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import com.zhuinden.monarchy.Monarchy +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.cache.CacheStrategy +import org.matrix.android.sdk.api.session.media.PreviewUrlData +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import java.util.Date +import javax.inject.Inject + +internal interface GetPreviewUrlTask : Task { + data class Params( + val url: String, + val timestamp: Long?, + val cacheStrategy: CacheStrategy + ) +} + +internal class DefaultGetPreviewUrlTask @Inject constructor( + private val mediaAPI: MediaAPI, + private val eventBus: EventBus, + @SessionDatabase private val monarchy: Monarchy +) : GetPreviewUrlTask { + + override suspend fun execute(params: GetPreviewUrlTask.Params): PreviewUrlData { + return when (params.cacheStrategy) { + CacheStrategy.NoCache -> doRequest(params.url, params.timestamp) + is CacheStrategy.TtlCache -> doRequestWithCache( + params.url, + params.timestamp, + params.cacheStrategy.validityDurationInMillis, + params.cacheStrategy.strict + ) + CacheStrategy.InfiniteCache -> doRequestWithCache( + params.url, + params.timestamp, + Long.MAX_VALUE, + true + ) + } + } + + private suspend fun doRequest(url: String, timestamp: Long?): PreviewUrlData { + return executeRequest(eventBus) { + apiCall = mediaAPI.getPreviewUrlData(url, timestamp) + } + .toPreviewUrlData(url) + } + + private fun JsonDict.toPreviewUrlData(url: String): PreviewUrlData { + return PreviewUrlData( + url = (get("og:url") as? String) ?: url, + siteName = get("og:site_name") as? String, + title = get("og:title") as? String, + description = get("og:description") as? String, + mxcUrl = get("og:image") as? String + ) + } + + private suspend fun doRequestWithCache(url: String, timestamp: Long?, validityDurationInMillis: Long, strict: Boolean): PreviewUrlData { + // Get data from cache + var dataFromCache: PreviewUrlData? = null + var isCacheValid = false + monarchy.doWithRealm { realm -> + val entity = PreviewUrlCacheEntity.get(realm, url) + dataFromCache = entity?.toDomain() + isCacheValid = entity != null && Date().time < entity.lastUpdatedTimestamp + validityDurationInMillis + } + + val finalDataFromCache = dataFromCache + if (finalDataFromCache != null && isCacheValid) { + return finalDataFromCache + } + + // No cache or outdated cache + val data = try { + doRequest(url, timestamp) + } catch (throwable: Throwable) { + // In case of error, we can return value from cache even if outdated + return finalDataFromCache + ?.takeIf { !strict } + ?: throw throwable + } + + // Store cache + monarchy.awaitTransaction { realm -> + val previewUrlCacheEntity = PreviewUrlCacheEntity.getOrCreate(realm, url) + previewUrlCacheEntity.urlFromServer = data.url + previewUrlCacheEntity.siteName = data.siteName + previewUrlCacheEntity.title = data.title + previewUrlCacheEntity.description = data.description + previewUrlCacheEntity.mxcUrl = data.mxcUrl + + previewUrlCacheEntity.lastUpdatedTimestamp = Date().time + } + + return data + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt new file mode 100644 index 0000000000..6c5dad2422 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetRawPreviewUrlTask : Task { + data class Params( + val url: String, + val timestamp: Long? + ) +} + +internal class DefaultGetRawPreviewUrlTask @Inject constructor( + private val mediaAPI: MediaAPI, + private val eventBus: EventBus +) : GetRawPreviewUrlTask { + + override suspend fun execute(params: GetRawPreviewUrlTask.Params): JsonDict { + return executeRequest(eventBus) { + apiCall = mediaAPI.getPreviewUrlData(params.url, params.timestamp) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt index 821d9b9875..bbb4f1e06a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt @@ -39,5 +39,5 @@ internal interface MediaAPI { * if it does not have the requested version available. */ @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url") - fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Int?): Call + fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): Call } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt index 84a82dc75a..bc58b3f444 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaModule.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.internal.session.media +import dagger.Binds import dagger.Module import dagger.Provides +import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.internal.session.SessionScope import retrofit2.Retrofit @@ -34,6 +36,15 @@ internal abstract class MediaModule { } } -// @Binds -// abstract fun bindGetHomeServerCapabilitiesTask(task: DefaultGetHomeServerCapabilitiesTask): GetHomeServerCapabilitiesTask + @Binds + abstract fun bindMediaService(service: DefaultMediaService): MediaService + + @Binds + abstract fun bindGetRawPreviewUrlTask(task: DefaultGetRawPreviewUrlTask): GetRawPreviewUrlTask + + @Binds + abstract fun bindGetPreviewUrlTask(task: DefaultGetPreviewUrlTask): GetPreviewUrlTask + + @Binds + abstract fun bindClearMediaCacheTask(task: DefaultClearPreviewUrlCacheTask): ClearPreviewUrlCacheTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt new file mode 100644 index 0000000000..dd1a9ead26 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/PreviewUrlMapper.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import org.matrix.android.sdk.api.session.media.PreviewUrlData +import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity + +/** + * PreviewUrlCacheEntity -> PreviewUrlData + */ +internal fun PreviewUrlCacheEntity.toDomain() = PreviewUrlData( + url = urlFromServer ?: url, + siteName = siteName, + title = title, + description = description, + mxcUrl = mxcUrl +) From dd150c6d7e78521d640849d88d732266d7302b7d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 18:14:06 +0100 Subject: [PATCH 040/218] Remove unnecessary non-null assertion --- .../android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt | 2 +- .../sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index 606f57b467..eb8b8b9730 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -264,7 +264,7 @@ class KeysBackupTest : InstrumentedTest { assertNotNull(decryption) // - Check decryptKeyBackupData() returns stg val sessionData = keysBackup - .decryptKeyBackupData(keyBackupData!!, + .decryptKeyBackupData(keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt index 944d1036d3..b6e5ae7364 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt @@ -111,7 +111,7 @@ class KeysBackupTestHelper( Assert.assertTrue(keysBackup.isEnabled) stateObserver.stopAndCheckStates(null) - return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!) + return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version) } /** From be20f9b455d735a066139afe1e66a54e6cf92db3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 18:41:16 +0100 Subject: [PATCH 041/218] PreviewUrl: extract url from Content --- .../session/media/UrlsExtractorTest.kt | 91 +++++++++++++++++++ .../session/media/DefaultMediaService.kt | 17 +++- .../internal/session/media/UrlsExtractor.kt | 40 ++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt new file mode 100644 index 0000000000..a3fc876de3 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.junit.runner.RunWith +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType + +@RunWith(AndroidJUnit4::class) +internal class UrlsExtractorTest : InstrumentedTest { + + private val urlsExtractor = UrlsExtractor() + + @Test + fun wrongEventTypeTest() { + createEvent(body = "https://matrix.org") + .copy(type = EventType.STATE_ROOM_GUEST_ACCESS) + .let { urlsExtractor.extract(it) } + .size shouldBeEqualTo 0 + } + + @Test + fun oneUrlTest() { + createEvent(body = "https://matrix.org") + .let { urlsExtractor.extract(it) } + .let { result -> + result.size shouldBeEqualTo 1 + result[0] shouldBeEqualTo "https://matrix.org" + } + } + + @Test + fun oneUrlWithParamTest() { + createEvent(body = "https://matrix.org?foo=bar") + .let { urlsExtractor.extract(it) } + .let { result -> + result.size shouldBeEqualTo 1 + result[0] shouldBeEqualTo "https://matrix.org?foo=bar" + } + } + + @Test + fun oneUrlInlinedTest() { + createEvent(body = "Hello https://matrix.org, how are you?") + .let { urlsExtractor.extract(it) } + .let { result -> + result.size shouldBeEqualTo 1 + result[0] shouldBeEqualTo "https://matrix.org" + } + } + + @Test + fun twoUrlsTest() { + createEvent(body = "https://matrix.org https://example.org") + .let { urlsExtractor.extract(it) } + .let { result -> + result.size shouldBeEqualTo 2 + result[0] shouldBeEqualTo "https://matrix.org" + result[1] shouldBeEqualTo "https://example.org" + } + } + + private fun createEvent(body: String): Event = Event( + type = EventType.MESSAGE, + content = MessageTextContent( + msgType = MessageType.MSGTYPE_TEXT, + body = body + ).toContent() + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt index 846b81ae72..084a88b8cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.media +import androidx.collection.LruCache import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.media.MediaService @@ -26,12 +27,23 @@ import javax.inject.Inject internal class DefaultMediaService @Inject constructor( private val clearPreviewUrlCacheTask: ClearPreviewUrlCacheTask, private val getPreviewUrlTask: GetPreviewUrlTask, - private val getRawPreviewUrlTask: GetRawPreviewUrlTask + private val getRawPreviewUrlTask: GetRawPreviewUrlTask, + private val urlsExtractor: UrlsExtractor ) : MediaService { + // Cache of extracted URLs + private val extractedUrlsCache = LruCache>(1_000) + override fun extractUrls(event: Event): List { - TODO("Not yet implemented") + val cacheKey = event.cacheKey() + return extractedUrlsCache.get(cacheKey) + ?: let { + urlsExtractor.extract(event) + .also { extractedUrlsCache.put(cacheKey, it) } + } } + private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}" + override suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict { return getRawPreviewUrlTask.execute(GetRawPreviewUrlTask.Params(url, timestamp)) } @@ -41,6 +53,7 @@ internal class DefaultMediaService @Inject constructor( } override suspend fun clearCache() { + extractedUrlsCache.evictAll() clearPreviewUrlCacheTask.execute(Unit) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt new file mode 100644 index 0000000000..789c57236b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.media + +import android.util.Patterns +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import javax.inject.Inject + +internal class UrlsExtractor @Inject constructor() { + private val urlRegex = Patterns.WEB_URL.toRegex() + + fun extract(event: Event): List { + return event.takeIf { it.getClearType() == EventType.MESSAGE } + ?.getClearContent() + ?.toModel() + ?.body + ?.let { urlRegex.findAll(it) } + ?.map { it.value } + ?.distinct() + ?.toList() + .orEmpty() + } +} From a36d5684b888c72e249e1e5ff85636fe252cfaf7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 18:52:35 +0100 Subject: [PATCH 042/218] Create extension for androidx.collection.LruCache --- .../session/media/DefaultMediaService.kt | 8 ++----- .../android/sdk/internal/util/LruCache.kt | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LruCache.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt index 084a88b8cf..1a400ccfcf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.media.PreviewUrlData import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.util.getOrPut import javax.inject.Inject internal class DefaultMediaService @Inject constructor( @@ -34,12 +35,7 @@ internal class DefaultMediaService @Inject constructor( private val extractedUrlsCache = LruCache>(1_000) override fun extractUrls(event: Event): List { - val cacheKey = event.cacheKey() - return extractedUrlsCache.get(cacheKey) - ?: let { - urlsExtractor.extract(event) - .also { extractedUrlsCache.put(cacheKey, it) } - } + return extractedUrlsCache.getOrPut(event.cacheKey()) { urlsExtractor.extract(event) } } private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LruCache.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LruCache.kt new file mode 100644 index 0000000000..0998601db6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LruCache.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.util + +import androidx.collection.LruCache + +@Suppress("NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") +internal inline fun LruCache.getOrPut(key: K, defaultValue: () -> V): V { + return get(key) ?: defaultValue().also { put(key, it) } +} From fcd9fe7d5a0c230409ac42b9c2644bdf9c5389c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 3 Dec 2020 19:32:49 +0100 Subject: [PATCH 043/218] PreviewUrl: layout for a single PreviewUrl --- .../res/layout/item_timeline_event_base.xml | 10 +++ vector/src/main/res/layout/url_preview.xml | 72 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 vector/src/main/res/layout/url_preview.xml diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 15016e0abe..4be92a9b1c 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -143,6 +143,16 @@ android:addStatesFromChildren="true" android:orientation="vertical"> + + + + + + + + + + + + + + + \ No newline at end of file From 48354c779374be1fea059fcf9084bd231563c184 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 07:46:09 +0100 Subject: [PATCH 044/218] PreviewUrl: Application part - WIP --- .../internal/session/media/UrlsExtractor.kt | 2 + .../app/core/ui/views/PreviewUrlView.kt | 147 ++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 8 + .../timeline/factory/MessageItemFactory.kt | 6 + .../detail/timeline/item/MessageTextItem.kt | 31 ++++ .../timeline/url/PreviewUrlRetriever.kt | 108 +++++++++++++ .../detail/timeline/url/PreviewUrlUiState.kt | 39 +++++ .../features/media/ImageContentRenderer.kt | 17 +- .../res/layout/item_timeline_event_base.xml | 11 -- .../item_timeline_event_text_message_stub.xml | 26 +++- vector/src/main/res/layout/url_preview.xml | 8 +- 11 files changed, 382 insertions(+), 21 deletions(-) create mode 100755 vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt index 789c57236b..b6cea04600 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType import javax.inject.Inject internal class UrlsExtractor @Inject constructor() { @@ -30,6 +31,7 @@ internal class UrlsExtractor @Inject constructor() { return event.takeIf { it.getClearType() == EventType.MESSAGE } ?.getClearContent() ?.toModel() + ?.takeIf { it.msgType == MessageType.MSGTYPE_TEXT || it.msgType == MessageType.MSGTYPE_EMOTE } ?.body ?.let { urlRegex.findAll(it) } ?.map { it.value } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt new file mode 100755 index 0000000000..a63273f00a --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.ui.views + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import butterknife.BindView +import butterknife.ButterKnife +import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState +import im.vector.app.features.media.ImageContentRenderer +import org.matrix.android.sdk.api.session.media.PreviewUrlData +import timber.log.Timber + +/** + * A View to display a PreviewUrl and some other state + */ +class PreviewUrlView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener { + + @BindView(R.id.url_preview_title) + lateinit var titleView: TextView + + @BindView(R.id.url_preview_image) + lateinit var imageView: ImageView + + @BindView(R.id.url_preview_description) + lateinit var descriptionView: TextView + + @BindView(R.id.url_preview_site) + lateinit var siteView: TextView + + var delegate: Delegate? = null + + init { + setupView() + } + + private var state: PreviewUrlUiState = PreviewUrlUiState.Unknown + + /** + * This methods is responsible for rendering the view according to the newState + * + * @param newState the newState representing the view + */ + fun render(newState: PreviewUrlUiState, + imageContentRenderer: ImageContentRenderer, + force: Boolean = false) { + if (newState == state && !force) { + Timber.v("State unchanged") + return + } + Timber.v("Rendering $newState") + + state = newState + + hideAll() + when (newState) { + PreviewUrlUiState.Unknown, + PreviewUrlUiState.NoUrl -> renderHidden() + PreviewUrlUiState.Loading -> renderLoading() + is PreviewUrlUiState.Error -> renderHidden() + is PreviewUrlUiState.Data -> renderData(newState.previewUrlData, imageContentRenderer) + } + } + + override fun onClick(v: View?) { + when (val finalState = state) { + is PreviewUrlUiState.Data -> delegate?.onUrlClicked(finalState.previewUrlData.url) + else -> Unit + } + } + + // PRIVATE METHODS **************************************************************************************************************************************** + + private fun setupView() { + inflate(context, R.layout.url_preview, this) + ButterKnife.bind(this) + + setOnClickListener(this) + } + + private fun renderHidden() { + isVisible = false + } + + private fun renderLoading() { + // TODO + isVisible = false + } + + private fun renderData(previewUrlData: PreviewUrlData, imageContentRenderer: ImageContentRenderer) { + isVisible = true + titleView.setTextOrHide(previewUrlData.title) + val mxcUrl = previewUrlData.mxcUrl + imageView.isVisible = mxcUrl != null + if (mxcUrl != null) { + imageContentRenderer.render(mxcUrl, imageView) + } + descriptionView.setTextOrHide(previewUrlData.description) + siteView.setTextOrHide(previewUrlData.siteName) + } + + /** + * Hide all views that are not visible in all state + */ + private fun hideAll() { + titleView.isVisible = false + imageView.isVisible = false + descriptionView.isVisible = false + siteView.isVisible = false + } + + /** + * An interface to delegate some actions to another object + */ + interface Delegate { + // TODO + fun onUrlClicked(url: String) + + // TODO + // fun close() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a83dddc9ac..77ed959300 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -40,6 +40,7 @@ import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.raw.wellknown.getElementWellknown @@ -112,6 +113,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val rainbowGenerator: RainbowGenerator, private val session: Session, private val rawService: RawService, + private val previewUrlRetriever: PreviewUrlRetriever, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val stickerPickerActionHandler: StickerPickerActionHandler, private val roomSummaryHolder: RoomSummaryHolder, @@ -1350,6 +1352,12 @@ class RoomDetailViewModel @AssistedInject constructor( override fun onTimelineUpdated(snapshot: List) { timelineEvents.accept(snapshot) + + // PreviewUrl + // TODO Check if URL preview is enable, check if encrypted room, etc. + snapshot.forEach { + previewUrlRetriever.getPreviewUrl(it.root, viewModelScope) + } } override fun onTimelineFailure(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 2b067ccf3f..f3be4337ef 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -58,6 +58,7 @@ import im.vector.app.features.home.room.detail.timeline.item.VerificationRequest import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem_ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.home.room.detail.timeline.tools.linkify +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.html.CodeVisitor import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor @@ -107,6 +108,7 @@ class MessageItemFactory @Inject constructor( private val defaultItemFactory: DefaultItemFactory, private val noticeItemFactory: NoticeItemFactory, private val avatarSizeProvider: AvatarSizeProvider, + private val previewUrlRetriever: PreviewUrlRetriever, private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val session: Session) { @@ -424,6 +426,8 @@ class MessageItemFactory @Inject constructor( } .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) .searchForPills(isFormatted) + .previewUrlRetriever(previewUrlRetriever) + .imageContentRenderer(imageContentRenderer) .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) .highlighted(highlight) @@ -529,6 +533,8 @@ class MessageItemFactory @Inject constructor( } } .leftGuideline(avatarSizeProvider.leftGuideline) + .previewUrlRetriever(previewUrlRetriever) + .imageContentRenderer(imageContentRenderer) .attributes(attributes) .highlighted(highlight) .movementMethod(createLinkMovementMethod(callback)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index feba62dea3..5ba5496f3d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -23,7 +23,11 @@ import androidx.core.widget.TextViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.ui.views.PreviewUrlView import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState +import im.vector.app.features.media.ImageContentRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageTextItem : AbsMessageItem() { @@ -37,10 +41,22 @@ abstract class MessageTextItem : AbsMessageItem() { @EpoxyAttribute var useBigFont: Boolean = false + @EpoxyAttribute + var previewUrlRetriever: PreviewUrlRetriever? = null + + @EpoxyAttribute + var imageContentRenderer: ImageContentRenderer? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var movementMethod: MovementMethod? = null + private val previewUrlViewUpdater = PreviewUrlViewUpdater() + override fun bind(holder: Holder) { + previewUrlViewUpdater.previewUrlView = holder.previewUrlView + previewUrlViewUpdater.imageContentRenderer = imageContentRenderer + previewUrlRetriever?.addListener(attributes.informationData.eventId, previewUrlViewUpdater) + if (useBigFont) { holder.messageView.textSize = 44F } else { @@ -65,12 +81,27 @@ abstract class MessageTextItem : AbsMessageItem() { holder.messageView.setTextFuture(textFuture) } + override fun unbind(holder: Holder) { + super.unbind(holder) + previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater) + } + override fun getViewType() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { val messageView by bind(R.id.messageTextView) + val previewUrlView by bind(R.id.messageUrlPreview) } + inner class PreviewUrlViewUpdater : PreviewUrlRetriever.PreviewUrlRetrieverListener { + var previewUrlView: PreviewUrlView? = null + var imageContentRenderer: ImageContentRenderer? = null + + override fun onStateUpdated(state: PreviewUrlUiState) { + val safeImageContentRenderer = imageContentRenderer ?: return + previewUrlView?.render(state, safeImageContentRenderer) + } + } companion object { private const val STUB_ID = R.id.messageContentTextStub } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt new file mode 100644 index 0000000000..94df822218 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.url + +import im.vector.app.core.di.ScreenScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.cache.CacheStrategy +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +@ScreenScope +class PreviewUrlRetriever @Inject constructor( + private val session: Session +) { + private val data = mutableMapOf() + private val listeners = mutableMapOf>() + + fun getPreviewUrl(event: Event, coroutineScope: CoroutineScope) { + val eventId = event.eventId ?: return + + val urlToRetrieve = synchronized(data) { + if (data[eventId] == null) { + // Keep only the first URL for the moment + val url = session.mediaService().extractUrls(event).firstOrNull() + if (url == null) { + updateState(eventId, PreviewUrlUiState.NoUrl) + } else { + updateState(eventId, PreviewUrlUiState.Loading) + } + url + } else { + // Already handled + null + } + } + + urlToRetrieve?.let { urlToRetrieve -> + coroutineScope.launch { + runCatching { + session.mediaService().getPreviewUrl( + url = urlToRetrieve, + timestamp = null, + cacheStrategy = CacheStrategy.TtlCache(CACHE_VALIDITY, false) + ) + }.fold( + { + synchronized(data) { + updateState(eventId, PreviewUrlUiState.Data(it)) + } + }, + { + synchronized(data) { + updateState(eventId, PreviewUrlUiState.Error(it)) + } + } + ) + } + } + } + + private fun updateState(eventId: String, state: PreviewUrlUiState) { + data[eventId] = state + // Notify the listener + listeners[eventId].orEmpty().forEach { + it.onStateUpdated(state) + } + } + + // Called by the Epoxy item during binding + fun addListener(key: String, listener: PreviewUrlRetrieverListener) { + listeners.getOrPut(key) { mutableSetOf() }.add(listener) + + // Give the current state if any + synchronized(data) { + listener.onStateUpdated(data[key] ?: PreviewUrlUiState.Unknown) + } + } + + // Called by the Epoxy item during unbinding + fun removeListener(key: String, listener: PreviewUrlRetrieverListener) { + listeners.getOrPut(key) { mutableSetOf() }.remove(listener) + } + + interface PreviewUrlRetrieverListener { + fun onStateUpdated(state: PreviewUrlUiState) + } + + companion object { + // One week in millis + private const val CACHE_VALIDITY: Long = 7 * 24 * 3_600 * 1_000 + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt new file mode 100644 index 0000000000..e411ba3031 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.url + +import org.matrix.android.sdk.api.session.media.PreviewUrlData + +/** + * The state representing a preview url UI state for an Event + */ +sealed class PreviewUrlUiState { + // No info + object Unknown : PreviewUrlUiState() + + // The event does not contain any URLs + object NoUrl : PreviewUrlUiState() + + // Loading + object Loading : PreviewUrlUiState() + + // Error + data class Error(val throwable: Throwable) : PreviewUrlUiState() + + // PreviewUrl data + data class Data(val previewUrlData: PreviewUrlData) : PreviewUrlUiState() +} diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 187c2e85c3..31492b299a 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -83,6 +83,19 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: STICKER } + /** + * For url preview + */ + fun render(mxcUrl: String, imageView: ImageView) { + val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() + val imageUrl = contentUrlResolver.resolveFullSize(mxcUrl) ?: return + + GlideApp.with(imageView) + .load(imageUrl) + .placeholder(R.drawable.ic_image) + .into(imageView) + } + /** * For gallery */ @@ -227,7 +240,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() val resolvedUrl = when (mode) { Mode.FULL_SIZE, - Mode.STICKER -> resolveUrl(data) + Mode.STICKER -> resolveUrl(data) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE) } // Fallback to base url @@ -295,7 +308,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: finalHeight = min(maxImageWidth * height / width, maxImageHeight) finalWidth = finalHeight * width / height } - Mode.STICKER -> { + Mode.STICKER -> { // limit on width val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2) finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp) diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 4be92a9b1c..cfde244217 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -87,7 +87,6 @@ android:id="@+id/messageContentTextStub" style="@style/TimelineContentStubBaseParams" android:layout_height="wrap_content" - android:inflatedId="@id/messageTextView" android:layout="@layout/item_timeline_event_text_message_stub" tools:visibility="visible" /> @@ -143,16 +142,6 @@ android:addStatesFromChildren="true" android:orientation="vertical"> - - - + android:orientation="vertical"> + + + + + + diff --git a/vector/src/main/res/layout/url_preview.xml b/vector/src/main/res/layout/url_preview.xml index c2a8cea108..fc896b359a 100644 --- a/vector/src/main/res/layout/url_preview.xml +++ b/vector/src/main/res/layout/url_preview.xml @@ -1,10 +1,11 @@ - + android:layout_height="wrap_content" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - \ No newline at end of file + \ No newline at end of file From fa7b0a24a73ee53dba502af774b6c45ad75d3f8f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 08:01:00 +0100 Subject: [PATCH 045/218] PreviewUrl: Application part - bugfix --- .../home/room/detail/timeline/item/MessageTextItem.kt | 2 +- .../home/room/detail/timeline/url/PreviewUrlRetriever.kt | 9 ++++----- .../home/room/detail/timeline/url}/PreviewUrlView.kt | 8 ++------ .../res/layout/item_timeline_event_text_message_stub.xml | 2 +- vector/src/main/res/layout/url_preview.xml | 1 - 5 files changed, 8 insertions(+), 14 deletions(-) rename vector/src/main/java/im/vector/app/{core/ui/views => features/home/room/detail/timeline/url}/PreviewUrlView.kt (93%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 5ba5496f3d..8bb5a84695 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -23,10 +23,10 @@ import androidx.core.widget.TextViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.core.ui.views.PreviewUrlView import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView import im.vector.app.features.media.ImageContentRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt index 94df822218..a36b2367f6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.url +import im.vector.app.BuildConfig import im.vector.app.core.di.ScreenScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -34,7 +35,7 @@ class PreviewUrlRetriever @Inject constructor( fun getPreviewUrl(event: Event, coroutineScope: CoroutineScope) { val eventId = event.eventId ?: return - val urlToRetrieve = synchronized(data) { + synchronized(data) { if (data[eventId] == null) { // Keep only the first URL for the moment val url = session.mediaService().extractUrls(event).firstOrNull() @@ -48,15 +49,13 @@ class PreviewUrlRetriever @Inject constructor( // Already handled null } - } - - urlToRetrieve?.let { urlToRetrieve -> + }?.let { urlToRetrieve -> coroutineScope.launch { runCatching { session.mediaService().getPreviewUrl( url = urlToRetrieve, timestamp = null, - cacheStrategy = CacheStrategy.TtlCache(CACHE_VALIDITY, false) + cacheStrategy = if (BuildConfig.DEBUG) CacheStrategy.NoCache else CacheStrategy.TtlCache(CACHE_VALIDITY, false) ) }.fold( { diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt similarity index 93% rename from vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index a63273f00a..c29d57539d 100755 --- a/vector/src/main/java/im/vector/app/core/ui/views/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.core.ui.views +package im.vector.app.features.home.room.detail.timeline.url import android.content.Context import android.util.AttributeSet @@ -27,10 +27,8 @@ import butterknife.BindView import butterknife.ButterKnife import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.session.media.PreviewUrlData -import timber.log.Timber /** * A View to display a PreviewUrl and some other state @@ -70,10 +68,8 @@ class PreviewUrlView @JvmOverloads constructor( imageContentRenderer: ImageContentRenderer, force: Boolean = false) { if (newState == state && !force) { - Timber.v("State unchanged") return } - Timber.v("Rendering $newState") state = newState @@ -121,7 +117,7 @@ class PreviewUrlView @JvmOverloads constructor( imageContentRenderer.render(mxcUrl, imageView) } descriptionView.setTextOrHide(previewUrlData.description) - siteView.setTextOrHide(previewUrlData.siteName) + siteView.setTextOrHide(previewUrlData.siteName.takeIf { it != previewUrlData.title }) } /** diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml index 914f4a9d9b..54d677c310 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml @@ -13,7 +13,7 @@ android:textSize="14sp" tools:text="@sample/matrix.json/data/message" /> - Date: Fri, 4 Dec 2020 08:31:14 +0100 Subject: [PATCH 046/218] PreviewUrl: protocol is mandatory (exclude rstp://) --- .../matrix/android/sdk/internal/session/media/UrlsExtractor.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt index b6cea04600..b7f9a88845 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType import javax.inject.Inject internal class UrlsExtractor @Inject constructor() { + // Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later private val urlRegex = Patterns.WEB_URL.toRegex() fun extract(event: Event): List { @@ -35,6 +36,7 @@ internal class UrlsExtractor @Inject constructor() { ?.body ?.let { urlRegex.findAll(it) } ?.map { it.value } + ?.filter { it.startsWith("https://") || it.startsWith("http://") } ?.distinct() ?.toList() .orEmpty() From 770041eceb5102702b83c2a68af6cd0b3fa06edf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 09:48:48 +0100 Subject: [PATCH 047/218] PreviewUrl: setting and e2e room --- .../features/home/room/detail/RoomDetailViewModel.kt | 11 ++++++++--- .../vector/app/features/settings/VectorPreferences.kt | 9 +++++++++ .../settings/VectorSettingsPreferencesFragment.kt | 6 +++--- .../src/main/res/xml/vector_settings_preferences.xml | 3 +-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 77ed959300..40dbf9627c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1354,9 +1354,14 @@ class RoomDetailViewModel @AssistedInject constructor( timelineEvents.accept(snapshot) // PreviewUrl - // TODO Check if URL preview is enable, check if encrypted room, etc. - snapshot.forEach { - previewUrlRetriever.getPreviewUrl(it.root, viewModelScope) + if (vectorPreferences.showUrlPreviews()) { + withState { state -> + snapshot + .takeIf { state.asyncRoomSummary.invoke()?.isEncrypted == false } + ?.forEach { + previewUrlRetriever.getPreviewUrl(it.root, viewModelScope) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 9d6ed0246c..c50692df82 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -783,6 +783,15 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_USE_ANALYTICS_KEY, false) } + /** + * Tells if the user wants to see URL previews in the timeline + * + * @return true if the user wants to see URL previews in the timeline + */ + fun showUrlPreviews(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_SHOW_URL_PREVIEW_KEY, true) + } + /** * Enable or disable the analytics tracking. * diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index a84a10f74c..58528dc405 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -64,9 +64,9 @@ class VectorSettingsPreferencesFragment @Inject constructor( } // Url preview + /* + TODO Note: we keep the setting client side for now findPreference(VectorPreferences.SETTINGS_SHOW_URL_PREVIEW_KEY)!!.let { - /* - TODO it.isChecked = session.isURLPreviewEnabled it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> @@ -100,8 +100,8 @@ class VectorSettingsPreferencesFragment @Inject constructor( false } - */ } + */ // update keep medias period findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY)!!.let { diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index a162bf28fb..ad4cf8e3ed 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -57,8 +57,7 @@ android:defaultValue="true" android:key="SETTINGS_SHOW_URL_PREVIEW_KEY" android:summary="@string/settings_inline_url_preview_summary" - android:title="@string/settings_inline_url_preview" - app:isPreferenceVisible="@bool/false_not_implemented" /> + android:title="@string/settings_inline_url_preview" /> Date: Fri, 4 Dec 2020 11:08:06 +0100 Subject: [PATCH 048/218] PreviewUrl: handle click --- .../home/room/detail/RoomDetailFragment.kt | 8 ++++++++ .../detail/timeline/TimelineEventController.kt | 13 ++++++++++++- .../timeline/factory/MessageItemFactory.kt | 2 ++ .../room/detail/timeline/item/MessageTextItem.kt | 6 ++++++ .../detail/timeline/url/PreviewUrlRetriever.kt | 2 +- .../detail/timeline/url/PreviewUrlUiState.kt | 2 +- .../room/detail/timeline/url/PreviewUrlView.kt | 16 +++------------- 7 files changed, 33 insertions(+), 16 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 3f5e476a5e..5e8d41c545 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1652,6 +1652,14 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState) } + override fun onPreviewUrlClicked(url: String) { + onUrlClicked(url, url) + } + + override fun onPreviewUrlCloseClicked(url: String) { + TODO("Not yet implemented") + } + private fun onShareActionClicked(action: EventSharedAction.Share) { if (action.messageContent is MessageTextContent) { shareText(requireContext(), action.messageContent.body) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index bddc7fa126..31c4ceaff5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -76,7 +76,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val backgroundHandler: Handler ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor { - interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback { + interface Callback : + BaseCallback, + ReactionPillCallback, + AvatarCallback, + UrlClickCallback, + ReadReceiptsCallback, + PreviewUrlCallback { fun onLoadMore(direction: Timeline.Direction) fun onEventInvisible(event: TimelineEvent) fun onEventVisible(event: TimelineEvent) @@ -118,6 +124,11 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec fun onUrlLongClicked(url: String): Boolean } + interface PreviewUrlCallback { + fun onPreviewUrlClicked(url: String) + fun onPreviewUrlCloseClicked(url: String) + } + // Map eventId to adapter position private val adapterPositionMapping = HashMap() private val modelCache = arrayListOf() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index f3be4337ef..96fa055d1f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -428,6 +428,7 @@ class MessageItemFactory @Inject constructor( .searchForPills(isFormatted) .previewUrlRetriever(previewUrlRetriever) .imageContentRenderer(imageContentRenderer) + .previewUrlCallback(callback) .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) .highlighted(highlight) @@ -535,6 +536,7 @@ class MessageItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) .previewUrlRetriever(previewUrlRetriever) .imageContentRenderer(imageContentRenderer) + .previewUrlCallback(callback) .attributes(attributes) .highlighted(highlight) .movementMethod(createLinkMovementMethod(callback)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 8bb5a84695..5bb7aff194 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -23,6 +23,7 @@ import androidx.core.widget.TextViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState @@ -44,6 +45,9 @@ abstract class MessageTextItem : AbsMessageItem() { @EpoxyAttribute var previewUrlRetriever: PreviewUrlRetriever? = null + @EpoxyAttribute + var previewUrlCallback: TimelineEventController.PreviewUrlCallback? = null + @EpoxyAttribute var imageContentRenderer: ImageContentRenderer? = null @@ -53,9 +57,11 @@ abstract class MessageTextItem : AbsMessageItem() { private val previewUrlViewUpdater = PreviewUrlViewUpdater() override fun bind(holder: Holder) { + // Preview URL previewUrlViewUpdater.previewUrlView = holder.previewUrlView previewUrlViewUpdater.imageContentRenderer = imageContentRenderer previewUrlRetriever?.addListener(attributes.informationData.eventId, previewUrlViewUpdater) + holder.previewUrlView.delegate = previewUrlCallback if (useBigFont) { holder.messageView.textSize = 44F diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt index a36b2367f6..0153a38719 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -60,7 +60,7 @@ class PreviewUrlRetriever @Inject constructor( }.fold( { synchronized(data) { - updateState(eventId, PreviewUrlUiState.Data(it)) + updateState(eventId, PreviewUrlUiState.Data(urlToRetrieve, it)) } }, { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt index e411ba3031..d86633b1e4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt @@ -35,5 +35,5 @@ sealed class PreviewUrlUiState { data class Error(val throwable: Throwable) : PreviewUrlUiState() // PreviewUrl data - data class Data(val previewUrlData: PreviewUrlData) : PreviewUrlUiState() + data class Data(val url: String, val previewUrlData: PreviewUrlData) : PreviewUrlUiState() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index c29d57539d..58c36a95c9 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -27,6 +27,7 @@ import butterknife.BindView import butterknife.ButterKnife import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.session.media.PreviewUrlData @@ -51,7 +52,7 @@ class PreviewUrlView @JvmOverloads constructor( @BindView(R.id.url_preview_site) lateinit var siteView: TextView - var delegate: Delegate? = null + var delegate: TimelineEventController.PreviewUrlCallback? = null init { setupView() @@ -85,7 +86,7 @@ class PreviewUrlView @JvmOverloads constructor( override fun onClick(v: View?) { when (val finalState = state) { - is PreviewUrlUiState.Data -> delegate?.onUrlClicked(finalState.previewUrlData.url) + is PreviewUrlUiState.Data -> delegate?.onPreviewUrlClicked(finalState.url) else -> Unit } } @@ -129,15 +130,4 @@ class PreviewUrlView @JvmOverloads constructor( descriptionView.isVisible = false siteView.isVisible = false } - - /** - * An interface to delegate some actions to another object - */ - interface Delegate { - // TODO - fun onUrlClicked(url: String) - - // TODO - // fun close() - } } From 679d9bae1c838c4213e263a199f4093f400206e4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 11:45:05 +0100 Subject: [PATCH 049/218] Fix bad image rendering --- .../home/room/detail/timeline/url/PreviewUrlView.kt | 7 ++----- .../im/vector/app/features/media/ImageContentRenderer.kt | 6 +++--- vector/src/main/res/layout/url_preview.xml | 7 ++++--- vector/src/main/res/values/colors_riotx.xml | 7 +++++++ vector/src/main/res/values/theme_black.xml | 1 + vector/src/main/res/values/theme_dark.xml | 1 + vector/src/main/res/values/theme_light.xml | 1 + 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index 58c36a95c9..29701cacbe 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -29,6 +29,7 @@ import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.media.ImageContentRenderer +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.media.PreviewUrlData /** @@ -112,11 +113,7 @@ class PreviewUrlView @JvmOverloads constructor( private fun renderData(previewUrlData: PreviewUrlData, imageContentRenderer: ImageContentRenderer) { isVisible = true titleView.setTextOrHide(previewUrlData.title) - val mxcUrl = previewUrlData.mxcUrl - imageView.isVisible = mxcUrl != null - if (mxcUrl != null) { - imageContentRenderer.render(mxcUrl, imageView) - } + imageView.isVisible = previewUrlData.mxcUrl?.let { imageContentRenderer.render(it, imageView) }.orFalse() descriptionView.setTextOrHide(previewUrlData.description) siteView.setTextOrHide(previewUrlData.siteName.takeIf { it != previewUrlData.title }) } diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 31492b299a..87315c91cd 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -86,14 +86,14 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: /** * For url preview */ - fun render(mxcUrl: String, imageView: ImageView) { + fun render(mxcUrl: String, imageView: ImageView): Boolean { val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() - val imageUrl = contentUrlResolver.resolveFullSize(mxcUrl) ?: return + val imageUrl = contentUrlResolver.resolveFullSize(mxcUrl) ?: return false GlideApp.with(imageView) .load(imageUrl) - .placeholder(R.drawable.ic_image) .into(imageView) + return true } /** diff --git a/vector/src/main/res/layout/url_preview.xml b/vector/src/main/res/layout/url_preview.xml index b9f355162e..ad6259a57e 100644 --- a/vector/src/main/res/layout/url_preview.xml +++ b/vector/src/main/res/layout/url_preview.xml @@ -11,7 +11,7 @@ android:id="@+id/url_preview_left_border" android:layout_width="2dp" android:layout_height="0dp" - android:background="#8D99A5" + android:background="?riotx_text_tertiary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -32,10 +32,11 @@ #FFA1B2D1 #FFA1B2D1 + + #FF8D99A5 + + #FF8D99A5 + + #FF8D99A5 + #FF61708B #FFA1B2D1 diff --git a/vector/src/main/res/values/theme_black.xml b/vector/src/main/res/values/theme_black.xml index 18ced0a071..ab0ecbe4e9 100644 --- a/vector/src/main/res/values/theme_black.xml +++ b/vector/src/main/res/values/theme_black.xml @@ -18,6 +18,7 @@ @color/riotx_header_panel_text_secondary_black @color/riotx_text_primary_black @color/riotx_text_secondary_black + @color/riotx_text_tertiary_black @color/riotx_text_primary_body_contrast_black @color/riotx_android_secondary_black @color/riotx_search_placeholder_black diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index cdd5cde488..6ebf8e2b9b 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -16,6 +16,7 @@ @color/riotx_header_panel_text_secondary_dark @color/riotx_text_primary_dark @color/riotx_text_secondary_dark + @color/riotx_text_tertiary_dark @color/riotx_text_primary_body_contrast_dark @color/riotx_android_secondary_dark @color/riotx_search_placeholder_dark diff --git a/vector/src/main/res/values/theme_light.xml b/vector/src/main/res/values/theme_light.xml index 3c1505bb60..d7b91a37a7 100644 --- a/vector/src/main/res/values/theme_light.xml +++ b/vector/src/main/res/values/theme_light.xml @@ -16,6 +16,7 @@ @color/riotx_header_panel_text_secondary_light @color/riotx_text_primary_light @color/riotx_text_secondary_light + @color/riotx_text_tertiary_light @color/riotx_text_primary_body_contrast_light @color/riotx_android_secondary_light @color/riotx_search_placeholder_light From 9089c549902e513b000c9a119cfe2829f47dcf77 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 11:53:37 +0100 Subject: [PATCH 050/218] Ripple effect --- .../main/res/layout/item_timeline_event_text_message_stub.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml index 54d677c310..dcff25ecf9 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml @@ -19,6 +19,7 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="4dp" + android:foreground="?attr/selectableItemBackground" android:visibility="gone" tools:visibility="visible" /> From c08c6520808718f0244c91c31f2a69efcf10ef4c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 12:41:26 +0100 Subject: [PATCH 051/218] PreviewUrl: handle close (in memory) --- .../home/room/detail/RoomDetailFragment.kt | 6 +++-- .../timeline/TimelineEventController.kt | 2 +- .../timeline/url/PreviewUrlRetriever.kt | 27 +++++++++++++++++-- .../detail/timeline/url/PreviewUrlUiState.kt | 4 ++- .../detail/timeline/url/PreviewUrlView.kt | 17 +++++++++--- .../src/main/res/drawable/ic_close_24dp.xml | 10 +++++++ vector/src/main/res/layout/url_preview.xml | 12 +++++++++ 7 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_close_24dp.xml diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 5e8d41c545..f211e89e7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -140,6 +140,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan @@ -215,6 +216,7 @@ class RoomDetailFragment @Inject constructor( private val session: Session, private val avatarRenderer: AvatarRenderer, private val timelineEventController: TimelineEventController, + private val previewUrlRetriever: PreviewUrlRetriever, autoCompleterFactory: AutoCompleter.Factory, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, @@ -1656,8 +1658,8 @@ class RoomDetailFragment @Inject constructor( onUrlClicked(url, url) } - override fun onPreviewUrlCloseClicked(url: String) { - TODO("Not yet implemented") + override fun onPreviewUrlCloseClicked(eventId: String, url: String) { + previewUrlRetriever.doNotShowPreviewUrlFor(eventId, url) } private fun onShareActionClicked(action: EventSharedAction.Share) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 31c4ceaff5..693383c751 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -126,7 +126,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec interface PreviewUrlCallback { fun onPreviewUrlClicked(url: String) - fun onPreviewUrlCloseClicked(url: String) + fun onPreviewUrlCloseClicked(eventId: String, url: String) } // Map eventId to adapter position diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt index 0153a38719..b4fbeaf325 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -32,13 +32,18 @@ class PreviewUrlRetriever @Inject constructor( private val data = mutableMapOf() private val listeners = mutableMapOf>() + // In memory list + private val blockedUrl = mutableSetOf() + fun getPreviewUrl(event: Event, coroutineScope: CoroutineScope) { val eventId = event.eventId ?: return synchronized(data) { if (data[eventId] == null) { // Keep only the first URL for the moment - val url = session.mediaService().extractUrls(event).firstOrNull() + val url = session.mediaService().extractUrls(event) + .firstOrNull() + ?.takeIf { it !in blockedUrl } if (url == null) { updateState(eventId, PreviewUrlUiState.NoUrl) } else { @@ -60,7 +65,12 @@ class PreviewUrlRetriever @Inject constructor( }.fold( { synchronized(data) { - updateState(eventId, PreviewUrlUiState.Data(urlToRetrieve, it)) + // Blocked after the request has been sent? + if (urlToRetrieve in blockedUrl) { + updateState(eventId, PreviewUrlUiState.NoUrl) + } else { + updateState(eventId, PreviewUrlUiState.Data(eventId, urlToRetrieve, it)) + } } }, { @@ -73,6 +83,19 @@ class PreviewUrlRetriever @Inject constructor( } } + fun doNotShowPreviewUrlFor(eventId: String, url: String) { + blockedUrl.add(url) + + // Notify the listener + synchronized(data) { + data[eventId] + ?.takeIf { it is PreviewUrlUiState.Data && it.url == url } + ?.let { + updateState(eventId, PreviewUrlUiState.NoUrl) + } + } + } + private fun updateState(eventId: String, state: PreviewUrlUiState) { data[eventId] = state // Notify the listener diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt index d86633b1e4..a8f8f7b0cb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlUiState.kt @@ -35,5 +35,7 @@ sealed class PreviewUrlUiState { data class Error(val throwable: Throwable) : PreviewUrlUiState() // PreviewUrl data - data class Data(val url: String, val previewUrlData: PreviewUrlData) : PreviewUrlUiState() + data class Data(val eventId: String, + val url: String, + val previewUrlData: PreviewUrlData) : PreviewUrlUiState() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index 29701cacbe..b314344356 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -53,6 +53,9 @@ class PreviewUrlView @JvmOverloads constructor( @BindView(R.id.url_preview_site) lateinit var siteView: TextView + @BindView(R.id.url_preview_close) + lateinit var closeView: View + var delegate: TimelineEventController.PreviewUrlCallback? = null init { @@ -78,10 +81,10 @@ class PreviewUrlView @JvmOverloads constructor( hideAll() when (newState) { PreviewUrlUiState.Unknown, - PreviewUrlUiState.NoUrl -> renderHidden() - PreviewUrlUiState.Loading -> renderLoading() + PreviewUrlUiState.NoUrl -> renderHidden() + PreviewUrlUiState.Loading -> renderLoading() is PreviewUrlUiState.Error -> renderHidden() - is PreviewUrlUiState.Data -> renderData(newState.previewUrlData, imageContentRenderer) + is PreviewUrlUiState.Data -> renderData(newState.previewUrlData, imageContentRenderer) } } @@ -92,6 +95,13 @@ class PreviewUrlView @JvmOverloads constructor( } } + private fun onCloseClick() { + when (val finalState = state) { + is PreviewUrlUiState.Data -> delegate?.onPreviewUrlCloseClicked(finalState.eventId, finalState.url) + else -> Unit + } + } + // PRIVATE METHODS **************************************************************************************************************************************** private fun setupView() { @@ -99,6 +109,7 @@ class PreviewUrlView @JvmOverloads constructor( ButterKnife.bind(this) setOnClickListener(this) + closeView.setOnClickListener { onCloseClick() } } private fun renderHidden() { diff --git a/vector/src/main/res/drawable/ic_close_24dp.xml b/vector/src/main/res/drawable/ic_close_24dp.xml new file mode 100644 index 0000000000..d69c331210 --- /dev/null +++ b/vector/src/main/res/drawable/ic_close_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/url_preview.xml b/vector/src/main/res/layout/url_preview.xml index ad6259a57e..a08c60c663 100644 --- a/vector/src/main/res/layout/url_preview.xml +++ b/vector/src/main/res/layout/url_preview.xml @@ -26,6 +26,7 @@ android:textColor="?riotx_text_primary" android:textSize="14sp" android:textStyle="bold" + app:layout_constraintEnd_toStartOf="@+id/url_preview_close" app:layout_constraintStart_toStartOf="@+id/url_preview_left_border" app:layout_constraintTop_toTopOf="parent" tools:text="Jo Malone denounces her former brand's John Boyega decision" /> @@ -71,4 +72,15 @@ app:layout_constraintTop_toBottomOf="@+id/url_preview_description" tools:text="BBC News" /> + + \ No newline at end of file From 2a19726e497c4dfd4d1a3416a4a54fe02a5f2c6f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 14:19:24 +0100 Subject: [PATCH 052/218] Cleanup and changelog --- CHANGES.md | 1 + .../matrix/android/sdk/internal/session/media/UrlsExtractor.kt | 2 +- .../app/features/settings/VectorSettingsPreferencesFragment.kt | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 06af610ecb..d75b514c7c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Changes in Element 1.0.12 (2020-XX-XX) Features ✨: - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) - Room setting: update join rules and guest access (#2442) + - Url preview (#481) Improvements 🙌: - Add Setting Item to Change PIN (#2462) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt index b7f9a88845..9d374c3428 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 58528dc405..841a239701 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -22,7 +22,6 @@ import android.widget.CheckedTextView import android.widget.LinearLayout import androidx.appcompat.app.AlertDialog import androidx.preference.Preference -import androidx.preference.SwitchPreference import im.vector.app.R import im.vector.app.core.extensions.restart import im.vector.app.core.preference.VectorListPreference From 5d3682cd440bccacca52c44923119f61c7b06960 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 14:19:36 +0100 Subject: [PATCH 053/218] More cleanup --- .../app/core/ui/views/JumpToReadMarkerView.kt | 28 +++++++++---------- .../im/vector/app/core/utils/Debouncer.kt | 28 +++++++++---------- .../java/im/vector/app/core/utils/Handler.kt | 28 +++++++++---------- .../helper/MessageInformationDataFactory.kt | 28 +++++++++---------- .../helper/MessageItemAttributesFactory.kt | 28 +++++++++---------- 5 files changed, 65 insertions(+), 75 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt b/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt index 169f24520b..3c48637e74 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt @@ -1,19 +1,17 @@ /* - - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package im.vector.app.core.ui.views diff --git a/vector/src/main/java/im/vector/app/core/utils/Debouncer.kt b/vector/src/main/java/im/vector/app/core/utils/Debouncer.kt index a5e0005c2a..bb38150797 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Debouncer.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Debouncer.kt @@ -1,19 +1,17 @@ /* - - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package im.vector.app.core.utils diff --git a/vector/src/main/java/im/vector/app/core/utils/Handler.kt b/vector/src/main/java/im/vector/app/core/utils/Handler.kt index c7ec97f53e..fe8760a522 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Handler.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Handler.kt @@ -1,19 +1,17 @@ /* - - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package im.vector.app.core.utils diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index f7a1a18d9f..8a8bf364e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -1,19 +1,17 @@ /* - - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package im.vector.app.features.home.room.detail.timeline.helper diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 3297f14622..c120fa671c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -1,19 +1,17 @@ /* - - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package im.vector.app.features.home.room.detail.timeline.helper From c2c9e37a368a3e2ba98b9031002bf84c6d9006de Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 14:36:11 +0100 Subject: [PATCH 054/218] PreviewUrl: fix layout issue and add more tests --- .../internal/session/media/UrlsExtractorTest.kt | 17 +++++++++++++++++ .../room/detail/timeline/url/PreviewUrlView.kt | 2 +- vector/src/main/res/layout/url_preview.xml | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt index a3fc876de3..9ee84fdfc6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt @@ -50,6 +50,13 @@ internal class UrlsExtractorTest : InstrumentedTest { } } + @Test + fun withoutProtocolTest() { + createEvent(body = "www.matrix.org") + .let { urlsExtractor.extract(it) } + .size shouldBeEqualTo 0 + } + @Test fun oneUrlWithParamTest() { createEvent(body = "https://matrix.org?foo=bar") @@ -60,6 +67,16 @@ internal class UrlsExtractorTest : InstrumentedTest { } } + @Test + fun oneUrlWithParamsTest() { + createEvent(body = "https://matrix.org?foo=bar&bar=foo") + .let { urlsExtractor.extract(it) } + .let { result -> + result.size shouldBeEqualTo 1 + result[0] shouldBeEqualTo "https://matrix.org?foo=bar&bar=foo" + } + } + @Test fun oneUrlInlinedTest() { createEvent(body = "Hello https://matrix.org, how are you?") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index b314344356..fafed08db1 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -117,7 +117,7 @@ class PreviewUrlView @JvmOverloads constructor( } private fun renderLoading() { - // TODO + // Just hide for the moment isVisible = false } diff --git a/vector/src/main/res/layout/url_preview.xml b/vector/src/main/res/layout/url_preview.xml index a08c60c663..a8c287b471 100644 --- a/vector/src/main/res/layout/url_preview.xml +++ b/vector/src/main/res/layout/url_preview.xml @@ -53,6 +53,7 @@ android:maxLines="4" android:textColor="?riotx_text_secondary" android:textSize="14sp" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/url_preview_left_border" app:layout_constraintTop_toBottomOf="@+id/url_preview_image" tools:text="The British perfumer says removing actor John Boyega from his own advert was “utterly despicable”." /> @@ -68,6 +69,7 @@ android:singleLine="true" android:textColor="?riotx_text_tertiary" android:textSize="14sp" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/url_preview_left_border" app:layout_constraintTop_toBottomOf="@+id/url_preview_description" tools:text="BBC News" /> From 78fe7e5c16cf9c29b115b4ab4a111cf09ddbd4ec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 14:56:26 +0100 Subject: [PATCH 055/218] No need to create a Set to remove an item from it --- .../home/room/detail/timeline/url/PreviewUrlRetriever.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt index b4fbeaf325..174841b599 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -116,7 +116,7 @@ class PreviewUrlRetriever @Inject constructor( // Called by the Epoxy item during unbinding fun removeListener(key: String, listener: PreviewUrlRetrieverListener) { - listeners.getOrPut(key) { mutableSetOf() }.remove(listener) + listeners[key]?.remove(listener) } interface PreviewUrlRetrieverListener { From 431ac5aa2d98e5c7cc180a18645202b96f44cb40 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Dec 2020 15:23:59 +0100 Subject: [PATCH 056/218] Fix layout issue --- .../main/res/layout/item_timeline_event_text_message_stub.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml index dcff25ecf9..7bdd0dd1e3 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml @@ -15,7 +15,7 @@ Date: Thu, 3 Dec 2020 17:39:36 +0000 Subject: [PATCH 057/218] Translated using Weblate (German) Currently translated at 97.3% (1933 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 75ee23cb12..630593f214 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2248,4 +2248,5 @@ Direktnachricht Geschichte der Anfragen von Schlüsselfreigaben senden Keine weiteren Ergebnisse + Starte die Diskussion \ No newline at end of file From b9d7333998aca9eb839cad8841195bc0a7b0c3af Mon Sep 17 00:00:00 2001 From: Hivaa Date: Thu, 3 Dec 2020 17:40:08 +0000 Subject: [PATCH 058/218] Translated using Weblate (Persian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index fc25126f48..67bd8797a6 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -506,7 +506,7 @@ فرستادن پرونده (%1$s / %2$s) بارگری پرونده %1$s… پرونده %1$s بارگیری شد! - (ویراسته) + (ویرایش شده) پالایش گفت‌وگوها… ایجاد اتاقی جدید فرستادن یک پیام مستقیم جدید From dacb9cd86c93fce4faa6a44e3403b70dfbf78e3b Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Thu, 3 Dec 2020 17:07:56 +0000 Subject: [PATCH 059/218] Translated using Weblate (Russian) Currently translated at 99.8% (1983 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 2974a3374b..43b93c0dfd 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -1880,7 +1880,7 @@ Посылает сообщение, окрашенное в цвет радуги Посылает данную эмоцию, окрашенную в цвет радуги Редактор сообщений - Включить сквозное шифрование + Включить сквозное шифрование… После включения шифрования оно не может быть отключено. Активировать шифрование\? После включения шифрование для комнаты не может быть отключено. Сообщения, отправленные в зашифрованном помещении, не могут быть замечены сервером, только участниками помещения. Включение шифрования может помешать правильной работе многих ботов и мостов. @@ -2351,4 +2351,13 @@ QR-код Отправить историю запросов на обмен ключами Начать беседу + Недавние + %s чтобы люди знали, о чём эта комната. + Это начало вашей истории диалога с %s. + Экспорт аудита + Диалог + Создать новый диалог через сканирование QR-кода + Создать новый диалог по Matrix ID + Отправить электронную почту и номера телефонов + Отправить электронную почту и номера телефонов \ No newline at end of file From 3e9cb987a1b182a03aed506b7e18b742272df9bf Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Thu, 3 Dec 2020 17:17:11 +0000 Subject: [PATCH 060/218] Translated using Weblate (German) Currently translated at 90.4% (190 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/de/ --- matrix-sdk-android/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/res/values-de/strings.xml b/matrix-sdk-android/src/main/res/values-de/strings.xml index 4c574d578a..bdeeafccb6 100644 --- a/matrix-sdk-android/src/main/res/values-de/strings.xml +++ b/matrix-sdk-android/src/main/res/values-de/strings.xml @@ -56,7 +56,7 @@ E-Mail-Adresse Telefonnummer - %1$s sandte einen Sticker. + %1$s hat einen Sticker gesendet. Einladung von %s Raumeinladung From 9bbae825e27d1882a53c1f827429a603fb54a648 Mon Sep 17 00:00:00 2001 From: Hivaa Date: Sun, 6 Dec 2020 13:11:14 +0000 Subject: [PATCH 061/218] Translated using Weblate (Persian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 67bd8797a6..ceecea724a 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -999,7 +999,7 @@ افزایش کارایی با فقط بار کردن اعضای اتاق در نمای نخست. کارساز خانگیتان هنوز از بار کردن تنبلانهٔ اعضای اتاق پشتیبانی نمی‌کند. بعداً تلاش کنید. هرگز پیام‌های رمزشده را از دست ندهید - شروع با استفاده از پشتیبان کلید + شروع به استفاده از پشتیبانِ کلید برون‌ریزی دستی کلیدها امن کردن پشتیبانتان با یک عبارت عبور. تنظیم عبارت عبور @@ -1024,7 +1024,7 @@ لطفاً یک کلید بازیابی وارد کنید من بودم هرگز پیام‌های رمزشده را از دست ندهید - شروع با استفاده از پشتیبان کلید + شروع به استفاده از پشتیبانِ کلید پشتیبان امن سپرامنیتی در برابر از دست دادن دسترسی به داده‌ها و پیام‌های رمزشده هرگز پیام‌های رمزشده را از دست ندهید From 19524eaa82bfc16489c8fa28d719b565aa83014d Mon Sep 17 00:00:00 2001 From: random Date: Sat, 5 Dec 2020 14:36:04 +0000 Subject: [PATCH 062/218] Translated using Weblate (Italian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 57 ++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index d7eb7a29d4..286c73ab39 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -1712,7 +1712,7 @@ Invia l\'emoticon in questione colorata ad arcobaleno Cronologia Editor messaggi - Attiva cifratura end-to-end + Attiva crittografia end-to-end… Una volta attivata, la cifratura non può essere disattivata. Attivare la cifratura\? Una volta attivata, la cifratura di una stanza non può essere disattivata. I messaggi inviati in una stanza cifrata non possono essere visti dal server, solo dai partecipanti della stanza. L\'attivazione della cifratura può impedire il funzionamento di molti bot e bridge. @@ -2254,4 +2254,59 @@ Messaggio diretto Invia cronologia di richieste condivisione chiave Nessun altro risultato + Collegamento Matrix + Codice QR non scansionato! + Codice QR non valido (URI non valido)! + Non puoi messaggiare te stesso! + Condividi via testo + Cerca contatti su Matrix + Imposta avatar + Non è stato fornito il consenso dell\'utente. + Condividi questo codice con le persone consentendo la scansione per aggiungerti e iniziare a chattare. + Il mio codice + Condividi il mio codice + Scansiona un codice QR + Non è un codice QR Matrix valido + 🔐️ Unisciti a me su Element + Ehy, parliamo su Element: %s + Invita amici + Aggiungi persone + "Argomento: " + Aggiungi un argomento + %s per far sapere alle persone di cosa parla questa stanza. + Questo è l\'inizio della tua cronologia di messaggi diretti con %s. + Questo è l\'inizio di questa conversazione. + Questo è l\'inizio di %s. + Non hai il permesso di attivare la crittografia in questa stanza. + Creazione stanza… + Alcuni caratteri non sono permessi + Inserisci un indirizzo della stanza + Questo indirizzo è già in uso + Indirizzo stanza + Dovresti attivarlo se questa stanza verrà usata solo per collaborazioni tra squadre interne nel tuo homeserver. Non può essere cambiato in seguito. + Impedisci a chiunque non faccia parte di %s di entrare in questa stanza + Nascondi avanzate + Mostra avanzate + %1$d di %2$d + Crea una nuova conversazione diretta scansionando un codice QR + Crea una nuova conversazione diretta via ID Matrix + Per trovare i contatti esistenti che conosci, accetti di inviare i dati di contatto (numeri di telefono e/o email) al server d\'identità configurato (%1$s)\? +\n +\nPer privacy, sarà inviato solo l\'hash dei dati da inviare. + Invio di email e numeri di telefono + Accetta + Revoca il mio consenso + Non hai accettato l\'invio di email e numeri di telefono a questo server d\'identità per trovare altri utenti dai tuoi contatti. + Hai accettato l\'invio di email e numeri di telefono a questo server d\'identità per trovare altri utenti dai tuoi contatti. + Invia email e numeri di telefono + Suggerimenti + Contatti + Utenti conosciuti + Recenti + Codice QR + Aggiungi da codice QR + Cerca per nome o ID + Per scansionare un codice QR, devi permettere l\'accesso alla fotocamera. + Permetti l\'autorizzazione per l\'accesso ai contatti. + Inizia a chattare \ No newline at end of file From 8bdf38456340433171ff993f50cf07a5a2c894e3 Mon Sep 17 00:00:00 2001 From: zeritti Date: Sat, 5 Dec 2020 15:37:04 +0000 Subject: [PATCH 063/218] Translated using Weblate (Czech) Currently translated at 100.0% (210 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/cs/ --- .../src/main/res/values-cs/strings.xml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-cs/strings.xml b/matrix-sdk-android/src/main/res/values-cs/strings.xml index ebf7590596..50dea12b09 100644 --- a/matrix-sdk-android/src/main/res/values-cs/strings.xml +++ b/matrix-sdk-android/src/main/res/values-cs/strings.xml @@ -218,4 +218,28 @@ %1$s vstoupili Založili jste diskusi %1$s založil diskusi + Prázdná místnost (byla %s) + + %1$s, %2$s, %3$s a %4$d další + %1$s, %2$s, %3$s a %4$d další + %1$s, %2$s, %3$s a %4$d dalších + + %1$s, %2$s, %3$s a %4$s + %1$s, %2$s a %3$s + 🎉 Účast všech serverů je zakázána! Tuto místnost již nelze použít. + Beze změny. + • Servery shodující se doslovně s IP jsou nyní zakázány. + • Servery shodující se doslovně s IP jsou nyní povoleny. + • Servery shodující se s %s byly odstraněny ze seznamu povolených. + • Servery shodující se s %s jsou nyní povoleny. + • Servery shodující se s %s byly odstraněny ze seznamu zakázaných. + • Servery shodující se s %s jsou nyní zakázány. + Změnili jste ACL serveru pro tuto místnost. + %s změnili ACL serveru pro tuto místnost. + • Server shodující se doslovně s IP je povolen. + • Server shodující se doslovně s IP je zakázán. + • Server shodující se s %s je povolen. + • Server shodující se s %s je zakázán. + Nastavili jste ACL serveru pro tuto místnost. + %s nastavili ACL serveru pro tuto místnost. \ No newline at end of file From 555bf1f0aee4bf77c111bcb99de40f179be90797 Mon Sep 17 00:00:00 2001 From: random Date: Sat, 5 Dec 2020 14:12:09 +0000 Subject: [PATCH 064/218] Translated using Weblate (Italian) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it/changelogs/40100100.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/it/changelogs/40100100.txt b/fastlane/metadata/android/it/changelogs/40100100.txt index 0c7cc8cc6c..5ca2f86e45 100644 --- a/fastlane/metadata/android/it/changelogs/40100100.txt +++ b/fastlane/metadata/android/it/changelogs/40100100.txt @@ -1 +1,2 @@ -// DA FARE +Questa nuova versione contiene soprattutto correzioni di errori e miglioramenti. L'invio di messaggi ora è molto più veloce. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.10 From c603ec0b389e594bc3da8e1f7beb4d5a78dd31a2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 7 Dec 2020 09:55:11 +0100 Subject: [PATCH 065/218] Format files --- .../timeline/factory/MessageItemFactory.kt | 24 +++++++++---------- .../detail/timeline/url/PreviewUrlView.kt | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 96fa055d1f..0a88f81a02 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -146,16 +146,16 @@ class MessageItemFactory @Inject constructor( // val all = event.root.toContent() // val ev = all.toModel() return when (messageContent) { - is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) - is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) - is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) + is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) + is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) + is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } @@ -166,7 +166,7 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { return when (messageContent.optionType) { - OPTION_TYPE_POLL -> { + OPTION_TYPE_POLL -> { MessagePollItem_() .attributes(attributes) .callback(callback) @@ -373,7 +373,7 @@ class MessageItemFactory @Inject constructor( val codeVisitor = CodeVisitor() codeVisitor.visit(localFormattedBody) when (codeVisitor.codeKind) { - CodeVisitor.Kind.BLOCK -> { + CodeVisitor.Kind.BLOCK -> { val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody) if (codeFormattedBlock == null) { buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes) @@ -389,7 +389,7 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes) } } - CodeVisitor.Kind.NONE -> { + CodeVisitor.Kind.NONE -> { buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index fafed08db1..9d8f438683 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -81,10 +81,10 @@ class PreviewUrlView @JvmOverloads constructor( hideAll() when (newState) { PreviewUrlUiState.Unknown, - PreviewUrlUiState.NoUrl -> renderHidden() - PreviewUrlUiState.Loading -> renderLoading() + PreviewUrlUiState.NoUrl -> renderHidden() + PreviewUrlUiState.Loading -> renderLoading() is PreviewUrlUiState.Error -> renderHidden() - is PreviewUrlUiState.Data -> renderData(newState.previewUrlData, imageContentRenderer) + is PreviewUrlUiState.Data -> renderData(newState.previewUrlData, imageContentRenderer) } } From 7869d731d47e2b306a00b728c68abfc58cecbeae Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 7 Dec 2020 10:38:34 +0100 Subject: [PATCH 066/218] Fix the rotate screen issue --- .../features/home/room/detail/RoomDetailAction.kt | 3 +++ .../features/home/room/detail/RoomDetailFragment.kt | 7 +++++-- .../home/room/detail/RoomDetailViewModel.kt | 9 ++++++++- .../room/detail/timeline/TimelineEventController.kt | 3 +++ .../detail/timeline/factory/MessageItemFactory.kt | 6 ++---- .../room/detail/timeline/item/MessageTextItem.kt | 2 ++ .../room/detail/timeline/url/PreviewUrlRetriever.kt | 13 +++++-------- 7 files changed, 28 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 8891218a11..e034e373f3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -98,4 +98,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class SetAvatarAction(val newAvatarUri: Uri, val newAvatarFileName: String) : RoomDetailAction() object QuickActionSetTopic : RoomDetailAction() data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val transitionView: View?) : RoomDetailAction() + + // Preview URL + data class DoNotShowPreviewUrlFor(val eventId: String, val url: String) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f211e89e7d..399bf564bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -216,7 +216,6 @@ class RoomDetailFragment @Inject constructor( private val session: Session, private val avatarRenderer: AvatarRenderer, private val timelineEventController: TimelineEventController, - private val previewUrlRetriever: PreviewUrlRetriever, autoCompleterFactory: AutoCompleter.Factory, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, @@ -1632,6 +1631,10 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(itemAction) } + override fun getPreviewUrlRetriever(): PreviewUrlRetriever { + return roomDetailViewModel.previewUrlRetriever + } + override fun onRoomCreateLinkClicked(url: String) { permalinkHandler .launch(requireContext(), url, object : NavigationInterceptor { @@ -1659,7 +1662,7 @@ class RoomDetailFragment @Inject constructor( } override fun onPreviewUrlCloseClicked(eventId: String, url: String) { - previewUrlRetriever.doNotShowPreviewUrlFor(eventId, url) + roomDetailViewModel.handle(RoomDetailAction.DoNotShowPreviewUrlFor(eventId, url)) } private fun onShareActionClicked(action: EventSharedAction.Share) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 40dbf9627c..ed25d41e9f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -113,7 +113,6 @@ class RoomDetailViewModel @AssistedInject constructor( private val rainbowGenerator: RainbowGenerator, private val session: Session, private val rawService: RawService, - private val previewUrlRetriever: PreviewUrlRetriever, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val stickerPickerActionHandler: StickerPickerActionHandler, private val roomSummaryHolder: RoomSummaryHolder, @@ -130,6 +129,9 @@ class RoomDetailViewModel @AssistedInject constructor( private var timelineEvents = PublishRelay.create>() val timeline = room.createTimeline(eventId, timelineSettings) + // Same lifecycle than the ViewModel (survive to screen rotation) + val previewUrlRetriever = PreviewUrlRetriever(session) + // Slot to keep a pending action during permission request var pendingAction: RoomDetailAction? = null @@ -288,9 +290,14 @@ class RoomDetailViewModel @AssistedInject constructor( RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) ) } + is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) }.exhaustive } + private fun handleDoNotShowPreviewUrlFor(action: RoomDetailAction.DoNotShowPreviewUrlFor) { + previewUrlRetriever.doNotShowPreviewUrlFor(action.eventId, action.url) + } + private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) { viewModelScope.launch(Dispatchers.IO) { try { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 693383c751..ba3ffe3174 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -48,6 +48,7 @@ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences @@ -97,6 +98,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // TODO move all callbacks to this? fun onTimelineItemAction(itemAction: RoomDetailAction) + + fun getPreviewUrlRetriever(): PreviewUrlRetriever } interface ReactionPillCallback { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 0a88f81a02..cd4f798769 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -58,7 +58,6 @@ import im.vector.app.features.home.room.detail.timeline.item.VerificationRequest import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem_ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.home.room.detail.timeline.tools.linkify -import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.html.CodeVisitor import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor @@ -108,7 +107,6 @@ class MessageItemFactory @Inject constructor( private val defaultItemFactory: DefaultItemFactory, private val noticeItemFactory: NoticeItemFactory, private val avatarSizeProvider: AvatarSizeProvider, - private val previewUrlRetriever: PreviewUrlRetriever, private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val session: Session) { @@ -426,7 +424,7 @@ class MessageItemFactory @Inject constructor( } .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) .searchForPills(isFormatted) - .previewUrlRetriever(previewUrlRetriever) + .previewUrlRetriever(callback?.getPreviewUrlRetriever()) .imageContentRenderer(imageContentRenderer) .previewUrlCallback(callback) .leftGuideline(avatarSizeProvider.leftGuideline) @@ -534,7 +532,7 @@ class MessageItemFactory @Inject constructor( } } .leftGuideline(avatarSizeProvider.leftGuideline) - .previewUrlRetriever(previewUrlRetriever) + .previewUrlRetriever(callback?.getPreviewUrlRetriever()) .imageContentRenderer(imageContentRenderer) .previewUrlCallback(callback) .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 5bb7aff194..66d9808d2b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -89,6 +89,8 @@ abstract class MessageTextItem : AbsMessageItem() { override fun unbind(holder: Holder) { super.unbind(holder) + previewUrlViewUpdater.previewUrlView = null + previewUrlViewUpdater.imageContentRenderer = null previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt index 174841b599..695661feeb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -17,18 +17,15 @@ package im.vector.app.features.home.room.detail.timeline.url import im.vector.app.BuildConfig -import im.vector.app.core.di.ScreenScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event -import javax.inject.Inject -@ScreenScope -class PreviewUrlRetriever @Inject constructor( - private val session: Session -) { +class PreviewUrlRetriever(session: Session) { + private val mediaService = session.mediaService() + private val data = mutableMapOf() private val listeners = mutableMapOf>() @@ -41,7 +38,7 @@ class PreviewUrlRetriever @Inject constructor( synchronized(data) { if (data[eventId] == null) { // Keep only the first URL for the moment - val url = session.mediaService().extractUrls(event) + val url = mediaService.extractUrls(event) .firstOrNull() ?.takeIf { it !in blockedUrl } if (url == null) { @@ -57,7 +54,7 @@ class PreviewUrlRetriever @Inject constructor( }?.let { urlToRetrieve -> coroutineScope.launch { runCatching { - session.mediaService().getPreviewUrl( + mediaService.getPreviewUrl( url = urlToRetrieve, timestamp = null, cacheStrategy = if (BuildConfig.DEBUG) CacheStrategy.NoCache else CacheStrategy.TtlCache(CACHE_VALIDITY, false) From c7efd1feb99179585e865affa866d680a96fefe2 Mon Sep 17 00:00:00 2001 From: aqulu Date: Tue, 8 Dec 2020 19:04:30 +0900 Subject: [PATCH 067/218] Convert StateService to suspend functions Signed-off-by: aqulu --- CHANGES.md | 2 +- matrix-sdk-android-rx/build.gradle | 1 + .../java/org/matrix/android/sdk/rx/RxRoom.kt | 35 +++--- .../api/session/room/state/StateService.kt | 16 +-- .../session/room/state/DefaultStateService.kt | 110 +++++++----------- .../home/room/detail/RoomDetailViewModel.kt | 25 ++-- .../RoomMemberProfileViewModel.kt | 4 +- .../roomprofile/alias/RoomAliasViewModel.kt | 11 +- .../members/RoomMemberListViewModel.kt | 3 +- .../features/widgets/WidgetPostAPIHandler.kt | 39 +++++-- 10 files changed, 124 insertions(+), 122 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b16a6690bc..b8ed81efb7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - StateService now exposes suspendable function instead of using MatrixCallback. Build 🧱: - Upgrade some dependencies and Kotlin version diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle index 37f41d0a2a..a99b5856ba 100644 --- a/matrix-sdk-android-rx/build.gradle +++ b/matrix-sdk-android-rx/build.gradle @@ -38,6 +38,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version" // Paging implementation "androidx.paging:paging-runtime-ktx:2.1.2" diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index bf4bcacc31..b938f60e39 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -17,14 +17,20 @@ package org.matrix.android.sdk.rx import android.net.Uri +import io.reactivex.Completable +import io.reactivex.Observable +import io.reactivex.Single +import kotlinx.coroutines.rx2.rxCompletable import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState @@ -32,11 +38,6 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single -import org.matrix.android.sdk.api.session.room.model.GuestAccess -import org.matrix.android.sdk.api.session.room.model.RoomJoinRules class RxRoom(private val room: Room) { @@ -121,28 +122,28 @@ class RxRoom(private val room: Room) { room.invite3pid(threePid, it) } - fun updateTopic(topic: String): Completable = completableBuilder { - room.updateTopic(topic, it) + fun updateTopic(topic: String): Completable = rxCompletable { + room.updateTopic(topic) } - fun updateName(name: String): Completable = completableBuilder { - room.updateName(name, it) + fun updateName(name: String): Completable = rxCompletable { + room.updateName(name) } - fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder { - room.updateHistoryReadability(readability, it) + fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = rxCompletable { + room.updateHistoryReadability(readability) } - fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = completableBuilder { - room.updateJoinRule(joinRules, guestAccess, it) + fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = rxCompletable { + room.updateJoinRule(joinRules, guestAccess) } - fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder { - room.updateAvatar(avatarUri, fileName, it) + fun updateAvatar(avatarUri: Uri, fileName: String): Completable = rxCompletable { + room.updateAvatar(avatarUri, fileName) } - fun deleteAvatar(): Completable = completableBuilder { - room.deleteAvatar(it) + fun deleteAvatar(): Completable = rxCompletable { + room.deleteAvatar() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 74e3faf38a..98dde5839f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -33,41 +33,41 @@ interface StateService { /** * Update the topic of the room */ - fun updateTopic(topic: String, callback: MatrixCallback): Cancelable + suspend fun updateTopic(topic: String) /** * Update the name of the room */ - fun updateName(name: String, callback: MatrixCallback): Cancelable + suspend fun updateName(name: String) /** * Update the canonical alias of the room * @param alias the canonical alias, or null to reset the canonical alias of this room * @param altAliases the alternative aliases for this room. It should include the canonical alias if any. */ - fun updateCanonicalAlias(alias: String?, altAliases: List, callback: MatrixCallback): Cancelable + suspend fun updateCanonicalAlias(alias: String?, altAliases: List) /** * Update the history readability of the room */ - fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback): Cancelable + suspend fun updateHistoryReadability(readability: RoomHistoryVisibility) /** * Update the join rule and/or the guest access */ - fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback): Cancelable + suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) /** * Update the avatar of the room */ - fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable + suspend fun updateAvatar(avatarUri: Uri, fileName: String) /** * Delete the avatar of the room */ - fun deleteAvatar(callback: MatrixCallback): Cancelable + suspend fun deleteAvatar() - fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback): Cancelable + suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict) fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 6015d945c4..607784b48f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -20,7 +20,7 @@ import android.net.Uri import androidx.lifecycle.LiveData import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -32,20 +32,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.util.awaitCallback internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, - private val taskExecutor: TaskExecutor, private val sendStateTask: SendStateTask, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val fileUploader: FileUploader, @@ -73,45 +67,41 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey) } - override fun sendStateEvent( + override suspend fun sendStateEvent( eventType: String, stateKey: String?, - body: JsonDict, - callback: MatrixCallback - ): Cancelable { - val params = SendStateTask.Params( - roomId = roomId, - stateKey = stateKey, - eventType = eventType, - body = body - ) - return sendStateTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + body: JsonDict + ) { + withContext(coroutineDispatchers.main) { + val params = SendStateTask.Params( + roomId = roomId, + stateKey = stateKey, + eventType = eventType, + body = body + ) + + sendStateTask.execute(params) + } } - override fun updateTopic(topic: String, callback: MatrixCallback): Cancelable { - return sendStateEvent( + override suspend fun updateTopic(topic: String) { + sendStateEvent( eventType = EventType.STATE_ROOM_TOPIC, body = mapOf("topic" to topic), - callback = callback, stateKey = null ) } - override fun updateName(name: String, callback: MatrixCallback): Cancelable { - return sendStateEvent( + override suspend fun updateName(name: String) { + sendStateEvent( eventType = EventType.STATE_ROOM_NAME, body = mapOf("name" to name), - callback = callback, stateKey = null ) } - override fun updateCanonicalAlias(alias: String?, altAliases: List, callback: MatrixCallback): Cancelable { - return sendStateEvent( + override suspend fun updateCanonicalAlias(alias: String?, altAliases: List) { + sendStateEvent( eventType = EventType.STATE_ROOM_CANONICAL_ALIAS, body = RoomCanonicalAliasContent( canonicalAlias = alias, @@ -123,64 +113,52 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private // Sort for the cleanup .sorted() ).toContent(), - callback = callback, stateKey = null ) } - override fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback): Cancelable { - return sendStateEvent( + override suspend fun updateHistoryReadability(readability: RoomHistoryVisibility) { + sendStateEvent( eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, body = mapOf("history_visibility" to readability), - callback = callback, stateKey = null ) } - override fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) { + withContext(coroutineDispatchers.main) { if (joinRules != null) { - awaitCallback { - sendStateEvent( - eventType = EventType.STATE_ROOM_JOIN_RULES, - body = RoomJoinRulesContent(joinRules).toContent(), - callback = it, - stateKey = null - ) - } + sendStateEvent( + eventType = EventType.STATE_ROOM_JOIN_RULES, + body = RoomJoinRulesContent(joinRules).toContent(), + stateKey = null + ) } if (guestAccess != null) { - awaitCallback { - sendStateEvent( - eventType = EventType.STATE_ROOM_GUEST_ACCESS, - body = RoomGuestAccessContent(guestAccess).toContent(), - callback = it, - stateKey = null - ) - } - } - } - } - - override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") - awaitCallback { sendStateEvent( - eventType = EventType.STATE_ROOM_AVATAR, - body = mapOf("url" to response.contentUri), - callback = it, + eventType = EventType.STATE_ROOM_GUEST_ACCESS, + body = RoomGuestAccessContent(guestAccess).toContent(), stateKey = null ) } } } - override fun deleteAvatar(callback: MatrixCallback): Cancelable { - return sendStateEvent( + override suspend fun updateAvatar(avatarUri: Uri, fileName: String) { + withContext(coroutineDispatchers.main) { + val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") + sendStateEvent( + eventType = EventType.STATE_ROOM_AVATAR, + body = mapOf("url" to response.contentUri), + stateKey = null + ) + } + } + + override suspend fun deleteAvatar() { + sendStateEvent( eventType = EventType.STATE_ROOM_AVATAR, body = emptyMap(), - callback = callback, stateKey = null ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a83dddc9ac..5e414422f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -292,9 +292,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) { viewModelScope.launch(Dispatchers.IO) { try { - awaitCallback { - room.updateAvatar(action.newAvatarUri, action.newAvatarFileName, it) - } + room.updateAvatar(action.newAvatarUri, action.newAvatarFileName) _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) @@ -854,8 +852,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { - launchSlashCommandFlow { - room.updateTopic(changeTopic.topic, it) + launchSlashCommandFlowSuspendable { + room.updateTopic(changeTopic.topic) } } @@ -876,9 +874,9 @@ class RoomDetailViewModel @AssistedInject constructor( ?.content ?.toModel() ?: return - launchSlashCommandFlow { + launchSlashCommandFlowSuspendable { currentPowerLevelsContent.setUserPowerLevel(setUserPowerLevel.userId, setUserPowerLevel.powerLevel) - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent(), it) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent()) } } @@ -920,6 +918,19 @@ class RoomDetailViewModel @AssistedInject constructor( lambda.invoke(matrixCallback) } + private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) + viewModelScope.launch { + val event = try { + block() + RoomDetailViewEvents.SlashCommandResultOk + } catch (failure: Exception) { + RoomDetailViewEvents.SlashCommandResultError(failure) + } + _viewEvents.post(event) + } + } + private fun handleSendReaction(action: RoomDetailAction.SendReaction) { room.sendReaction(action.targetEventId, action.reaction) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 78562ea351..39b5884308 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -166,9 +166,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) try { - awaitCallback { - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent(), it) - } + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, currentPowerLevelsContent.toContent()) _viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 5873d9ce8a..af0972913a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -301,21 +301,20 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List, closeForm: Boolean) { postLoading(true) - room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback { - override fun onSuccess(data: Unit) { + viewModelScope.launch { + try { + room.updateCanonicalAlias(canonicalAlias, alternativeAliases) setState { copy( isLoading = false, publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState ) } - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { postLoading(false) _viewEvents.post(RoomAliasViewEvents.Failure(failure)) } - }) + } } private fun handleAddLocalAlias() = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 9e402c675b..fe8ed63cce 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -197,8 +197,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState room.sendStateEvent( eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE, stateKey = action.stateKey, - body = emptyMap(), - callback = NoOpMatrixCallback() + body = emptyMap() ) } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index a4d759250d..fbd08b0c9f 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -21,6 +21,8 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.resources.StringProvider +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes @@ -310,12 +312,19 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo val params = HashMap() params["status"] = status - room.sendStateEvent( - eventType = EventType.PLUMBING, - stateKey = null, - body = params, - callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) - ) + + GlobalScope.launch { + try { + room.sendStateEvent( + eventType = EventType.PLUMBING, + stateKey = null, + body = params + ) + widgetPostAPIMediator.sendSuccess(eventData) + } catch (failure: Exception) { + widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) + } + } } /** @@ -333,12 +342,18 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo Timber.d(description) val content = eventData["content"] as JsonDict val stateKey = "_$userId" - room.sendStateEvent( - eventType = EventType.BOT_OPTIONS, - stateKey = stateKey, - body = content, - callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) - ) + GlobalScope.launch { + try { + room.sendStateEvent( + eventType = EventType.BOT_OPTIONS, + stateKey = stateKey, + body = content + ) + widgetPostAPIMediator.sendSuccess(eventData) + } catch (failure: Exception) { + widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) + } + } } /** From f3bc39a0c57c7f28d71313f6fa26a8e24164de49 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:14:55 +0100 Subject: [PATCH 068/218] Cleanup --- .../android/sdk/internal/session/sync/job/SyncThread.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 74cba5e796..424c24663c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker import org.matrix.android.sdk.internal.session.sync.SyncTask -import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.Debouncer import org.matrix.android.sdk.internal.util.createUIHandler @@ -50,14 +49,13 @@ private const val RETRY_WAIT_TIME_MS = 10_000L private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L internal class SyncThread @Inject constructor(private val syncTask: SyncTask, - private val typingUsersTracker: DefaultTypingUsersTracker, private val networkConnectivityChecker: NetworkConnectivityChecker, private val backgroundDetectionObserver: BackgroundDetectionObserver, private val activeCallHandler: ActiveCallHandler ) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.Idle - private var liveState = MutableLiveData(state) + private var liveState = MutableLiveData(state) private val lock = Object() private val syncScope = CoroutineScope(SupervisorJob()) private val debouncer = Debouncer(createUIHandler()) @@ -231,7 +229,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, return } state = newState - debouncer.debounce("post_state", Runnable { + debouncer.debounce("post_state", { liveState.value = newState }, 150) } From 28bfea6af0f611ab40981b0b2b8181da21108556 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:31:16 +0100 Subject: [PATCH 069/218] This code is for debug build (see the path), so no need to check again --- .../interceptors/FormattedJsonHttpLogger.kt | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 630f6f1e29..849a464867 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -38,31 +38,28 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { */ @Synchronized override fun log(@NonNull message: String) { - // In RELEASE there is no log, but for sure, test again BuildConfig.DEBUG - if (BuildConfig.DEBUG) { - Timber.v(message) + Timber.v(message) - if (message.startsWith("{")) { - // JSON Detected - try { - val o = JSONObject(message) - logJson(o.toString(INDENT_SPACE)) - } catch (e: JSONException) { - // Finally this is not a JSON string... - Timber.e(e) - } - } else if (message.startsWith("[")) { - // JSON Array detected - try { - val o = JSONArray(message) - logJson(o.toString(INDENT_SPACE)) - } catch (e: JSONException) { - // Finally not JSON... - Timber.e(e) - } + if (message.startsWith("{")) { + // JSON Detected + try { + val o = JSONObject(message) + logJson(o.toString(INDENT_SPACE)) + } catch (e: JSONException) { + // Finally this is not a JSON string... + Timber.e(e) + } + } else if (message.startsWith("[")) { + // JSON Array detected + try { + val o = JSONArray(message) + logJson(o.toString(INDENT_SPACE)) + } catch (e: JSONException) { + // Finally not JSON... + Timber.e(e) } - // Else not a json string to log } + // Else not a json string to log } private fun logJson(formattedJson: String) { From a0c8a8e97ce5e7739f9881b9e7bfadcd7663dc5b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:36:12 +0100 Subject: [PATCH 070/218] Log HTTP requests and responses in production (level BASIC, i.e. without any private data) --- CHANGES.md | 1 + gradle.properties | 2 +- matrix-sdk-android/build.gradle | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1005040328..12cfd8ef83 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,7 @@ Test: Other changes: - Remove "Status.im" theme #2424 + - Log HTTP requests and responses in production (level BASIC, i.e. without any private data) Changes in Element 1.0.11 (2020-11-27) =================================================== diff --git a/gradle.properties b/gradle.properties index b3f11e08a3..200866be25 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx2048m org.gradle.vfs.watch=true vector.debugPrivateData=false -vector.httpLogLevel=NONE +vector.httpLogLevel=BASIC # Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above #vector.debugPrivateData=true diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index d961560c17..7f0d5c1bbf 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -63,7 +63,7 @@ android { release { buildConfigField "boolean", "LOG_PRIVATE_DATA", "false" - buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE" + buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BASIC" } } From b43f3b3b6aec9413b0f0c165dad79d0ee330023a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 11:57:18 +0100 Subject: [PATCH 071/218] Log some details about the request which has failed --- .../org/matrix/android/sdk/internal/network/Request.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index e6cec7f7ac..2535a5347a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -16,14 +16,15 @@ package org.matrix.android.sdk.internal.network -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.shouldBeRetried -import org.matrix.android.sdk.internal.network.ssl.CertUtil import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.shouldBeRetried +import org.matrix.android.sdk.internal.network.ssl.CertUtil import retrofit2.Call import retrofit2.awaitResponse +import timber.log.Timber import java.io.IOException internal suspend inline fun executeRequest(eventBus: EventBus?, @@ -49,6 +50,9 @@ internal class Request(private val eventBus: EventBus?) { throw response.toFailure(eventBus) } } catch (exception: Throwable) { + // Log some details about the request which has failed + Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") + // Check if this is a certificateException CertUtil.getCertificateException(exception) // TODO Support certificate error once logged From dda2685bd8693d5317866c69d9c21e7c76c85b35 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 13:33:01 +0100 Subject: [PATCH 072/218] Upgrade Realm dependency to 10.1.2 --- CHANGES.md | 1 + matrix-sdk-android/build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 12cfd8ef83..b0be0a1145 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ SDK API changes ⚠️: Build 🧱: - Upgrade some dependencies and Kotlin version - Use fragment-ktx and preference-ktx dependencies (fix lint issue KtxExtensionAvailable) + - Upgrade Realm dependency to 10.1.2 Test: - diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7f0d5c1bbf..519b8439c9 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.0.0" + classpath "io.realm:realm-gradle-plugin:10.1.2" } } From 416f57b1d791e71722d0847ba4549c328e0bab5e Mon Sep 17 00:00:00 2001 From: aqulu Date: Tue, 8 Dec 2020 22:02:00 +0900 Subject: [PATCH 073/218] Fix failing test compilation Signed-off-by: aqulu --- .../crypto/encryption/EncryptionTest.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt index e42059c639..da5e90abdd 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt @@ -17,13 +17,13 @@ package org.matrix.android.sdk.internal.crypto.encryption import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.runBlocking import org.amshove.kluent.shouldBe import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.Room @@ -57,13 +57,14 @@ class EncryptionTest : InstrumentedTest { @Test fun test_EncryptionStateEvent() { performTest(roomShouldBeEncrypted = true) { room -> - // Send an encryption Event as a State Event - room.sendStateEvent( - eventType = EventType.STATE_ROOM_ENCRYPTION, - stateKey = null, - body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent(), - callback = NoOpMatrixCallback() - ) + runBlocking { + // Send an encryption Event as a State Event + room.sendStateEvent( + eventType = EventType.STATE_ROOM_ENCRYPTION, + stateKey = null, + body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() + ) + } } } From c889deaab1dccd2207612a00b2c9b0112a4bb6f5 Mon Sep 17 00:00:00 2001 From: aqulu Date: Tue, 8 Dec 2020 22:02:49 +0900 Subject: [PATCH 074/218] Remove unused imports Signed-off-by: aqulu --- .../matrix/android/sdk/api/session/room/state/StateService.kt | 2 -- .../vector/app/features/roomprofile/alias/RoomAliasViewModel.kt | 1 - .../app/features/roomprofile/members/RoomMemberListViewModel.kt | 1 - 3 files changed, 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 98dde5839f..444366e912 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -18,13 +18,11 @@ package org.matrix.android.sdk.api.session.room.state import android.net.Uri import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index af0972913a..f470eeefc2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -30,7 +30,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index fe8ed63cce..9f15e62b3b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -30,7 +30,6 @@ import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue From 19d421df84485effcdc6d5f3ec9fc018f5185f35 Mon Sep 17 00:00:00 2001 From: aqulu Date: Tue, 8 Dec 2020 22:06:21 +0900 Subject: [PATCH 075/218] Remove coroutine context change for sendStateEvent Signed-off-by: aqulu --- .../session/room/state/DefaultStateService.kt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 607784b48f..f71b8868ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -72,16 +72,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private stateKey: String?, body: JsonDict ) { - withContext(coroutineDispatchers.main) { - val params = SendStateTask.Params( - roomId = roomId, - stateKey = stateKey, - eventType = eventType, - body = body - ) - - sendStateTask.execute(params) - } + val params = SendStateTask.Params( + roomId = roomId, + stateKey = stateKey, + eventType = eventType, + body = body + ) + sendStateTask.execute(params) } override suspend fun updateTopic(topic: String) { From 40b9f031325cac0a7474a3706341318e62d48dc4 Mon Sep 17 00:00:00 2001 From: aqulu Date: Tue, 8 Dec 2020 22:11:35 +0900 Subject: [PATCH 076/218] Remove explicit coroutine context changes Signed-off-by: aqulu --- .../session/room/state/DefaultStateService.kt | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index f71b8868ed..78663e8ce2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -20,7 +20,6 @@ import android.net.Uri import androidx.lifecycle.LiveData import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -36,12 +35,10 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, private val sendStateTask: SendStateTask, - private val coroutineDispatchers: MatrixCoroutineDispatchers, private val fileUploader: FileUploader, private val addRoomAliasTask: AddRoomAliasTask ) : StateService { @@ -123,33 +120,29 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) { - withContext(coroutineDispatchers.main) { - if (joinRules != null) { - sendStateEvent( - eventType = EventType.STATE_ROOM_JOIN_RULES, - body = RoomJoinRulesContent(joinRules).toContent(), - stateKey = null - ) - } - if (guestAccess != null) { - sendStateEvent( - eventType = EventType.STATE_ROOM_GUEST_ACCESS, - body = RoomGuestAccessContent(guestAccess).toContent(), - stateKey = null - ) - } + if (joinRules != null) { + sendStateEvent( + eventType = EventType.STATE_ROOM_JOIN_RULES, + body = RoomJoinRulesContent(joinRules).toContent(), + stateKey = null + ) + } + if (guestAccess != null) { + sendStateEvent( + eventType = EventType.STATE_ROOM_GUEST_ACCESS, + body = RoomGuestAccessContent(guestAccess).toContent(), + stateKey = null + ) } } override suspend fun updateAvatar(avatarUri: Uri, fileName: String) { - withContext(coroutineDispatchers.main) { - val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") - sendStateEvent( - eventType = EventType.STATE_ROOM_AVATAR, - body = mapOf("url" to response.contentUri), - stateKey = null - ) - } + val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") + sendStateEvent( + eventType = EventType.STATE_ROOM_AVATAR, + body = mapOf("url" to response.contentUri), + stateKey = null + ) } override suspend fun deleteAvatar() { From 7152dead1da6272836601e18986b941fb985cf02 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 16:47:29 +0100 Subject: [PATCH 077/218] Rename method --- .../org/matrix/android/sdk/internal/session/SessionModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 32949d60c4..f3a9fc59e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -169,8 +169,8 @@ internal abstract class SessionModule { @JvmStatic @Provides @SessionDownloadsDirectory - fun providesCacheDir(@SessionId sessionId: String, - context: Context): File { + fun providesDownloadsCacheDir(@SessionId sessionId: String, + context: Context): File { return File(context.cacheDir, "downloads/$sessionId") } From 4d4fe687ac10e0f6ea0b073ee8ae3f8b5e5851aa Mon Sep 17 00:00:00 2001 From: Uumas Date: Sun, 6 Dec 2020 22:28:15 +0000 Subject: [PATCH 078/218] Translated using Weblate (Finnish) Currently translated at 79.9% (1587 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- vector/src/main/res/values-fi/strings.xml | 41 +++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 5a1426d644..bb382c6820 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -13,7 +13,7 @@ OK Peruuta - Säästä + Tallenna Poistu Lähetä Lähetä uudelleen @@ -1231,7 +1231,7 @@ Yksityisviestit Uusi huone LUO - Huoneen nimi + Nimi Julkinen Kuka tahansa voi liittyä tähän huoneeseen Luottamustietoa haettaessa tapahtui virhe @@ -1711,7 +1711,7 @@ Viimeaikaiset huoneet Muut huoneet Lähettää annetun viestin väritettynä sateenkaaren väreillä - Ota käyttöön osapuolten välinen salaus + Ota käyttöön osapuolten välinen salaus… Salausta ei voi enää poistaa käytöstä sen jälkeen kun se on otettu käyttöön. Otetaanko salaus käyttöön\? Salausta ei voi ottaa pois käytöstä sen jälkeen kun se on otettu käyttöön. Salattuja viestejä ei pysty lukemaan edes palvelin, vain ainoastaan huoneessa olijat. Salauksen käyttöönotto voi estää bottien ja siltojen toiminnan huoneessa. @@ -1833,4 +1833,39 @@ Jätä huomiotta Tauko Toista + Käyttäjän huomiotta jättäminen piilottaa kyseisen käyttäjän viestit sinulta. +\n +\nVoit perua tämän milloin tahansa yleisissä asetuksissa. + Jätä käyttäjä huomiotta + Alenna + Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä huoneessa, oikeuksia ei voi enää saada takaisin. + Alenna itsesi\? + Peru kutsu + Anna lupa hakea yhteystiedot. + QR-koodin skannaaminen vaatii luvan kameran käyttöön. + Palaa puheluun + Aktiivinen puhelu (%s) + Kysy varmistusta ennen puhelun aloittamista + Estä vahinkopuhelut + SSL Virhe. + Takakamera + Etukamera + Vaihda kameraa + Langattomat kuulokkeet + Kuulokkeet + Kaiutin + Puhelin + Valitse äänilaite + Element-puhelu epäonnistui + Lähetä avaimen jakopyyntöjen historia + Ei enempää tuloksia + Ilmoitukset + Onnistui + Kopioi + Lopeta puhelu + Hylkää + Sovelman poistaminen epäonnistui + Sovelman lisääminen epäonnistui + Et voi aloittaa puhelua itsesi kanssa. Odota, että muut osallistujat hyväksyvät kutsun + Et voi aloittaa puhelua itsesi kanssa \ No newline at end of file From 7667ef686d795c75feed58b8174c7cc59db8d448 Mon Sep 17 00:00:00 2001 From: OLIVIER Thomas Date: Sun, 6 Dec 2020 20:51:07 +0000 Subject: [PATCH 079/218] Translated using Weblate (French) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 41cc30447d..61a679ef1c 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2159,7 +2159,7 @@ Activer la HD Désactiver la HD Arrière - Frontale + Frontal Vous n’avez pas la permission de démarrer un appel Vous n’avez pas la permission de démarrer une téléconférence Réinitialiser From 9339b7e31de02e222fdb5fee9b589c7128e80913 Mon Sep 17 00:00:00 2001 From: Jeanne Lavoie Date: Sun, 6 Dec 2020 20:50:44 +0000 Subject: [PATCH 080/218] Translated using Weblate (French) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 37 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 61a679ef1c..a38ba7ceb9 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -102,8 +102,8 @@ Mot de passe Nouveau mot de passe Nom d’utilisateur - Adresse e-mail - Adresse e-mail (facultatif) + Adresse électronique + Adresse électronique (facultatif) Numéro de téléphone Numéro de téléphone (facultatif) Répéter le mot de passe @@ -704,9 +704,9 @@ Donner la permission Choisir une autre option Avatar d’avertissement - Envoyer un sticker - Envoyer un sticker - Vous n’avez aucun jeu d\'étiquettes activé pour le moment. + Envoyer un autocollant + Envoyer un autocollant + Vous n’avez aucun jeu d\'autocollants activé pour le moment. \n \nVoulez-vous en ajouter \? Désactiver le compte @@ -1269,7 +1269,7 @@ Conditions de service Examiner les conditions Être découvrable par les autres - Utiliser des robots, passerelles, widgets et packs de stickers + Utiliser des robots, passerelles, widgets et jeux d\'autocollants Lu à Aucun Révoquer @@ -1388,7 +1388,7 @@ Vous ne pouvez pas faire cela depuis Element mobile Une authentification est nécessaire Intégrations - Utilisez un gestionnaire d’intégrations pour gérer les bots, les passerelles, les widgets et les packs de stickers. + Utilisez un gestionnaire d’intégrations pour gérer les bots, les passerelles, les widgets et les jeux d\'autocollants. \nLes gestionnaires d’intégrations reçoivent des données de configuration et peuvent modifier des widgets, envoyer des invitations de salon et définir des rangs à votre place. Autoriser les intégrations Widget @@ -2231,4 +2231,27 @@ Autoriser l\'accès à vos contacts. Pour scanner un QR code, vous devez autoriser l\'accès à votre appareil photo. Débuter la discussion + Lien Matrix + Code QR non scanné ! + Code QR invalide (URI invalide) ! + Impossible avec vous-même ! + Partager par texto + Rechercher des contacts sur Matrix + Définir l\'avatar + Le consentement de l\'utilisateur n\'a pas été fourni. + Partagez ce code avec des gens pour qu\'ils puissent le scanner pour vous ajouter et commencer à discuter. + Mon code + Partager mon code + Scanner un code QR + Ce n\'est pas un code QR matrix valide + 🔐️ Rejoins-moi sur element + Salut, parle-moi sur Element : %s + Ajouter des amis + Ajouter des gens + "Sujet : " + Ajouter un sujet + Vous devriez l\'activer si le salon n\'est utilisé que pour collaborer avec des équipes internes sur votre serveur d\'accueil. Ceci ne peut pas être changé plus tard. + Bloquer les personnes qui ne sont pas membres de %s de rejoindre ce salon + Masquer les avancés + Afficher les avancés \ No newline at end of file From 682dff9c1c9a9c12d9c41714ea2a2704ac299871 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 7 Dec 2020 15:22:30 +0000 Subject: [PATCH 081/218] Translated using Weblate (Italian) Currently translated at 100.0% (1986 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 286c73ab39..557aca192f 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -1490,7 +1490,7 @@ Messaggi non letti È la tua conversazione. Tienitela. Chatta con persone direttamente o in gruppi - Tieni private le conversazioni con la cifratura + Tieni private le conversazioni con la crittografia Estendi e personalizza la tua esperienza Inizia Seleziona un server From 2d9043fbedf6ad6bc61195fd737edbaf865b631e Mon Sep 17 00:00:00 2001 From: Kaede Date: Mon, 7 Dec 2020 20:00:27 +0000 Subject: [PATCH 082/218] Translated using Weblate (Japanese) Currently translated at 50.1% (996 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 45bb33c807..e4c0ea4531 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1112,4 +1112,9 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ QR コード QR コードによる追加 コードを共有 + Element で会話しましょう: %s + フレンドを招待 + 名前または ID で検索 + 既知のユーザー + 無効な QR コード (無効な URI)! \ No newline at end of file From 6190fb3511f0f93e76341c1c964272aaa98c588f Mon Sep 17 00:00:00 2001 From: random Date: Mon, 7 Dec 2020 15:38:50 +0000 Subject: [PATCH 083/218] Translated using Weblate (Italian) Currently translated at 100.0% (210 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/it/ --- .../src/main/res/values-it/strings.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/matrix-sdk-android/src/main/res/values-it/strings.xml b/matrix-sdk-android/src/main/res/values-it/strings.xml index 5eab8c57df..c1a5cf85cb 100644 --- a/matrix-sdk-android/src/main/res/values-it/strings.xml +++ b/matrix-sdk-android/src/main/res/values-it/strings.xml @@ -214,4 +214,27 @@ %1$s è entrato Hai creato la discussione %1$s ha creato la discussione + Stanza vuota (era %s) + + %1$s, %2$s, %3$s e %4$d altro + %1$s, %2$s, %3$s e altri %4$d + + %1$s, %2$s, %3$s e %4$s + %1$s, %2$s e %3$s + 🎉 Tutti i server sono banditi dalla partecipazione! Questa stanza non può più essere usata. + Nessuna modifica. + • I server che corrispondono a IP letterali ora sono banditi. + • I server che corrispondono a IP letterali ora sono permessi. + • I server che corrispondono a %s sono stati rimossi dalla lista dei consentiti. + • I server che corrispondono a %s ora sono permessi. + • I server che corrispondono a %s sono stati rimossi dalla lista di ban. + • I server che corrispondono a %s ora sono banditi. + Hai cambiato le ACL del server per questa stanza. + %s ha cambiato le ACL del server per questa stanza. + • I server che corrispondono a IP letterali sono banditi. + • I server che corrispondono a IP letterali sono permessi. + • I server che corrispondono a %s sono permessi. + • I server che corrispondono a %s sono banditi. + Hai impostato le ACL del server per questa stanza. + %s ha impostato le ACL del server per questa stanza. \ No newline at end of file From 24a9ddaa5e3aad9f42550948c87b1e78d85eee04 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 17:20:40 +0100 Subject: [PATCH 084/218] FileService: remove useless FileService.DownloadMode --- CHANGES.md | 2 +- .../sdk/api/session/file/FileService.kt | 18 --------------- .../sdk/internal/di/MatrixComponent.kt | 3 --- .../android/sdk/internal/di/MatrixModule.kt | 7 ------ .../internal/session/DefaultFileService.kt | 23 ++----------------- .../sdk/internal/session/SessionModule.kt | 5 ++-- .../app/core/glide/VectorGlideModelLoader.kt | 2 -- .../home/room/detail/RoomDetailFragment.kt | 3 --- .../home/room/detail/RoomDetailViewModel.kt | 2 -- .../features/media/BaseAttachmentProvider.kt | 1 - .../media/DataAttachmentRoomProvider.kt | 1 - .../media/RoomEventsAttachmentProvider.kt | 1 - .../features/media/VideoContentRenderer.kt | 3 --- .../uploads/RoomUploadsViewModel.kt | 3 --- 14 files changed, 6 insertions(+), 68 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b0be0a1145..7ba7b7cb76 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,7 +18,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - FileService: remove useless FileService.DownloadMode Build 🧱: - Upgrade some dependencies and Kotlin version diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index 31f016be14..dd592d84a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -27,23 +27,6 @@ import java.io.File */ interface FileService { - enum class DownloadMode { - /** - * Download file in external storage - */ - TO_EXPORT, - - /** - * Download file in cache - */ - FOR_INTERNAL_USE, - - /** - * Download file in file provider path - */ - FOR_EXTERNAL_SHARE - } - enum class FileState { IN_CACHE, DOWNLOADING, @@ -55,7 +38,6 @@ interface FileService { * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ fun downloadFile( - downloadMode: DownloadMode, id: String, fileName: String, mimeType: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index d3f08fde36..f959104e11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -71,9 +71,6 @@ internal interface MatrixComponent { @CacheDirectory fun cacheDir(): File - @ExternalFilesDirectory - fun externalFilesDir(): File? - fun olmManager(): OlmManager fun taskExecutor(): TaskExecutor diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index 71cbd8f1a1..b58fb3e683 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -57,13 +57,6 @@ internal object MatrixModule { return context.cacheDir } - @JvmStatic - @Provides - @ExternalFilesDirectory - fun providesExternalFilesDir(context: Context): File? { - return context.getExternalFilesDir(null) - } - @JvmStatic @Provides @MatrixScope diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 861ae7c7ee..d71c3262c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -29,8 +29,6 @@ import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments -import org.matrix.android.sdk.internal.di.CacheDirectory -import org.matrix.android.sdk.internal.di.ExternalFilesDirectory import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER @@ -54,10 +52,6 @@ import javax.inject.Inject internal class DefaultFileService @Inject constructor( private val context: Context, - @CacheDirectory - private val cacheDirectory: File, - @ExternalFilesDirectory - private val externalFilesDirectory: File?, @SessionDownloadsDirectory private val sessionCacheDirectory: File, private val contentUrlResolver: ContentUrlResolver, @@ -81,8 +75,7 @@ internal class DefaultFileService @Inject constructor( * Download file in the cache folder, and eventually decrypt it * TODO looks like files are copied 3 times */ - override fun downloadFile(downloadMode: FileService.DownloadMode, - id: String, + override fun downloadFile(id: String, fileName: String, mimeType: String?, url: String?, @@ -162,7 +155,7 @@ internal class DefaultFileService @Inject constructor( Timber.v("## FileService: cache hit for $url") } - Try.just(copyFile(destFile, downloadMode)) + Try.just(destFile) } }.fold({ callback.onFailure(it) @@ -232,18 +225,6 @@ internal class DefaultFileService @Inject constructor( return FileProvider.getUriForFile(context, authority, targetFile) } - private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File { - // TODO some of this seems outdated, will need to be re-worked - return when (downloadMode) { - FileService.DownloadMode.TO_EXPORT -> - file.copyTo(File(externalFilesDirectory, file.name), true) - FileService.DownloadMode.FOR_EXTERNAL_SHARE -> - file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true) - FileService.DownloadMode.FOR_INTERNAL_USE -> - file - } - } - override fun getCacheSize(): Int { return downloadFolder.walkTopDown() .onEnter { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f3a9fc59e3..96b44917bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory import org.matrix.android.sdk.internal.di.Authenticated +import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory @@ -170,8 +171,8 @@ internal abstract class SessionModule { @Provides @SessionDownloadsDirectory fun providesDownloadsCacheDir(@SessionId sessionId: String, - context: Context): File { - return File(context.cacheDir, "downloads/$sessionId") + @CacheDirectory cacheFile: File): File { + return File(cacheFile, "downloads/$sessionId") } @JvmStatic diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 71bd3ccc05..cf40926ba4 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -28,7 +28,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.media.ImageContentRenderer import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.file.FileService import timber.log.Timber import java.io.File import java.io.IOException @@ -110,7 +109,6 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde } // Use the file vector service, will avoid flickering and redownload after upload fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, mimeType = data.mimeType, id = data.eventId, url = data.url, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 3f5e476a5e..bbce180e80 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -174,7 +174,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -1657,7 +1656,6 @@ class RoomDetailFragment @Inject constructor( shareText(requireContext(), action.messageContent.body) } else if (action.messageContent is MessageWithAttachmentContent) { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.eventId, fileName = action.messageContent.body, mimeType = action.messageContent.mimeType, @@ -1692,7 +1690,6 @@ class RoomDetailFragment @Inject constructor( return } session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.eventId, fileName = action.messageContent.body, mimeType = action.messageContent.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a83dddc9ac..a13ee3be62 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -69,7 +69,6 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams @@ -1033,7 +1032,6 @@ class RoomDetailViewModel @AssistedInject constructor( } } else { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = action.eventId, fileName = action.messageFileContent.getFileName(), mimeType = action.messageFileContent.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index e23b905919..5f61ca36e4 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -153,7 +153,6 @@ abstract class BaseAttachmentProvider( } else { target.onVideoFileLoading(info.uid) fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, mimeType = data.mimeType, elementToDecrypt = data.elementToDecrypt, diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 18312b4aa0..6f58c1a4f3 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -77,7 +77,6 @@ class DataAttachmentRoomProvider( override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { val item = getItem(position) fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = item.eventId, fileName = item.filename, mimeType = item.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 1e2761dde0..9b895dbc4d 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -125,7 +125,6 @@ class RoomEventsAttachmentProvider( as? MessageWithAttachmentContent ?: return@let fileService.downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = timelineEvent.eventId, fileName = messageContent.body, mimeType = messageContent.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index f8cd09ce2f..35375bc8ce 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -27,7 +27,6 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.utils.isLocalFile import kotlinx.android.parcel.Parcelize import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import timber.log.Timber import java.io.File @@ -76,7 +75,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: activeSessionHolder.getActiveSession().fileService() .downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, fileName = data.filename, mimeType = data.mimeType, @@ -116,7 +114,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder: activeSessionHolder.getActiveSession().fileService() .downloadFile( - downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, fileName = data.filename, mimeType = data.mimeType, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 763eed5474..95d7ce8e93 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -30,7 +30,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt @@ -134,7 +133,6 @@ class RoomUploadsViewModel @AssistedInject constructor( try { val file = awaitCallback { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.uploadEvent.eventId, fileName = action.uploadEvent.contentWithAttachmentContent.body, url = action.uploadEvent.contentWithAttachmentContent.getFileUrl(), @@ -155,7 +153,6 @@ class RoomUploadsViewModel @AssistedInject constructor( try { val file = awaitCallback { session.fileService().downloadFile( - downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.uploadEvent.eventId, fileName = action.uploadEvent.contentWithAttachmentContent.body, mimeType = action.uploadEvent.contentWithAttachmentContent.mimeType, From 8e11ba21edfe1283b6055ac36bbbdac352fa27f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 17:37:24 +0100 Subject: [PATCH 085/218] Glide: No Disk cache for encrypted images --- .../im/vector/app/features/media/ImageContentRenderer.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 187c2e85c3..cf214b391a 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -23,6 +23,7 @@ import android.view.View import android.widget.ImageView import androidx.core.view.updateLayoutParams import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestListener @@ -129,6 +130,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: GlideApp .with(contextView) .load(data) + .diskCacheStrategy(DiskCacheStrategy.NONE) } else { // Clear image val resolvedUrl = resolveUrl(data) @@ -183,6 +185,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: GlideApp .with(imageView) .load(data) + .diskCacheStrategy(DiskCacheStrategy.NONE) } else { // Clear image val resolvedUrl = resolveUrl(data) @@ -214,14 +217,16 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: .into(imageView) } - fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest { + private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest { return createGlideRequest(data, mode, GlideApp.with(imageView), size) } fun createGlideRequest(data: Data, mode: Mode, glideRequests: GlideRequests, size: Size = processSize(data, mode)): GlideRequest { return if (data.elementToDecrypt != null) { // Encrypted image - glideRequests.load(data) + glideRequests + .load(data) + .diskCacheStrategy(DiskCacheStrategy.NONE) } else { // Clear image val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() From 42ab7f1b4f8098cb18f24e41cbb9331ce70e6896 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 17:42:42 +0100 Subject: [PATCH 086/218] Add space between image and text And remove useless `apply` block --- .../timeline/factory/EncryptedItemFactory.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index f77e39c245..e88c1f3797 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -82,10 +82,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat when (cryptoError) { MXCryptoError.ErrorType.KEYS_WITHHELD -> { span { - apply { - drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let { - image(it, "baseline") - } + drawableProvider.getDrawable(R.drawable.ic_forbidden, colorFromAttribute)?.let { + image(it, "baseline") + +" " } span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_final)) { textStyle = "italic" @@ -95,10 +94,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat } else -> { span { - apply { - drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let { - image(it, "baseline") - } + drawableProvider.getDrawable(R.drawable.ic_clock, colorFromAttribute)?.let { + image(it, "baseline") + +" " } span(stringProvider.getString(R.string.notice_crypto_unable_to_decrypt_friendly)) { textStyle = "italic" From 237cb63fc2b21674e6e09ff064e451d4007f8ab3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 18:04:42 +0100 Subject: [PATCH 087/218] Small formatting --- .../internal/session/DefaultFileService.kt | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index d71c3262c2..37b27cdbae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -21,6 +21,13 @@ import android.net.Uri import android.webkit.MimeTypeMap import androidx.core.content.FileProvider import arrow.core.Try +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.buffer +import okio.sink +import okio.source import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -36,13 +43,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient -import okhttp3.Request -import okio.buffer -import okio.sink -import okio.source import timber.log.Timber import java.io.File import java.io.IOException @@ -157,30 +157,33 @@ internal class DefaultFileService @Inject constructor( Try.just(destFile) } - }.fold({ - callback.onFailure(it) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) + }.fold( + { throwable -> + callback.onFailure(throwable) + // notify concurrent requests + val toNotify = synchronized(ongoing) { + ongoing[unwrappedUrl]?.also { + ongoing.remove(unwrappedUrl) + } + } + toNotify?.forEach { otherCallbacks -> + tryOrNull { otherCallbacks.onFailure(throwable) } + } + }, + { file -> + callback.onSuccess(file) + // notify concurrent requests + val toNotify = synchronized(ongoing) { + ongoing[unwrappedUrl]?.also { + ongoing.remove(unwrappedUrl) + } + } + Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") + toNotify?.forEach { otherCallbacks -> + tryOrNull { otherCallbacks.onSuccess(file) } + } } - } - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onFailure(it) } - } - }, { file -> - callback.onSuccess(file) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) - } - } - Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onSuccess(file) } - } - }) + ) }.toCancelable() } From 62791e4b36df0020af82fb2098f5bd12d5c3f5f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 18:35:17 +0100 Subject: [PATCH 088/218] Encrypted files: store decrypted file in a dedicated folder --- .../sdk/api/session/file/FileService.kt | 7 +- .../internal/session/DefaultFileService.kt | 65 +++++++++++++------ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index dd592d84a3..e13aed628c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -60,10 +60,15 @@ interface FileService { fun fileState(mxcUrl: String, mimeType: String?): FileState /** - * Clears all the files downloaded by the service + * Clears all the files downloaded by the service, including decrypted files */ fun clearCache() + /** + * Clears all the decrypted files by the service + */ + fun clearDecryptedCache() + /** * Get size of cached files */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 37b27cdbae..062d09e101 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -63,7 +63,15 @@ internal class DefaultFileService @Inject constructor( private fun String.safeFileName() = URLEncoder.encode(this, Charsets.US_ASCII.displayName()) - private val downloadFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded file (not decrypted) + private val legacyFolder = File(sessionCacheDirectory, "MF") + private val downloadFolder = File(sessionCacheDirectory, "F") + private val decryptedFolder = File(downloadFolder, "D") + + init { + // Clear the legacy downloaded files + legacyFolder.deleteRecursively() + } /** * Retain ongoing downloads to avoid re-downloading and already downloading file @@ -103,8 +111,8 @@ internal class DefaultFileService @Inject constructor( return taskExecutor.executorScope.launch(coroutineDispatchers.main) { withContext(coroutineDispatchers.io) { Try { - if (!downloadFolder.exists()) { - downloadFolder.mkdirs() + if (!decryptedFolder.exists()) { + decryptedFolder.mkdirs() } // ensure we use unique file name by using URL (mapped to suitable file name) // Also we need to add extension for the FileProvider, if not it lot's of app that it's @@ -134,29 +142,42 @@ internal class DefaultFileService @Inject constructor( Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") - if (elementToDecrypt != null) { - Timber.v("## FileService: decrypt file") - val decryptSuccess = destFile.outputStream().buffered().use { - MXEncryptedAttachments.decryptAttachment( - source.inputStream(), - elementToDecrypt, - it - ) - } - response.close() - if (!decryptSuccess) { - return@flatMap Try.Failure(IllegalStateException("Decryption error")) - } - } else { - writeToFile(source.inputStream(), destFile) - response.close() - } + // Write the file to cache (encrypted version if the file is encrypted) + writeToFile(source.inputStream(), destFile) + response.close() } else { Timber.v("## FileService: cache hit for $url") } Try.just(destFile) } + }.flatMap { downloadedFile -> + // Decrypt if necessary + if (elementToDecrypt != null) { + val decryptedFile = File(decryptedFolder, fileForUrl(unwrappedUrl, mimeType)) + + if (!decryptedFile.exists()) { + Timber.v("## FileService: decrypt file") + val decryptSuccess = decryptedFile.outputStream().buffered().use { outputStream -> + downloadedFile.inputStream().use { inputStream -> + MXEncryptedAttachments.decryptAttachment( + inputStream, + elementToDecrypt, + outputStream + ) + } + } + if (!decryptSuccess) { + return@flatMap Try.Failure(IllegalStateException("Decryption error")) + } + } else { + Timber.v("## FileService: cache hit for decrypted file") + } + Try.just(decryptedFile) + } else { + // Clear file + Try.just(downloadedFile) + } }.fold( { throwable -> callback.onFailure(throwable) @@ -240,4 +261,8 @@ internal class DefaultFileService @Inject constructor( override fun clearCache() { downloadFolder.deleteRecursively() } + + override fun clearDecryptedCache() { + decryptedFolder.deleteRecursively() + } } From 7057b2970b4a84acbcce1fc31f6b92bcba6f4f50 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Dec 2020 19:31:29 +0100 Subject: [PATCH 089/218] Improve FileService API: add facility methods to deal with MessageWithAttachment object --- .../sdk/api/session/file/FileService.kt | 50 +++++++++++++++++-- .../internal/session/DefaultFileService.kt | 24 ++++++--- .../home/room/detail/RoomDetailFragment.kt | 10 +--- .../home/room/detail/RoomDetailViewModel.kt | 13 ++--- .../timeline/factory/MessageItemFactory.kt | 4 +- .../uploads/RoomUploadsViewModel.kt | 10 +--- 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index e13aed628c..d3327ba920 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -18,8 +18,12 @@ package org.matrix.android.sdk.api.session.file import android.net.Uri import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent +import org.matrix.android.sdk.api.session.room.model.message.getFileName +import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt +import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File /** @@ -45,19 +49,59 @@ interface FileService { elementToDecrypt: ElementToDecrypt?, callback: MatrixCallback): Cancelable - fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean + fun downloadFile( + id: String, + messageContent: MessageWithAttachmentContent, + callback: MatrixCallback): Cancelable = + downloadFile( + id = id, + fileName = messageContent.getFileName(), + mimeType = messageContent.mimeType, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), + callback = callback + ) + + fun isFileInCache(mxcUrl: String?, + mimeType: String?, + elementToDecrypt: ElementToDecrypt? + ): Boolean + + fun isFileInCache(messageContent: MessageWithAttachmentContent) = + isFileInCache( + mxcUrl = messageContent.getFileUrl(), + mimeType = messageContent.mimeType, + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) /** * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it) */ - fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri? + fun getTemporarySharableURI(mxcUrl: String?, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): Uri? + + fun getTemporarySharableURI(messageContent: MessageWithAttachmentContent): Uri? = + getTemporarySharableURI( + mxcUrl = messageContent.getFileUrl(), + mimeType = messageContent.mimeType, + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() + ) /** * Get information on the given file. * Mimetype should be the same one as passed to downloadFile (limitation for now) */ - fun fileState(mxcUrl: String, mimeType: String?): FileState + fun fileState(mxcUrl: String?, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): FileState + + fun fileState(messageContent: MessageWithAttachmentContent): FileState = + fileState( + mxcUrl = messageContent.getFileUrl(), + mimeType = messageContent.mimeType, + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() + ) /** * Clears all the files downloaded by the service, including decrypted files diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 062d09e101..1e5dff107e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -225,12 +225,23 @@ internal class DefaultFileService @Inject constructor( return if (extension != null) "${url.safeFileName()}.$extension" else url.safeFileName() } - override fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean { - return File(downloadFolder, fileForUrl(mxcUrl, mimeType)).exists() + override fun isFileInCache(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Boolean { + return fileState(mxcUrl, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE } - override fun fileState(mxcUrl: String, mimeType: String?): FileService.FileState { - if (isFileInCache(mxcUrl, mimeType)) return FileService.FileState.IN_CACHE + private fun getClearFile(mxcUrl: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): File { + return if (elementToDecrypt == null) { + // Clear file + File(downloadFolder, fileForUrl(mxcUrl, mimeType)) + } else { + // Encrypted file + File(decryptedFolder, fileForUrl(mxcUrl, mimeType)) + } + } + + override fun fileState(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileService.FileState { + mxcUrl ?: return FileService.FileState.UNKNOWN + if (getClearFile(mxcUrl, mimeType, elementToDecrypt).exists()) return FileService.FileState.IN_CACHE val isDownloading = synchronized(ongoing) { ongoing[mxcUrl] != null } @@ -241,10 +252,11 @@ internal class DefaultFileService @Inject constructor( * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it) */ - override fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri? { + override fun getTemporarySharableURI(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Uri? { + mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" - val targetFile = File(downloadFolder, fileForUrl(mxcUrl, mimeType)) + val targetFile = getClearFile(mxcUrl, mimeType, elementToDecrypt) if (!targetFile.exists()) return null return FileProvider.getUriForFile(context, authority, targetFile) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index bbce180e80..a229f72755 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1657,10 +1657,7 @@ class RoomDetailFragment @Inject constructor( } else if (action.messageContent is MessageWithAttachmentContent) { session.fileService().downloadFile( id = action.eventId, - fileName = action.messageContent.body, - mimeType = action.messageContent.mimeType, - url = action.messageContent.getFileUrl(), - elementToDecrypt = action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.messageContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { if (isAdded) { @@ -1691,10 +1688,7 @@ class RoomDetailFragment @Inject constructor( } session.fileService().downloadFile( id = action.eventId, - fileName = action.messageContent.body, - mimeType = action.messageContent.mimeType, - url = action.messageContent.getFileUrl(), - elementToDecrypt = action.messageContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.messageContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { if (isAdded) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a13ee3be62..86f22a55ad 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1009,10 +1009,10 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { - val mxcUrl = action.messageFileContent.getFileUrl() + val mxcUrl = action.messageFileContent.getFileUrl() ?: return val isLocalSendingFile = action.senderId == session.myUserId - && mxcUrl?.startsWith("content://") ?: false - val isDownloaded = mxcUrl?.let { session.fileService().isFileInCache(it, action.messageFileContent.mimeType) } ?: false + && mxcUrl.startsWith("content://") + val isDownloaded = session.fileService().isFileInCache(action.messageFileContent) if (isLocalSendingFile) { tryOrNull { Uri.parse(mxcUrl) }?.let { _viewEvents.post(RoomDetailViewEvents.OpenFile( @@ -1023,7 +1023,7 @@ class RoomDetailViewModel @AssistedInject constructor( } } else if (isDownloaded) { // we can open it - session.fileService().getTemporarySharableURI(mxcUrl!!, action.messageFileContent.mimeType)?.let { uri -> + session.fileService().getTemporarySharableURI(action.messageFileContent)?.let { uri -> _viewEvents.post(RoomDetailViewEvents.OpenFile( action.messageFileContent.mimeType, uri, @@ -1033,10 +1033,7 @@ class RoomDetailViewModel @AssistedInject constructor( } else { session.fileService().downloadFile( id = action.eventId, - fileName = action.messageFileContent.getFileName(), - mimeType = action.messageFileContent.mimeType, - url = mxcUrl, - elementToDecrypt = action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.messageFileContent, callback = object : MatrixCallback { override fun onSuccess(data: File) { _viewEvents.post(RoomDetailViewEvents.DownloadFileState( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 2b067ccf3f..213c50b6ac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -204,7 +204,7 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .izLocalFile(fileUrl.isLocalFile()) - .izDownloaded(session.fileService().isFileInCache(fileUrl, messageContent.mimeType)) + .izDownloaded(session.fileService().isFileInCache(fileUrl, messageContent.mimeType, messageContent.encryptedFileInfo?.toElementToDecrypt())) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) @@ -264,7 +264,7 @@ class MessageItemFactory @Inject constructor( .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) .izLocalFile(messageContent.getFileUrl().isLocalFile()) - .izDownloaded(session.fileService().isFileInCache(mxcUrl, messageContent.mimeType)) + .izDownloaded(session.fileService().isFileInCache(messageContent)) .mxcUrl(mxcUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 95d7ce8e93..bd37cecd56 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -134,10 +134,7 @@ class RoomUploadsViewModel @AssistedInject constructor( val file = awaitCallback { session.fileService().downloadFile( id = action.uploadEvent.eventId, - fileName = action.uploadEvent.contentWithAttachmentContent.body, - url = action.uploadEvent.contentWithAttachmentContent.getFileUrl(), - mimeType = action.uploadEvent.contentWithAttachmentContent.mimeType, - elementToDecrypt = action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.uploadEvent.contentWithAttachmentContent, callback = it ) } @@ -154,10 +151,7 @@ class RoomUploadsViewModel @AssistedInject constructor( val file = awaitCallback { session.fileService().downloadFile( id = action.uploadEvent.eventId, - fileName = action.uploadEvent.contentWithAttachmentContent.body, - mimeType = action.uploadEvent.contentWithAttachmentContent.mimeType, - url = action.uploadEvent.contentWithAttachmentContent.getFileUrl(), - elementToDecrypt = action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(), + messageContent = action.uploadEvent.contentWithAttachmentContent, callback = it) } _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) From ed822becc68c16e03330b0139a7287861ccf72de Mon Sep 17 00:00:00 2001 From: aqulu Date: Wed, 9 Dec 2020 08:39:00 +0900 Subject: [PATCH 090/218] Fix try-catch behavior of sendStateEvent actions Signed-off-by: aqulu --- .../features/widgets/WidgetPostAPIHandler.kt | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index fbd08b0c9f..3906ea687c 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -22,6 +22,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.resources.StringProvider import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -312,18 +313,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo val params = HashMap() params["status"] = status - - GlobalScope.launch { - try { - room.sendStateEvent( - eventType = EventType.PLUMBING, - stateKey = null, - body = params - ) - widgetPostAPIMediator.sendSuccess(eventData) - } catch (failure: Exception) { - widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) - } + launchWidgetAPIAction(widgetPostAPIMediator, eventData) { + room.sendStateEvent( + eventType = EventType.PLUMBING, + stateKey = null, + body = params + ) } } @@ -342,17 +337,13 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo Timber.d(description) val content = eventData["content"] as JsonDict val stateKey = "_$userId" - GlobalScope.launch { - try { - room.sendStateEvent( - eventType = EventType.BOT_OPTIONS, - stateKey = stateKey, - body = content - ) - widgetPostAPIMediator.sendSuccess(eventData) - } catch (failure: Exception) { - widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) - } + + launchWidgetAPIAction(widgetPostAPIMediator, eventData) { + room.sendStateEvent( + eventType = EventType.BOT_OPTIONS, + stateKey = stateKey, + body = content + ) } } @@ -471,4 +462,19 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo private fun createWidgetAPICallback(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): WidgetAPICallback { return WidgetAPICallback(widgetPostAPIMediator, eventData, stringProvider) } + + private fun launchWidgetAPIAction(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict, block: suspend () -> Unit): Job { + return GlobalScope.launch { + kotlin.runCatching { + block() + }.fold( + onSuccess = { + widgetPostAPIMediator.sendSuccess(eventData) + }, + onFailure = { + widgetPostAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) + } + ) + } + } } From ca7796114cb56aac7f645c4c20e70082403b531a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 10:50:21 +0100 Subject: [PATCH 091/218] DefaultFileService: better management of the files and the filenames --- .../sdk/api/session/file/FileService.kt | 6 + .../internal/session/DefaultFileService.kt | 141 ++++++++++++------ .../session/content/UploadContentWorker.kt | 3 +- .../android/sdk/internal/util/FileSaver.kt | 3 + .../timeline/factory/MessageItemFactory.kt | 8 +- 5 files changed, 110 insertions(+), 51 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index d3327ba920..d0f53f25de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -63,6 +63,7 @@ interface FileService { ) fun isFileInCache(mxcUrl: String?, + fileName: String, mimeType: String?, elementToDecrypt: ElementToDecrypt? ): Boolean @@ -70,6 +71,7 @@ interface FileService { fun isFileInCache(messageContent: MessageWithAttachmentContent) = isFileInCache( mxcUrl = messageContent.getFileUrl(), + fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) @@ -78,12 +80,14 @@ interface FileService { * (if not other app won't be able to access it) */ fun getTemporarySharableURI(mxcUrl: String?, + fileName: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Uri? fun getTemporarySharableURI(messageContent: MessageWithAttachmentContent): Uri? = getTemporarySharableURI( mxcUrl = messageContent.getFileUrl(), + fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) @@ -93,12 +97,14 @@ interface FileService { * Mimetype should be the same one as passed to downloadFile (limitation for now) */ fun fileState(mxcUrl: String?, + fileName: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileState fun fileState(messageContent: MessageWithAttachmentContent): FileState = fileState( mxcUrl = messageContent.getFileUrl(), + fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 1e5dff107e..006ced8530 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -25,9 +25,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request -import okio.buffer -import okio.sink -import okio.source import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -41,13 +38,12 @@ import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProg import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber import java.io.File import java.io.IOException -import java.io.InputStream -import java.net.URLEncoder import javax.inject.Inject internal class DefaultFileService @Inject constructor( @@ -61,8 +57,6 @@ internal class DefaultFileService @Inject constructor( private val taskExecutor: TaskExecutor ) : FileService { - private fun String.safeFileName() = URLEncoder.encode(this, Charsets.US_ASCII.displayName()) - // Folder to store downloaded file (not decrypted) private val legacyFolder = File(sessionCacheDirectory, "MF") private val downloadFolder = File(sessionCacheDirectory, "F") @@ -89,21 +83,21 @@ internal class DefaultFileService @Inject constructor( url: String?, elementToDecrypt: ElementToDecrypt?, callback: MatrixCallback): Cancelable { - val unwrappedUrl = url ?: return NoOpCancellable.also { + url ?: return NoOpCancellable.also { callback.onFailure(IllegalArgumentException("url is null")) } - Timber.v("## FileService downloadFile $unwrappedUrl") + Timber.v("## FileService downloadFile $url") synchronized(ongoing) { - val existing = ongoing[unwrappedUrl] + val existing = ongoing[url] if (existing != null) { Timber.v("## FileService downloadFile is already downloading.. ") existing.add(callback) return NoOpCancellable } else { // mark as tracked - ongoing[unwrappedUrl] = ArrayList() + ongoing[url] = ArrayList() // and proceed to download } } @@ -117,9 +111,9 @@ internal class DefaultFileService @Inject constructor( // ensure we use unique file name by using URL (mapped to suitable file name) // Also we need to add extension for the FileProvider, if not it lot's of app that it's // shared with will not function well (even if mime type is passed in the intent) - File(downloadFolder, fileForUrl(unwrappedUrl, mimeType)) - }.flatMap { destFile -> - if (!destFile.exists()) { + getFiles(url, fileName, mimeType, elementToDecrypt) + }.flatMap { cachedFiles -> + if (!cachedFiles.file.exists()) { val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) val request = Request.Builder() @@ -143,23 +137,23 @@ internal class DefaultFileService @Inject constructor( Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") // Write the file to cache (encrypted version if the file is encrypted) - writeToFile(source.inputStream(), destFile) + writeToFile(source.inputStream(), cachedFiles.file) response.close() } else { Timber.v("## FileService: cache hit for $url") } - Try.just(destFile) + Try.just(cachedFiles) } - }.flatMap { downloadedFile -> + }.flatMap { cachedFiles -> // Decrypt if necessary - if (elementToDecrypt != null) { - val decryptedFile = File(decryptedFolder, fileForUrl(unwrappedUrl, mimeType)) - - if (!decryptedFile.exists()) { + if (cachedFiles.decryptedFile != null) { + if (!cachedFiles.decryptedFile.exists()) { Timber.v("## FileService: decrypt file") - val decryptSuccess = decryptedFile.outputStream().buffered().use { outputStream -> - downloadedFile.inputStream().use { inputStream -> + // Ensure the parent folder exists + cachedFiles.decryptedFile.parentFile?.mkdirs() + val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> + cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> MXEncryptedAttachments.decryptAttachment( inputStream, elementToDecrypt, @@ -173,18 +167,18 @@ internal class DefaultFileService @Inject constructor( } else { Timber.v("## FileService: cache hit for decrypted file") } - Try.just(decryptedFile) + Try.just(cachedFiles.decryptedFile) } else { // Clear file - Try.just(downloadedFile) + Try.just(cachedFiles.file) } }.fold( { throwable -> callback.onFailure(throwable) // notify concurrent requests val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) + ongoing[url]?.also { + ongoing.remove(url) } } toNotify?.forEach { otherCallbacks -> @@ -195,8 +189,8 @@ internal class DefaultFileService @Inject constructor( callback.onSuccess(file) // notify concurrent requests val toNotify = synchronized(ongoing) { - ongoing[unwrappedUrl]?.also { - ongoing.remove(unwrappedUrl) + ongoing[url]?.also { + ongoing.remove(url) } } Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") @@ -208,6 +202,7 @@ internal class DefaultFileService @Inject constructor( }.toCancelable() } + /* fun storeDataFor(url: String, mimeType: String?, inputStream: InputStream) { val file = File(downloadFolder, fileForUrl(url, mimeType)) val source = inputStream.source().buffer() @@ -219,29 +214,70 @@ internal class DefaultFileService @Inject constructor( } } } + */ - private fun fileForUrl(url: String, mimeType: String?): String { - val extension = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) } - return if (extension != null) "${url.safeFileName()}.$extension" else url.safeFileName() - } - - override fun isFileInCache(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Boolean { - return fileState(mxcUrl, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE - } - - private fun getClearFile(mxcUrl: String, mimeType: String?, elementToDecrypt: ElementToDecrypt?): File { - return if (elementToDecrypt == null) { - // Clear file - File(downloadFolder, fileForUrl(mxcUrl, mimeType)) - } else { - // Encrypted file - File(decryptedFolder, fileForUrl(mxcUrl, mimeType)) + private fun safeFileName(fileName: String, mimeType: String?): String { + return buildString { + // filename has to be safe for the Android System + val result = fileName.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_") + append(result) + // Check that the extension is correct regarding the mimeType + val extensionFromMime = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) } + if (extensionFromMime != null) { + // Compare + val fileExtension = result.substringAfterLast(delimiter = ".", missingDelimiterValue = "") + if (fileExtension.isEmpty() || fileExtension != extensionFromMime) { + // Missing extension, or diff in extension, add the one provided by the mimetype + append(".") + append(extensionFromMime) + } + } } } - override fun fileState(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileService.FileState { + override fun isFileInCache(mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): Boolean { + return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE + } + + internal data class CachedFiles( + // This is the downloaded file. Can be clear or encrypted + val file: File, + // This is the decrypted file. Null if the original file is not encrypted + val decryptedFile: File? + ) { + fun getClearFile(): File = decryptedFile ?: file + } + + private fun getFiles(mxcUrl: String, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): CachedFiles { + val hashFolder = mxcUrl.md5() + val safeFileName = safeFileName(fileName, mimeType) + return if (elementToDecrypt == null) { + // Clear file + CachedFiles( + File(downloadFolder, "$hashFolder/$safeFileName"), + null + ) + } else { + // Encrypted file + CachedFiles( + File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"), + File(decryptedFolder, "$hashFolder/$safeFileName"), + ) + } + } + + override fun fileState(mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): FileService.FileState { mxcUrl ?: return FileService.FileState.UNKNOWN - if (getClearFile(mxcUrl, mimeType, elementToDecrypt).exists()) return FileService.FileState.IN_CACHE + if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).file.exists()) return FileService.FileState.IN_CACHE val isDownloading = synchronized(ongoing) { ongoing[mxcUrl] != null } @@ -252,11 +288,14 @@ internal class DefaultFileService @Inject constructor( * Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION * (if not other app won't be able to access it) */ - override fun getTemporarySharableURI(mxcUrl: String?, mimeType: String?, elementToDecrypt: ElementToDecrypt?): Uri? { + override fun getTemporarySharableURI(mxcUrl: String?, + fileName: String, + mimeType: String?, + elementToDecrypt: ElementToDecrypt?): Uri? { mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" - val targetFile = getClearFile(mxcUrl, mimeType, elementToDecrypt) + val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).getClearFile() if (!targetFile.exists()) return null return FileProvider.getUriForFile(context, authority, targetFile) } @@ -277,4 +316,8 @@ internal class DefaultFileService @Inject constructor( override fun clearDecryptedCache() { decryptedFolder.deleteRecursively() } + + companion object { + private const val ENCRYPTED_FILENAME = "encrypted.bin" + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 4a30d6c1e6..8df5082c33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -199,9 +199,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}") try { + /* TODO context.contentResolver.openInputStream(attachment.queryUri)?.let { fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it) - } + } */ Timber.v("## FileService: cache storage updated") } catch (failure: Throwable) { Timber.e(failure, "## FileService: Failed to update file cache") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt index 4dc54d3b19..fb5e3a5774 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt @@ -25,6 +25,9 @@ import java.io.InputStream */ @WorkerThread fun writeToFile(inputStream: InputStream, outputFile: File) { + // Ensure the parent folder exists, else it will crash + outputFile.parentFile?.mkdirs() + outputFile.outputStream().use { inputStream.copyTo(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 213c50b6ac..34086043da 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -84,6 +84,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_POLL +import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent @@ -204,7 +205,12 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .izLocalFile(fileUrl.isLocalFile()) - .izDownloaded(session.fileService().isFileInCache(fileUrl, messageContent.mimeType, messageContent.encryptedFileInfo?.toElementToDecrypt())) + .izDownloaded(session.fileService().isFileInCache( + fileUrl, + messageContent.getFileName(), + messageContent.mimeType, + messageContent.encryptedFileInfo?.toElementToDecrypt()) + ) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) From 1c43f92e49aae4d21cc28b2b193ccb3f2d1d973c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:20:48 +0100 Subject: [PATCH 092/218] DefaultFileService: store just sent file --- .../internal/session/DefaultFileService.kt | 57 +++++++++++-------- .../session/content/UploadContentWorker.kt | 19 ++++--- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 006ced8530..54ff90631b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -111,7 +111,7 @@ internal class DefaultFileService @Inject constructor( // ensure we use unique file name by using URL (mapped to suitable file name) // Also we need to add extension for the FileProvider, if not it lot's of app that it's // shared with will not function well (even if mime type is passed in the intent) - getFiles(url, fileName, mimeType, elementToDecrypt) + getFiles(url, fileName, mimeType, elementToDecrypt != null) }.flatMap { cachedFiles -> if (!cachedFiles.file.exists()) { val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) @@ -202,24 +202,29 @@ internal class DefaultFileService @Inject constructor( }.toCancelable() } - /* - fun storeDataFor(url: String, mimeType: String?, inputStream: InputStream) { - val file = File(downloadFolder, fileForUrl(url, mimeType)) - val source = inputStream.source().buffer() - file.sink().buffer().let { sink -> - source.use { input -> - sink.use { output -> - output.writeAll(input) - } - } + fun storeDataFor(mxcUrl: String, + filename: String?, + mimeType: String?, + originalFile: File, + encryptedFile: File?) { + val files = getFiles(mxcUrl, filename, mimeType, encryptedFile != null) + if (encryptedFile != null) { + // We switch the two files here, original file it the decrypted file + files.decryptedFile?.let { originalFile.copyTo(it) } + encryptedFile.copyTo(files.file) + } else { + // Just copy the original file + originalFile.copyTo(files.file) } } - */ - private fun safeFileName(fileName: String, mimeType: String?): String { + private fun safeFileName(fileName: String?, mimeType: String?): String { return buildString { // filename has to be safe for the Android System - val result = fileName.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_") + val result = fileName + ?.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_") + ?.takeIf { it.isNotEmpty() } + ?: DEFAULT_FILENAME append(result) // Check that the extension is correct regarding the mimeType val extensionFromMime = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) } @@ -252,23 +257,23 @@ internal class DefaultFileService @Inject constructor( } private fun getFiles(mxcUrl: String, - fileName: String, + fileName: String?, mimeType: String?, - elementToDecrypt: ElementToDecrypt?): CachedFiles { + isEncrypted: Boolean): CachedFiles { val hashFolder = mxcUrl.md5() val safeFileName = safeFileName(fileName, mimeType) - return if (elementToDecrypt == null) { - // Clear file - CachedFiles( - File(downloadFolder, "$hashFolder/$safeFileName"), - null - ) - } else { + return if (isEncrypted) { // Encrypted file CachedFiles( File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"), File(decryptedFolder, "$hashFolder/$safeFileName"), ) + } else { + // Clear file + CachedFiles( + File(downloadFolder, "$hashFolder/$safeFileName"), + null + ) } } @@ -277,7 +282,7 @@ internal class DefaultFileService @Inject constructor( mimeType: String?, elementToDecrypt: ElementToDecrypt?): FileService.FileState { mxcUrl ?: return FileService.FileState.UNKNOWN - if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).file.exists()) return FileService.FileState.IN_CACHE + if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE val isDownloading = synchronized(ongoing) { ongoing[mxcUrl] != null } @@ -295,7 +300,7 @@ internal class DefaultFileService @Inject constructor( mxcUrl ?: return null // this string could be extracted no? val authority = "${context.packageName}.mx-sdk.fileprovider" - val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt).getClearFile() + val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).getClearFile() if (!targetFile.exists()) return null return FileProvider.getUriForFile(context, authority, targetFile) } @@ -319,5 +324,7 @@ internal class DefaultFileService @Inject constructor( companion object { private const val ENCRYPTED_FILENAME = "encrypted.bin" + // The extension would be added from the mimetype + private const val DEFAULT_FILENAME = "file" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 8df5082c33..77f39a7768 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -174,14 +174,15 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } } + val encryptedFile: File? val contentUploadResponse = if (params.isEncrypted) { Timber.v("## FileService: Encrypt file") - val tmpEncrypted = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir) + encryptedFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir) .also { filesToDelete.add(it) } uploadedFileEncryptedFileInfo = - MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), tmpEncrypted) { read, total -> + MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total -> notifyTracker(params) { contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong()) } @@ -190,19 +191,23 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter Timber.v("## FileService: Uploading file") fileUploader - .uploadFile(tmpEncrypted, attachment.name, "application/octet-stream", progressListener) + .uploadFile(encryptedFile, attachment.name, "application/octet-stream", progressListener) } else { Timber.v("## FileService: Clear file") + encryptedFile = null fileUploader .uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener) } Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}") try { - /* TODO - context.contentResolver.openInputStream(attachment.queryUri)?.let { - fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it) - } */ + fileService.storeDataFor( + mxcUrl = contentUploadResponse.contentUri, + filename = params.attachment.name, + mimeType = params.attachment.getSafeMimeType(), + originalFile = workingFile, + encryptedFile = encryptedFile + ) Timber.v("## FileService: cache storage updated") } catch (failure: Throwable) { Timber.e(failure, "## FileService: Failed to update file cache") From 283e10dfefd3a3ead990b1a649bc525d1df3127e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:26:49 +0100 Subject: [PATCH 093/218] Use filename if available --- .../sdk/internal/session/room/send/DefaultSendService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 5a71ff7b76..8828f3dfed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -177,7 +177,7 @@ internal class DefaultSendService @AssistedInject constructor( val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, mimeType = messageContent.info.mimeType!!, - name = messageContent.body, + name = messageContent.getFileName(), queryUri = Uri.parse(messageContent.url), type = ContentAttachmentData.Type.FILE ) From e4968c4119014a1bc6ff796b6b8ccf5a720aa7a9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:27:03 +0100 Subject: [PATCH 094/218] Doc and internal --- .../sdk/internal/session/content/ContentUploadResponse.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt index b5de26b39d..1ebe5b2eb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ContentUploadResponse.kt @@ -20,6 +20,9 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class ContentUploadResponse( +internal data class ContentUploadResponse( + /** + * Required. The MXC URI to the uploaded content. + */ @Json(name = "content_uri") val contentUri: String ) From 0956baecf958bc6db35a26b3af74da8eb4ed8214 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:27:37 +0100 Subject: [PATCH 095/218] Delete unencrypted files each time the app is started --- .../im/vector/app/features/home/HomeActivityViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 680ec17415..90d128320b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -71,12 +71,18 @@ class HomeActivityViewModel @AssistedInject constructor( private var onceTrusted = false init { + cleanupFiles() observeInitialSync() mayBeInitializeCrossSigning() checkSessionPushIsOn() observeCrossSigningReset() } + private fun cleanupFiles() { + // Mitigation: delete all cached decrypted files each time the application is started. + activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache() + } + private fun observeCrossSigningReset() { val safeActiveSession = activeSessionHolder.getSafeActiveSession() ?: return From 4bd538e448a62d2775ef34a6236e988a3c5983ab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 12:49:25 +0100 Subject: [PATCH 096/218] Changelog and update comment --- CHANGES.md | 1 + .../matrix/android/sdk/internal/session/DefaultFileService.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7ba7b7cb76..7904834e62 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Changes in Element 1.0.12 (2020-XX-XX) Features ✨: - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) - Room setting: update join rules and guest access (#2442) + - Store encrypted file in cache and cleanup decrypted file at each app start Improvements 🙌: - Add Setting Item to Change PIN (#2462) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 54ff90631b..730c0dd82f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -57,9 +57,11 @@ internal class DefaultFileService @Inject constructor( private val taskExecutor: TaskExecutor ) : FileService { - // Folder to store downloaded file (not decrypted) + // Legacy folder, will be deleted private val legacyFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded files (not decrypted) private val downloadFolder = File(sessionCacheDirectory, "F") + // Folder to store decrypted files private val decryptedFolder = File(downloadFolder, "D") init { From 75071cf1d9ab15ee715df1697e1faedc1cc41e81 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Dec 2020 13:50:14 +0100 Subject: [PATCH 097/218] Cleanup --- .../internal/network/interceptors/FormattedJsonHttpLogger.kt | 1 - .../matrix/android/sdk/internal/session/DefaultFileService.kt | 2 +- .../vector/app/features/home/room/detail/RoomDetailFragment.kt | 2 -- .../vector/app/features/home/room/detail/RoomDetailViewModel.kt | 2 -- .../app/features/roomprofile/uploads/RoomUploadsViewModel.kt | 2 -- 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 849a464867..34ed28d467 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.network.interceptors import androidx.annotation.NonNull -import org.matrix.android.sdk.BuildConfig import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONArray import org.json.JSONException diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 730c0dd82f..ee4f5da41e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -268,7 +268,7 @@ internal class DefaultFileService @Inject constructor( // Encrypted file CachedFiles( File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"), - File(decryptedFolder, "$hashFolder/$safeFileName"), + File(decryptedFolder, "$hashFolder/$safeFileName") ) } else { // Clear file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index a229f72755..f8168140a3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -184,7 +184,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -193,7 +192,6 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 86f22a55ad..182ee6016d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -79,7 +79,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.OptionItem -import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -91,7 +90,6 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.util.awaitCallback diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index bd37cecd56..bf2b56fc9b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -31,8 +31,6 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap From d54571d0a6a768997327c053d7231a8ba1adcc54 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 9 Dec 2020 18:45:33 +0300 Subject: [PATCH 098/218] Emoji library added with Google style. --- vector/build.gradle | 4 ++++ vector/src/main/java/im/vector/app/VectorApplication.kt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index 62ff3951af..28d8fe5c1b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -440,6 +440,10 @@ dependencies { implementation 'com.google.zxing:core:3.3.3' implementation 'me.dm7.barcodescanner:zxing:1.9.13' + // Emoji Keyboard + implementation 'com.vanniktech:emoji-material:0.7.0' + implementation 'com.vanniktech:emoji-google:0.7.0' + // TESTS testImplementation 'junit:junit:4.13' testImplementation "org.amshove.kluent:kluent-android:$kluent_version" diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 5be313d719..921e8c0780 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -36,6 +36,8 @@ import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController import com.facebook.stetho.Stetho import com.gabrielittner.threetenbp.LazyThreeTen +import com.vanniktech.emoji.EmojiManager +import com.vanniktech.emoji.google.GoogleEmojiProvider import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.DaggerVectorComponent import im.vector.app.core.di.HasVectorInjector @@ -184,6 +186,8 @@ class VectorApplication : addAction(Intent.ACTION_SCREEN_OFF) addAction(Intent.ACTION_SCREEN_ON) }) + + EmojiManager.install(GoogleEmojiProvider()) } private fun enableStrictModeIfNeeded() { From 3d975b7fba2716fa385429a82eefc7200f06df62 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 9 Dec 2020 18:46:33 +0300 Subject: [PATCH 099/218] Update composer icons. --- vector/src/main/res/drawable/bg_send.xml | 6 +++++ .../src/main/res/drawable/ic_attachment.xml | 26 ++++++++++++++----- .../src/main/res/drawable/ic_insert_emoji.xml | 21 +++++++++++++++ vector/src/main/res/drawable/ic_keyboard.xml | 4 +++ 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/res/drawable/bg_send.xml create mode 100644 vector/src/main/res/drawable/ic_insert_emoji.xml create mode 100644 vector/src/main/res/drawable/ic_keyboard.xml diff --git a/vector/src/main/res/drawable/bg_send.xml b/vector/src/main/res/drawable/bg_send.xml new file mode 100644 index 0000000000..6b12664032 --- /dev/null +++ b/vector/src/main/res/drawable/bg_send.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_attachment.xml b/vector/src/main/res/drawable/ic_attachment.xml index 944998c1cd..ea01e94372 100644 --- a/vector/src/main/res/drawable/ic_attachment.xml +++ b/vector/src/main/res/drawable/ic_attachment.xml @@ -1,7 +1,21 @@ - - + + + + diff --git a/vector/src/main/res/drawable/ic_insert_emoji.xml b/vector/src/main/res/drawable/ic_insert_emoji.xml new file mode 100644 index 0000000000..c35000342b --- /dev/null +++ b/vector/src/main/res/drawable/ic_insert_emoji.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_keyboard.xml b/vector/src/main/res/drawable/ic_keyboard.xml new file mode 100644 index 0000000000..5e5d431abb --- /dev/null +++ b/vector/src/main/res/drawable/ic_keyboard.xml @@ -0,0 +1,4 @@ + + + From a96cc19eb600aa8857ec46d82fa87f06059834fd Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 9 Dec 2020 18:47:34 +0300 Subject: [PATCH 100/218] Update composer layout by adding emoji icon. --- ...constraint_set_composer_layout_compact.xml | 69 ++++++++--------- ...onstraint_set_composer_layout_expanded.xml | 76 +++++++++---------- .../main/res/layout/merge_composer_layout.xml | 18 ++--- 3 files changed, 78 insertions(+), 85 deletions(-) diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml index ac67db6a64..6b9fbd4885 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml @@ -77,9 +77,9 @@ android:alpha="0" app:layout_constraintEnd_toStartOf="parent" app:layout_constraintTop_toBottomOf="parent" + app:tint="?riotx_text_primary" tools:ignore="MissingConstraints,MissingPrefix" - tools:src="@drawable/ic_edit" - app:tint="?riotx_text_primary" /> + tools:src="@drawable/ic_edit" /> + app:tint="@color/riotx_notice" + tools:ignore="MissingPrefix" + tools:visibility="visible" /> - - + tools:ignore="MissingPrefix" /> - + tools:visibility="visible" /> + app:layout_constraintTop_toTopOf="@id/sendButton" + app:layout_goneMarginEnd="8dp" /> diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml b/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml index dba996309e..f52b072ece 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml @@ -83,9 +83,9 @@ app:layout_constraintEnd_toEndOf="@id/composer_related_message_avatar_view" app:layout_constraintStart_toStartOf="@id/composer_related_message_avatar_view" app:layout_constraintTop_toBottomOf="@id/composer_related_message_avatar_view" - tools:src="@drawable/ic_edit" app:tint="?riotx_text_primary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_edit" /> - - - - - - - - + + + + diff --git a/vector/src/main/res/layout/merge_composer_layout.xml b/vector/src/main/res/layout/merge_composer_layout.xml index 908b3f009b..ea2bc1bf30 100644 --- a/vector/src/main/res/layout/merge_composer_layout.xml +++ b/vector/src/main/res/layout/merge_composer_layout.xml @@ -70,8 +70,8 @@ android:id="@+id/composer_related_message_action_image" android:layout_width="0dp" android:layout_height="0dp" - tools:ignore="MissingConstraints,MissingPrefix" - app:tint="?riotx_text_primary" /> + app:tint="?riotx_text_primary" + tools:ignore="MissingConstraints,MissingPrefix" /> - - + android:background="?android:attr/selectableItemBackground" + android:src="@drawable/ic_insert_emoji" + tools:ignore="MissingConstraints" /> Date: Wed, 9 Dec 2020 18:48:14 +0300 Subject: [PATCH 101/218] Add emoji keyboard, remove profile picture from composer. --- .../home/room/detail/RoomDetailFragment.kt | 24 ++++++++++++++++--- .../room/detail/composer/ComposerEditText.kt | 4 ++-- .../room/detail/composer/TextComposerView.kt | 4 ++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 3f5e476a5e..f880b632d4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -69,6 +69,7 @@ import com.airbnb.mvrx.withState import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText import com.jakewharton.rxbinding3.widget.textChanges +import com.vanniktech.emoji.EmojiPopup import im.vector.app.R import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper @@ -296,6 +297,8 @@ class RoomDetailFragment @Inject constructor( private var lockSendButton = false private val activeCallViewHolder = ActiveCallViewHolder() + private lateinit var emojiPopup: EmojiPopup + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) @@ -311,6 +314,7 @@ class RoomDetailFragment @Inject constructor( setupActiveCallView() setupJumpToBottomView() setupConfBannerView() + setupEmojiPopup() roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) @@ -478,6 +482,23 @@ class RoomDetailFragment @Inject constructor( } } + private fun setupEmojiPopup() { + EmojiPopup + .Builder + .fromRootView(rootConstraintLayout) + .setKeyboardAnimationStyle(R.style.emoji_fade_animation_style) + .setOnEmojiPopupShownListener { composerLayout.composerEmojiButton.setImageResource(R.drawable.ic_keyboard) } + .setOnEmojiPopupDismissListener { composerLayout.composerEmojiButton.setImageResource(R.drawable.ic_insert_emoji) } + .build(composerLayout.composerEditText) + .also { + emojiPopup = it + } + + composerLayout.composerEmojiButton.debouncedClicks { + emojiPopup.toggle() + } + } + private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) { navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo)) } @@ -1211,9 +1232,6 @@ class RoomDetailFragment @Inject constructor( scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline timelineEventController.update(state) inviteView.visibility = View.GONE - val uid = session.myUserId - val meMember = state.myRoomMember() - avatarRenderer.render(MatrixItem.UserItem(uid, meMember?.displayName, meMember?.avatarUrl), composerLayout.composerAvatarImageView) if (state.tombstoneEvent == null) { if (state.canSendMessage) { composerLayout.visibility = View.VISIBLE diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt index 353ab783db..2257e5ee81 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt @@ -24,16 +24,16 @@ import android.text.Editable import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection -import androidx.appcompat.widget.AppCompatEditText import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat +import com.vanniktech.emoji.EmojiEditText import im.vector.app.core.extensions.ooi import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.features.html.PillImageSpan import timber.log.Timber class ComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle) - : AppCompatEditText(context, attrs, defStyleAttr) { + : EmojiEditText(context, attrs, defStyleAttr) { interface Callback { fun onRichContentSelected(contentUri: Uri): Boolean diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt index f4b14571c0..af0e1a91f0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt @@ -72,8 +72,8 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib @BindView(R.id.composerEditText) lateinit var composerEditText: ComposerEditText - @BindView(R.id.composer_avatar_view) - lateinit var composerAvatarImageView: ImageView + @BindView(R.id.composer_emoji) + lateinit var composerEmojiButton: ImageButton @BindView(R.id.composer_shield) lateinit var composerShieldImageView: ImageView From 7ba11163497208f1ab86356973af63686428eee5 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Wed, 9 Dec 2020 15:32:56 +0000 Subject: [PATCH 102/218] Translated using Weblate (German) Currently translated at 98.1% (1949 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 630593f214..bcb33267a5 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2249,4 +2249,20 @@ Geschichte der Anfragen von Schlüsselfreigaben senden Keine weiteren Ergebnisse Starte die Diskussion + Neue direkte Konversation mit Matrix-ID erstellen + E-Mails und Telefonnummern senden + Autorisieren + Meine Zustimmung widerrufen + Du hast zugestimmt E-Mails und Telefonnummern an diesen Identitätsserver zu senden, um von anderen Nutzenden entdeckt zu werden. + Du hast nicht zugestimmt E-Mails und Telefonnummern an diesen Identitätsserver zu senden, um von anderen Nutzenden entdeckt zu werden. + E-Mails und Telefonnummern senden + Vorschläge + Kontakte + Bekannte Nutzer/innen + Kürzlich + QR-Code + Hinzufügen via QR-Code + Nach Name oder ID suchen + Gib die Erlaubnis, um auf die Kamera zu zugreifen. + Um den QR-Code zu scannen, muss der Zugriff auf die Kamera erlaubt werden. \ No newline at end of file From a19ca8a8205232409f7fdc10f1a287209e1c993b Mon Sep 17 00:00:00 2001 From: Mitja Sorsa Date: Wed, 9 Dec 2020 04:48:39 +0000 Subject: [PATCH 103/218] Translated using Weblate (Finnish) Currently translated at 80.2% (1593 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- vector/src/main/res/values-fi/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index bb382c6820..7aa069ba9a 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -1840,7 +1840,7 @@ Alenna Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä huoneessa, oikeuksia ei voi enää saada takaisin. Alenna itsesi\? - Peru kutsu + Peruuta kutsu Anna lupa hakea yhteystiedot. QR-koodin skannaaminen vaatii luvan kameran käyttöön. Palaa puheluun @@ -1868,4 +1868,5 @@ Sovelman lisääminen epäonnistui Et voi aloittaa puhelua itsesi kanssa. Odota, että muut osallistujat hyväksyvät kutsun Et voi aloittaa puhelua itsesi kanssa + Peruuta kutsu \ No newline at end of file From 280d3f22a745637e517c9dea903e3d6887ee9c98 Mon Sep 17 00:00:00 2001 From: Uumas Date: Wed, 9 Dec 2020 04:48:32 +0000 Subject: [PATCH 104/218] Translated using Weblate (Finnish) Currently translated at 80.2% (1593 of 1986 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- vector/src/main/res/values-fi/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 7aa069ba9a..adbbb22f42 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -1869,4 +1869,10 @@ Et voi aloittaa puhelua itsesi kanssa. Odota, että muut osallistujat hyväksyvät kutsun Et voi aloittaa puhelua itsesi kanssa Peruuta kutsu + Korkea laatu päälle + Korkea laatu pois + Reaaliaikaisen yhteyden muodostus epäonnistui. +\nPyydäthän kotipalvelimesi ylläpitäjää asentamaan TURN-palvelimen varmistaaksesi puhelujen luotettavan toiminnan. + Vastaa + Aloita keskustelu \ No newline at end of file From c2fc9fe0ee29dd17ada7bb3ded1410312fe78e8c Mon Sep 17 00:00:00 2001 From: Mitja Sorsa Date: Wed, 9 Dec 2020 16:11:58 +0000 Subject: [PATCH 105/218] Translated using Weblate (Finnish) Currently translated at 73.8% (155 of 210 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/fi/ --- .../src/main/res/values-fi/strings.xml | 132 ++++++++++++++---- 1 file changed, 101 insertions(+), 31 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-fi/strings.xml b/matrix-sdk-android/src/main/res/values-fi/strings.xml index fccd22d3b6..1e3788476f 100644 --- a/matrix-sdk-android/src/main/res/values-fi/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fi/strings.xml @@ -1,7 +1,6 @@ - + %1$s lähetti kuvan. - Käyttäjän %s kutsu %1$s kutsui käyttäjän %2$s %1$s kutsui sinut @@ -29,11 +28,9 @@ kaikki. tuntematon (%s). %1$s otti käyttöön osapuolten välisen salauksen (%2$s) - %1$s lähetti VoIP-konferenssipyynnön VoIP-konferenssi alkoi VoIP-konferenssi päättyi - (myös kuva vaihdettiin) %1$s poisti huoneen nimen %1$s poisti huoneen aiheen @@ -42,44 +39,31 @@ %1$s hyväksyi kutsun käyttäjän %2$s puolesta ** Salauksen purku epäonnistui: %s ** Lähettäjän laite ei ole lähettänyt avaimia tähän viestiin. - Viestin lähetys epäonnistui - Kuvan lataaminen epäonnistui - Verkkovirhe Matrix-virhe - Tällä hetkellä ei ole mahdollista liittyä uudelleen tyhjään huoneeseen. - Salattu viesti - Sähköpostiosoite Puhelinnumero - - Takaisinveto epäonnistui %1$s: %2$s - - Kutsu käyttäjältä %s + Kutsu käyttäjältä %s + Huonekutsu %1$s ja %2$s Tyhjä huone - - %1$s lähetti tarran. - %1$s ja yksi muu %1$s ja %2$d muuta - Viesti poistettu %1$s poisti viestin Viesti poistettu [syy: %1$s] %1$s poisti viestin [syy: %2$s] - Alkusynkronointi: \nTuodaan tiliä… Alkusynkronointi: @@ -96,12 +80,9 @@ \nTuodaan yhteisöjä Alkusynkronointi: \nTuodaan tilin tietoja - %s päivitti tämän huoneen. - Lähetetään viestiä… Tyhjennä lähetysjono - %1$s veti takaisin käyttäjän %2$s liittymiskutsun huoneeseen Henkilön %1$s kutsu. Syy: %2$s %1$s kutsui henkilön %2$s. Syy: %3$s @@ -116,28 +97,117 @@ %1$s kumosi kutsun liittyä huoneeseen käyttäjälle %2$s. Syy: %3$s %1$s hyväksyi kutsun liityäkseen huoneeseen %2$s. Syy: %3$s %1$s veti takaisin käyttäjän %2$s kutsun. Syy: %3$s - %1$s lisäsi tälle huoneelle osoitteen %2$s. %1$s lisäsi tälle huoneelle osoitteet %2$s. - %1$s poisti tältä huoneelta osoitteen %2$s. %1$s poisti tältä huoneelta osoitteet %3$s. - %1$s lisäsi tälle huoneelle osoitteen %2$s ja poisti osoitteen %3$s. - %1$s asetti tämän huoneen pääosoitteeksi %2$s. %1$s poisti tämän huoneen pääosoitteen. - %1$s salli vieraiden liittyä huoneeseen. %1$s esti vieraita liittymästä huoneeseen. - %1$s laittoi päälle osapuolten välisen salauksen. %1$s laittoi päälle osapuolisten välisen salauksen (tuntematon algoritmi %2$s). - %s haluaa varmentaa salausavaimesi, mutta asiakasohjelmasi ei tue keskustelun aikana tapahtuvaa avainten varmennusta. Joudut käyttämään perinteistä varmennustapaa. - - + Hyväksyit käyttäjän %1$s kutsun. Syy: %2$s + Peruutit kutsun liittyä huoneeseen käyttäjältä %1$s. Syy: %2$s + Lähetit kutsun liittyä huoneeseen käyttäjälle %1$s. Syy: %2$s + Estit käyttäjän %1$s. Syy: %2$s + Peruutit eston %1$s. Syy: %2$s + Poistit käyttäjän %1$s. Syy: %2$s + Hylkäsit kutsun. Syy: %1$s + Lähdit. Syy: %1$s + %1$s lähti. Syy: %2$s + Poistuit huoneesta. Syy: %1$s + Liityit. Syy: %1$s + %1$s liittyi. Syy: %2$s + Liityit ryhmään. Syy: %1$s + Kutsuit %1$s. Syy: %2$s + Kutsusi. Syy: %1$s + Tyhjä huone (oli %s) + %1$s, %2$s, %3$s ja %4$s + %1$s, %2$s ja %3$s + Mukautettu + Mukautettu (%1$d) + Oletus + Valvoja + Ylläpitäjä + %1$s muutti %2$s sovelmaa + Poistit %1$s sovelman + %1$s poisti %2$s sovelman + Lisäsit %1$s sovelman + %1$s lisäsi %2$s sovelman + Muutit %1$s sovelmaa + Hyväksyit kutsun henkilölle %1$s + Peruutit kutsun henkilöltä %1$s + %1$s peruutti kutsun henkilöltä %2$s + Peruutit henkilön %1$s kutsun liittyä ryhmään + Kutsuit %1$s + %1$s kutsui %2$s + Lähetit henkilölle %1$s kutsun liittyä huoneeseen + Päivitit profiilisi %1$s + Poistit huoneen profiilikuvan + %1$s poisti huoneen profiilikuvan + Poistit huoneen aiheen + Poistit huoneen nimen + Pyysit ryhmäpuhelua + 🎉 Kaikki palvelimet on estetty osallistumasta! Tätä huonetta ei voi enää käyttää. + Ei muutosta. + • Palvelimet jotka %s poistettiin estolistalta. + • Palvelimen haku %s on nyt kielletty. + • Palvelimen haku %s on sallittu. + • Palvelimen haku %s on kielletty. + %1$s on estänyt vieraita liittymästä huoneeseen. + Estit vieraita liittymästä huoneeseen. + Annoit vieraille luvan liittyä huoneeseen. + Annoit vieraille luvan liittyä tänne. + %1$s on antanut vieraille luvan liittyä tänne. + Poistit tämän huoneen pääosoitteen. + Otit käyttöön päästä päähän -salauksen. + Olet estänyt vieraiden liittymisen huoneeseen. + Otit päästä päähän -salauksen käyttöön (tuntematon algoritmi %1$s). + Päivitit tässä. + %s päivitti täällä. + Päivitit tämän huoneen. + Otit päästä päähän -salauksen käyttöön (%1$s) + Teit tulevista viesteistä näkyviä käyttäjälle %1$s + %1$s teki tulevista viesteistä näkyviä käyttäjälle %2$s + Teit tulevan huonehistorian näkyväksi %1$s + Lopetit puhelun. + Vastasit puheluun. + Lähetit tietoja puhelun valmistelemiseksi. + %s lähetti tietoja puhelun valmistelemiseksi. + Aloitit äänipuhelun. + Aloitit videopuhelun. + Vaihdoit huoneen nimeksi: %1$s + Vaihdoit huoneen profiilikuvaa + %1$s muutti huoneen profiilikuvaa + Vaihdoit aiheen: %1$s + Poistit nimimerkkisi (se oli %1$s) + Vaihdoit nimimerkkisi %1$s nimeen %2$s + Asetit nimimerkiksesi %1$s + Kutsusi + Vaihdoit profiilikuvaasi + Peruutit %1$sn kutsun + Estit %1$s + Poistit eston %1$s + Poistit %1$s + Hylkäsit kutsun + Poistuit huoneesta + %1$s poistui huoneesta + Poistuit huoneesta + Liityit + %1$s liittyi + Liityit huoneeseen + Kutsuit %1$s + Loit keskustelun + %1$s loi keskustelun + Loit huoneen + %1$s loi huoneen + Lähetit tarran. + Lähetit kuvan. + \ No newline at end of file From 5186ee6e43a0930fdc4f6ba741c13065e07a5256 Mon Sep 17 00:00:00 2001 From: Mitja Sorsa Date: Wed, 9 Dec 2020 13:49:28 +0000 Subject: [PATCH 106/218] Translated using Weblate (Finnish) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fi/ --- .../android/fi/changelogs/40100100.txt | 2 ++ .../metadata/android/fi/full_description.txt | 30 +++++++++++++++++++ .../metadata/android/fi/short_description.txt | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/fi/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/fi/full_description.txt diff --git a/fastlane/metadata/android/fi/changelogs/40100100.txt b/fastlane/metadata/android/fi/changelogs/40100100.txt new file mode 100644 index 0000000000..0717ff27d7 --- /dev/null +++ b/fastlane/metadata/android/fi/changelogs/40100100.txt @@ -0,0 +1,2 @@ +Tämä versio sisältää virheenkorjauksia ja muita parannuksia. Viestien lähettäminen on nyt paljon nopeampaa. +Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/fi/full_description.txt b/fastlane/metadata/android/fi/full_description.txt new file mode 100644 index 0000000000..70def518da --- /dev/null +++ b/fastlane/metadata/android/fi/full_description.txt @@ -0,0 +1,30 @@ +Element on uudenlainen viestinsovellus, joka: + +1. Antaa sinun päättää yksityisyydestäsi. +2. Antaa sinun kommunikoida kenen tahansa kanssa Matrix-verkossa ja jopa sen ulkopuolella siltaamalla sovelluksiin, kuten Slack +3. Suojaa sinua mainonnalta, tietojen keräämiseltä ja suljetuilta alustoilta +4. Suojaa sinut päästä päähän -salauksella sekä ristiin varmentamisella muiden todentamiseksi + +Element eroaa täysin muista viestintäsovelluksista, koska se on hajautettu ja avointa lähdekoodia. + +Element antaa sinun isännöidä itse - valita isännän - jotta sinulla on yksityisyys ja voit hallita tietojasi sekä keskustelujasi. Se antaa sinulle pääsyn avoimeen verkkoon; joten et ole jumissa Elementin käyttäjissä. + +Element pystyy tekemään kaiken tämän, koska se toimii Matrixilla - avoimella, hajautetun viestinnän standardilla. + +Element antaa sinulle hallinnan antamalla sinun valita, kuka isännöi keskustelujasi. Element-sovelluksessa voit valita isännän eri tavoin: + +1. Hanki ilmainen tili Matrix-kehittäjien ylläpitämällä matrix.org-palvelimella tai valitse tuhansista vapaaehtoisten ylläpitämistä julkisista palvelimista. +2. Isännöi tiliäsi itse suorittamalla palvelinta omalla laitteellasi +3. Luo tili mukautetulla palvelimella yksinkertaisesti tilaamalla Element Matrix Services -palvelu + +Miksi valita Element? + +OMAT TIEDOT: Sinä päätät, missä tietosi ja viestisi säilytetään. Hallitset sitä itse, eikä jokin MEGAYHTIÖ, joka tutkii tietojasi tai antaa niitä kolmansille osapuolille. + +AVOIN KOMMUNIKOINYI JA YHTEISTYÖ: Voit keskustella kaikkien muiden Matrix-verkon käyttäjien kanssa, riippumatta siitä käyttävätkö he Elementiä tai muuta Matrix-sovellusta, ja vaikka he käyttäisivät eri viestijärjestelmiä, kuten Slack, IRC tai XMPP. + +ERITTÄIN TURVALLINEN: Vahva päästä päähän -salaus (vain keskustelussa olevat voivat purkaa viestien salauksen), ja ristiin varmentaminen keskustelun osallistujien laitteiden tarkistamiseksi. + +TÄYDELLISTÄ VIESTINTÄÄ: Viestit, ääni- ja videopuhelut, tiedostojen jakaminen, näytön jakaminen ja koko joukko integraatioita, botteja ja widgettejä. Rakenna huoneita, yhteisöjä, pidä yhteyttä ja tee asioita. + +MISSÄ TAHANSA OLETKIN: Pidä yhteyttä missä tahansa, täysin synkronoidun viestihistorian kautta kaikilla laitteillasi ja verkossa osoitteessa https://app.element.io. diff --git a/fastlane/metadata/android/fi/short_description.txt b/fastlane/metadata/android/fi/short_description.txt index 64f35a7dff..5573ac1cb9 100644 --- a/fastlane/metadata/android/fi/short_description.txt +++ b/fastlane/metadata/android/fi/short_description.txt @@ -1 +1 @@ -Turvallista, hajautettua keskustelua ja VoIP-puheluita. Pidä tietosi turvassa. +Turvallista, hajautettua, keskusteluja ja VoIP-puheluita. Pidä tietosi turvassa. From 08964d8548bbf1544885e950b5bcdf73b724a6d8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 10 Dec 2020 12:51:15 +0300 Subject: [PATCH 107/218] Fix emoji keyboard orientation bug. --- .../home/room/detail/RoomDetailFragment.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f880b632d4..bc0f5ce6dc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -53,7 +53,6 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import butterknife.BindView import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.addGlidePreloader @@ -290,15 +289,11 @@ class RoomDetailFragment @Inject constructor( private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var keyboardStateUtils: KeyboardStateUtils - @BindView(R.id.composerLayout) - lateinit var composerLayout: TextComposerView private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView private var lockSendButton = false private val activeCallViewHolder = ActiveCallViewHolder() - private lateinit var emojiPopup: EmojiPopup - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) @@ -483,16 +478,13 @@ class RoomDetailFragment @Inject constructor( } private fun setupEmojiPopup() { - EmojiPopup + val emojiPopup = EmojiPopup .Builder .fromRootView(rootConstraintLayout) .setKeyboardAnimationStyle(R.style.emoji_fade_animation_style) - .setOnEmojiPopupShownListener { composerLayout.composerEmojiButton.setImageResource(R.drawable.ic_keyboard) } - .setOnEmojiPopupDismissListener { composerLayout.composerEmojiButton.setImageResource(R.drawable.ic_insert_emoji) } + .setOnEmojiPopupShownListener { composerLayout?.composerEmojiButton?.setImageResource(R.drawable.ic_keyboard) } + .setOnEmojiPopupDismissListener { composerLayout?.composerEmojiButton?.setImageResource(R.drawable.ic_insert_emoji) } .build(composerLayout.composerEditText) - .also { - emojiPopup = it - } composerLayout.composerEmojiButton.debouncedClicks { emojiPopup.toggle() From a9f5ed3869e412136f89a8d66d5cf54781c774d9 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 10 Dec 2020 12:56:09 +0300 Subject: [PATCH 108/218] Add emoji license. --- vector/src/main/assets/open_source_licenses.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index ac80b0d691..acf0bec14f 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -347,11 +347,6 @@ SOFTWARE.
Copyright 2017 Gabriel Ittner. -
  • - Android-multipicker-library -
    - Copyright 2018 Kumar Bibek -
  • htmlcompressor
    @@ -390,6 +385,11 @@ SOFTWARE.
    Copyright 2018, Aleksandr Nikiforov
  • +
  • + Emoji +
    + Copyright (C) 2016 - Niklas Baudy, Ruben Gees, Mario Đanić and contributors +
  •  Apache License
    
    From 4007982db6df660bd9d3e9bc401249504f773f77 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 10 Dec 2020 12:59:09 +0300
    Subject: [PATCH 109/218] Changelog added.
    
    ---
     CHANGES.md | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 1005040328..1381a1e7ca 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -4,6 +4,7 @@ Changes in Element 1.0.12 (2020-XX-XX)
     Features ✨:
      - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428)
      - Room setting: update join rules and guest access (#2442)
    + - Emoji Keyboard (#2520)
     
     Improvements 🙌:
      - Add Setting Item to Change PIN (#2462)
    
    From c1cb23d728296cf71c3b97f0bd67f0d548603b54 Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 10 Dec 2020 13:12:19 +0300
    Subject: [PATCH 110/218] Fix ripple effect of the send button.
    
    ---
     vector/src/main/res/drawable/bg_send.xml | 13 ++++++++-----
     1 file changed, 8 insertions(+), 5 deletions(-)
    
    diff --git a/vector/src/main/res/drawable/bg_send.xml b/vector/src/main/res/drawable/bg_send.xml
    index 6b12664032..4b357d7ab1 100644
    --- a/vector/src/main/res/drawable/bg_send.xml
    +++ b/vector/src/main/res/drawable/bg_send.xml
    @@ -1,6 +1,9 @@
     
    -
    -
    -    
    -
    \ No newline at end of file
    +
    +    
    +        
    +            
    +        
    +    
    +    
    +
    \ No newline at end of file
    
    From e6949c85fb255062cb009d955d2f8281538c1a06 Mon Sep 17 00:00:00 2001
    From: Jadran Prodan 
    Date: Thu, 10 Dec 2020 10:28:31 +0000
    Subject: [PATCH 111/218] Added translation using Weblate (Slovenian)
    
    ---
     vector/src/main/res/values-sl/strings.xml | 2 ++
     1 file changed, 2 insertions(+)
     create mode 100644 vector/src/main/res/values-sl/strings.xml
    
    diff --git a/vector/src/main/res/values-sl/strings.xml b/vector/src/main/res/values-sl/strings.xml
    new file mode 100644
    index 0000000000..a6b3daec93
    --- /dev/null
    +++ b/vector/src/main/res/values-sl/strings.xml
    @@ -0,0 +1,2 @@
    +
    +
    \ No newline at end of file
    
    From 752bde413ddc223313ebd5156a3e56736c3121ec Mon Sep 17 00:00:00 2001
    From: Onuray Sahin 
    Date: Thu, 10 Dec 2020 13:47:23 +0300
    Subject: [PATCH 112/218] Fix copyright.
    
    ---
     .../android/sdk/api/session/room/timeline/EventTypeFilter.kt    | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt
    index f2f6929fdd..18faa6a452 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/EventTypeFilter.kt
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2020 New Vector Ltd
    + * Copyright 2020 The Matrix.org Foundation C.I.C.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    
    From 5e2f091ec105e0018e97f086b6d124eaba8ec3b3 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Thu, 10 Dec 2020 13:36:00 +0100
    Subject: [PATCH 113/218] Remove useless parameter `id`
    
    ---
     .../sdk/api/session/file/FileService.kt       | 19 +++++++------------
     .../internal/session/DefaultFileService.kt    |  3 +--
     .../app/core/glide/VectorGlideModelLoader.kt  |  5 ++---
     .../home/room/detail/RoomDetailFragment.kt    |  2 --
     .../home/room/detail/RoomDetailViewModel.kt   |  1 -
     .../features/media/BaseAttachmentProvider.kt  |  5 ++---
     .../media/DataAttachmentRoomProvider.kt       |  3 +--
     .../media/RoomEventsAttachmentProvider.kt     |  1 -
     .../features/media/VideoContentRenderer.kt    |  2 --
     .../uploads/RoomUploadsViewModel.kt           |  2 --
     10 files changed, 13 insertions(+), 30 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
    index d0f53f25de..bcdb5ea257 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
    @@ -41,20 +41,15 @@ interface FileService {
          * Download a file.
          * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
          */
    -    fun downloadFile(
    -            id: String,
    -            fileName: String,
    -            mimeType: String?,
    -            url: String?,
    -            elementToDecrypt: ElementToDecrypt?,
    -            callback: MatrixCallback): Cancelable
    +    fun downloadFile(fileName: String,
    +                     mimeType: String?,
    +                     url: String?,
    +                     elementToDecrypt: ElementToDecrypt?,
    +                     callback: MatrixCallback): Cancelable
     
    -    fun downloadFile(
    -            id: String,
    -            messageContent: MessageWithAttachmentContent,
    -            callback: MatrixCallback): Cancelable =
    +    fun downloadFile(messageContent: MessageWithAttachmentContent,
    +                     callback: MatrixCallback): Cancelable =
                 downloadFile(
    -                    id = id,
                         fileName = messageContent.getFileName(),
                         mimeType = messageContent.mimeType,
                         url = messageContent.getFileUrl(),
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
    index ee4f5da41e..07cde3da60 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
    @@ -79,8 +79,7 @@ internal class DefaultFileService @Inject constructor(
          * Download file in the cache folder, and eventually decrypt it
          * TODO looks like files are copied 3 times
          */
    -    override fun downloadFile(id: String,
    -                              fileName: String,
    +    override fun downloadFile(fileName: String,
                                   mimeType: String?,
                                   url: String?,
                                   elementToDecrypt: ElementToDecrypt?,
    diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
    index cf40926ba4..9a7cf1eb76 100644
    --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
    +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
    @@ -109,10 +109,9 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde
             }
             // Use the file vector service, will avoid flickering and redownload after upload
             fileService.downloadFile(
    -                mimeType = data.mimeType,
    -                id = data.eventId,
    -                url = data.url,
                     fileName = data.filename,
    +                mimeType = data.mimeType,
    +                url = data.url,
                     elementToDecrypt = data.elementToDecrypt,
                     callback = object : MatrixCallback {
                         override fun onSuccess(data: File) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    index f8168140a3..f1ae79a0aa 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    @@ -1654,7 +1654,6 @@ class RoomDetailFragment @Inject constructor(
                 shareText(requireContext(), action.messageContent.body)
             } else if (action.messageContent is MessageWithAttachmentContent) {
                 session.fileService().downloadFile(
    -                    id = action.eventId,
                         messageContent = action.messageContent,
                         callback = object : MatrixCallback {
                             override fun onSuccess(data: File) {
    @@ -1685,7 +1684,6 @@ class RoomDetailFragment @Inject constructor(
                 return
             }
             session.fileService().downloadFile(
    -                id = action.eventId,
                     messageContent = action.messageContent,
                     callback = object : MatrixCallback {
                         override fun onSuccess(data: File) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    index 182ee6016d..7bba9728ca 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    @@ -1030,7 +1030,6 @@ class RoomDetailViewModel @AssistedInject constructor(
                 }
             } else {
                 session.fileService().downloadFile(
    -                    id = action.eventId,
                         messageContent = action.messageFileContent,
                         callback = object : MatrixCallback {
                             override fun onSuccess(data: File) {
    diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt
    index 5f61ca36e4..90b17f80d7 100644
    --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt
    @@ -153,11 +153,10 @@ abstract class BaseAttachmentProvider(
             } else {
                 target.onVideoFileLoading(info.uid)
                 fileService.downloadFile(
    -                    id = data.eventId,
    -                    mimeType = data.mimeType,
    -                    elementToDecrypt = data.elementToDecrypt,
                         fileName = data.filename,
    +                    mimeType = data.mimeType,
                         url = data.url,
    +                    elementToDecrypt = data.elementToDecrypt,
                         callback = object : MatrixCallback {
                             override fun onSuccess(data: File) {
                                 target.onVideoFileReady(info.uid, data)
    diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt
    index 6f58c1a4f3..584b13f32b 100644
    --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt
    @@ -77,10 +77,9 @@ class DataAttachmentRoomProvider(
         override fun getFileForSharing(position: Int, callback: (File?) -> Unit) {
             val item = getItem(position)
             fileService.downloadFile(
    -                id = item.eventId,
                     fileName = item.filename,
                     mimeType = item.mimeType,
    -                url = item.url ?: "",
    +                url = item.url,
                     elementToDecrypt = item.elementToDecrypt,
                     callback = object : MatrixCallback {
                         override fun onSuccess(data: File) {
    diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    index 9b895dbc4d..569d006fba 100644
    --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    @@ -125,7 +125,6 @@ class RoomEventsAttachmentProvider(
                         as? MessageWithAttachmentContent
                         ?: return@let
                 fileService.downloadFile(
    -                    id = timelineEvent.eventId,
                         fileName = messageContent.body,
                         mimeType = messageContent.mimeType,
                         url = messageContent.getFileUrl(),
    diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt
    index 35375bc8ce..d8eddc7331 100644
    --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt
    @@ -75,7 +75,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
     
                     activeSessionHolder.getActiveSession().fileService()
                             .downloadFile(
    -                                id = data.eventId,
                                     fileName = data.filename,
                                     mimeType = data.mimeType,
                                     url = data.url,
    @@ -114,7 +113,6 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
     
                     activeSessionHolder.getActiveSession().fileService()
                             .downloadFile(
    -                                id = data.eventId,
                                     fileName = data.filename,
                                     mimeType = data.mimeType,
                                     url = data.url,
    diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt
    index bf2b56fc9b..b62b633a36 100644
    --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt
    @@ -131,7 +131,6 @@ class RoomUploadsViewModel @AssistedInject constructor(
                 try {
                     val file = awaitCallback {
                         session.fileService().downloadFile(
    -                            id = action.uploadEvent.eventId,
                                 messageContent = action.uploadEvent.contentWithAttachmentContent,
                                 callback = it
                         )
    @@ -148,7 +147,6 @@ class RoomUploadsViewModel @AssistedInject constructor(
                 try {
                     val file = awaitCallback {
                         session.fileService().downloadFile(
    -                            id = action.uploadEvent.eventId,
                                 messageContent = action.uploadEvent.contentWithAttachmentContent,
                                 callback = it)
                     }
    
    From 3d578c147cf599b9feaae7627afd81070ec4addc Mon Sep 17 00:00:00 2001
    From: Hivaa 
    Date: Wed, 9 Dec 2020 16:30:30 +0000
    Subject: [PATCH 114/218] Translated using Weblate (Persian)
    
    Currently translated at 100.0% (1986 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/
    ---
     vector/src/main/res/values-fa/strings.xml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
    index ceecea724a..0663dd1dca 100644
    --- a/vector/src/main/res/values-fa/strings.xml
    +++ b/vector/src/main/res/values-fa/strings.xml
    @@ -321,7 +321,7 @@
         تذکّرهای سوم‌شخص
         حق رونوشت
         سیاست محرمانگی
    -    عکس نمایه
    +    عکس پروفایل
         نام نمایشی
         رایانامه
         افزودن نشانی رایانامه
    
    From c9535509e8a5d19184fea097def9290bcbfd03bf Mon Sep 17 00:00:00 2001
    From: Nikita Epifanov 
    Date: Thu, 10 Dec 2020 07:48:43 +0000
    Subject: [PATCH 115/218] Translated using Weblate (Russian)
    
    Currently translated at 100.0% (1986 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
    ---
     vector/src/main/res/values-ru/strings.xml | 18 +++++++++---------
     1 file changed, 9 insertions(+), 9 deletions(-)
    
    diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
    index 43b93c0dfd..3411846b4d 100644
    --- a/vector/src/main/res/values-ru/strings.xml
    +++ b/vector/src/main/res/values-ru/strings.xml
    @@ -557,7 +557,7 @@
         Разрешить
         Проверить сессию
         Чтобы убедиться, что этой сессии можно доверять, обратитесь к его владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии:
    -    "Если совпадает, то нажмите кнопку подтвердить ниже. Если не совпадает, возможно кто-то пытается перехватить сессию и  вы захотите добавить его в черный список. В будущем данный процесс будет улучшен."
    +    Если совпадает, то нажмите кнопку подтвердить ниже. Если не совпадает, возможно кто-то пытается перехватить сессию и вы захотите добавить его в черный список. В будущем данный процесс будет улучшен.
         Я проверил, что ключи совпадают
         
         Комната содержит неизвестные сессии
    @@ -647,7 +647,7 @@
         Зашифрованное сообщение
         Сведения о сообществе
         Загрузка…
    -    "Закрыть  приложение"
    +    Закрыть приложение
         Сообщества
         Поиск сообществ
         Пригласить
    @@ -1053,7 +1053,7 @@
         Удаление резервной копии…
         Чтобы использовать резервную копию ключа в этой сессии, восстановите его с помощью своей парольной фразы или ключа восстановления.
         Резервная копия имеет недействительную подпись из подтвержденной сессии %s
    -    "Резервная копия имеет  действительную подпись из неподтвержденной сессии %s"
    +    Резервная копия имеет действительную подпись из неподтвержденной сессии %s
         Резервная копия имеет действительно подпись из подтверждённой сессии %s.
         Резервная копия имеет действительную подпись с этой сессии.
         Резервная копия подписана сессией с идентификатором %s.
    @@ -1192,7 +1192,7 @@
         Использовать настройку
         Проверить сессию
         Ваше устройство использует устаревший TLS протокол, уязвимый для атак, для вашей же безопасности вам отказано в подключении
    -    Приложениям не" нужно  подключаться к HomeServer в фоновом режиме, это должно снизить расход заряда батареи"
    +    Приложениям не нужно подключаться к домашнему серверу в фоновом режиме, это должно снизить расход заряда батареи
         Клавиша Ввод отправит сообщение вместо переноса строки
         Воспроизвести звук затвора
         неизвестный IP
    @@ -1252,7 +1252,7 @@
         Несоответствие пользователя
         Неизвестная ошибка
         Резервная копия существует на домашнем сервере
    -    "Похоже, у вас уже есть резервная копия  ключа настройки из другой сессии. Хотите заменить его новым\?"
    +    Похоже, у вас уже есть резервная копия ключа настройки из другой сессии. Хотите заменить его новым\?
         Заменить
         Стоп
         Проверка состояния резервного копирования
    @@ -1348,7 +1348,7 @@
         Ссылка скопирована в буфер обмена
         Добавить по Matrix ID
         Создание комнаты…
    -    "Результат не найден, используйте добавить matrix ID  для поиска на сервере."
    +    Результатов не найдено, используйте \"Добавить по matrix ID\" для поиска на сервере.
         Начните печатать, чтобы получить результат
         Фильтр по имени пользователя или ID…
         Присоединение к комнате…
    @@ -1406,7 +1406,7 @@
         Настроить идентификационный сервер
         Изменить идентификационный сервер
         В настоящее время вы используете %1$s для обнаружения и быть найденным вашими контактами.
    -    "Вы в настоящее время не используете идентификационный сервер. Чтобы обнаружить и быть найденным  вашими существующими контактами, настройте один из них ниже."
    +    Вы в настоящее время не используете идентификационный сервер. Чтобы обнаружить и быть найденным вашими существующими контактами, настройте один из них ниже.
         Видимые адреса электронной почты
         Доступные номера телефонов
         В ожидании
    @@ -1444,7 +1444,7 @@
         Cyrl
         Используйте менеджер интеграций чтобы управлять ботами, мостами, виджетами и наборами стикеров.
     \nМенеджеры интеграций получают данные о конфигурации, могут изменять виджеты, отправлять приглашения в комнаты и устанавливать права от вашего имени.
    -    "Использование может оставить cookie на вашем устройстве и отправить данные в  %s:"
    +    Использование может оставить cookie на вашем устройстве и отправить данные в %s:
         Использование может отправить данные в %s:
         Не удалось загрузить виджет.
     \n%s
    @@ -1929,7 +1929,7 @@
         Сообщения, содержащие @room
         Отладка
         Настройки важности уведомлений для событий
    -    Используйте последнюю версию Element на других ваших устройствах, веб-клиент Element, Element для ПК, Element для iOS, Element для Андроид или другой клиент Matrix, поддерживающий кросс-подпись
    +    Используйте последнюю версию Element на других ваших устройствах, веб-клиент Element, Element для ПК, Element для iOS, Element для Android или другой клиент Matrix, поддерживающий кросс-подпись
         Используйте последнюю версию Element на других ваших устройствах:
         Подтвердите новую сессию вашей учетной записи: %1$s
         Настроить безопасное резервное копирование
    
    From 3473a7ef5e03a1d3e19778c2a1dfb7bb624822a1 Mon Sep 17 00:00:00 2001
    From: Nikita Epifanov 
    Date: Thu, 10 Dec 2020 07:30:44 +0000
    Subject: [PATCH 116/218] Translated using Weblate (Russian)
    
    Currently translated at 100.0% (210 of 210 strings)
    
    Translation: Element Android/Element Android Sdk
    Translate-URL: https://translate.element.io/projects/element-android/element-sdk/ru/
    ---
     .../src/main/res/values-ru/strings.xml          | 17 +++++++++++++++--
     1 file changed, 15 insertions(+), 2 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/res/values-ru/strings.xml b/matrix-sdk-android/src/main/res/values-ru/strings.xml
    index 84a3ac3ec3..5ef5a4f447 100644
    --- a/matrix-sdk-android/src/main/res/values-ru/strings.xml
    +++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml
    @@ -225,8 +225,8 @@
         %1$s вошел(ла)
         Вы создали обсуждение
         %1$s создал(а) обсуждение
    -    Вы обновили.
    -    %s обновлена.
    +    Вы обновили эту комнату.
    +    %s обновил(а) эту комнату.
         
             %1$s, %2$s, %3$s и %4$d другой
             %1$s, %2$s, %3$s и %4$d других
    @@ -239,4 +239,17 @@
         Без изменений.
         Пустая комната (была %s)
         • Соответствующий сервер %s заблокирован.
    +    • Сервер, соответствующий буквальным IP-адресам, теперь запрещён.
    +    • Сервер, соответствующий буквальным IP-адресам, теперь разрешён.
    +    • Сервер, соответствующий %s, теперь запрещён.
    +    • Сервер, соответствующий %s, теперь разрешён.
    +    • Сервер, соответствующий %s, был удалён из списка блокировки.
    +    • Сервер, соответствующий буквальным IP-адресам, запрещён.
    +    • Сервер, соответствующий буквальным IP-адресам, разрешён.
    +    • Сервер, соответствующий %s, разрешён.
    +    • Сервер, соответствующий %s, был удалён из разрешённого списка.
    +    Вы изменили права доступа сервера (ACL) для этой комнаты.
    +    %s изменил права доступа сервера (ACL) для этой комнаты.
    +    Вы настроили права доступа сервера (ACL) для этой комнаты.
    +    %s устанавливает права доступа сервера (ACL) для этой комнаты.
     
    \ No newline at end of file
    
    From 4cb7754b77fa76f0721014d36ebf3f2de92c9075 Mon Sep 17 00:00:00 2001
    From: Jadran Prodan 
    Date: Thu, 10 Dec 2020 12:21:24 +0000
    Subject: [PATCH 117/218] Translated using Weblate (Slovenian)
    
    Currently translated at 0.3% (7 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
    ---
     vector/src/main/res/values-sl/strings.xml | 10 +++++++++-
     1 file changed, 9 insertions(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/values-sl/strings.xml b/vector/src/main/res/values-sl/strings.xml
    index a6b3daec93..d491fb8b86 100644
    --- a/vector/src/main/res/values-sl/strings.xml
    +++ b/vector/src/main/res/values-sl/strings.xml
    @@ -1,2 +1,10 @@
     
    -
    \ No newline at end of file
    +
    +    Ime
    +    Prijava hrošča
    +    Pošlji ekransko sliko
    +    Ponvno pošlji
    +    Pošlji
    +    Sinhroniziram…
    +    ZDA
    +
    \ No newline at end of file
    
    From 38843f74ab251981ef4303e125cce3f4bfcb2e60 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 1 Dec 2020 17:07:24 +0100
    Subject: [PATCH 118/218] No need for WRITE_EXTERNAL permission to send
     attachment to the app (anymore?)
    
    ---
     .../home/room/detail/RoomDetailFragment.kt    | 22 +------------------
     .../home/room/detail/RoomDetailViewModel.kt   |  3 ---
     2 files changed, 1 insertion(+), 24 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    index 93b2b69ba5..29036a91fb 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    @@ -1100,18 +1100,6 @@ class RoomDetailFragment @Inject constructor(
             }
         }
     
    -    private val writingFileActivityResultLauncher = registerForPermissionsResult { allGranted ->
    -        if (allGranted) {
    -            val pendingUri = roomDetailViewModel.pendingUri
    -            if (pendingUri != null) {
    -                roomDetailViewModel.pendingUri = null
    -                sendUri(pendingUri)
    -            }
    -        } else {
    -            cleanUpAfterPermissionNotGranted()
    -        }
    -    }
    -
         private fun setupComposer() {
             val composerEditText = composerLayout.composerEditText
             autoCompleter.setup(composerEditText)
    @@ -1157,14 +1145,7 @@ class RoomDetailFragment @Inject constructor(
                 }
     
                 override fun onRichContentSelected(contentUri: Uri): Boolean {
    -                // We need WRITE_EXTERNAL permission
    -                return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), writingFileActivityResultLauncher)) {
    -                    sendUri(contentUri)
    -                } else {
    -                    roomDetailViewModel.pendingUri = contentUri
    -                    // Always intercept when we request some permission
    -                    true
    -                }
    +                return sendUri(contentUri)
                 }
             }
         }
    @@ -1561,7 +1542,6 @@ class RoomDetailFragment @Inject constructor(
         private fun cleanUpAfterPermissionNotGranted() {
             // Reset all pending data
             roomDetailViewModel.pendingAction = null
    -        roomDetailViewModel.pendingUri = null
             attachmentsHelper.pendingType = null
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    index 7bba9728ca..6db2a9205a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    @@ -128,9 +128,6 @@ class RoomDetailViewModel @AssistedInject constructor(
         // Slot to keep a pending action during permission request
         var pendingAction: RoomDetailAction? = null
     
    -    // Slot to keep a pending uri during permission request
    -    var pendingUri: Uri? = null
    -
         // Slot to store if we want to prevent preview of attachment
         var preventAttachmentPreview = false
     
    
    From eb30b9fae9df12082b051d6cfa3bebe99c172ad8 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 1 Dec 2020 17:11:05 +0100
    Subject: [PATCH 119/218] Show preview when sending attachment from the
     keyboard (#2440) It's actually a revert of
     a3b205b310fa10c8a82b22c2bd3cbdd348ce92f3
    
    ---
     CHANGES.md                                    |  1 +
     .../home/room/detail/RoomDetailFragment.kt    | 24 +++++++------------
     .../home/room/detail/RoomDetailViewModel.kt   |  3 ---
     3 files changed, 9 insertions(+), 19 deletions(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index a4aa0b7a0f..206e48170f 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -15,6 +15,7 @@ Bugfix 🐛:
      - Fix cancellation of sending event (#2438)
      - Double bottomsheet effect after verify with passphrase
      - EditText cursor jumps to the start while typing fast (#2469)
    + - Show preview when sending attachment from the keyboard (#2440)
     
     Translations 🗣:
      -
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    index 29036a91fb..c471b90e8e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    @@ -1176,11 +1176,9 @@ class RoomDetailFragment @Inject constructor(
         }
     
         private fun sendUri(uri: Uri): Boolean {
    -        roomDetailViewModel.preventAttachmentPreview = true
             val shareIntent = Intent(Intent.ACTION_SEND, uri)
             val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent)
             if (!isHandled) {
    -            roomDetailViewModel.preventAttachmentPreview = false
                 Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
             }
             return isHandled
    @@ -1936,24 +1934,18 @@ class RoomDetailFragment @Inject constructor(
     // AttachmentsHelper.Callback
     
         override fun onContentAttachmentsReady(attachments: List) {
    -        if (roomDetailViewModel.preventAttachmentPreview) {
    -            roomDetailViewModel.preventAttachmentPreview = false
    -            roomDetailViewModel.handle(RoomDetailAction.SendMedia(attachments, false))
    -        } else {
    -            val grouped = attachments.toGroupedContentAttachmentData()
    -            if (grouped.notPreviewables.isNotEmpty()) {
    -                // Send the not previewable attachments right now (?)
    -                roomDetailViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false))
    -            }
    -            if (grouped.previewables.isNotEmpty()) {
    -                val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables))
    -                contentAttachmentActivityResultLauncher.launch(intent)
    -            }
    +        val grouped = attachments.toGroupedContentAttachmentData()
    +        if (grouped.notPreviewables.isNotEmpty()) {
    +            // Send the not previewable attachments right now (?)
    +            roomDetailViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false))
    +        }
    +        if (grouped.previewables.isNotEmpty()) {
    +            val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables))
    +            contentAttachmentActivityResultLauncher.launch(intent)
             }
         }
     
         override fun onAttachmentsProcessFailed() {
    -        roomDetailViewModel.preventAttachmentPreview = false
             Toast.makeText(requireContext(), R.string.error_attachment, Toast.LENGTH_SHORT).show()
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    index 6db2a9205a..13362c8a2e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
    @@ -128,9 +128,6 @@ class RoomDetailViewModel @AssistedInject constructor(
         // Slot to keep a pending action during permission request
         var pendingAction: RoomDetailAction? = null
     
    -    // Slot to store if we want to prevent preview of attachment
    -    var preventAttachmentPreview = false
    -
         private var trackUnreadMessages = AtomicBoolean(false)
         private var mostRecentDisplayedEvent: TimelineEvent? = null
     
    
    From 439029467ad0c65d28ed8df1fb1f4a4e5f14e4cc Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 1 Dec 2020 17:37:06 +0100
    Subject: [PATCH 120/218] Attachment preview also for Gif files
    
    ---
     .idea/dictionaries/bmarty.xml                                 | 2 ++
     .../vector/app/features/attachments/ContentAttachmentData.kt  | 4 +++-
     2 files changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
    index 5ad39614b7..16cc35cebe 100644
    --- a/.idea/dictionaries/bmarty.xml
    +++ b/.idea/dictionaries/bmarty.xml
    @@ -24,6 +24,8 @@
           pbkdf
           pids
           pkcs
    +      previewable
    +      previewables
           riotx
           signin
           signout
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt
    index bd13c0dac4..3ca4f1b13e 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt
    @@ -18,10 +18,12 @@ package im.vector.app.features.attachments
     
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
     
    +private val listOfPreviewableMimeTypes = listOf("image/jpeg", "image/png", "image/jpg", "image/gif")
    +
     fun ContentAttachmentData.isPreviewable(): Boolean {
         // For now the preview only supports still image
         return type == ContentAttachmentData.Type.IMAGE
    -            && listOf("image/jpeg", "image/png", "image/jpg").contains(getSafeMimeType() ?: "")
    +            && listOfPreviewableMimeTypes.contains(getSafeMimeType() ?: "")
     }
     
     data class GroupedContentAttachmentData(
    
    From 21271b6510a0a01144f6491dfeda6137a39df8f2 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Tue, 1 Dec 2020 17:55:37 +0100
    Subject: [PATCH 121/218] Do not compress GIFs (#1616, #1254)
    
    ---
     CHANGES.md                                                   | 1 +
     .../sdk/internal/session/content/UploadContentWorker.kt      | 5 ++++-
     2 files changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 206e48170f..422d084db6 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -16,6 +16,7 @@ Bugfix 🐛:
      - Double bottomsheet effect after verify with passphrase
      - EditText cursor jumps to the start while typing fast (#2469)
      - Show preview when sending attachment from the keyboard (#2440)
    + - Do not compress GIFs (#1616, #1254)
     
     Translations 🗣:
      -
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
    index 77f39a7768..a72141e0ab 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
    @@ -151,7 +151,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                             params.attachment.size
                     )
     
    -                if (attachment.type == ContentAttachmentData.Type.IMAGE && params.compressBeforeSending) {
    +                if (attachment.type == ContentAttachmentData.Type.IMAGE
    +                        // Do not compress gif
    +                        && attachment.mimeType != "image/gif"
    +                        && params.compressBeforeSending) {
                         fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
                                 .also { compressedFile ->
                                     // Get new Bitmap size
    
    From ca75eae0aa6ed7d9aae85679274825acf2bbf7b0 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Wed, 2 Dec 2020 10:00:06 +0100
    Subject: [PATCH 122/218] Create MimeTypes object
    
    ---
     .../session/content/ContentAttachmentData.kt  |  3 +-
     .../room/model/message/MessageImageContent.kt |  3 +-
     .../matrix/android/sdk/api/util/MimeTypes.kt  | 38 +++++++++++++++++++
     .../session/content/ThumbnailExtractor.kt     |  3 +-
     .../session/content/UploadContentWorker.kt    |  7 ++--
     .../session/profile/DefaultProfileService.kt  |  3 +-
     .../room/create/CreateRoomBodyBuilder.kt      |  3 +-
     .../session/room/state/DefaultStateService.kt |  3 +-
     .../app/core/resources/ResourceUtils.kt       | 12 ++----
     .../core/utils/ExternalApplicationsUtil.kt    | 28 ++++++++------
     .../features/attachments/AttachmentsMapper.kt | 11 ++++--
     .../attachments/ContentAttachmentData.kt      |  8 +++-
     .../attachments/preview/Extensions.kt         |  6 ++-
     .../timeline/factory/MessageItemFactory.kt    |  3 +-
     .../media/DataAttachmentRoomProvider.kt       |  3 +-
     .../media/RoomEventsAttachmentProvider.kt     |  3 +-
     .../app/features/rageshake/BugReporter.kt     |  5 ++-
     17 files changed, 100 insertions(+), 42 deletions(-)
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
    index 4677c2be32..4164b84ecd 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
    @@ -21,6 +21,7 @@ import android.os.Parcelable
     import androidx.exifinterface.media.ExifInterface
     import com.squareup.moshi.JsonClass
     import kotlinx.android.parcel.Parcelize
    +import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
     
     @Parcelize
     @JsonClass(generateAdapter = true)
    @@ -45,5 +46,5 @@ data class ContentAttachmentData(
             VIDEO
         }
     
    -    fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType
    +    fun getSafeMimeType() = mimeType?.normalizeMimeType()
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
    index 859f7fd104..73e27b64e3 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
    @@ -20,6 +20,7 @@ import com.squareup.moshi.Json
     import com.squareup.moshi.JsonClass
     import org.matrix.android.sdk.api.session.events.model.Content
     import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
     
     @JsonClass(generateAdapter = true)
    @@ -54,5 +55,5 @@ data class MessageImageContent(
             @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
     ) : MessageImageInfoContent {
         override val mimeType: String?
    -        get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: "image/*"
    +        get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
    new file mode 100644
    index 0000000000..c74999b4ab
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
    @@ -0,0 +1,38 @@
    +/*
    + * Copyright 2020 The Matrix.org Foundation C.I.C.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.api.util
    +
    +import org.matrix.android.sdk.api.extensions.orFalse
    +
    +// The Android SDK does not provide constant for mime type, add some of them here
    +object MimeTypes {
    +    const val Any: String = "*/*"
    +    const val OctetStream = "application/octet-stream"
    +
    +    const val Images = "image/*"
    +
    +    const val Png = "image/png"
    +    const val BadJpg = "image/jpg"
    +    const val Jpeg = "image/jpeg"
    +    const val Gif = "image/gif"
    +
    +    fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this
    +
    +    fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse()
    +    fun String?.isMimeTypeVideo() = this?.startsWith("video/").orFalse()
    +    fun String?.isMimeTypeAudio() = this?.startsWith("audio/").orFalse()
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
    index 8c3aad6a1f..4b31db59b1 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
    @@ -20,6 +20,7 @@ import android.content.Context
     import android.graphics.Bitmap
     import android.media.MediaMetadataRetriever
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
    +import org.matrix.android.sdk.api.util.MimeTypes
     import timber.log.Timber
     import java.io.ByteArrayOutputStream
     
    @@ -58,7 +59,7 @@ internal object ThumbnailExtractor {
                         height = thumbnailHeight,
                         size = thumbnailSize.toLong(),
                         bytes = outputStream.toByteArray(),
    -                    mimeType = "image/jpeg"
    +                    mimeType = MimeTypes.Jpeg
                 )
                 thumbnail.recycle()
                 outputStream.reset()
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
    index a72141e0ab..672d407d25 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
    @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
     import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
     import org.matrix.android.sdk.internal.database.mapper.ContentMapper
    @@ -153,7 +154,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     
                     if (attachment.type == ContentAttachmentData.Type.IMAGE
                             // Do not compress gif
    -                        && attachment.mimeType != "image/gif"
    +                        && attachment.mimeType != MimeTypes.Gif
                             && params.compressBeforeSending) {
                         fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
                                 .also { compressedFile ->
    @@ -194,7 +195,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                         Timber.v("## FileService: Uploading file")
     
                         fileUploader
    -                            .uploadFile(encryptedFile, attachment.name, "application/octet-stream", progressListener)
    +                            .uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener)
                     } else {
                         Timber.v("## FileService: Clear file")
                         encryptedFile = null
    @@ -261,7 +262,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                                 val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
                                 val contentUploadResponse = fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
                                         "thumb_${params.attachment.name}",
    -                                    "application/octet-stream",
    +                                    MimeTypes.OctetStream,
                                         thumbnailProgressListener)
                                 UploadThumbnailResult(
                                         contentUploadResponse.contentUri,
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
    index 5265e4f17d..500d43408e 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
    @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
     import org.matrix.android.sdk.api.session.profile.ProfileService
     import org.matrix.android.sdk.api.util.Cancelable
     import org.matrix.android.sdk.api.util.JsonDict
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.api.util.Optional
     import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
     import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
    @@ -80,7 +81,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
     
         override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback): Cancelable {
             return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) {
    -            val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg")
    +            val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
                 setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
                 userStore.updateAvatar(userId, response.contentUri)
             }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
    index 79ff9db087..fb840b4eb3 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
    @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
     import org.matrix.android.sdk.api.session.identity.IdentityServiceError
     import org.matrix.android.sdk.api.session.identity.toMedium
     import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.internal.crypto.DeviceListManager
     import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
     import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
    @@ -96,7 +97,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
                     fileUploader.uploadFromUri(
                             uri = avatarUri,
                             filename = UUID.randomUUID().toString(),
    -                        mimeType = "image/jpeg")
    +                        mimeType = MimeTypes.Jpeg)
                 }
                         ?.let { response ->
                             Event(
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
    index 6015d945c4..a93ec8e797 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
    @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
     import org.matrix.android.sdk.api.session.room.state.StateService
     import org.matrix.android.sdk.api.util.Cancelable
     import org.matrix.android.sdk.api.util.JsonDict
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.api.util.Optional
     import org.matrix.android.sdk.internal.session.content.FileUploader
     import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
    @@ -164,7 +165,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
     
         override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable {
             return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
    -            val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")
    +            val response = fileUploader.uploadFromUri(avatarUri, fileName, MimeTypes.Jpeg)
                 awaitCallback {
                     sendStateEvent(
                             eventType = EventType.STATE_ROOM_AVATAR,
    diff --git a/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt b/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt
    index 7ab2271c57..f14c9b834d 100644
    --- a/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt
    +++ b/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt
    @@ -20,17 +20,11 @@ import android.content.Context
     import android.net.Uri
     import android.webkit.MimeTypeMap
     import im.vector.app.core.utils.getFileExtension
    +import org.matrix.android.sdk.api.util.MimeTypes
    +import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
     import timber.log.Timber
     import java.io.InputStream
     
    -/**
    - * Mime types
    - */
    -const val MIME_TYPE_JPEG = "image/jpeg"
    -const val MIME_TYPE_JPG = "image/jpg"
    -const val MIME_TYPE_IMAGE_ALL = "image/*"
    -const val MIME_TYPE_ALL_CONTENT = "*/*"
    -
     data class Resource(
             var mContentStream: InputStream? = null,
             var mMimeType: String? = null
    @@ -55,7 +49,7 @@ data class Resource(
          * @return true if the opened resource is a jpeg one.
          */
         fun isJpegResource(): Boolean {
    -        return MIME_TYPE_JPEG == mMimeType || MIME_TYPE_JPG == mMimeType
    +        return mMimeType.normalizeMimeType() == MimeTypes.Jpeg
         }
     }
     
    diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
    index 4c6aa51348..45db8ea91d 100644
    --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
    +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
    @@ -48,6 +48,10 @@ import okio.buffer
     import okio.sink
     import okio.source
     import org.matrix.android.sdk.api.extensions.tryOrNull
    +import org.matrix.android.sdk.api.util.MimeTypes
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeAudio
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeVideo
     import timber.log.Timber
     import java.io.File
     import java.io.FileInputStream
    @@ -138,7 +142,7 @@ fun openFileSelection(activity: Activity,
         fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultipleSelection)
     
         fileIntent.addCategory(Intent.CATEGORY_OPENABLE)
    -    fileIntent.type = "*/*"
    +    fileIntent.type = MimeTypes.Any
     
         try {
             activityResultLauncher
    @@ -182,7 +186,7 @@ fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): Strin
         // The Galaxy S not only requires the name of the file to output the image to, but will also not
         // set the mime type of the picture it just took (!!!). We assume that the Galaxy S takes image/jpegs
         // so the attachment uploader doesn't freak out about there being no mimetype in the content database.
    -    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    +    values.put(MediaStore.Images.Media.MIME_TYPE, MimeTypes.Jpeg)
         var dummyUri: Uri? = null
         try {
             dummyUri = activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
    @@ -344,10 +348,10 @@ fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String
                 put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
             }
             val externalContentUri = when {
    -            mediaMimeType?.startsWith("image/") == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    -            mediaMimeType?.startsWith("video/") == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    -            mediaMimeType?.startsWith("audio/") == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    -            else                                        -> MediaStore.Downloads.EXTERNAL_CONTENT_URI
    +            mediaMimeType?.isMimeTypeImage() == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    +            mediaMimeType?.isMimeTypeVideo() == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    +            mediaMimeType?.isMimeTypeAudio() == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    +            else                                     -> MediaStore.Downloads.EXTERNAL_CONTENT_URI
             }
     
             val uri = context.contentResolver.insert(externalContentUri, values)
    @@ -365,7 +369,7 @@ fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String
                 notificationUtils.buildDownloadFileNotification(
                         uri,
                         filename,
    -                    mediaMimeType ?: "application/octet-stream"
    +                    mediaMimeType ?: MimeTypes.OctetStream
                 ).let { notification ->
                     notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification)
                 }
    @@ -385,10 +389,10 @@ private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: Str
     
         GlobalScope.launch(Dispatchers.IO) {
             val dest = when {
    -            mediaMimeType?.startsWith("image/") == true -> Environment.DIRECTORY_PICTURES
    -            mediaMimeType?.startsWith("video/") == true -> Environment.DIRECTORY_MOVIES
    -            mediaMimeType?.startsWith("audio/") == true -> Environment.DIRECTORY_MUSIC
    -            else                                        -> Environment.DIRECTORY_DOWNLOADS
    +            mediaMimeType?.isMimeTypeImage() == true -> Environment.DIRECTORY_PICTURES
    +            mediaMimeType?.isMimeTypeVideo() == true -> Environment.DIRECTORY_MOVIES
    +            mediaMimeType?.isMimeTypeAudio() == true -> Environment.DIRECTORY_MUSIC
    +            else                                     -> Environment.DIRECTORY_DOWNLOADS
             }
             val downloadDir = Environment.getExternalStoragePublicDirectory(dest)
             try {
    @@ -405,7 +409,7 @@ private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: Str
                             savedFile.name,
                             title,
                             true,
    -                        mediaMimeType ?: "application/octet-stream",
    +                        mediaMimeType ?: MimeTypes.OctetStream,
                             savedFile.absolutePath,
                             savedFile.length(),
                             true)
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt
    index 9c9d8f8017..4e8dcaacb7 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt
    @@ -23,6 +23,9 @@ import im.vector.lib.multipicker.entity.MultiPickerFileType
     import im.vector.lib.multipicker.entity.MultiPickerImageType
     import im.vector.lib.multipicker.entity.MultiPickerVideoType
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeAudio
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeVideo
     import timber.log.Timber
     
     fun MultiPickerContactType.toContactAttachment(): ContactAttachment {
    @@ -59,10 +62,10 @@ fun MultiPickerAudioType.toContentAttachmentData(): ContentAttachmentData {
     
     private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type {
         return when {
    -        mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE
    -        mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO
    -        mimeType?.startsWith("audio/") == true -> ContentAttachmentData.Type.AUDIO
    -        else                                   -> ContentAttachmentData.Type.FILE
    +        mimeType?.isMimeTypeImage() == true -> ContentAttachmentData.Type.IMAGE
    +        mimeType?.isMimeTypeVideo() == true -> ContentAttachmentData.Type.VIDEO
    +        mimeType?.isMimeTypeAudio() == true -> ContentAttachmentData.Type.AUDIO
    +        else                                -> ContentAttachmentData.Type.FILE
         }
     }
     
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt
    index 3ca4f1b13e..e35ab96365 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt
    @@ -17,8 +17,14 @@
     package im.vector.app.features.attachments
     
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
    +import org.matrix.android.sdk.api.util.MimeTypes
     
    -private val listOfPreviewableMimeTypes = listOf("image/jpeg", "image/png", "image/jpg", "image/gif")
    +private val listOfPreviewableMimeTypes = listOf(
    +        MimeTypes.Jpeg,
    +        MimeTypes.BadJpg,
    +        MimeTypes.Png,
    +        MimeTypes.Gif
    +)
     
     fun ContentAttachmentData.isPreviewable(): Boolean {
         // For now the preview only supports still image
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt
    index bd06f8cf0b..853f9f8997 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt
    @@ -17,12 +17,14 @@
     package im.vector.app.features.attachments.preview
     
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
    +import org.matrix.android.sdk.api.util.MimeTypes
    +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage
     
     /**
      * All images are editable, expect Gif
      */
     fun ContentAttachmentData.isEditable(): Boolean {
         return type == ContentAttachmentData.Type.IMAGE
    -            && getSafeMimeType()?.startsWith("image/") == true
    -            && getSafeMimeType() != "image/gif"
    +            && getSafeMimeType()?.isMimeTypeImage() == true
    +            && getSafeMimeType() != MimeTypes.Gif
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index 34086043da..2a98fd2dd7 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -88,6 +88,7 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileName
     import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
     import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
     import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
     import javax.inject.Inject
    @@ -311,7 +312,7 @@ class MessageItemFactory @Inject constructor(
                     .leftGuideline(avatarSizeProvider.leftGuideline)
                     .imageContentRenderer(imageContentRenderer)
                     .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
    -                .playable(messageContent.info?.mimeType == "image/gif")
    +                .playable(messageContent.info?.mimeType == MimeTypes.Gif)
                     .highlighted(highlight)
                     .mediaData(data)
                     .apply {
    diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt
    index 584b13f32b..328d8f943e 100644
    --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt
    @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.MatrixCallback
     import org.matrix.android.sdk.api.session.file.FileService
     import org.matrix.android.sdk.api.session.room.Room
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
    +import org.matrix.android.sdk.api.util.MimeTypes
     import java.io.File
     
     class DataAttachmentRoomProvider(
    @@ -38,7 +39,7 @@ class DataAttachmentRoomProvider(
             return getItem(position).let {
                 when (it) {
                     is ImageContentRenderer.Data -> {
    -                    if (it.mimeType == "image/gif") {
    +                    if (it.mimeType == MimeTypes.Gif) {
                             AttachmentInfo.AnimatedImage(
                                     uid = it.eventId,
                                     url = it.url ?: "",
    diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    index 569d006fba..53c5dac9ad 100644
    --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt
    @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
     import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
    +import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
     import java.io.File
     
    @@ -56,7 +57,7 @@ class RoomEventsAttachmentProvider(
                             allowNonMxcUrls = it.root.sendState.isSending()
     
                     )
    -                if (content.mimeType == "image/gif") {
    +                if (content.mimeType == MimeTypes.Gif) {
                         AttachmentInfo.AnimatedImage(
                                 uid = it.eventId,
                                 url = content.url ?: "",
    diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
    index 96248187aa..7be7624a48 100755
    --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
    +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
    @@ -46,6 +46,7 @@ import okhttp3.Response
     import org.json.JSONException
     import org.json.JSONObject
     import org.matrix.android.sdk.api.Matrix
    +import org.matrix.android.sdk.api.util.MimeTypes
     import timber.log.Timber
     import java.io.File
     import java.io.IOException
    @@ -274,7 +275,7 @@ class BugReporter @Inject constructor(
     
                         // add the gzipped files
                         for (file in gzippedFiles) {
    -                        builder.addFormDataPart("compressed-log", file.name, file.asRequestBody("application/octet-stream".toMediaTypeOrNull()))
    +                        builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
                         }
     
                         mBugReportFiles.addAll(gzippedFiles)
    @@ -295,7 +296,7 @@ class BugReporter @Inject constructor(
                                     }
     
                                     builder.addFormDataPart("file",
    -                                        logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody("application/octet-stream".toMediaTypeOrNull()))
    +                                        logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
                                 } catch (e: Exception) {
                                     Timber.e(e, "## sendBugReport() : fail to write screenshot$e")
                                 }
    
    From 49cad8feec7096612a339fb7b97a7891d4dee0a7 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 11 Dec 2020 11:49:46 +0100
    Subject: [PATCH 123/218] Rename files.
    
    ---
     .../features/home/room/detail/RoomDetailFragment.kt  |  2 +-
     .../home/room/detail/composer/TextComposerView.kt    | 12 ++++++------
     ...merge_composer_layout.xml => composer_layout.xml} |  2 +-
     ...ml => composer_layout_constraint_set_compact.xml} |  0
     ...l => composer_layout_constraint_set_expanded.xml} |  0
     5 files changed, 8 insertions(+), 8 deletions(-)
     rename vector/src/main/res/layout/{merge_composer_layout.xml => composer_layout.xml} (98%)
     rename vector/src/main/res/layout/{constraint_set_composer_layout_compact.xml => composer_layout_constraint_set_compact.xml} (100%)
     rename vector/src/main/res/layout/{constraint_set_composer_layout_expanded.xml => composer_layout_constraint_set_expanded.xml} (100%)
    
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    index 93b2b69ba5..a8cd8590c4 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
    @@ -165,7 +165,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
     import io.reactivex.schedulers.Schedulers
     import kotlinx.android.parcel.Parcelize
     import kotlinx.android.synthetic.main.fragment_room_detail.*
    -import kotlinx.android.synthetic.main.merge_composer_layout.view.*
    +import kotlinx.android.synthetic.main.composer_layout.view.*
     import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
     import org.billcarsonfr.jsonviewer.JSonViewerDialog
     import org.commonmark.parser.Parser
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt
    index af0e1a91f0..f232e9a65e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt
    @@ -36,7 +36,7 @@ import androidx.transition.TransitionSet
     import butterknife.BindView
     import butterknife.ButterKnife
     import im.vector.app.R
    -import kotlinx.android.synthetic.main.merge_composer_layout.view.*
    +import kotlinx.android.synthetic.main.composer_layout.view.*
     import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
     
     /**
    @@ -86,7 +86,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
             get() = composerEditText.text
     
         init {
    -        inflate(context, R.layout.merge_composer_layout, this)
    +        inflate(context, R.layout.composer_layout, this)
             ButterKnife.bind(this)
             collapse(false)
             composerEditText.callback = object : ComposerEditText.Callback {
    @@ -110,20 +110,20 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
         }
     
         fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
    -        if (currentConstraintSetId == R.layout.constraint_set_composer_layout_compact) {
    +        if (currentConstraintSetId == R.layout.composer_layout_constraint_set_compact) {
                 // ignore we good
                 return
             }
    -        currentConstraintSetId = R.layout.constraint_set_composer_layout_compact
    +        currentConstraintSetId = R.layout.composer_layout_constraint_set_compact
             applyNewConstraintSet(animate, transitionComplete)
         }
     
         fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
    -        if (currentConstraintSetId == R.layout.constraint_set_composer_layout_expanded) {
    +        if (currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded) {
                 // ignore we good
                 return
             }
    -        currentConstraintSetId = R.layout.constraint_set_composer_layout_expanded
    +        currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded
             applyNewConstraintSet(animate, transitionComplete)
         }
     
    diff --git a/vector/src/main/res/layout/merge_composer_layout.xml b/vector/src/main/res/layout/composer_layout.xml
    similarity index 98%
    rename from vector/src/main/res/layout/merge_composer_layout.xml
    rename to vector/src/main/res/layout/composer_layout.xml
    index ea2bc1bf30..cb5dcbc42c 100644
    --- a/vector/src/main/res/layout/merge_composer_layout.xml
    +++ b/vector/src/main/res/layout/composer_layout.xml
    @@ -4,7 +4,7 @@
         xmlns:tools="http://schemas.android.com/tools"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
    -    tools:constraintSet="@layout/constraint_set_composer_layout_compact"
    +    tools:constraintSet="@layout/composer_layout_constraint_set_compact"
         tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
     
         
         
     
         
     
         
    +        app:layout_constraintEnd_toEndOf="@id/related_message_background"
    +        app:layout_constraintStart_toStartOf="@+id/related_message_background"
    +        app:layout_constraintTop_toTopOf="@id/related_message_background" />
     
         
    +        app:layout_constraintBottom_toBottomOf="@id/related_message_background"
    +        app:layout_constraintEnd_toEndOf="@id/related_message_background"
    +        app:layout_constraintStart_toStartOf="@+id/related_message_background" />
     
         
    Date: Fri, 11 Dec 2020 12:17:23 +0100
    Subject: [PATCH 125/218] Fix Layout issue (visible only on RTL) (#2523)
    
    ---
     .../main/res/layout/composer_layout_constraint_set_compact.xml  | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    index 4c91ba6b38..e1eb0f01c3 100644
    --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    @@ -113,7 +113,7 @@
             android:layout_width="16dp"
             android:layout_height="16dp"
             app:layout_constraintBottom_toBottomOf="@id/attachmentButton"
    -        app:layout_constraintEnd_toStartOf="@+id/attachmentButton"
    +        app:layout_constraintEnd_toStartOf="@+id/composerEditText"
             app:layout_constraintStart_toEndOf="@+id/attachmentButton"
             app:layout_constraintTop_toTopOf="@id/attachmentButton"
             tools:src="@drawable/ic_shield_black"
    
    From 32fd3be73235cd6392ac911237f6b10bb6d5dc79 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 11 Dec 2020 12:41:35 +0100
    Subject: [PATCH 126/218] Better alignment of button and animation, bigger
     touch area for send button
    
    ---
     vector/src/main/res/drawable/bg_send.xml       |  6 +++++-
     .../composer_layout_constraint_set_compact.xml | 16 ++++++++--------
     ...composer_layout_constraint_set_expanded.xml | 18 +++++++++---------
     3 files changed, 22 insertions(+), 18 deletions(-)
    
    diff --git a/vector/src/main/res/drawable/bg_send.xml b/vector/src/main/res/drawable/bg_send.xml
    index 4b357d7ab1..8ab95bf5c5 100644
    --- a/vector/src/main/res/drawable/bg_send.xml
    +++ b/vector/src/main/res/drawable/bg_send.xml
    @@ -1,6 +1,10 @@
     
     
    -    
    +    
             
                 
             
    diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    index e1eb0f01c3..231e58c68a 100644
    --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    @@ -102,20 +102,20 @@
             android:layout_margin="12dp"
             android:background="?android:attr/selectableItemBackground"
             android:src="@drawable/ic_attachment"
    -        app:layout_constraintBottom_toBottomOf="parent"
    +        app:layout_constraintBottom_toBottomOf="@id/sendButton"
             app:layout_constraintEnd_toStartOf="@+id/composer_shield"
             app:layout_constraintStart_toStartOf="parent"
    -        app:layout_constraintTop_toTopOf="parent"
    +        app:layout_constraintTop_toTopOf="@id/sendButton"
             tools:ignore="MissingPrefix" />
     
         
     
    @@ -145,11 +145,11 @@
     
         
     
         
     
         
    Date: Fri, 11 Dec 2020 12:46:44 +0100
    Subject: [PATCH 127/218] Bigger touch area for the other buttons
    
    ---
     .../composer_layout_constraint_set_compact.xml      | 13 ++++++-------
     .../composer_layout_constraint_set_expanded.xml     | 12 ++++++------
     2 files changed, 12 insertions(+), 13 deletions(-)
    
    diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    index 231e58c68a..2de3efc8ee 100644
    --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    @@ -97,9 +97,9 @@
     
         
    Date: Fri, 11 Dec 2020 15:08:32 +0300
    Subject: [PATCH 128/218] Code review fixes.
    
    ---
     .../session/room/timeline/DefaultTimeline.kt  | 11 +++++-----
     .../timeline/TimelineHiddenReadReceipts.kt    | 21 +++++++++++++++++--
     .../helper/TimelineSettingsFactory.kt         |  4 ++--
     3 files changed, 27 insertions(+), 9 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
    index 0dc0d33d38..86b0497bd0 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
    @@ -784,19 +784,20 @@ internal class DefaultTimeline(
         }
     
         private fun List.filterEventsWithSettings(): List {
    -        return filter {
    -            val filterType = !settings.filters.filterTypes || settings.filters.allowedTypes.map { it.eventType }.contains(it.root.type)
    +        return filter { event ->
    +            val filterType = !settings.filters.filterTypes
    +                    || settings.filters.allowedTypes.any { it.eventType == event.root.type && (it.stateKey == null || it.stateKey == event.root.senderId) }
                 if (!filterType) return@filter false
     
    -            val filterEdits = if (settings.filters.filterEdits && it.root.getClearType() == EventType.MESSAGE) {
    -                val messageContent = it.root.getClearContent().toModel()
    +            val filterEdits = if (settings.filters.filterEdits && event.root.getClearType() == EventType.MESSAGE) {
    +                val messageContent = event.root.getClearContent().toModel()
                     messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE
                 } else {
                     true
                 }
                 if (!filterEdits) return@filter false
     
    -            val filterRedacted = settings.filters.filterRedacted && it.root.isRedacted()
    +            val filterRedacted = settings.filters.filterRedacted && event.root.isRedacted()
                 !filterRedacted
             }
         }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
    index 6fe6013108..fa517bebf2 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
    @@ -151,8 +151,25 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
             beginGroup()
             var needOr = false
             if (settings.filters.filterTypes) {
    -            val allowedTypes = settings.filters.allowedTypes.map { it.eventType }.toTypedArray()
    -            not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", allowedTypes)
    +            beginGroup()
    +            // Events: A, B, C, D, (E and S1), F, G, (H and S1), I
    +            // Allowed: A, B, C, (E and S1), G, (H and S2)
    +            // Result: D, F, H, I
    +            settings.filters.allowedTypes.forEachIndexed { index, filter ->
    +                if (filter.stateKey == null) {
    +                    notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", filter.eventType)
    +                } else {
    +                    beginGroup()
    +                    notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", filter.eventType)
    +                    or()
    +                    notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.STATE_KEY}", filter.stateKey)
    +                    endGroup()
    +                }
    +                if (index != settings.filters.allowedTypes.size - 1) {
    +                    and()
    +                }
    +            }
    +            endGroup()
                 needOr = true
             }
             if (settings.filters.filterUseless) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt
    index ff396646c0..1983b05ed3 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt
    @@ -40,7 +40,7 @@ class TimelineSettingsFactory @Inject constructor(
                                 filterTypes = false),
                         buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
             } else {
    -            val allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES.filterDisplayableTypes()
    +            val allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES.createAllowedEventTypeFilters()
                 TimelineSettings(
                         initialSize = 30,
                         filters = TimelineEventFilters(
    @@ -53,7 +53,7 @@ class TimelineSettingsFactory @Inject constructor(
             }
         }
     
    -    private fun List.filterDisplayableTypes(): List {
    +    private fun List.createAllowedEventTypeFilters(): List {
             return map {
                 EventTypeFilter(
                         eventType = it,
    
    From 9b0c2e420d1b9499c5dd99267e3138aae953eb29 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 11 Dec 2020 13:18:22 +0100
    Subject: [PATCH 129/218] Reorder Views (no other change)
    
    ---
     .../src/main/res/layout/composer_layout.xml   | 56 +++++++--------
     ...composer_layout_constraint_set_compact.xml | 72 +++++++++----------
     ...omposer_layout_constraint_set_expanded.xml | 46 ++++++------
     3 files changed, 87 insertions(+), 87 deletions(-)
    
    diff --git a/vector/src/main/res/layout/composer_layout.xml b/vector/src/main/res/layout/composer_layout.xml
    index a1d6675278..cb0b37d844 100644
    --- a/vector/src/main/res/layout/composer_layout.xml
    +++ b/vector/src/main/res/layout/composer_layout.xml
    @@ -39,13 +39,6 @@
             tools:ignore="MissingConstraints"
             tools:src="@tools:sample/avatars" />
     
    -    
    -
         
     
    -    
    -
    -    
    -
         
     
         
     
    +    
    +
         
     
    +    
    +
    +    
    +
     
    diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    index 2de3efc8ee..a4dfcf019c 100644
    --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
    @@ -95,6 +95,16 @@
             tools:ignore="MissingPrefix"
             tools:visibility="visible" />
     
    +    
    +
         
     
    -    
    -
    -    
    -
    -    
    -
         
     
    +    
    +
    +    
    +
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml b/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml
    index 75dbdfd2a8..8a76c0547e 100644
    --- a/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml
    +++ b/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml
    @@ -100,6 +100,16 @@
             app:tint="@color/riotx_notice"
             tools:ignore="MissingPrefix" />
     
    +    
    +
         
     
    +    
    +
         
     
    -    
    -
         
     
    -    
    -
     
    \ No newline at end of file
    
    From bd9da8eaa6dddf74e23f678e01539c01955d462b Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 11 Dec 2020 15:06:22 +0100
    Subject: [PATCH 130/218] element:// support + basic peeking + fix join via
     server
    
    ---
     .../org/matrix/android/sdk/rx/RxSession.kt    |   3 +-
     .../session/permalinks/PermalinkService.kt    |   1 +
     .../sdk/api/session/room/RoomService.kt       |  14 +-
     .../session/room/DefaultRoomService.kt        |  25 ++-
     .../sdk/internal/session/room/RoomAPI.kt      |   3 +
     .../sdk/internal/session/room/RoomModule.kt   |  10 +
     .../room/alias/GetRoomIdByAliasTask.kt        |  14 +-
     .../room/alias/RoomAliasDescription.kt        |   2 +-
     .../session/room/peeking/PeekRoomTask.kt      | 173 ++++++++++++++++++
     .../room/peeking/ResolveRoomStateTask.kt      |  42 +++++
     vector/src/main/AndroidManifest.xml           |   5 +
     .../vector/app/features/home/HomeActivity.kt  |  34 +++-
     .../features/permalink/PermalinkHandler.kt    |  41 +++--
     .../app/features/popup/PopupAlertManager.kt   |   2 +-
     .../roompreview/RoomPreviewActivity.kt        |   1 +
     .../RoomPreviewNoPreviewFragment.kt           |  76 ++++++--
     .../roompreview/RoomPreviewViewModel.kt       |  56 +++++-
     .../roompreview/RoomPreviewViewState.kt       |  28 ++-
     .../fragment_room_preview_no_preview.xml      |  10 +-
     vector/src/main/res/values/strings.xml        |   3 +-
     20 files changed, 488 insertions(+), 55 deletions(-)
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
    
    diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
    index 0e5b88adb2..a7b269fcc6 100644
    --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
    +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
    @@ -47,6 +47,7 @@ import org.matrix.android.sdk.api.util.toOptional
     import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
     import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
     import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
    +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
     
     class RxSession(private val session: Session) {
     
    @@ -139,7 +140,7 @@ class RxSession(private val session: Session) {
         }
     
         fun getRoomIdByAlias(roomAlias: String,
    -                         searchOnServer: Boolean): Single> = singleBuilder {
    +                         searchOnServer: Boolean): Single> = singleBuilder {
             session.getRoomIdByAlias(roomAlias, searchOnServer, it)
         }
     
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
    index ac1d726d03..aefc086b43 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
    @@ -25,6 +25,7 @@ interface PermalinkService {
     
         companion object {
             const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
    +        const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
         }
     
         /**
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
    index 477bef66cf..441a64e2c0 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
    @@ -18,12 +18,15 @@ package org.matrix.android.sdk.api.session.room
     
     import androidx.lifecycle.LiveData
     import org.matrix.android.sdk.api.MatrixCallback
    +import org.matrix.android.sdk.api.session.events.model.Event
     import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
     import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
     import org.matrix.android.sdk.api.util.Cancelable
     import org.matrix.android.sdk.api.util.Optional
    +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
    +import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
     
     /**
      * This interface defines methods to get rooms. It's implemented at the session level.
    @@ -120,7 +123,7 @@ interface RoomService {
          */
         fun getRoomIdByAlias(roomAlias: String,
                              searchOnServer: Boolean,
    -                         callback: MatrixCallback>): Cancelable
    +                         callback: MatrixCallback>): Cancelable
     
         /**
          * Delete a room alias
    @@ -163,4 +166,13 @@ interface RoomService {
          * @return a LiveData of the optional found room member
          */
         fun getRoomMemberLive(userId: String, roomId: String): LiveData>
    +
    +    fun getRoomState(roomId: String, callback: MatrixCallback>)
    +
    +    /**
    +     * Use this if you want to get information from a room that you are not yet in (or invited)
    +     * It might be possible to get some information on this room if it is public or if guest access is allowed
    +     * This call will try to gather some information on this room, but it could fail and get nothing more
    +     */
    +    fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback)
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
    index 9ec985e0b6..e540105800 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
    @@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
     import androidx.lifecycle.Transformations
     import com.zhuinden.monarchy.Monarchy
     import org.matrix.android.sdk.api.MatrixCallback
    +import org.matrix.android.sdk.api.session.events.model.Event
     import org.matrix.android.sdk.api.session.room.Room
     import org.matrix.android.sdk.api.session.room.RoomService
     import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
    @@ -35,10 +36,14 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie
     import org.matrix.android.sdk.internal.di.SessionDatabase
     import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
     import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
    +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
     import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
     import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
     import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
     import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
    +import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
    +import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
    +import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
     import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
     import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
     import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
    @@ -55,6 +60,8 @@ internal class DefaultRoomService @Inject constructor(
             private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
             private val roomIdByAliasTask: GetRoomIdByAliasTask,
             private val deleteRoomAliasTask: DeleteRoomAliasTask,
    +        private val resolveRoomStateTask: ResolveRoomStateTask,
    +        private val peekRoomTask: PeekRoomTask,
             private val roomGetter: RoomGetter,
             private val roomSummaryDataSource: RoomSummaryDataSource,
             private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
    @@ -119,7 +126,7 @@ internal class DefaultRoomService @Inject constructor(
                     .executeBy(taskExecutor)
         }
     
    -    override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback>): Cancelable {
    +    override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback>): Cancelable {
             return roomIdByAliasTask
                     .configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
                         this.callback = callback
    @@ -154,4 +161,20 @@ internal class DefaultRoomService @Inject constructor(
                 results.firstOrNull().toOptional()
             }
         }
    +
    +    override fun getRoomState(roomId: String, callback: MatrixCallback>) {
    +        resolveRoomStateTask
    +                .configureWith(ResolveRoomStateTask.Params(roomId)) {
    +                    this.callback = callback
    +                }
    +                .executeBy(taskExecutor)
    +    }
    +
    +    override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) {
    +        peekRoomTask
    +                .configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
    +                    this.callback = callback
    +                }
    +                .executeBy(taskExecutor)
    +    }
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
    index 955a251b52..f335987085 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
    @@ -354,4 +354,7 @@ internal interface RoomAPI {
         fun deleteTag(@Path("userId") userId: String,
                       @Path("roomId") roomId: String,
                       @Path("tag") tag: String): Call
    +
    +    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
    +    fun getRoomState(@Path("roomId") roomId: String) : Call>
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
    index 3a94396a61..92f4ea2aea 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
    @@ -57,6 +57,10 @@ import org.matrix.android.sdk.internal.session.room.membership.leaving.DefaultLe
     import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
     import org.matrix.android.sdk.internal.session.room.membership.threepid.DefaultInviteThreePidTask
     import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask
    +import org.matrix.android.sdk.internal.session.room.peeking.DefaultPeekRoomTask
    +import org.matrix.android.sdk.internal.session.room.peeking.DefaultResolveRoomStateTask
    +import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
    +import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
     import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask
     import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask
     import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
    @@ -223,4 +227,10 @@ internal abstract class RoomModule {
     
         @Binds
         abstract fun bindDeleteTagFromRoomTask(task: DefaultDeleteTagFromRoomTask): DeleteTagFromRoomTask
    +
    +    @Binds
    +    abstract fun bindResolveRoomStateTask(task: DefaultResolveRoomStateTask): ResolveRoomStateTask
    +
    +    @Binds
    +    abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
    index 3c47ee6ef0..2fe290ead2 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
    @@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
     import org.matrix.android.sdk.internal.task.Task
     import javax.inject.Inject
     
    -internal interface GetRoomIdByAliasTask : Task> {
    +internal interface GetRoomIdByAliasTask : Task> {
         data class Params(
                 val roomAlias: String,
                 val searchOnServer: Boolean
    @@ -42,21 +42,21 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
             private val eventBus: EventBus
     ) : GetRoomIdByAliasTask {
     
    -    override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional {
    +    override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional {
             var roomId = Realm.getInstance(monarchy.realmConfiguration).use {
                 RoomSummaryEntity.findByAlias(it, params.roomAlias)?.roomId
             }
             return if (roomId != null) {
    -            Optional.from(roomId)
    +            Optional.from(RoomAliasDescription(roomId))
             } else if (!params.searchOnServer) {
    -            Optional.from(null)
    +            Optional.from(null)
             } else {
    -            roomId = tryOrNull("## Failed to get roomId from alias") {
    +            val description  = tryOrNull("## Failed to get roomId from alias") {
                     executeRequest(eventBus) {
                         apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
                     }
    -            }?.roomId
    -            Optional.from(roomId)
    +            }
    +            Optional.from(description)
             }
         }
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt
    index ada3839fa0..d1f93c50be 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt
    @@ -20,7 +20,7 @@ import com.squareup.moshi.Json
     import com.squareup.moshi.JsonClass
     
     @JsonClass(generateAdapter = true)
    -internal data class RoomAliasDescription(
    +data class RoomAliasDescription(
             /**
              * The room ID for this alias.
              */
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    new file mode 100644
    index 0000000000..ae557adb5f
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    @@ -0,0 +1,173 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.internal.session.room.peeking
    +
    +import org.matrix.android.sdk.api.MatrixPatterns
    +import org.matrix.android.sdk.api.session.events.model.EventType
    +import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
    +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
    +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
    +import org.matrix.android.sdk.api.session.room.model.RoomNameContent
    +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
    +import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
    +import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
    +import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
    +import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
    +import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
    +import org.matrix.android.sdk.internal.task.Task
    +import javax.inject.Inject
    +
    +sealed class PeekResult {
    +    data class Success(
    +            val roomId: String,
    +            val alias: String?,
    +            val name: String?,
    +            val topic: String?,
    +            val avatarUrl: String?,
    +            val numJoinedMembers: Int?,
    +            val viaServers: List
    +    ) : PeekResult()
    +
    +    data class PeekingNotAllowed(
    +            val roomId: String,
    +            val alias: String?,
    +            val viaServers: List
    +    ) : PeekResult()
    +
    +    object UnknownAlias : PeekResult()
    +}
    +
    +internal interface PeekRoomTask : Task {
    +    data class Params(
    +            val roomIdOrAlias: String
    +    )
    +}
    +
    +internal class DefaultPeekRoomTask @Inject constructor(
    +        private val getRoomIdByAliasTask: GetRoomIdByAliasTask,
    +        private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
    +        private val getPublicRoomTask: GetPublicRoomTask,
    +        private val resolveRoomStateTask: ResolveRoomStateTask
    +) : PeekRoomTask {
    +
    +    override suspend fun execute(params: PeekRoomTask.Params): PeekResult {
    +        val roomId: String?
    +        val serverList: List
    +        val isAlias: Boolean
    +        if (MatrixPatterns.isRoomAlias(params.roomIdOrAlias)) {
    +            isAlias = true
    +            // get alias description
    +            val aliasDescription = getRoomIdByAliasTask
    +                    .execute(GetRoomIdByAliasTask.Params(params.roomIdOrAlias, true))
    +                    .getOrNull()
    +                    ?: return PeekResult.UnknownAlias
    +
    +            roomId = aliasDescription.roomId
    +            serverList = aliasDescription.servers
    +        } else {
    +            isAlias = false
    +            roomId = params.roomIdOrAlias
    +            serverList = emptyList()
    +        }
    +
    +        // Is it a public room?
    +        val publicRepoResult = when (getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))) {
    +            RoomDirectoryVisibility.PRIVATE -> {
    +                // We cannot resolve this room :/
    +                null
    +            }
    +            RoomDirectoryVisibility.PUBLIC -> {
    +                // Try to find it in directory
    +                val filter = if (isAlias) PublicRoomsFilter(searchTerm = params.roomIdOrAlias.substring(1))
    +                else null
    +
    +                getPublicRoomTask.execute(GetPublicRoomTask.Params(
    +                        server = serverList.firstOrNull(),
    +                        publicRoomsParams = PublicRoomsParams(
    +                                filter = filter,
    +                                limit = 20.takeIf { filter != null } ?: 100
    +                        )
    +                )).chunk?.firstOrNull { it.roomId == roomId }
    +            }
    +        }
    +
    +        if (publicRepoResult != null) {
    +            return PeekResult.Success(
    +                    roomId = roomId,
    +                    alias = publicRepoResult.getPrimaryAlias() ?: params.roomIdOrAlias.takeIf { isAlias },
    +                    avatarUrl = publicRepoResult.avatarUrl,
    +                    name = publicRepoResult.name,
    +                    topic = publicRepoResult.topic,
    +                    numJoinedMembers = publicRepoResult.numJoinedMembers,
    +                    viaServers = serverList
    +            )
    +        }
    +
    +        // mm... try to peek state ? maybe the room is not public but yet allow guest to get events?
    +        // this could be slow
    +        try {
    +            val stateEvents = resolveRoomStateTask
    +                    .execute(ResolveRoomStateTask.Params(roomId))
    +            val name = stateEvents.lastOrNull {
    +                it.type == EventType.STATE_ROOM_NAME
    +                        && it.stateKey == ""
    +            }?.let { it.content?.toModel()?.name }
    +
    +            val topic = stateEvents.lastOrNull {
    +                it.type == EventType.STATE_ROOM_TOPIC
    +                        && it.stateKey == ""
    +            }?.let { it.content?.toModel()?.topic }
    +
    +            val avatarUrl = stateEvents.lastOrNull {
    +                it.type == EventType.STATE_ROOM_AVATAR
    +            }?.let { it.content?.toModel()?.avatarUrl }
    +
    +            val alias = stateEvents.lastOrNull {
    +                it.type == EventType.STATE_ROOM_CANONICAL_ALIAS
    +            }?.let {
    +                it.content?.toModel()?.canonicalAlias
    +                        ?: it.content?.toModel()?.alternativeAliases?.firstOrNull()
    +            }
    +
    +            // not sure if it's the right way to do that :/
    +            val memberCount = stateEvents.filter {
    +                it.type == EventType.STATE_ROOM_MEMBER
    +                        && it.stateKey?.isNotEmpty() == true
    +            }.distinctBy { it.stateKey }
    +                    .count()
    +
    +            return PeekResult.Success(
    +                    roomId = roomId,
    +                    alias = alias,
    +                    avatarUrl = avatarUrl,
    +                    name = name,
    +                    topic = topic,
    +                    numJoinedMembers = memberCount,
    +                    viaServers = serverList
    +            )
    +        } catch (failure: Throwable) {
    +            // Would be M_FORBIDDEN if cannot peek :/
    +            // User XXX not in room !XXX, and room previews are disabled
    +            return PeekResult.PeekingNotAllowed(
    +                    roomId = roomId,
    +                    alias = params.roomIdOrAlias.takeIf { isAlias },
    +                    viaServers = serverList
    +            )
    +        }
    +    }
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
    new file mode 100644
    index 0000000000..289fdb498f
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
    @@ -0,0 +1,42 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.internal.session.room.peeking
    +
    +import org.greenrobot.eventbus.EventBus
    +import org.matrix.android.sdk.api.session.events.model.Event
    +import org.matrix.android.sdk.internal.network.executeRequest
    +import org.matrix.android.sdk.internal.session.room.RoomAPI
    +import org.matrix.android.sdk.internal.task.Task
    +import javax.inject.Inject
    +
    +internal interface ResolveRoomStateTask : Task> {
    +    data class Params(
    +            val roomId: String
    +    )
    +}
    +
    +internal class DefaultResolveRoomStateTask @Inject constructor(
    +        private val roomAPI: RoomAPI,
    +        private val eventBus: EventBus
    +) : ResolveRoomStateTask {
    +
    +    override suspend fun execute(params: ResolveRoomStateTask.Params): List {
    +        return executeRequest(eventBus) {
    +            apiCall = roomAPI.getRoomState(params.roomId)
    +        }
    +    }
    +}
    diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
    index e9bd03cb4b..fc3c7366ec 100644
    --- a/vector/src/main/AndroidManifest.xml
    +++ b/vector/src/main/AndroidManifest.xml
    @@ -199,6 +199,11 @@
                     
                     
                     
    +                
    +                
    +
                 
             
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    index 7dde0edf32..4ec786f841 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    @@ -126,9 +126,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
                     .observe()
                     .subscribe { sharedAction ->
                         when (sharedAction) {
    -                        is HomeActivitySharedAction.OpenDrawer  -> drawerLayout.openDrawer(GravityCompat.START)
    +                        is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
                             is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START)
    -                        is HomeActivitySharedAction.OpenGroup   -> {
    +                        is HomeActivitySharedAction.OpenGroup -> {
                                 drawerLayout.closeDrawer(GravityCompat.START)
                                 replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
                             }
    @@ -145,9 +145,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
             homeActivityViewModel.observeViewEvents {
                 when (it) {
                     is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
    -                is HomeActivityViewEvents.OnNewSession                  -> handleOnNewSession(it)
    -                HomeActivityViewEvents.PromptToEnableSessionPush        -> handlePromptToEnablePush()
    -                is HomeActivityViewEvents.OnCrossSignedInvalidated      -> handleCrossSigningInvalidated(it)
    +                is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
    +                HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
    +                is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
                 }.exhaustive
             }
             homeActivityViewModel.subscribe(this) { renderState(it) }
    @@ -162,9 +162,27 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
     
         private fun handleIntent(intent: Intent?) {
             intent?.dataString?.let { deepLink ->
    -            if (!deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) return@let
    +            val resolvedLink = if (deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) {
    +                deepLink
    +            } else if (deepLink.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE)) {
    +                // This is a bit hugly, but for now just convert to matrix.to link for compatibility
    +                val service = activeSessionHolder.getSafeActiveSession()?.permalinkService()
    +                val roomLinkPrefix = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
    +                val userPrefix = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
    +                when {
    +                    deepLink.startsWith(userPrefix)     -> {
    +                        val userId = deepLink.substring(userPrefix.length)
    +                        service?.createPermalink(userId)
    +                    }
    +                    deepLink.startsWith(roomLinkPrefix) -> {
    +                        val param = deepLink.substring(roomLinkPrefix.length)
    +                        service?.createRoomPermalink(param)
    +                    }
    +                    else                                -> null
    +                }
    +            } else null
     
    -            permalinkHandler.launch(this, deepLink,
    +            permalinkHandler.launch(this, resolvedLink,
                         navigationInterceptor = this,
                         buildTask = true)
                         // .delay(500, TimeUnit.MILLISECONDS)
    @@ -180,7 +198,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
     
         private fun renderState(state: HomeActivityViewState) {
             when (val status = state.initialSyncProgressServiceStatus) {
    -            is InitialSyncProgressService.Status.Idle        -> {
    +            is InitialSyncProgressService.Status.Idle -> {
                     waiting_view.isVisible = false
                 }
                 is InitialSyncProgressService.Status.Progressing -> {
    diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    index f1149d8990..4e8b73cb2b 100644
    --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    @@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkData
     import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
     import org.matrix.android.sdk.api.session.room.model.Membership
     import org.matrix.android.sdk.api.util.Optional
    +import org.matrix.android.sdk.api.util.toOptional
     import org.matrix.android.sdk.rx.rx
     import javax.inject.Inject
     
    @@ -76,7 +77,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
                 buildTask: Boolean
         ): Single {
             return when (permalinkData) {
    -            is PermalinkData.RoomLink     -> {
    +            is PermalinkData.RoomLink -> {
                     permalinkData.getRoomId()
                             .observeOn(AndroidSchedulers.mainThread())
                             .map {
    @@ -92,11 +93,11 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
                                 true
                             }
                 }
    -            is PermalinkData.GroupLink    -> {
    +            is PermalinkData.GroupLink -> {
                     navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
                     Single.just(true)
                 }
    -            is PermalinkData.UserLink     -> {
    +            is PermalinkData.UserLink -> {
                     if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
                         navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
                     }
    @@ -111,7 +112,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
         private fun PermalinkData.RoomLink.getRoomId(): Single> {
             val session = activeSessionHolder.getSafeActiveSession()
             return if (isRoomAlias && session != null) {
    -            session.rx().getRoomIdByAlias(roomIdOrAlias, true).subscribeOn(Schedulers.io())
    +            session.rx().getRoomIdByAlias(roomIdOrAlias, true).map { it.getOrNull()?.roomId.toOptional() }.subscribeOn(Schedulers.io())
             } else {
                 Single.just(Optional.from(roomIdOrAlias))
             }
    @@ -149,16 +150,28 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
                     navigator.openRoom(context, roomId, eventId, buildTask)
                 }
                 else                             -> {
    -                val roomPreviewData = RoomPreviewData(
    -                        roomId = roomId,
    -                        eventId = eventId,
    -                        roomAlias = roomAlias ?: roomSummary?.canonicalAlias,
    -                        roomName = roomSummary?.displayName,
    -                        avatarUrl = roomSummary?.avatarUrl,
    -                        buildTask = buildTask,
    -                        homeServers = permalinkData.viaParameters
    -                )
    -                navigator.openRoomPreview(context, roomPreviewData)
    +                if (roomSummary == null) {
    +                    // we don't know this room, try to peek
    +                    val roomPreviewData = RoomPreviewData(
    +                            roomId = roomId,
    +                            roomAlias = roomAlias,
    +                            peekFromServer = true,
    +                            buildTask = buildTask,
    +                            homeServers = permalinkData.viaParameters
    +                    )
    +                    navigator.openRoomPreview(context, roomPreviewData)
    +                } else {
    +                    val roomPreviewData = RoomPreviewData(
    +                            roomId = roomId,
    +                            eventId = eventId,
    +                            roomAlias = roomAlias ?: roomSummary.canonicalAlias,
    +                            roomName = roomSummary.displayName,
    +                            avatarUrl = roomSummary.avatarUrl,
    +                            buildTask = buildTask,
    +                            homeServers = permalinkData.viaParameters
    +                    )
    +                    navigator.openRoomPreview(context, roomPreviewData)
    +                }
                 }
             }
         }
    diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
    index b2257b250a..c677a99175 100644
    --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
    +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
    @@ -130,7 +130,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy = emptyList(),
    +        val peekFromServer: Boolean = false,
             val buildTask: Boolean = false
     ) : Parcelable {
         val matrixItem: MatrixItem
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt
    index 108c3bacf1..bc4552fc11 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt
    @@ -20,6 +20,8 @@ import android.os.Bundle
     import android.view.View
     import androidx.core.view.isVisible
     import androidx.transition.TransitionManager
    +import com.airbnb.mvrx.Loading
    +import com.airbnb.mvrx.Success
     import com.airbnb.mvrx.args
     import com.airbnb.mvrx.fragmentViewModel
     import com.airbnb.mvrx.withState
    @@ -30,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.roomdirectory.JoinState
     import kotlinx.android.synthetic.main.fragment_room_preview_no_preview.*
    +import org.matrix.android.sdk.api.util.MatrixItem
     import javax.inject.Inject
     
     /**
    @@ -48,22 +51,6 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
         override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
             super.onViewCreated(view, savedInstanceState)
             setupToolbar(roomPreviewNoPreviewToolbar)
    -        val titleText = roomPreviewData.roomName ?: roomPreviewData.roomAlias ?: roomPreviewData.roomId
    -
    -        // Toolbar
    -        avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewToolbarAvatar)
    -        roomPreviewNoPreviewToolbarTitle.text = titleText
    -
    -        // Screen
    -        avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewAvatar)
    -        roomPreviewNoPreviewName.text = titleText
    -        roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic)
    -
    -        if (roomPreviewData.worldReadable) {
    -            roomPreviewNoPreviewLabel.setText(R.string.room_preview_world_readable_room_not_supported_yet)
    -        } else {
    -            roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview)
    -        }
     
             roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback {
                 override fun onButtonClicked() {
    @@ -100,7 +87,62 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
                 // Quit this screen
                 requireActivity().finish()
                 // Open room
    -            navigator.openRoom(requireActivity(), roomPreviewData.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
    +            navigator.openRoom(requireActivity(), state.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
    +        }
    +
    +        val bestName = state.roomName ?: state.roomAlias ?: state.roomId
    +        when (state.peekingState) {
    +            is Loading -> {
    +                roomPreviewPeekingProgress.isVisible = true
    +                roomPreviewNoPreviewJoin.isVisible = false
    +            }
    +            is Success -> {
    +                roomPreviewPeekingProgress.isVisible = false
    +                when (state.peekingState.invoke()) {
    +                    PeekingState.FOUND     -> {
    +                        // show join buttons
    +                        roomPreviewNoPreviewJoin.isVisible = true
    +                        renderState(bestName, state.matrixItem(), state.roomTopic)
    +                    }
    +                    PeekingState.NO_ACCESS -> {
    +                        roomPreviewNoPreviewJoin.isVisible = true
    +                        roomPreviewNoPreviewLabel.isVisible = true
    +                        roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join)
    +                        renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic)
    +                    }
    +                    else                   -> {
    +                        roomPreviewNoPreviewJoin.isVisible = false
    +                        roomPreviewNoPreviewLabel.isVisible = true
    +                        roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found)
    +                        renderState(bestName, null, state.roomTopic)
    +                    }
    +                }
    +            }
    +            else       -> {
    +                // Render with initial state, no peeking
    +                roomPreviewPeekingProgress.isVisible = false
    +                roomPreviewNoPreviewJoin.isVisible = true
    +                renderState(bestName, state.matrixItem(), state.roomTopic)
    +                roomPreviewNoPreviewLabel.isVisible = false
    +            }
             }
         }
    +
    +    private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) {
    +        // Toolbar
    +        if (matrixItem != null) {
    +            roomPreviewNoPreviewToolbarAvatar.isVisible = true
    +            roomPreviewNoPreviewAvatar.isVisible = true
    +            avatarRenderer.render(matrixItem, roomPreviewNoPreviewToolbarAvatar)
    +            avatarRenderer.render(matrixItem, roomPreviewNoPreviewAvatar)
    +        } else {
    +            roomPreviewNoPreviewToolbarAvatar.isVisible = false
    +            roomPreviewNoPreviewAvatar.isVisible = false
    +        }
    +        roomPreviewNoPreviewToolbarTitle.text = roomName
    +
    +        // Screen
    +        roomPreviewNoPreviewName.text = roomName
    +        roomPreviewNoPreviewTopic.setTextOrHide(topic)
    +    }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    index 900ba537b5..a18a41285b 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    @@ -16,8 +16,11 @@
     
     package im.vector.app.features.roomdirectory.roompreview
     
    +import androidx.lifecycle.viewModelScope
     import com.airbnb.mvrx.FragmentViewModelContext
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.MvRxViewModelFactory
    +import com.airbnb.mvrx.Success
     import com.airbnb.mvrx.ViewModelContext
     import com.squareup.inject.assisted.Assisted
     import com.squareup.inject.assisted.AssistedInject
    @@ -25,12 +28,17 @@ import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.EmptyViewEvents
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.roomdirectory.JoinState
    +import kotlinx.coroutines.Dispatchers
    +import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.MatrixCallback
    +import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.query.QueryStringValue
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.Membership
     import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
    +import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
    +import org.matrix.android.sdk.internal.util.awaitCallback
     import org.matrix.android.sdk.rx.rx
     import timber.log.Timber
     
    @@ -56,6 +64,52 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
             // Observe joined room (from the sync)
             observeRoomSummary()
             observeMembershipChanges()
    +
    +        if (initialState.shouldPeekFromServer) {
    +            setState {
    +                copy(peekingState = Loading())
    +            }
    +            viewModelScope.launch(Dispatchers.IO) {
    +                val peekResult = tryOrNull {
    +                    awaitCallback {
    +                        session.peekRoom(initialState.roomAlias ?: initialState.roomId, it)
    +                    }
    +                }
    +
    +                when (peekResult) {
    +                    is PeekResult.Success -> {
    +                        setState {
    +                            copy(
    +                                    roomId = peekResult.roomId,
    +                                    avatarUrl = peekResult.avatarUrl,
    +                                    roomAlias = peekResult.alias ?: initialState.roomAlias,
    +                                    roomTopic = peekResult.topic,
    +                                    homeServers = peekResult.viaServers,
    +                                    peekingState = Success(PeekingState.FOUND)
    +                            )
    +                        }
    +                    }
    +                    is PeekResult.PeekingNotAllowed -> {
    +                        setState {
    +                            copy(
    +                                    roomId = peekResult.roomId,
    +                                    roomAlias = peekResult.alias ?: initialState.roomAlias,
    +                                    homeServers = peekResult.viaServers,
    +                                    peekingState = Success(PeekingState.NO_ACCESS)
    +                            )
    +                        }
    +                    }
    +                    PeekResult.UnknownAlias,
    +                    null -> {
    +                        setState {
    +                            copy(
    +                                    peekingState = Success(PeekingState.NOT_FOUND)
    +                            )
    +                        }
    +                    }
    +                }
    +            }
    +        }
         }
     
         private fun observeRoomSummary() {
    @@ -82,7 +136,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
                     .subscribe {
                         val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown
                         val joinState = when (changeMembership) {
    -                        is ChangeMembershipState.Joining       -> JoinState.JOINING
    +                        is ChangeMembershipState.Joining -> JoinState.JOINING
                             is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR
                             // Other cases are handled by room summary
                             else                                   -> null
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt
    index 6816e54481..c0f7591f87 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt
    @@ -16,13 +16,31 @@
     
     package im.vector.app.features.roomdirectory.roompreview
     
    +import com.airbnb.mvrx.Async
     import com.airbnb.mvrx.MvRxState
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.features.roomdirectory.JoinState
    +import org.matrix.android.sdk.api.util.MatrixItem
    +
    +enum class PeekingState {
    +    UNKNOWN,
    +    FOUND,
    +    NOT_FOUND,
    +    NO_ACCESS
    +}
     
     data class RoomPreviewViewState(
    +
    +        val peekingState: Async = Uninitialized,
             // The room id
             val roomId: String = "",
             val roomAlias: String? = null,
    +
    +        val roomName: String? = null,
    +        val roomTopic: String? = null,
    +        val avatarUrl: String? = null,
    +
    +        val shouldPeekFromServer: Boolean = false,
             /**
              * Can be empty when the server is the current user's home server.
              */
    @@ -36,6 +54,14 @@ data class RoomPreviewViewState(
         constructor(args: RoomPreviewData) : this(
                 roomId = args.roomId,
                 roomAlias = args.roomAlias,
    -            homeServers = args.homeServers
    +            homeServers = args.homeServers,
    +            roomName = args.roomName,
    +            roomTopic = args.topic,
    +            avatarUrl = args.avatarUrl,
    +            shouldPeekFromServer = args.peekFromServer
         )
    +
    +    fun matrixItem() : MatrixItem {
    +        return MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
    +    }
     }
    diff --git a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml
    index 2f4db6f116..906c7a21ab 100644
    --- a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml
    +++ b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml
    @@ -54,6 +54,14 @@
     
             
     
    +        
     
             
     
                     "This room can't be previewed"
         "The preview of world-readable room is not supported yet in Element"
    -
    +    This room is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.
    +    "This room can't be previewed. Do you want to join it?"
         "Rooms"
         "Direct Messages"
     
    
    From 544345bbf34654f822a7f354bd49381817b7f368 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 11 Dec 2020 15:16:10 +0100
    Subject: [PATCH 131/218] Update change log
    
    ---
     CHANGES.md | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 1381a1e7ca..18ebc1dcbd 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -9,11 +9,13 @@ Features ✨:
     Improvements 🙌:
      - Add Setting Item to Change PIN (#2462)
      - Improve room history visibility setting UX (#1579)
    + - Matrix.to deeplink custom scheme support
     
     Bugfix 🐛:
      - Fix cancellation of sending event (#2438)
      - Double bottomsheet effect after verify with passphrase
      - EditText cursor jumps to the start while typing fast (#2469)
    + - No known servers error is given when joining rooms on new Gitter bridge (#2516)
     
     Translations 🗣:
      -
    
    From 5461fd40609d504f4a330483cf4340b8484d56c2 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 11 Dec 2020 16:35:04 +0100
    Subject: [PATCH 132/218] Some cleanup up
    
    ---
     .../sdk/api/session/room/RoomService.kt       |  5 +-
     .../api/session/room/peeking/PeekResult.kt    | 37 +++++++++
     .../session/room/DefaultRoomService.kt        |  2 +-
     .../sdk/internal/session/room/RoomAPI.kt      | 14 ++--
     .../room/alias/GetRoomIdByAliasTask.kt        |  4 +-
     .../session/room/peeking/PeekRoomTask.kt      | 75 ++++++-----------
     .../room/peeking/ResolveRoomStateTask.kt      |  2 +-
     vector/src/main/AndroidManifest.xml           | 20 +++--
     .../vector/app/features/home/HomeActivity.kt  | 55 ++++++-------
     .../features/permalink/PermalinkHandler.kt    |  6 +-
     .../roomdirectory/roompreview/PeekingState.kt | 23 ++++++
     .../roompreview/RoomPreviewViewModel.kt       | 80 ++++++++++---------
     .../roompreview/RoomPreviewViewState.kt       |  8 --
     13 files changed, 188 insertions(+), 143 deletions(-)
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
     create mode 100644 vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/PeekingState.kt
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
    index 441a64e2c0..5f02b77a1e 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
    @@ -23,10 +23,10 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
     import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
    +import org.matrix.android.sdk.api.session.room.peeking.PeekResult
     import org.matrix.android.sdk.api.util.Cancelable
     import org.matrix.android.sdk.api.util.Optional
     import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
    -import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
     
     /**
      * This interface defines methods to get rooms. It's implemented at the session level.
    @@ -167,6 +167,9 @@ interface RoomService {
          */
         fun getRoomMemberLive(userId: String, roomId: String): LiveData>
     
    +    /**
    +     * Get some state events about a room
    +     */
         fun getRoomState(roomId: String, callback: MatrixCallback>)
     
         /**
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
    new file mode 100644
    index 0000000000..db70dadef3
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
    @@ -0,0 +1,37 @@
    +/*
    + * Copyright 2020 The Matrix.org Foundation C.I.C.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.api.session.room.peeking
    +
    +sealed class PeekResult {
    +    data class Success(
    +            val roomId: String,
    +            val alias: String?,
    +            val name: String?,
    +            val topic: String?,
    +            val avatarUrl: String?,
    +            val numJoinedMembers: Int?,
    +            val viaServers: List
    +    ) : PeekResult()
    +
    +    data class PeekingNotAllowed(
    +            val roomId: String,
    +            val alias: String?,
    +            val viaServers: List
    +    ) : PeekResult()
    +
    +    object UnknownAlias : PeekResult()
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
    index e540105800..383dd876d3 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
    @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
     import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
    +import org.matrix.android.sdk.api.session.room.peeking.PeekResult
     import org.matrix.android.sdk.api.util.Cancelable
     import org.matrix.android.sdk.api.util.Optional
     import org.matrix.android.sdk.api.util.toOptional
    @@ -41,7 +42,6 @@ import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
     import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
     import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
     import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
    -import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
     import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
     import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
     import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
    index f335987085..aa92c1cb3b 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
    @@ -183,7 +183,7 @@ internal interface RoomAPI {
                        @Body body: ThreePidInviteBody): Call
     
         /**
    -     * Send a generic state events
    +     * Send a generic state event
          *
          * @param roomId         the room id.
          * @param stateEventType the state event type
    @@ -195,7 +195,7 @@ internal interface RoomAPI {
                            @Body params: JsonDict): Call
     
         /**
    -     * Send a generic state events
    +     * Send a generic state event
          *
          * @param roomId         the room id.
          * @param stateEventType the state event type
    @@ -208,6 +208,13 @@ internal interface RoomAPI {
                            @Path("state_key") stateKey: String,
                            @Body params: JsonDict): Call
     
    +    /**
    +     * Get state events of a room
    +     * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state
    +     */
    +    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
    +    fun getRoomState(@Path("roomId") roomId: String) : Call>
    +
         /**
          * Send a relation event to a room.
          *
    @@ -354,7 +361,4 @@ internal interface RoomAPI {
         fun deleteTag(@Path("userId") userId: String,
                       @Path("roomId") roomId: String,
                       @Path("tag") tag: String): Call
    -
    -    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
    -    fun getRoomState(@Path("roomId") roomId: String) : Call>
     }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
    index 2fe290ead2..543d605707 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
    @@ -43,13 +43,13 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
     ) : GetRoomIdByAliasTask {
     
         override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional {
    -        var roomId = Realm.getInstance(monarchy.realmConfiguration).use {
    +        val roomId = Realm.getInstance(monarchy.realmConfiguration).use {
                 RoomSummaryEntity.findByAlias(it, params.roomAlias)?.roomId
             }
             return if (roomId != null) {
                 Optional.from(RoomAliasDescription(roomId))
             } else if (!params.searchOnServer) {
    -            Optional.from(null)
    +            Optional.from(null)
             } else {
                 val description  = tryOrNull("## Failed to get roomId from alias") {
                     executeRequest(eventBus) {
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    index ae557adb5f..9fba01efad 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2020 New Vector Ltd
    + * Copyright 2020 The Matrix.org Foundation C.I.C.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    @@ -26,32 +26,13 @@ import org.matrix.android.sdk.api.session.room.model.RoomNameContent
     import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
     import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
     import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
    +import org.matrix.android.sdk.api.session.room.peeking.PeekResult
     import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
     import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
     import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
     import org.matrix.android.sdk.internal.task.Task
     import javax.inject.Inject
     
    -sealed class PeekResult {
    -    data class Success(
    -            val roomId: String,
    -            val alias: String?,
    -            val name: String?,
    -            val topic: String?,
    -            val avatarUrl: String?,
    -            val numJoinedMembers: Int?,
    -            val viaServers: List
    -    ) : PeekResult()
    -
    -    data class PeekingNotAllowed(
    -            val roomId: String,
    -            val alias: String?,
    -            val viaServers: List
    -    ) : PeekResult()
    -
    -    object UnknownAlias : PeekResult()
    -}
    -
     internal interface PeekRoomTask : Task {
         data class Params(
                 val roomIdOrAlias: String
    @@ -66,11 +47,10 @@ internal class DefaultPeekRoomTask @Inject constructor(
     ) : PeekRoomTask {
     
         override suspend fun execute(params: PeekRoomTask.Params): PeekResult {
    -        val roomId: String?
    +        val roomId: String
             val serverList: List
    -        val isAlias: Boolean
    -        if (MatrixPatterns.isRoomAlias(params.roomIdOrAlias)) {
    -            isAlias = true
    +        val isAlias = MatrixPatterns.isRoomAlias(params.roomIdOrAlias)
    +        if (isAlias) {
                 // get alias description
                 val aliasDescription = getRoomIdByAliasTask
                         .execute(GetRoomIdByAliasTask.Params(params.roomIdOrAlias, true))
    @@ -80,7 +60,6 @@ internal class DefaultPeekRoomTask @Inject constructor(
                 roomId = aliasDescription.roomId
                 serverList = aliasDescription.servers
             } else {
    -            isAlias = false
                 roomId = params.roomIdOrAlias
                 serverList = emptyList()
             }
    @@ -91,7 +70,7 @@ internal class DefaultPeekRoomTask @Inject constructor(
                     // We cannot resolve this room :/
                     null
                 }
    -            RoomDirectoryVisibility.PUBLIC -> {
    +            RoomDirectoryVisibility.PUBLIC  -> {
                     // Try to find it in directory
                     val filter = if (isAlias) PublicRoomsFilter(searchTerm = params.roomIdOrAlias.substring(1))
                     else null
    @@ -121,34 +100,30 @@ internal class DefaultPeekRoomTask @Inject constructor(
             // mm... try to peek state ? maybe the room is not public but yet allow guest to get events?
             // this could be slow
             try {
    -            val stateEvents = resolveRoomStateTask
    -                    .execute(ResolveRoomStateTask.Params(roomId))
    -            val name = stateEvents.lastOrNull {
    -                it.type == EventType.STATE_ROOM_NAME
    -                        && it.stateKey == ""
    -            }?.let { it.content?.toModel()?.name }
    +            val stateEvents = resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId))
    +            val name = stateEvents
    +                    .lastOrNull { it.type == EventType.STATE_ROOM_NAME && it.stateKey == "" }
    +                    ?.let { it.content?.toModel()?.name }
     
    -            val topic = stateEvents.lastOrNull {
    -                it.type == EventType.STATE_ROOM_TOPIC
    -                        && it.stateKey == ""
    -            }?.let { it.content?.toModel()?.topic }
    +            val topic = stateEvents
    +                    .lastOrNull { it.type == EventType.STATE_ROOM_TOPIC && it.stateKey == "" }
    +                    ?.let { it.content?.toModel()?.topic }
     
    -            val avatarUrl = stateEvents.lastOrNull {
    -                it.type == EventType.STATE_ROOM_AVATAR
    -            }?.let { it.content?.toModel()?.avatarUrl }
    +            val avatarUrl = stateEvents
    +                    .lastOrNull { it.type == EventType.STATE_ROOM_AVATAR }
    +                    ?.let { it.content?.toModel()?.avatarUrl }
     
    -            val alias = stateEvents.lastOrNull {
    -                it.type == EventType.STATE_ROOM_CANONICAL_ALIAS
    -            }?.let {
    -                it.content?.toModel()?.canonicalAlias
    -                        ?: it.content?.toModel()?.alternativeAliases?.firstOrNull()
    -            }
    +            val alias = stateEvents
    +                    .lastOrNull { it.type == EventType.STATE_ROOM_CANONICAL_ALIAS }
    +                    ?.let {
    +                        it.content?.toModel()?.canonicalAlias
    +                                ?: it.content?.toModel()?.alternativeAliases?.firstOrNull()
    +                    }
     
                 // not sure if it's the right way to do that :/
    -            val memberCount = stateEvents.filter {
    -                it.type == EventType.STATE_ROOM_MEMBER
    -                        && it.stateKey?.isNotEmpty() == true
    -            }.distinctBy { it.stateKey }
    +            val memberCount = stateEvents
    +                    .filter { it.type == EventType.STATE_ROOM_MEMBER && it.stateKey?.isNotEmpty() == true }
    +                    .distinctBy { it.stateKey }
                         .count()
     
                 return PeekResult.Success(
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
    index 289fdb498f..03ea2408f0 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2020 New Vector Ltd
    + * Copyright 2020 The Matrix.org Foundation C.I.C.
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
    diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
    index fc3c7366ec..bf839b807c 100644
    --- a/vector/src/main/AndroidManifest.xml
    +++ b/vector/src/main/AndroidManifest.xml
    @@ -81,8 +81,9 @@
                     android:resource="@xml/shortcuts" />
             
     
    -        
    +        
             
    -        
    +        
                 
                     
    +
                     
                     
     
                     
                     
                     
    -                
    -                
    +                
    +                
     
                 
             
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    index 4ec786f841..e1837ccb1b 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    @@ -126,9 +126,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
                     .observe()
                     .subscribe { sharedAction ->
                         when (sharedAction) {
    -                        is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
    +                        is HomeActivitySharedAction.OpenDrawer  -> drawerLayout.openDrawer(GravityCompat.START)
                             is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START)
    -                        is HomeActivitySharedAction.OpenGroup -> {
    +                        is HomeActivitySharedAction.OpenGroup   -> {
                                 drawerLayout.closeDrawer(GravityCompat.START)
                                 replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
                             }
    @@ -145,9 +145,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
             homeActivityViewModel.observeViewEvents {
                 when (it) {
                     is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
    -                is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
    -                HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
    -                is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
    +                is HomeActivityViewEvents.OnNewSession                  -> handleOnNewSession(it)
    +                HomeActivityViewEvents.PromptToEnableSessionPush        -> handlePromptToEnablePush()
    +                is HomeActivityViewEvents.OnCrossSignedInvalidated      -> handleCrossSigningInvalidated(it)
                 }.exhaustive
             }
             homeActivityViewModel.subscribe(this) { renderState(it) }
    @@ -162,29 +162,27 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
     
         private fun handleIntent(intent: Intent?) {
             intent?.dataString?.let { deepLink ->
    -            val resolvedLink = if (deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) {
    -                deepLink
    -            } else if (deepLink.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE)) {
    -                // This is a bit hugly, but for now just convert to matrix.to link for compatibility
    -                val service = activeSessionHolder.getSafeActiveSession()?.permalinkService()
    -                val roomLinkPrefix = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
    -                val userPrefix = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
    -                when {
    -                    deepLink.startsWith(userPrefix)     -> {
    -                        val userId = deepLink.substring(userPrefix.length)
    -                        service?.createPermalink(userId)
    +            val resolvedLink = when {
    +                deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)               -> deepLink
    +                deepLink.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE) -> {
    +                    // This is a bit ugly, but for now just convert to matrix.to link for compatibility
    +                    when {
    +                        deepLink.startsWith(USER_LINK_PREFIX) -> deepLink.substring(USER_LINK_PREFIX.length)
    +                        deepLink.startsWith(ROOM_LINK_PREFIX) -> deepLink.substring(ROOM_LINK_PREFIX.length)
    +                        else                                  -> null
    +                    }?.let {
    +                        activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(it)
                         }
    -                    deepLink.startsWith(roomLinkPrefix) -> {
    -                        val param = deepLink.substring(roomLinkPrefix.length)
    -                        service?.createRoomPermalink(param)
    -                    }
    -                    else                                -> null
                     }
    -            } else null
    +                else                                                                   -> null
    +            }
     
    -            permalinkHandler.launch(this, resolvedLink,
    +            permalinkHandler.launch(
    +                    context = this,
    +                    deepLink = resolvedLink,
                         navigationInterceptor = this,
    -                    buildTask = true)
    +                    buildTask = true
    +            )
                         // .delay(500, TimeUnit.MILLISECONDS)
                         .observeOn(AndroidSchedulers.mainThread())
                         .subscribe { isHandled ->
    @@ -198,7 +196,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
     
         private fun renderState(state: HomeActivityViewState) {
             when (val status = state.initialSyncProgressServiceStatus) {
    -            is InitialSyncProgressService.Status.Idle -> {
    +            is InitialSyncProgressService.Status.Idle        -> {
                     waiting_view.isVisible = false
                 }
                 is InitialSyncProgressService.Status.Progressing -> {
    @@ -363,11 +361,11 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
                     bugReporter.openBugReportScreen(this, false)
                     return true
                 }
    -            R.id.menu_home_filter -> {
    +            R.id.menu_home_filter     -> {
                     navigator.openRoomsFiltering(this)
                     return true
                 }
    -            R.id.menu_home_setting -> {
    +            R.id.menu_home_setting    -> {
                     navigator.openSettings(this)
                     return true
                 }
    @@ -408,5 +406,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
                             putExtra(MvRx.KEY_ARG, args)
                         }
             }
    +
    +        private const val ROOM_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
    +        private const val USER_LINK_PREFIX = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    index 4e8b73cb2b..a7d69c783c 100644
    --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt
    @@ -77,7 +77,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
                 buildTask: Boolean
         ): Single {
             return when (permalinkData) {
    -            is PermalinkData.RoomLink -> {
    +            is PermalinkData.RoomLink     -> {
                     permalinkData.getRoomId()
                             .observeOn(AndroidSchedulers.mainThread())
                             .map {
    @@ -93,11 +93,11 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
                                 true
                             }
                 }
    -            is PermalinkData.GroupLink -> {
    +            is PermalinkData.GroupLink    -> {
                     navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
                     Single.just(true)
                 }
    -            is PermalinkData.UserLink -> {
    +            is PermalinkData.UserLink     -> {
                     if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
                         navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
                     }
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/PeekingState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/PeekingState.kt
    new file mode 100644
    index 0000000000..918264e594
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/PeekingState.kt
    @@ -0,0 +1,23 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.roomdirectory.roompreview
    +
    +enum class PeekingState {
    +    FOUND,
    +    NOT_FOUND,
    +    NO_ACCESS
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    index a18a41285b..72c4c58a42 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt
    @@ -36,8 +36,8 @@ import org.matrix.android.sdk.api.query.QueryStringValue
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.Membership
    +import org.matrix.android.sdk.api.session.room.peeking.PeekResult
     import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
    -import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
     import org.matrix.android.sdk.internal.util.awaitCallback
     import org.matrix.android.sdk.rx.rx
     import timber.log.Timber
    @@ -66,46 +66,50 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
             observeMembershipChanges()
     
             if (initialState.shouldPeekFromServer) {
    -            setState {
    -                copy(peekingState = Loading())
    +            peekRoomFromServer()
    +        }
    +    }
    +
    +    private fun peekRoomFromServer() {
    +        setState {
    +            copy(peekingState = Loading())
    +        }
    +        viewModelScope.launch(Dispatchers.IO) {
    +            val peekResult = tryOrNull {
    +                awaitCallback {
    +                    session.peekRoom(initialState.roomAlias ?: initialState.roomId, it)
    +                }
                 }
    -            viewModelScope.launch(Dispatchers.IO) {
    -                val peekResult = tryOrNull {
    -                    awaitCallback {
    -                        session.peekRoom(initialState.roomAlias ?: initialState.roomId, it)
    +
    +            when (peekResult) {
    +                is PeekResult.Success           -> {
    +                    setState {
    +                        copy(
    +                                roomId = peekResult.roomId,
    +                                avatarUrl = peekResult.avatarUrl,
    +                                roomAlias = peekResult.alias ?: initialState.roomAlias,
    +                                roomTopic = peekResult.topic,
    +                                homeServers = peekResult.viaServers,
    +                                peekingState = Success(PeekingState.FOUND)
    +                        )
                         }
                     }
    -
    -                when (peekResult) {
    -                    is PeekResult.Success -> {
    -                        setState {
    -                            copy(
    -                                    roomId = peekResult.roomId,
    -                                    avatarUrl = peekResult.avatarUrl,
    -                                    roomAlias = peekResult.alias ?: initialState.roomAlias,
    -                                    roomTopic = peekResult.topic,
    -                                    homeServers = peekResult.viaServers,
    -                                    peekingState = Success(PeekingState.FOUND)
    -                            )
    -                        }
    +                is PeekResult.PeekingNotAllowed -> {
    +                    setState {
    +                        copy(
    +                                roomId = peekResult.roomId,
    +                                roomAlias = peekResult.alias ?: initialState.roomAlias,
    +                                homeServers = peekResult.viaServers,
    +                                peekingState = Success(PeekingState.NO_ACCESS)
    +                        )
                         }
    -                    is PeekResult.PeekingNotAllowed -> {
    -                        setState {
    -                            copy(
    -                                    roomId = peekResult.roomId,
    -                                    roomAlias = peekResult.alias ?: initialState.roomAlias,
    -                                    homeServers = peekResult.viaServers,
    -                                    peekingState = Success(PeekingState.NO_ACCESS)
    -                            )
    -                        }
    -                    }
    -                    PeekResult.UnknownAlias,
    -                    null -> {
    -                        setState {
    -                            copy(
    -                                    peekingState = Success(PeekingState.NOT_FOUND)
    -                            )
    -                        }
    +                }
    +                PeekResult.UnknownAlias,
    +                null                            -> {
    +                    setState {
    +                        copy(
    +                                peekingState = Success(PeekingState.NOT_FOUND)
    +                        )
                         }
                     }
                 }
    @@ -136,7 +140,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
                     .subscribe {
                         val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown
                         val joinState = when (changeMembership) {
    -                        is ChangeMembershipState.Joining -> JoinState.JOINING
    +                        is ChangeMembershipState.Joining       -> JoinState.JOINING
                             is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR
                             // Other cases are handled by room summary
                             else                                   -> null
    diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt
    index c0f7591f87..5a73f29ca3 100644
    --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt
    @@ -22,15 +22,7 @@ import com.airbnb.mvrx.Uninitialized
     import im.vector.app.features.roomdirectory.JoinState
     import org.matrix.android.sdk.api.util.MatrixItem
     
    -enum class PeekingState {
    -    UNKNOWN,
    -    FOUND,
    -    NOT_FOUND,
    -    NO_ACCESS
    -}
    -
     data class RoomPreviewViewState(
    -
             val peekingState: Async = Uninitialized,
             // The room id
             val roomId: String = "",
    
    From 071611b81c7088f142aec6502e7b1dad5c0fc1be Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 11 Dec 2020 16:38:22 +0100
    Subject: [PATCH 133/218] We are not supposed to fallback on alternative alias
    
    ---
     .../sdk/internal/session/room/peeking/PeekRoomTask.kt        | 5 +----
     1 file changed, 1 insertion(+), 4 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    index 9fba01efad..5a82d74537 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
    @@ -115,10 +115,7 @@ internal class DefaultPeekRoomTask @Inject constructor(
     
                 val alias = stateEvents
                         .lastOrNull { it.type == EventType.STATE_ROOM_CANONICAL_ALIAS }
    -                    ?.let {
    -                        it.content?.toModel()?.canonicalAlias
    -                                ?: it.content?.toModel()?.alternativeAliases?.firstOrNull()
    -                    }
    +                    ?.let { it.content?.toModel()?.canonicalAlias }
     
                 // not sure if it's the right way to do that :/
                 val memberCount = stateEvents
    
    From d8a1939c697098c5edd76f0de6599da97668f1fc Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 11 Dec 2020 17:50:59 +0100
    Subject: [PATCH 134/218] Fix number of enum
    
    ---
     tools/check/forbidden_strings_in_code.txt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
    index 63a3fad109..fc510e585c 100644
    --- a/tools/check/forbidden_strings_in_code.txt
    +++ b/tools/check/forbidden_strings_in_code.txt
    @@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1
     # android\.text\.TextUtils
     
     ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
    -enum class===83
    +enum class===84
     
     ### Do not import temporary legacy classes
     import org.matrix.android.sdk.internal.legacy.riot===3
    
    From a5094f97d5be604a6657aad3389d35ecad2a6c8d Mon Sep 17 00:00:00 2001
    From: AlexanderLevchenkoTechs 
    Date: Fri, 11 Dec 2020 22:04:39 +0000
    Subject: [PATCH 135/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 43.1% (857 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 17 +++++++++++++++--
     1 file changed, 15 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index fdc69678fd..03b995745b 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -1012,10 +1012,23 @@
         Створити нову кімнату
         Кімнати
         Пошук в зашифрованих кімнатах не підтримується на даний момент.
    -    Запитувати підтвердження перед початком дзвінку
    -    Запобігати випадковим дзвінкам
    +    Запитувати підтвердження перед початком виклику
    +    Запобігати випадковим викликам
         Мені не потрібні мої зашифровані повідомлення
         Скасувати зміни
         SSL помилка.
         Ви не можете здійснити дзвінок із самим собою, дочекайтесь, доки інші учасники приймуть ваше запрошення
    +    ФАЙЛИ
    +    В цій кімнаті немає медіа
    +    МЕДІА
    +    Завантаження
    +    Підтримується тільки в зашифрованих кімнатах
    +    Інші кімнати
    +    Нова кімната
    +    Створити нову кімнату
    +    Залишити відгук
    +    Режим розробника
    +    Додаткові налаштування
    +    Розширені налаштування сповіщень
    +    Голос та відео
     
    \ No newline at end of file
    
    From 69cd0e3ea210f9580401afc982565aeba83938d2 Mon Sep 17 00:00:00 2001
    From: Ihor Hordiichuk 
    Date: Sun, 13 Dec 2020 00:33:17 +0000
    Subject: [PATCH 136/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 45.2% (898 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 106 +++++++++++++---------
     1 file changed, 62 insertions(+), 44 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index 03b995745b..2b31387ddf 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -9,7 +9,7 @@
         Темна Тема
         OLED Тема
         
    -    Синхронізую
    +    Синхронізація…
         Слухати події
         
         Повідомлення
    @@ -127,7 +127,7 @@
         
         Увійти
         Реєстрація
    -    Вогонь!
    +    Надіслати
         Пропустити
         Скинути
         Повернутися на екран входу
    @@ -212,7 +212,7 @@
         Вхідний виклик
         Вхідний Відеовиклик
         Вхідний Голосовий Виклик
    -    Виконується Виклик
    +    Виконується виклик…
         Абонент не відповідає.
         Медіавиклик не вдався
         Неможливо ініціювати камеру
    @@ -228,10 +228,10 @@
         Для здійснення аудіодзвінків потрібен доступ до мікрофону.
         \n\nБудь ласка, надайте його у наступному виринаючому вікні, щоб мати змогу здійснити дзвінок.
         Для здійснення відеодзвінків потрібен доступ до камери та мікрофону.\n\nБудь ласка, надайте його у наступних виринаючих вікнах, щоб мати змогу їх здійснити.
    -    Element потребує доступу до ваших контактів, щоб знайти інших користувачів Matrix за їх email та номерами телефонів.\n\nБудь ласка, надайте його у наступному виринаючому діалозі, щоб знати, які з контактів теж використовують Element.
    -    Element потребує доступу до ваших контактів, щоб знайти інших користувачів Matrix по email чи за номерами телефонів.
    -
    -Надати Element доступ до ваших контактів?
    +    Element потребує доступу до ваших контактів, щоб знайти інших користувачів Matrix за їх електронними адресами та номерами телефонів. Якщо ви згодні, надайте його у наступному діалоговому вікні, щоб знати, які з контактів теж використовують Element.
    +    Element потребує доступу до ваших контактів, щоб знайти інших користувачів Matrix за електронною поштою чи номерами телефонів.
    +\n
    +\nНадати Element доступ до ваших контактів\?
         Вибачте.. Дію не виконано через нестачу дозволів
         
         Збережено
    @@ -329,9 +329,9 @@
         ПРИЄДНАЛИСЯ
         
         Причина скарги на цей вміст
    -    Бажаєте сховати всі повідомлення цього користувача?
    -
    -Зауважте, що це перезавантажить додаток та може зайняти деякий час.
    +    Бажаєте сховати всі повідомлення цього користувача\?
    +\n
    +\nЗауважте, що це перезавантажить застосунок та може тривати деякий час.
         Скасувати відвантаження
         Скасувати завантаження
         
    @@ -356,7 +356,7 @@
         Введіть id кімнати чи аліас
         
         Огляд каталогу
    -    Пошук у каталозі..
    +    Пошук у каталозі…
         
         Обране
         Не терміново
    @@ -433,7 +433,7 @@
         Операція потребує додаткової аутентифікації.\nДля продовження введіть ваш пароль.
         Аутентифікація
         Пароль:
    -    Вогонь!
    +    Надіслати
         Залоговано як
         Cервер
         Сервер Аутентифікації
    @@ -452,9 +452,9 @@
         Підтвердіть пароль
         Не вдалося оновити пароль
         Пароль успішно оновлено
    -    Показувати всі повідомлення %s?
    -
    -Зауважте, що це перезавантажить додаток та може зайняти деякий час.
    +    Показувати всі повідомлення %s\?
    +\n
    +\nЗауважте, що це перезавантажить застосунок та може тривати деякий час.
         Ви справді бажаєте видалити цю ціль сповіщень?
         Справді бажаєте видалити %1$s %2$s?
         Оберіть країну
    @@ -553,9 +553,9 @@
         Експорт
         Введіть парольну фразу
         Підтвердіть парольну фразу
    -    E2E ключі кімнати збережено у \'%s\'
    -
    -Попередження: цей файл може бути видалений, якщо видалити додаток.
    +    E2E ключі кімнати збережено до \'%s\'.
    +\n
    +\nПопередження: цей файл може бути видалено, якщо видалити застосунок.
         Імпортувати E2E ключі кімнати
         Імпортувати ключі кімнати
         Імпортувати ключі з локального файлу
    @@ -643,7 +643,7 @@
         Запросити
         Спільноти
         Нема груп
    -    "Струснути  пристрій, щоб повідомити про помилку"
    +    Струснути пристрій, щоб повідомити про помилку
         Ви дійсно хочете почати новий чат з %s?
         Ви впевнені, що бажаєте почати голосовий виклик?
         Ви впевнені, що бажаєте почати відео виклик?
    @@ -666,9 +666,9 @@
         Нормальний
         Відправити наліпку
         Відправити стікер
    -    У вас зараз не має доступних стікерів.
    -
    -Добавити зараз?
    +    У вас зараз не має стікерів.
    +\n
    +\nДодати зараз\?
         
             %d активний участник
             %d активних участника
    @@ -681,7 +681,7 @@
             %d участників
             
         
    -    Ви впевнені, що хочете забанити цього користувача в цьому чаті?
    +    Користувачів з забороною буде вилучено з цієї кімнати й вони не зможуть приєднатися знову.
         
             %d нове повідомлення
             %d нових повідомленя
    @@ -720,22 +720,22 @@
         Якщо можливо, будь ласка, напишіть опис англійською.
         продовжити з…
         
    -        %d сек.
    -        %d сек.
    -        %d сек.
    -        
    +        %dс
    +        %dsс
    +        %dsс
    +        %dsс
         
         
    -        %d хв.
    -        %d хв.
    -        %d хв.
    -        
    +        %dхв
    +        %dхв
    +        %dхв
    +        %dхв
         
         
    -        %d год.
    -        %d год.
    -        %d год.
    -        
    +        %dгод
    +        %dгод
    +        %dгод
    +        %dгод
         
         
             %d день
    @@ -751,10 +751,10 @@
         Надіслати зашифровану відповідь…
         Надіслати відповідь (незашифровано)…
         
    -        %d обрано
    -        "%d  обрано"
    -        "%d  обрано"
    -        
    +        %d вибрано
    +        %d вибрано
    +        %d вибрано
    +        %d вибрано
         
         
             %d кімната
    @@ -867,11 +867,11 @@
         Щоб продовжити користуватися домашнім сервером %1$s Вам слід ознайомитись та прийняти Умови використання.
         Ознайомитись зараз
         Деактивувати обліковий запис
    -    Це зробить Ваш обліковий запис непридатним до подальшого використання. Ви не будете спроможні заходити у систему, та ніхто інший не зможе зареєструватися з таким самим ID. Це спричинить вихід Вашого облікового запису з усіх кімнат, в яких він зареєстрований, а також видалить усі данні облікового запису із Вашого серверу ідентифікації. Ця дія незворотня.
    -
    -Деактивація Вашого облікового запису за замовчанням не спричинить видалення надісланих Вами повідомлень. Якщо Ви бажаєте позбавитись Ваших повідомлень, будь ласка, поставте позначку під цим текстом.
    -
    -Відображення повідомлень у Matrix подібне до електронної пошти. Таке видалення означає, що Ваші надіслані повідомлення не будуть поширюватись серед нових або незареєстрованих користувачів, проте зареєстрованим користувачам, які мали доступ до цих повідомлень, все ще будуть доступні їх копії.
    +    Це зробить ваш обліковий запис непридатним до подальшого використання. Ви не зможете заходити до системи, та ніхто інший не зможе зареєструватися з таким самим ID. Це спричинить вихід з вашого облікового запису з усіх кімнат, до яких ви долучилися, а також вилучить усі данні облікового запису із вашого сервера ідентифікації. Ця дія незворотня.
    +\n
    +\nДеактивація вашого облікового запису типово не спричинить видалення надісланих вами повідомлень. Якщо ви бажаєте позбавитись цих повідомлень, залиште позначку під цим текстом.
    +\n
    +\nПоказ повідомлень у Matrix подібне до електронної пошти. Таке видалення означає, що ваші надіслані повідомлення не будуть поширюватись серед нових або незареєстрованих користувачів, проте зареєстрованим користувачам, які мали доступ до цих повідомлень й надалі будуть доступні їхні копії.
         Будь ласка, після деактивації мого облікового запису видаліть усі надіслані мною повідомлення (Увага: це спричинить неповне відображення розмов для нових користувачів)
         Для продовження, будь ласка, введіть Ваш пароль:
         Деактивувати обліковий запис
    @@ -1031,4 +1031,22 @@
         Додаткові налаштування
         Розширені налаштування сповіщень
         Голос та відео
    +    Налаштування
    +    Інші сеанси
    +    Поточний сеанс
    +    Налаштування
    +    Сповіщення
    +    Налаштування
    +    Відкликати
    +    немає
    +    Ви втратите доступ до своїх зашифрованих повідомлень, якщо не зробите резервну копію ключів перед виходом з системи.
    +    Безпечне резервне копіювання ключів має бути активним у всіх ваших сеансах, щоб не втратити доступу до ваших зашифрованих повідомлень.
    +    Триває резервне копіювання ключів. Якщо вийти зараз, ви втратите доступ до своїх зашифрованих повідомлень.
    +    Резервне копіювання ключів не завершено, зачекайте…
    +    Увімкнути мікрофон
    +    Вимкнути мікрофон
    +    Відерити бесіду
    +    Роль
    +    Призначити роль
    +    Надіслати
     
    \ No newline at end of file
    
    From 6f4c30785438aacdede671765bfc7772555a4bd1 Mon Sep 17 00:00:00 2001
    From: AlexanderLevchenkoTechs 
    Date: Fri, 11 Dec 2020 22:50:49 +0000
    Subject: [PATCH 137/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 45.2% (898 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 26 ++++++++++++++++++++---
     1 file changed, 23 insertions(+), 3 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index 2b31387ddf..0a256e4bdf 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -136,7 +136,7 @@
         Новий пароль
         Логін
         Email адреса
    -    Email адреса (oдодаткова)
    +    Email адреса (додаткова)
         Номер телефону
         Номер телефону (додатковий)
         Повторіть пароль
    @@ -661,7 +661,7 @@
         Всі повідомлення
         Тільки упоминания
         Без звука
    -    Добавити ярлик на головний екран
    +    Додати ярлик на головний екран
         Конфіденційність сповіщень
         Нормальний
         Відправити наліпку
    @@ -1026,7 +1026,7 @@
         Інші кімнати
         Нова кімната
         Створити нову кімнату
    -    Залишити відгук
    +    Внести пропозицію
         Режим розробника
         Додаткові налаштування
         Розширені налаштування сповіщень
    @@ -1049,4 +1049,24 @@
         Роль
         Призначити роль
         Надіслати
    +    Номери телефонів
    +    Email адреси
    +    Скасувати запрошення
    +    🔐️ Приєднуйтесь до мене в Element
    +    Привіт, поспілкуйся зі мною в Element: %s
    +    Запросити друзів
    +    Всі спільноти
    +    Показувати заглушку на місці видалених повідомлень
    +    Основні
    +    Не вдалось надіслати пропозицію (%s)
    +    Дякуємо, вашу пропозицію було успішно надіслано
    +    Опишіть вашу пропозицію тут
    +    Будь ласка, залиште вашу пропозицію нижче.
    +    Показувати видалені повідомлення
    +    Натискання Enter на програмній клавіатурі буде відправляти повідомлення замість додавання переносу рядку
    +    Відправляти повідомлення натисканням Enter
    +    Налаштування кімнати
    +    Дізнатись більше
    +    В цій кімнаті немає файлів
    +    %1$s о %2$s
     
    \ No newline at end of file
    
    From 74283e7d50ce7c5bcae56936b8eff9eb68132543 Mon Sep 17 00:00:00 2001
    From: Ihor Hordiichuk 
    Date: Sun, 13 Dec 2020 00:55:04 +0000
    Subject: [PATCH 138/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 45.7% (908 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 16 +++++++++++++---
     1 file changed, 13 insertions(+), 3 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index 0a256e4bdf..d4748243aa 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -56,7 +56,7 @@
         Голосовий виклик
         Відеовиклик
         Глообальний пошук
    -    Позначити все як прочитане
    +    Позначити все прочитаним
         Історичний
         Швидка відповідь
         Відкрити
    @@ -1026,7 +1026,7 @@
         Інші кімнати
         Нова кімната
         Створити нову кімнату
    -    Внести пропозицію
    +    Запропонувати
         Режим розробника
         Додаткові налаштування
         Розширені налаштування сповіщень
    @@ -1061,7 +1061,7 @@
         Не вдалось надіслати пропозицію (%s)
         Дякуємо, вашу пропозицію було успішно надіслано
         Опишіть вашу пропозицію тут
    -    Будь ласка, залиште вашу пропозицію нижче.
    +    Залиште вашу пропозицію внизу.
         Показувати видалені повідомлення
         Натискання Enter на програмній клавіатурі буде відправляти повідомлення замість додавання переносу рядку
         Відправляти повідомлення натисканням Enter
    @@ -1069,4 +1069,14 @@
         Дізнатись більше
         В цій кімнаті немає файлів
         %1$s о %2$s
    +    Жодного номера телефону не додано до вашого облікового запису
    +    У ваш обліковий запис не додано жодної електронної адреси
    +    Для вашої приватності Element підтримує лише надсилання хешованих електронних адрес користувачів та номера телефону.
    +    Ви не погодилися надіслати електронні адреси та телефонні номери на цей сервер ідентифікації для виявлення інших користувачів із ваших контактів.
    +    Ви погодилися надіслати електронні адреси та телефонні номери на цей сервер ідентифікації для виявлення інших користувачів із ваших контактів.
    +    Надіслати електронні адреси та номери телефонів
    +    Надіслати електронні адреси та номери телефонів
    +    Керування електронними адресами та номерами телефонів, пов’язаними з вашим обліковим записом Matrix
    +    Електорнні адреси та номери телефонів
    +    Надіслати історію запитів спільного доступу до ключових даних
     
    \ No newline at end of file
    
    From 45a058632992d9b16c5baaa11a045d5d0412fcff Mon Sep 17 00:00:00 2001
    From: Nikita Epifanov 
    Date: Fri, 11 Dec 2020 12:08:51 +0000
    Subject: [PATCH 139/218] Translated using Weblate (Russian)
    
    Currently translated at 100.0% (1986 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
    ---
     vector/src/main/res/values-ru/strings.xml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
    index 3411846b4d..7716955d23 100644
    --- a/vector/src/main/res/values-ru/strings.xml
    +++ b/vector/src/main/res/values-ru/strings.xml
    @@ -2324,7 +2324,7 @@
         Пригласить друзей
         Добавить людей
         "Тема: "
    -    Добавить тему
    +    Добавьте тему
         Это начало разговора.
         Это начало %s.
         У вас нет разрешения на включение шифрования в этой комнате.
    
    From 2bdb425085f68173bc1986d13a2c9c372e122e63 Mon Sep 17 00:00:00 2001
    From: Ihor Hordiichuk 
    Date: Sun, 13 Dec 2020 01:34:36 +0000
    Subject: [PATCH 140/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 48.0% (955 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 53 ++++++++++++++++++++++-
     1 file changed, 51 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index d4748243aa..d3855ffe97 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -530,7 +530,7 @@
         
         Каталог
         
    -    %s намагався завантажити певну точку на шкалі часу цієї кімнати, але не зміг її знайти.
    +    %s намагався завантажити певну точку зі стрічки подій цієї кімнати, але не зміг її знайти.
         
         Дані про наскрізне шифрування
         Інформація про подію
    @@ -934,7 +934,7 @@
         Запуск сервісу
         Резервна копія ключа
         Використати резервну копію ключа
    -    Тут буде список Ваших прямих чатів
    +    Тут з\'явиться список ваших прямих чатів
         Повідомлення видалено
         Прямі чати
         Прямі чати
    @@ -1079,4 +1079,53 @@
         Керування електронними адресами та номерами телефонів, пов’язаними з вашим обліковим записом Matrix
         Електорнні адреси та номери телефонів
         Надіслати історію запитів спільного доступу до ключових даних
    +    Стрічка подій
    +    Не прчитані повідомлення
    +    Провести пальцем, щоб відповісти у стрічці подій
    +    Показувати приховані події у стрічці подій
    +    Усунення несправностей
    +    APK служб Google Play доступні та оновлені.
    +    Перевірка служб Play
    +    Перевірка налаштувань
    +    Не вдалося завантажити власні правила, повторіть спробу.
    +    Деякі сповіщення вимкнено у ваших власних налаштуваннях.
    +    Зверніть увагу, що для деяких типів повідомлень встановлено беззвучність (беззвучні сповіщення).
    +    Власні налаштування.
    +    Увімкнути
    +    Сповіщення не ввімкнено для цього сеансу.
    +\nПеревірте налаштування Element.
    +    Сповіщення ввімкнено для цього сеансу.
    +    Налаштування сеансу.
    +    Увімкнути
    +    Сповіщення для вашого облікового запису вимкнено.
    +\nПеревірте налаштування облікового запису.
    +    Сповіщення ввімкнено для вашого облікового запису.
    +    Налаштування облікового запису.
    +    Сповіщення ввімкнено в налаштуваннях системи.
    +    Налаштування системи.
    +    Виявлення несправностей
    +    Перевірка… (%1$d з %2$d)
    +    Перевірити
    +    Сповіщення усунення несправностей
    +    Захистити приватність діалогів шифруванням
    +    Фільтрувати діалоги…
    +    Діалоги
    +    У вас більше немає непрочитаних повідомлень
    +    Все прочитано!
    +    Встановти важливість сповіщень за подіями
    +    Сховати розширені
    +    Показати розширені
    +    Власні та розширені налаштування
    +    (Розширені)
    +    Довідка та опис
    +    Налаштування
    +    Виберіть колір світлодіода, вібрацію, звук…
    +    Налаштування приглушених сповіщень
    +    Налаштування сповіщень викликів
    +    Налаштування гучних сповіщень
    +    Початкова синхронізація…
    +    Видимі електронні адреси
    +    Недійсна відповідь виявлення домашнього сервера
    +    Налаштуйте свою видимість.
    +    Видимість
     
    \ No newline at end of file
    
    From f83b47800876a10bf709cf8ebf1f8b3a86554ffc Mon Sep 17 00:00:00 2001
    From: strix aluco 
    Date: Sun, 13 Dec 2020 00:55:11 +0000
    Subject: [PATCH 141/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 48.0% (955 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index d3855ffe97..186bf81a9b 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -1078,7 +1078,7 @@
         Надіслати електронні адреси та номери телефонів
         Керування електронними адресами та номерами телефонів, пов’язаними з вашим обліковим записом Matrix
         Електорнні адреси та номери телефонів
    -    Надіслати історію запитів спільного доступу до ключових даних
    +    Надіслати історію запитів спільного доступу до ключів
         Стрічка подій
         Не прчитані повідомлення
         Провести пальцем, щоб відповісти у стрічці подій
    
    From b915c91c8633695c5da0997a64022096dacdc2c2 Mon Sep 17 00:00:00 2001
    From: Ihor Hordiichuk 
    Date: Sat, 12 Dec 2020 13:50:55 +0000
    Subject: [PATCH 142/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 28.0% (59 of 210 strings)
    
    Translation: Element Android/Element Android Sdk
    Translate-URL: https://translate.element.io/projects/element-android/element-sdk/uk/
    ---
     .../src/main/res/values-uk/strings.xml        | 34 +++++--------------
     1 file changed, 9 insertions(+), 25 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/res/values-uk/strings.xml b/matrix-sdk-android/src/main/res/values-uk/strings.xml
    index eb5071f190..2477487379 100644
    --- a/matrix-sdk-android/src/main/res/values-uk/strings.xml
    +++ b/matrix-sdk-android/src/main/res/values-uk/strings.xml
    @@ -1,39 +1,34 @@
    -
    +
     
         %1$s: %2$s
         %1$s надіслав(ла) зображення.
    -
         %s запрошення
         %1$s запросив(ла) %2$s
         Зашифроване повідомлення
    -
         
         Запрошення від %s
         Запрошення до кімнати
         %1$s і %2$s
         Порожня кімната
    -
    -
         %1$s надіслав(ла) наліпку.
    -
         %1$s запросив(ла) Вас
    -    %1$s приєднався(лась)
    +    %1$s приєднується
         %1$s покинув(ла)
         %1$s відхилив(ла) запрошення
         %1$s копнув(ла) %2$s
         %1$s розблокував(ла) %2$s
         %1$s заблокував(ла) %2$s
         %1$s відкликав(ла) запрошення для %2$s
    -    %1$s змінив(ла) свій аватар
    -    %1$s встановив(ла) собі ім’я %2$s
    -    %1$s змінив(ла) своє ім’я з %2$s на %3$s
    +    %1$s змінює свій аватар
    +    %1$s встановлюють собі назву %2$s
    +    %1$s змінює своє ім’я з %2$s на %3$s
         %1$s прибрав(ла) своє ім’я (%2$s)
    -    %1$s змінив(ла) тему на: %2$s
    +    %1$s змінює тему на: %2$s
         %1$s змінив(ла) назву кімнати на: %2$s
         %s розпочав(ла) відеодзвінок.
         %s розпочав(ла) голосовий дзвінок.
         %s відповів(ла) на дзвінок.
    -    %s завершив(ла) дзвінок.
    +    %s завершує дзвінок.
         %1$s зробив(ла) майбутню історію кімнати видимою для %2$s
         усіх співрозмовників, з моменту їх запрошення.
         усіх співрозмовників, з моменту їх приєднання.
    @@ -41,44 +36,33 @@
         будь-кого.
         невідомо (%s).
         %1$s увімкнув(ла) наскрізне шифрування (%2$s)
    -
         %1$s запросив(ла) VoIP конференцію
         VoIP конференція розпочалась
         VoIP конференція завершилась
    -
         (аватар також змінено)
         %1$s прибрав(ла) назву кімнати
         %1$s прибрав(ла) тему кімнати
         %1$s оновив(ла) свій профіль %2$s
         %1$s надіслав(ла) запрошення %2$s приєднатися до кімнати
         %1$s прийняв(ла) запрошення у %2$s
    -
         ** Неможливо розшифрувати: %s **
         Пристрій відправника не надіслав нам ключ для цього повідомлення.
    -
         Неможливо відредагувати
         Не вдалося надіслати повідомлення
    -
         Не вдалося завантажити зображення
    -
         Помилка мережі
         Помилка Matrix
    -
         Наразі неможливо переприєднатися до порожньої кімнати.
    -
         Адреса електронної пошти
         Номер телефону
    -
         
             %1$s та 1 інший
             %1$s та %2$d інші
             %1$s та %2$d інших
    -        
    +        
         
    -
         %s вдосконалили цю кімнату.
    -
         Повідомлення видалено
         %1$s видалили повідомлення
         Повідомлення видалено [причина: %1$s]
    -
    +
    \ No newline at end of file
    
    From 03428ea9f54f4a684d5251b228c6c2438794c4a5 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Thu, 3 Dec 2020 10:59:08 +0100
    Subject: [PATCH 143/218] Social Login
    
    And new custom homeserver completion (and remember history)
    ---
     .../java/org/matrix/android/sdk/api/Matrix.kt |   4 +
     .../java/org/matrix/android/sdk/api/Matrix.kt |   4 +
     .../sdk/api/auth/HomeServerHistoryService.kt  |  29 ++++
     .../sdk/api/auth/data/IdentityProvider.kt     |  42 +++++
     .../sdk/api/auth/data/LoginFlowResult.kt      |   1 +
     .../auth/DefaultAuthenticationService.kt      |   2 +
     .../auth/DefaultHomeServerHistoryService.kt   |  50 ++++++
     .../internal/auth/data/LoginFlowResponse.kt   |  11 +-
     .../registration/RegistrationFlowResponse.kt  |  12 ++
     .../database/model/KnownServerUrlEntity.kt    |  27 ++++
     .../sdk/internal/di/MatrixComponent.kt        |   3 +
     .../sdk/internal/raw/GlobalRealmMigration.kt  |  39 +++++
     .../sdk/internal/raw/GlobalRealmModule.kt     |   4 +-
     .../android/sdk/internal/raw/RawModule.kt     |   7 +
     .../im/vector/app/core/di/FragmentModule.kt   |  11 +-
     .../im/vector/app/core/di/VectorComponent.kt  |   3 +
     .../im/vector/app/core/di/VectorModule.kt     |   7 +
     .../vector/app/features/login/LoginAction.kt  |   3 +-
     .../app/features/login/LoginActivity.kt       |  55 +++----
     .../app/features/login/LoginFragment.kt       |  29 ++--
     .../im/vector/app/features/login/LoginMode.kt |  32 +++-
     .../login/LoginServerSelectionFragment.kt     |   2 +-
     .../login/LoginServerUrlFormFragment.kt       |  14 +-
     .../LoginSignUpSignInSelectionFragment.kt     | 125 ++++++++++++++-
     .../login/LoginSignUpSignInSsoFragment.kt     |  99 ------------
     .../app/features/login/LoginViewModel.kt      | 105 +++++++-----
     .../app/features/login/LoginViewState.kt      |   8 +-
     .../features/login/SocialLoginButtonsView.kt  | 149 ++++++++++++++++++
     .../signout/soft/SoftLogoutController.kt      |  10 +-
     .../signout/soft/SoftLogoutFragment.kt        |  21 ++-
     .../signout/soft/SoftLogoutViewModel.kt       |   8 +-
     ...social_google_background_selector_dark.xml |   7 +
     ...ocial_google_background_selector_light.xml |   6 +
     .../src/main/res/drawable/ic_social_apple.xml |  12 ++
     .../main/res/drawable/ic_social_facebook.xml  |  12 ++
     .../main/res/drawable/ic_social_github.xml    |  12 ++
     .../main/res/drawable/ic_social_google.xml    |  36 +++++
     .../main/res/drawable/ic_social_twitter.xml   |  12 ++
     vector/src/main/res/layout/fragment_login.xml |  37 ++++-
     .../layout/fragment_login_server_url_form.xml |   4 +-
     ...fragment_login_signup_signin_selection.xml |  35 +++-
     vector/src/main/res/values/attrs.xml          |  14 ++
     vector/src/main/res/values/colors_riotx.xml   |   1 +
     vector/src/main/res/values/strings.xml        |   6 +
     vector/src/main/res/values/styles_riot.xml    |  98 ++++++++++++
     vector/src/main/res/values/theme_dark.xml     |   6 +
     vector/src/main/res/values/theme_light.xml    |   7 +
     47 files changed, 1001 insertions(+), 220 deletions(-)
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/IdentityProvider.kt
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/KnownServerUrlEntity.kt
     create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
     delete mode 100644 vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSsoFragment.kt
     create mode 100644 vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
     create mode 100644 vector/src/main/res/color/button_social_google_background_selector_dark.xml
     create mode 100644 vector/src/main/res/color/button_social_google_background_selector_light.xml
     create mode 100644 vector/src/main/res/drawable/ic_social_apple.xml
     create mode 100644 vector/src/main/res/drawable/ic_social_facebook.xml
     create mode 100644 vector/src/main/res/drawable/ic_social_github.xml
     create mode 100644 vector/src/main/res/drawable/ic_social_google.xml
     create mode 100644 vector/src/main/res/drawable/ic_social_twitter.xml
    
    diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
    index 0d71af864b..03943cea14 100644
    --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
    +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
    @@ -25,6 +25,7 @@ import androidx.work.WorkManager
     import com.zhuinden.monarchy.Monarchy
     import org.matrix.android.sdk.BuildConfig
     import org.matrix.android.sdk.api.auth.AuthenticationService
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.legacy.LegacySessionImporter
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.common.DaggerTestMatrixComponent
    @@ -49,6 +50,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
         @Inject internal lateinit var olmManager: OlmManager
         @Inject internal lateinit var sessionManager: SessionManager
    +    @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
     
         private val uiHandler = Handler(Looper.getMainLooper())
     
    @@ -71,6 +73,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
     
         fun rawService() = rawService
     
    +    fun homeServerHistoryService() = homeServerHistoryService
    +
         fun legacySessionImporter(): LegacySessionImporter {
             return legacySessionImporter
         }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
    index cf6f37cec8..a5d457222f 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
    @@ -23,6 +23,7 @@ import androidx.work.WorkManager
     import com.zhuinden.monarchy.Monarchy
     import org.matrix.android.sdk.BuildConfig
     import org.matrix.android.sdk.api.auth.AuthenticationService
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.legacy.LegacySessionImporter
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.internal.SessionManager
    @@ -47,6 +48,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
         @Inject internal lateinit var olmManager: OlmManager
         @Inject internal lateinit var sessionManager: SessionManager
    +    @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
     
         init {
             Monarchy.init(context)
    @@ -65,6 +67,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
     
         fun rawService() = rawService
     
    +    fun homeServerHistoryService() = homeServerHistoryService
    +
         fun legacySessionImporter(): LegacySessionImporter {
             return legacySessionImporter
         }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt
    new file mode 100644
    index 0000000000..a52d13d95c
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt
    @@ -0,0 +1,29 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.api.auth
    +
    +/**
    + * A simple service to remember homeservers you already connected to.
    + */
    +interface HomeServerHistoryService {
    +
    +    fun getKnownServersUrls(): List
    +
    +    fun addHomeServerToHistory(url: String)
    +
    +    fun clearHistory()
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/IdentityProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/IdentityProvider.kt
    new file mode 100644
    index 0000000000..66c002e3c6
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/IdentityProvider.kt
    @@ -0,0 +1,42 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.api.auth.data
    +
    +import android.os.Parcelable
    +import com.squareup.moshi.Json
    +import com.squareup.moshi.JsonClass
    +import kotlinx.android.parcel.Parcelize
    +
    +@JsonClass(generateAdapter = true)
    +@Parcelize
    +data class IdentityProvider(
    +        /**
    +         * The id field would be opaque with the accepted characters matching unreserved URI characters as defined in RFC3986
    +         * - this was chosen to avoid having to encode special characters in the URL. Max length 128.
    +         */
    +        @Json(name = "id") val id: String,
    +        /**
    +         * The name field should be the human readable string intended for printing by the client.
    +         * */
    +        @Json(name = "name") val name: String?,
    +        /**
    +         * The icon field is the only optional field and should point to an icon representing the IdP.
    +         *  If present then it must be an HTTPS URL to an image resource.
    +         *  This should be hosted by the homeserver service provider to not leak the client's IP address unnecessarily.
    +         */
    +        @Json(name = "icon") val icon: String?
    +) : Parcelable
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
    index 64d3ddcca5..53c18385f7 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
    @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.auth.data
     sealed class LoginFlowResult {
         data class Success(
                 val supportedLoginTypes: List,
    +            val ssoIdentityProviders: List?,
                 val isLoginAndRegistrationSupported: Boolean,
                 val homeServerUrl: String,
                 val isOutdatedHomeserver: Boolean
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
    index 3d5a0efcd4..ba3bd29b9f 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
    @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.auth.AuthenticationService
     import org.matrix.android.sdk.api.auth.data.Credentials
     import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
     import org.matrix.android.sdk.api.auth.data.LoginFlowResult
    +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
     import org.matrix.android.sdk.api.auth.login.LoginWizard
     import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
     import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
    @@ -278,6 +279,7 @@ internal class DefaultAuthenticationService @Inject constructor(
             }
             return LoginFlowResult.Success(
                     loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
    +                loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.identityProvider,
                     versions.isLoginAndRegistrationSupportedBySdk(),
                     homeServerUrl,
                     !versions.isSupportedBySdk()
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt
    new file mode 100644
    index 0000000000..2f1da60768
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt
    @@ -0,0 +1,50 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.internal.auth
    +
    +import com.zhuinden.monarchy.Monarchy
    +import io.realm.kotlin.where
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
    +import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
    +import org.matrix.android.sdk.internal.di.GlobalDatabase
    +import javax.inject.Inject
    +
    +class DefaultHomeServerHistoryService @Inject constructor(
    +        @GlobalDatabase private val monarchy: Monarchy
    +) : HomeServerHistoryService {
    +
    +    override fun getKnownServersUrls(): List {
    +        return monarchy.fetchAllMappedSync(
    +                { realm ->
    +                    realm.where()
    +                },
    +                { it.url }
    +        )
    +    }
    +
    +    override fun addHomeServerToHistory(url: String) {
    +        monarchy.writeAsync { realm ->
    +            KnownServerUrlEntity(url).let {
    +                realm.insertOrUpdate(it)
    +            }
    +        }
    +    }
    +
    +    override fun clearHistory() {
    +        monarchy.runTransactionSync { it.where().findAll().deleteAllFromRealm() }
    +    }
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
    index 8acdee3608..54d38cd336 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
    @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.data
     
     import com.squareup.moshi.Json
     import com.squareup.moshi.JsonClass
    +import org.matrix.android.sdk.api.auth.data.IdentityProvider
     
     @JsonClass(generateAdapter = true)
     internal data class LoginFlowResponse(
    @@ -34,5 +35,13 @@ internal data class LoginFlow(
              * The login type. This is supplied as the type when logging in.
              */
             @Json(name = "type")
    -        val type: String?
    +        val type: String?,
    +
    +        /**
    +         * Augments m.login.sso flow discovery definition to include metadata on the supported IDPs
    +         * the client can show a button for each of the supported providers
    +         * See MSC #2858
    +         */
    +        @Json(name = "identity_providers")
    +        val identityProvider: List?
     )
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt
    index 5b105c4d40..3461a4d738 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt
    @@ -51,6 +51,18 @@ data class RegistrationFlowResponse(
              * The information that the client will need to know in order to use a given type of authentication.
              * For each login stage type presented, that type may be present as a key in this dictionary.
              * For example, the public key of reCAPTCHA stage could be given here.
    +         * other example
    +         *  "params": {
    +         *       "m.login.sso": {
    +         *          "identity_providers": [
    +         *              {
    +         *                  "id": "google",
    +         *                  "name": "Google",
    +         *                  "icon": "https://..."
    +         *              }
    +         *          ]
    +         *      }
    +         *  }
              */
             @Json(name = "params")
             val params: JsonDict? = null
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/KnownServerUrlEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/KnownServerUrlEntity.kt
    new file mode 100644
    index 0000000000..1ebdc22ab4
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/KnownServerUrlEntity.kt
    @@ -0,0 +1,27 @@
    +/*
    + * Copyright 2020 The Matrix.org Foundation C.I.C.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + * http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.internal.database.model
    +
    +import io.realm.RealmObject
    +import io.realm.annotations.PrimaryKey
    +
    +internal open class KnownServerUrlEntity(
    +        @PrimaryKey
    +        var url: String = ""
    +) : RealmObject() {
    +    companion object
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
    index f959104e11..9d6fa29bb2 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
    @@ -25,6 +25,7 @@ import okhttp3.OkHttpClient
     import org.matrix.android.sdk.api.Matrix
     import org.matrix.android.sdk.api.MatrixConfiguration
     import org.matrix.android.sdk.api.auth.AuthenticationService
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.internal.SessionManager
     import org.matrix.android.sdk.internal.auth.AuthModule
    @@ -62,6 +63,8 @@ internal interface MatrixComponent {
     
         fun rawService(): RawService
     
    +    fun homeServerHistoryService(): HomeServerHistoryService
    +
         fun context(): Context
     
         fun matrixConfiguration(): MatrixConfiguration
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
    new file mode 100644
    index 0000000000..aded751dba
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
    @@ -0,0 +1,39 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.matrix.android.sdk.internal.raw
    +
    +import io.realm.DynamicRealm
    +import io.realm.RealmMigration
    +import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
    +import timber.log.Timber
    +
    +internal object GlobalRealmMigration : RealmMigration {
    +
    +    // Current schema version
    +    const val SCHEMA_VERSION = 1L
    +
    +    override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
    +        Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
    +
    +        if (oldVersion <= 0) migrateTo1(realm)
    +    }
    +
    +    private fun migrateTo1(realm: DynamicRealm) {
    +        realm.schema.create("KnownServerUrlEntity")
    +                .addField(KnownServerUrlEntityFields.URL, String::class.java)
    +    }
    +}
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt
    index e4e4160193..770a49c904 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt
    @@ -17,6 +17,7 @@
     package org.matrix.android.sdk.internal.raw
     
     import io.realm.annotations.RealmModule
    +import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
     import org.matrix.android.sdk.internal.database.model.RawCacheEntity
     
     /**
    @@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.database.model.RawCacheEntity
      */
     @RealmModule(library = true,
             classes = [
    -            RawCacheEntity::class
    +            RawCacheEntity::class,
    +            KnownServerUrlEntity::class
             ])
     internal class GlobalRealmModule
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
    index aee2a52818..a368dd77c9 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
    @@ -24,7 +24,9 @@ import dagger.Module
     import dagger.Provides
     import io.realm.RealmConfiguration
     import okhttp3.OkHttpClient
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.raw.RawService
    +import org.matrix.android.sdk.internal.auth.DefaultHomeServerHistoryService
     import org.matrix.android.sdk.internal.database.RealmKeysUtils
     import org.matrix.android.sdk.internal.di.GlobalDatabase
     import org.matrix.android.sdk.internal.di.MatrixScope
    @@ -57,6 +59,8 @@ internal abstract class RawModule {
                             realmKeysUtils.configureEncryption(this, DB_ALIAS)
                         }
                         .name("matrix-sdk-global.realm")
    +                    .schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
    +                    .migration(GlobalRealmMigration)
                         .modules(GlobalRealmModule())
                         .build()
             }
    @@ -77,4 +81,7 @@ internal abstract class RawModule {
     
         @Binds
         abstract fun bindCleanRawCacheTask(task: DefaultCleanRawCacheTask): CleanRawCacheTask
    +
    +    @Binds
    +    abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
     }
    diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
    index 188ca32559..6b0048047d 100644
    --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
    @@ -64,7 +64,6 @@ import im.vector.app.features.login.LoginResetPasswordSuccessFragment
     import im.vector.app.features.login.LoginServerSelectionFragment
     import im.vector.app.features.login.LoginServerUrlFormFragment
     import im.vector.app.features.login.LoginSignUpSignInSelectionFragment
    -import im.vector.app.features.login.LoginSignUpSignInSsoFragment
     import im.vector.app.features.login.LoginSplashFragment
     import im.vector.app.features.login.LoginWaitForEmailFragment
     import im.vector.app.features.login.LoginWebFragment
    @@ -229,11 +228,11 @@ interface FragmentModule {
         @IntoMap
         @FragmentKey(LoginSignUpSignInSelectionFragment::class)
         fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment
    -
    -    @Binds
    -    @IntoMap
    -    @FragmentKey(LoginSignUpSignInSsoFragment::class)
    -    fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
    +//
    +//    @Binds
    +//    @IntoMap
    +//    @FragmentKey(LoginSignUpSignInSsoFragment::class)
    +//    fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
     
         @Binds
         @IntoMap
    diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt
    index 28f3a52efa..273a142ff1 100644
    --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt
    @@ -59,6 +59,7 @@ import im.vector.app.features.settings.VectorPreferences
     import im.vector.app.features.ui.UiStateRepository
     import org.matrix.android.sdk.api.Matrix
     import org.matrix.android.sdk.api.auth.AuthenticationService
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.api.session.Session
     import javax.inject.Singleton
    @@ -127,6 +128,8 @@ interface VectorComponent {
     
         fun rawService(): RawService
     
    +    fun homeServerHistoryService(): HomeServerHistoryService
    +
         fun bugReporter(): BugReporter
     
         fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler
    diff --git a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt
    index 1d7cd33241..77cad0ae73 100644
    --- a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt
    @@ -33,6 +33,7 @@ import im.vector.app.features.ui.SharedPreferencesUiStateRepository
     import im.vector.app.features.ui.UiStateRepository
     import org.matrix.android.sdk.api.Matrix
     import org.matrix.android.sdk.api.auth.AuthenticationService
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.legacy.LegacySessionImporter
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.api.session.Session
    @@ -85,6 +86,12 @@ abstract class VectorModule {
             fun providesRawService(matrix: Matrix): RawService {
                 return matrix.rawService()
             }
    +
    +        @Provides
    +        @JvmStatic
    +        fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
    +            return matrix.homeServerHistoryService()
    +        }
         }
     
         @Binds
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
    index eb5aa86b3b..9480df4108 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
    @@ -18,6 +18,7 @@ package im.vector.app.features.login
     
     import im.vector.app.core.platform.VectorViewModelAction
     import org.matrix.android.sdk.api.auth.data.Credentials
    +import org.matrix.android.sdk.api.auth.data.IdentityProvider
     import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
     import org.matrix.android.sdk.internal.network.ssl.Fingerprint
     
    @@ -60,7 +61,7 @@ sealed class LoginAction : VectorViewModelAction {
         object ResetResetPassword : ResetAction()
     
         // For the soft logout case
    -    data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
    +    data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String, val identityProvider: List?) : LoginAction()
     
         data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
     
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    index 01e835b4e3..52efe128fb 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
    @@ -112,7 +112,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
     
         private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
             when (loginViewEvents) {
    -            is LoginViewEvents.RegistrationFlowResult                     -> {
    +            is LoginViewEvents.RegistrationFlowResult -> {
                     // Check that all flows are supported by the application
                     if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) {
                         // Display a popup to propose use web fallback
    @@ -133,7 +133,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
                         }
                     }
                 }
    -            is LoginViewEvents.OutdatedHomeserver                         -> {
    +            is LoginViewEvents.OutdatedHomeserver -> {
                     AlertDialog.Builder(this)
                             .setTitle(R.string.login_error_outdated_homeserver_title)
                             .setMessage(R.string.login_error_outdated_homeserver_warning_content)
    @@ -141,7 +141,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
                             .show()
                     Unit
                 }
    -            is LoginViewEvents.OpenServerSelection                        ->
    +            is LoginViewEvents.OpenServerSelection ->
                     addFragmentToBackstack(R.id.loginFragmentContainer,
                             LoginServerSelectionFragment::class.java,
                             option = { ft ->
    @@ -153,28 +153,24 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
                                 // TODO Disabled because it provokes a flickering
                                 // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
                             })
    -            is LoginViewEvents.OnServerSelectionDone                      -> onServerSelectionDone(loginViewEvents)
    -            is LoginViewEvents.OnSignModeSelected                         -> onSignModeSelected(loginViewEvents)
    -            is LoginViewEvents.OnLoginFlowRetrieved                       ->
    +            is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents)
    +            is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents)
    +            is LoginViewEvents.OnLoginFlowRetrieved ->
                     addFragmentToBackstack(R.id.loginFragmentContainer,
    -                        if (loginViewEvents.isSso) {
    -                            LoginSignUpSignInSsoFragment::class.java
    -                        } else {
    -                            LoginSignUpSignInSelectionFragment::class.java
    -                        },
    +                        LoginSignUpSignInSelectionFragment::class.java,
                             option = commonOption)
    -            is LoginViewEvents.OnWebLoginError                            -> onWebLoginError(loginViewEvents)
    -            is LoginViewEvents.OnForgetPasswordClicked                    ->
    +            is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents)
    +            is LoginViewEvents.OnForgetPasswordClicked ->
                     addFragmentToBackstack(R.id.loginFragmentContainer,
                             LoginResetPasswordFragment::class.java,
                             option = commonOption)
    -            is LoginViewEvents.OnResetPasswordSendThreePidDone            -> {
    +            is LoginViewEvents.OnResetPasswordSendThreePidDone -> {
                     supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
                     addFragmentToBackstack(R.id.loginFragmentContainer,
                             LoginResetPasswordMailConfirmationFragment::class.java,
                             option = commonOption)
                 }
    -            is LoginViewEvents.OnResetPasswordMailConfirmationSuccess     -> {
    +            is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> {
                     supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
                     addFragmentToBackstack(R.id.loginFragmentContainer,
                             LoginResetPasswordSuccessFragment::class.java,
    @@ -184,20 +180,20 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
                     // Go back to the login fragment
                     supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
                 }
    -            is LoginViewEvents.OnSendEmailSuccess                         ->
    +            is LoginViewEvents.OnSendEmailSuccess ->
                     addFragmentToBackstack(R.id.loginFragmentContainer,
                             LoginWaitForEmailFragment::class.java,
                             LoginWaitForEmailFragmentArgument(loginViewEvents.email),
                             tag = FRAGMENT_REGISTRATION_STAGE_TAG,
                             option = commonOption)
    -            is LoginViewEvents.OnSendMsisdnSuccess                        ->
    +            is LoginViewEvents.OnSendMsisdnSuccess ->
                     addFragmentToBackstack(R.id.loginFragmentContainer,
                             LoginGenericTextInputFormFragment::class.java,
                             LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn),
                             tag = FRAGMENT_REGISTRATION_STAGE_TAG,
                             option = commonOption)
                 is LoginViewEvents.Failure,
    -            is LoginViewEvents.Loading                                    ->
    +            is LoginViewEvents.Loading ->
                     // This is handled by the Fragments
                     Unit
             }.exhaustive
    @@ -234,25 +230,26 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
             when (loginViewEvents.serverType) {
                 ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
                 ServerType.EMS,
    -            ServerType.Other     -> addFragmentToBackstack(R.id.loginFragmentContainer,
    +            ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
                         LoginServerUrlFormFragment::class.java,
                         option = commonOption)
    -            ServerType.Unknown   -> Unit /* Should not happen */
    +            ServerType.Unknown -> Unit /* Should not happen */
             }
         }
     
         private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state ->
             // state.signMode could not be ready yet. So use value from the ViewEvent
             when (loginViewEvents.signMode) {
    -            SignMode.Unknown            -> error("Sign mode has to be set before calling this method")
    -            SignMode.SignUp             -> {
    +            SignMode.Unknown -> error("Sign mode has to be set before calling this method")
    +            SignMode.SignUp -> {
                     // This is managed by the LoginViewEvents
                 }
    -            SignMode.SignIn             -> {
    +            SignMode.SignIn -> {
                     // It depends on the LoginMode
                     when (state.loginMode) {
                         LoginMode.Unknown,
    -                    LoginMode.Sso         -> error("Developer error")
    +                    is LoginMode.Sso      -> error("Developer error")
    +                    is LoginMode.SsoAndPassword,
                         LoginMode.Password    -> addFragmentToBackstack(R.id.loginFragmentContainer,
                                 LoginFragment::class.java,
                                 tag = FRAGMENT_LOGIN_TAG,
    @@ -331,17 +328,17 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
                         LoginCaptchaFragmentArgument(stage.publicKey),
                         tag = FRAGMENT_REGISTRATION_STAGE_TAG,
                         option = commonOption)
    -            is Stage.Email     -> addFragmentToBackstack(R.id.loginFragmentContainer,
    +            is Stage.Email -> addFragmentToBackstack(R.id.loginFragmentContainer,
                         LoginGenericTextInputFormFragment::class.java,
                         LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
                         tag = FRAGMENT_REGISTRATION_STAGE_TAG,
                         option = commonOption)
    -            is Stage.Msisdn    -> addFragmentToBackstack(R.id.loginFragmentContainer,
    +            is Stage.Msisdn -> addFragmentToBackstack(R.id.loginFragmentContainer,
                         LoginGenericTextInputFormFragment::class.java,
                         LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
                         tag = FRAGMENT_REGISTRATION_STAGE_TAG,
                         option = commonOption)
    -            is Stage.Terms     -> addFragmentToBackstack(R.id.loginFragmentContainer,
    +            is Stage.Terms -> addFragmentToBackstack(R.id.loginFragmentContainer,
                         LoginTermsFragment::class.java,
                         LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))),
                         tag = FRAGMENT_REGISTRATION_STAGE_TAG,
    @@ -350,6 +347,10 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
             }
         }
     
    +    override fun onBackPressed() {
    +        super.onBackPressed()
    +    }
    +
         override fun configure(toolbar: Toolbar) {
             configureToolbar(toolbar)
         }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    index 3ee0d6d9df..0ce4317deb 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
    @@ -37,6 +37,7 @@ import io.reactivex.Observable
     import io.reactivex.functions.BiFunction
     import io.reactivex.rxkotlin.subscribeBy
     import kotlinx.android.synthetic.main.fragment_login.*
    +import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
     import org.matrix.android.sdk.api.failure.Failure
     import org.matrix.android.sdk.api.failure.MatrixError
     import org.matrix.android.sdk.api.failure.isInvalidPassword
    @@ -79,15 +80,17 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
         private fun setupAutoFill(state: LoginViewState) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                 when (state.signMode) {
    -                SignMode.Unknown            -> error("developer error")
    -                SignMode.SignUp             -> {
    +                SignMode.Unknown -> error("developer error")
    +                SignMode.SignUp -> {
                         loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
                         passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
    +                    loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP
                     }
                     SignMode.SignIn,
                     SignMode.SignInWithMatrixId -> {
                         loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
                         passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
    +                    loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN
                     }
                 }.exhaustive
             }
    @@ -142,9 +145,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
                 loginPasswordNotice.isVisible = true
             } else {
                 val resId = when (state.signMode) {
    -                SignMode.Unknown            -> error("developer error")
    -                SignMode.SignUp             -> R.string.login_signup_to
    -                SignMode.SignIn             -> R.string.login_connect_to
    +                SignMode.Unknown -> error("developer error")
    +                SignMode.SignUp -> R.string.login_signup_to
    +                SignMode.SignIn -> R.string.login_connect_to
                     SignMode.SignInWithMatrixId -> R.string.login_connect_to
                 }
     
    @@ -155,20 +158,28 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
                         loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
                         loginNotice.text = getString(R.string.login_server_matrix_org_text)
                     }
    -                ServerType.EMS       -> {
    +                ServerType.EMS -> {
                         loginServerIcon.isVisible = true
                         loginServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services)
                         loginTitle.text = getString(resId, "Element Matrix Services")
                         loginNotice.text = getString(R.string.login_server_modular_text)
                     }
    -                ServerType.Other     -> {
    +                ServerType.Other -> {
                         loginServerIcon.isVisible = false
                         loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
                         loginNotice.text = getString(R.string.login_server_other_text)
                     }
    -                ServerType.Unknown   -> Unit /* Should not happen */
    +                ServerType.Unknown -> Unit /* Should not happen */
                 }
                 loginPasswordNotice.isVisible = false
    +
    +            if (state.loginMode is LoginMode.SsoAndPassword) {
    +                loginSocialLoginContainer.isVisible = true
    +                loginSocialLoginButtons.identityProviders = state.loginMode.identityProviders
    +            } else {
    +                loginSocialLoginContainer.isVisible = false
    +                loginSocialLoginButtons.identityProviders = null
    +            }
             }
         }
     
    @@ -257,7 +268,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
                     passwordShown = false
                     renderPasswordField()
                 }
    -            is Fail    -> {
    +            is Fail -> {
                     val error = state.asyncLoginAction.error
                     if (error is Failure.ServerError
                             && error.error.code == MatrixError.M_FORBIDDEN
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt
    index 9a930dcd86..5121bf39ba 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt
    @@ -16,9 +16,31 @@
     
     package im.vector.app.features.login
     
    -enum class LoginMode {
    -    Unknown,
    -    Password,
    -    Sso,
    -    Unsupported
    +import android.os.Parcelable
    +import kotlinx.android.parcel.Parcelize
    +import org.matrix.android.sdk.api.auth.data.IdentityProvider
    +
    +sealed class LoginMode : Parcelable
    +/** because persist state */ {
    +    @Parcelize object Unknown : LoginMode()
    +    @Parcelize object Password : LoginMode()
    +    @Parcelize data class Sso(val identityProviders: List?) : LoginMode()
    +    @Parcelize data class SsoAndPassword(val identityProviders: List?) : LoginMode()
    +    @Parcelize object Unsupported : LoginMode()
    +}
    +
    +fun LoginMode.ssoProviders() : List? {
    +    return when (this) {
    +        is LoginMode.Sso            -> identityProviders
    +        is LoginMode.SsoAndPassword -> identityProviders
    +        else                        -> null
    +    }
    +}
    +
    +fun LoginMode.hasSso() : Boolean {
    +    return when (this) {
    +        is LoginMode.Sso            -> true
    +        is LoginMode.SsoAndPassword -> true
    +        else                        -> false
    +    }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt
    index f4595de634..c1854a3269 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt
    @@ -83,7 +83,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
     
             if (state.loginMode != LoginMode.Unknown) {
                 // LoginFlow for matrix.org has been retrieved
    -            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
    +            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode is LoginMode.Sso)))
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
    index af959fecd4..1cd3148456 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
    @@ -20,8 +20,10 @@ import android.annotation.SuppressLint
     import android.os.Bundle
     import android.view.View
     import android.view.inputmethod.EditorInfo
    +import android.widget.ArrayAdapter
     import androidx.core.view.isVisible
     import butterknife.OnClick
    +import com.google.android.material.textfield.TextInputLayout
     import com.jakewharton.rxbinding3.widget.textChanges
     import im.vector.app.R
     import im.vector.app.core.extensions.hideKeyboard
    @@ -55,6 +57,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
     
             loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
                 if (actionId == EditorInfo.IME_ACTION_DONE) {
    +                loginServerUrlFormHomeServerUrl.dismissDropDown()
                     submit()
                     return@setOnEditorActionListener true
                 }
    @@ -81,6 +84,13 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
                     loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
                 }
             }
    +        val completions =  state.knownCustomHomeServersUrls
    +        loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter(requireContext(), android.R.layout.select_dialog_item,
    +                completions
    +        ))
    +        loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU
    +                .takeIf { completions.isNotEmpty() }
    +                ?: TextInputLayout.END_ICON_NONE
         }
     
         @OnClick(R.id.loginServerUrlFormLearnMore)
    @@ -105,7 +115,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
                     loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server)
                 }
                 else                -> {
    -                loginServerUrlFormHomeServerUrl.setText(serverUrl)
    +                loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/)
                     loginViewModel.handle(LoginAction.UpdateHomeServer(serverUrl))
                 }
             }
    @@ -131,7 +141,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
     
             if (state.loginMode != LoginMode.Unknown) {
                 // The home server url is valid
    -            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
    +            loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode is LoginMode.Sso)))
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt
    index fa2f6b9df8..00b03835ae 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt
    @@ -16,21 +16,36 @@
     
     package im.vector.app.features.login
     
    +import android.content.ComponentName
    +import android.net.Uri
    +import androidx.browser.customtabs.CustomTabsClient
    +import androidx.browser.customtabs.CustomTabsServiceConnection
    +import androidx.browser.customtabs.CustomTabsSession
     import androidx.core.view.isVisible
     import butterknife.OnClick
    +import com.airbnb.mvrx.withState
     import im.vector.app.R
     import im.vector.app.core.extensions.toReducedUrl
    +import im.vector.app.core.utils.openUrlInChromeCustomTab
     import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
    +import org.matrix.android.sdk.api.auth.data.IdentityProvider
     import javax.inject.Inject
     
     /**
      * In this screen, the user is asked to sign up or to sign in to the homeserver
      */
    -open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
    +class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
    +
    +    // Map of sso urls by providers if any
    +    private var ssoUrls = emptyMap().toMutableMap()
    +
    +    private var customTabsServiceConnection: CustomTabsServiceConnection? = null
    +    private var customTabsClient: CustomTabsClient? = null
    +    private var customTabsSession: CustomTabsSession? = null
     
         override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection
     
    -    protected fun setupUi(state: LoginViewState) {
    +    private fun setupUi(state: LoginViewState) {
             when (state.serverType) {
                 ServerType.MatrixOrg -> {
                     loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
    @@ -51,16 +66,110 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
                 }
                 ServerType.Unknown   -> Unit /* Should not happen */
             }
    +
    +        val identityProviders = state.loginMode.ssoProviders()
    +        if (state.loginMode.hasSso() && identityProviders.isNullOrEmpty().not()) {
    +            loginSignupSigninSignInSocialLoginContainer.isVisible = true
    +            loginSignupSigninSocialLoginButtons.identityProviders = identityProviders
    +            loginSignupSigninSocialLoginButtons.listener = object: SocialLoginButtonsView.InteractionListener {
    +                override fun onProviderSelected(id: IdentityProvider) {
    +                    ssoUrls[id.id]?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
    +                }
    +            }
    +        } else {
    +            loginSignupSigninSignInSocialLoginContainer.isVisible = false
    +            loginSignupSigninSocialLoginButtons.identityProviders = null
    +        }
         }
     
    -    private fun setupButtons() {
    -        loginSignupSigninSubmit.text = getString(R.string.login_signup)
    -        loginSignupSigninSignIn.isVisible = true
    +    override fun onStart() {
    +        super.onStart()
    +        val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() }
    +        if (hasSSO) {
    +            val packageName = CustomTabsClient.getPackageName(requireContext(), null)
    +
    +            // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
    +            if (packageName != null) {
    +                customTabsServiceConnection = object : CustomTabsServiceConnection() {
    +                    override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
    +                        customTabsClient = client
    +                                .also { it.warmup(0L) }
    +
    +                        // prefetch urls
    +                        prefetchSsoUrls()
    +                    }
    +
    +                    override fun onServiceDisconnected(name: ComponentName?) {
    +                    }
    +                }
    +                        .also {
    +                            CustomTabsClient.bindCustomTabsService(
    +                                    requireContext(),
    +                                    // Despite the API, packageName cannot be null
    +                                    packageName,
    +                                    it
    +                            )
    +                        }
    +            }
    +        }
    +    }
    +
    +    override fun onStop() {
    +        super.onStop()
    +        val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() }
    +        if (hasSSO) {
    +            customTabsServiceConnection?.let { requireContext().unbindService(it) }
    +            customTabsServiceConnection = null
    +        }
    +    }
    +
    +    private fun prefetchSsoUrls() = withState(loginViewModel) { state ->
    +        val providers = state.loginMode.ssoProviders()
    +        if (providers.isNullOrEmpty()) {
    +            state.getSsoUrl(null).let {
    +                ssoUrls[null] = it
    +                prefetchUrl(it)
    +            }
    +        } else {
    +            providers.forEach { identityProvider ->
    +                state.getSsoUrl(identityProvider.id).let {
    +                    ssoUrls[identityProvider.id] = it
    +                    // we don't prefetch for privacy reasons
    +                }
    +            }
    +        }
    +    }
    +
    +    private fun prefetchUrl(url: String) {
    +        if (customTabsSession == null) {
    +            customTabsSession = customTabsClient?.newSession(null)
    +        }
    +
    +        customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
    +    }
    +
    +    private fun setupButtons(state: LoginViewState) {
    +        when (state.loginMode) {
    +            is LoginMode.Sso -> {
    +                // change to only one button that is sign in with sso
    +                loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
    +                loginSignupSigninSignIn.isVisible = false
    +            }
    +            else             -> {
    +                loginSignupSigninSubmit.text = getString(R.string.login_signup)
    +                loginSignupSigninSignIn.isVisible = true
    +            }
    +        }
         }
     
         @OnClick(R.id.loginSignupSigninSubmit)
    -    open fun submit() {
    -        loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
    +    fun submit() = withState(loginViewModel) { state ->
    +        if (state.loginMode is LoginMode.Sso) {
    +            ssoUrls[null]?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
    +        } else {
    +            loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
    +        }
    +        Unit
         }
     
         @OnClick(R.id.loginSignupSigninSignIn)
    @@ -74,6 +183,6 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
     
         override fun updateWithState(state: LoginViewState) {
             setupUi(state)
    -        setupButtons()
    +        setupButtons(state)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSsoFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSsoFragment.kt
    deleted file mode 100644
    index 8d33ec2e27..0000000000
    --- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSsoFragment.kt
    +++ /dev/null
    @@ -1,99 +0,0 @@
    -/*
    - * Copyright (c) 2020 New Vector Ltd
    - *
    - * Licensed under the Apache License, Version 2.0 (the "License");
    - * you may not use this file except in compliance with the License.
    - * You may obtain a copy of the License at
    - *
    - *     http://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing, software
    - * distributed under the License is distributed on an "AS IS" BASIS,
    - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    - * See the License for the specific language governing permissions and
    - * limitations under the License.
    - */
    -
    -package im.vector.app.features.login
    -
    -import android.content.ComponentName
    -import android.net.Uri
    -import androidx.browser.customtabs.CustomTabsClient
    -import androidx.browser.customtabs.CustomTabsServiceConnection
    -import androidx.browser.customtabs.CustomTabsSession
    -import androidx.core.view.isVisible
    -import im.vector.app.R
    -import im.vector.app.core.utils.openUrlInChromeCustomTab
    -import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
    -import javax.inject.Inject
    -
    -/**
    - * In this screen, the user is asked to sign up or to sign in using SSO
    - * This Fragment binds a CustomTabsServiceConnection if available, then prefetch the SSO url, as it will be likely to be opened.
    - */
    -open class LoginSignUpSignInSsoFragment @Inject constructor() : LoginSignUpSignInSelectionFragment() {
    -
    -    private var ssoUrl: String? = null
    -    private var customTabsServiceConnection: CustomTabsServiceConnection? = null
    -    private var customTabsClient: CustomTabsClient? = null
    -    private var customTabsSession: CustomTabsSession? = null
    -
    -    override fun onStart() {
    -        super.onStart()
    -
    -        val packageName = CustomTabsClient.getPackageName(requireContext(), null)
    -
    -        // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
    -        if (packageName != null) {
    -            customTabsServiceConnection = object : CustomTabsServiceConnection() {
    -                override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
    -                    customTabsClient = client
    -                            .also { it.warmup(0L) }
    -                }
    -
    -                override fun onServiceDisconnected(name: ComponentName?) {
    -                }
    -            }
    -                    .also {
    -                        CustomTabsClient.bindCustomTabsService(
    -                                requireContext(),
    -                                // Despite the API, packageName cannot be null
    -                                packageName,
    -                                it
    -                        )
    -                    }
    -        }
    -    }
    -
    -    private fun prefetchUrl(url: String) {
    -        if (ssoUrl != null) return
    -
    -        ssoUrl = url
    -        if (customTabsSession == null) {
    -            customTabsSession = customTabsClient?.newSession(null)
    -        }
    -
    -        customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
    -    }
    -
    -    override fun onStop() {
    -        super.onStop()
    -        customTabsServiceConnection?.let { requireContext().unbindService(it) }
    -        customTabsServiceConnection = null
    -    }
    -
    -    private fun setupButtons() {
    -        loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
    -        loginSignupSigninSignIn.isVisible = false
    -    }
    -
    -    override fun submit() {
    -        ssoUrl?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
    -    }
    -
    -    override fun updateWithState(state: LoginViewState) {
    -        setupUi(state)
    -        setupButtons()
    -        prefetchUrl(state.getSsoUrl())
    -    }
    -}
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    index 1f47916538..e00e4065e1 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    @@ -28,6 +28,7 @@ import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.ViewModelContext
     import com.squareup.inject.assisted.Assisted
     import com.squareup.inject.assisted.AssistedInject
    +import im.vector.app.BuildConfig
     import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.extensions.configureAndStart
    @@ -38,6 +39,7 @@ import im.vector.app.core.utils.ensureTrailingSlash
     import im.vector.app.features.signout.soft.SoftLogoutActivity
     import org.matrix.android.sdk.api.MatrixCallback
     import org.matrix.android.sdk.api.auth.AuthenticationService
    +import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
     import org.matrix.android.sdk.api.auth.data.LoginFlowResult
     import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
    @@ -63,7 +65,8 @@ class LoginViewModel @AssistedInject constructor(
             private val activeSessionHolder: ActiveSessionHolder,
             private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
             private val reAuthHelper: ReAuthHelper,
    -        private val stringProvider: StringProvider
    +        private val stringProvider: StringProvider,
    +        private val homeServerHistoryService: HomeServerHistoryService
     ) : VectorViewModel(initialState) {
     
         @AssistedInject.Factory
    @@ -71,12 +74,21 @@ class LoginViewModel @AssistedInject constructor(
             fun create(initialState: LoginViewState): LoginViewModel
         }
     
    +    init {
    +        if (BuildConfig.DEBUG) {
    +            homeServerHistoryService.addHomeServerToHistory("http://10.0.2.2:8080")
    +        }
    +        setState {
    +            copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls())
    +        }
    +    }
    +
         companion object : MvRxViewModelFactory {
     
             @JvmStatic
             override fun create(viewModelContext: ViewModelContext, state: LoginViewState): LoginViewModel? {
                 return when (val activity: FragmentActivity = (viewModelContext as ActivityViewModelContext).activity()) {
    -                is LoginActivity      -> activity.loginViewModelFactory.create(state)
    +                is LoginActivity -> activity.loginViewModelFactory.create(state)
                     is SoftLogoutActivity -> activity.loginViewModelFactory.create(state)
                     else                  -> error("Invalid Activity")
                 }
    @@ -108,20 +120,20 @@ class LoginViewModel @AssistedInject constructor(
     
         override fun handle(action: LoginAction) {
             when (action) {
    -            is LoginAction.UpdateServerType           -> handleUpdateServerType(action)
    -            is LoginAction.UpdateSignMode             -> handleUpdateSignMode(action)
    -            is LoginAction.InitWith                   -> handleInitWith(action)
    -            is LoginAction.UpdateHomeServer           -> handleUpdateHomeserver(action).also { lastAction = action }
    -            is LoginAction.LoginOrRegister            -> handleLoginOrRegister(action).also { lastAction = action }
    -            is LoginAction.LoginWithToken             -> handleLoginWithToken(action)
    -            is LoginAction.WebLoginSuccess            -> handleWebLoginSuccess(action)
    -            is LoginAction.ResetPassword              -> handleResetPassword(action)
    +            is LoginAction.UpdateServerType -> handleUpdateServerType(action)
    +            is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
    +            is LoginAction.InitWith -> handleInitWith(action)
    +            is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
    +            is LoginAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
    +            is LoginAction.LoginWithToken -> handleLoginWithToken(action)
    +            is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
    +            is LoginAction.ResetPassword -> handleResetPassword(action)
                 is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
    -            is LoginAction.RegisterAction             -> handleRegisterAction(action)
    -            is LoginAction.ResetAction                -> handleResetAction(action)
    +            is LoginAction.RegisterAction -> handleRegisterAction(action)
    +            is LoginAction.ResetAction -> handleResetAction(action)
                 is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
    -            is LoginAction.UserAcceptCertificate      -> handleUserAcceptCertificate(action)
    -            is LoginAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
    +            is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
    +            is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
             }.exhaustive
         }
     
    @@ -129,11 +141,13 @@ class LoginViewModel @AssistedInject constructor(
             // It happen when we get the login flow, or during direct authentication.
             // So alter the homeserver config and retrieve again the login flow
             when (val finalLastAction = lastAction) {
    -            is LoginAction.UpdateHomeServer ->
    +            is LoginAction.UpdateHomeServer -> {
    +                rememberHomeServer(finalLastAction.homeServerUrl)
                     currentHomeServerConnectionConfig
                             ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
                             ?.let { getLoginFlow(it) }
    -            is LoginAction.LoginOrRegister  ->
    +            }
    +            is LoginAction.LoginOrRegister ->
                     handleDirectLogin(
                             finalLastAction,
                             HomeServerConnectionConfig.Builder()
    @@ -145,6 +159,13 @@ class LoginViewModel @AssistedInject constructor(
             }
         }
     
    +    private fun rememberHomeServer(homeServerUrl: String) {
    +        homeServerHistoryService.addHomeServerToHistory(homeServerUrl)
    +        setState {
    +            copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls())
    +        }
    +    }
    +
         private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
             val safeLoginWizard = loginWizard
     
    @@ -184,7 +205,7 @@ class LoginViewModel @AssistedInject constructor(
             setState {
                 copy(
                         signMode = SignMode.SignIn,
    -                    loginMode = LoginMode.Sso,
    +                    loginMode = LoginMode.Sso(action.identityProvider),
                         homeServerUrl = action.homeServerUrl,
                         deviceId = action.deviceId
                 )
    @@ -193,14 +214,14 @@ class LoginViewModel @AssistedInject constructor(
     
         private fun handleRegisterAction(action: LoginAction.RegisterAction) {
             when (action) {
    -            is LoginAction.CaptchaDone                  -> handleCaptchaDone(action)
    -            is LoginAction.AcceptTerms                  -> handleAcceptTerms()
    -            is LoginAction.RegisterDummy                -> handleRegisterDummy()
    -            is LoginAction.AddThreePid                  -> handleAddThreePid(action)
    -            is LoginAction.SendAgainThreePid            -> handleSendAgainThreePid()
    -            is LoginAction.ValidateThreePid             -> handleValidateThreePid(action)
    +            is LoginAction.CaptchaDone -> handleCaptchaDone(action)
    +            is LoginAction.AcceptTerms -> handleAcceptTerms()
    +            is LoginAction.RegisterDummy -> handleRegisterDummy()
    +            is LoginAction.AddThreePid -> handleAddThreePid(action)
    +            is LoginAction.SendAgainThreePid -> handleSendAgainThreePid()
    +            is LoginAction.ValidateThreePid -> handleValidateThreePid(action)
                 is LoginAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action)
    -            is LoginAction.StopEmailValidationCheck     -> handleStopEmailValidationCheck()
    +            is LoginAction.StopEmailValidationCheck -> handleStopEmailValidationCheck()
             }
         }
     
    @@ -237,7 +258,7 @@ class LoginViewModel @AssistedInject constructor(
                 }
     
                 when (data) {
    -                is RegistrationResult.Success      -> onSessionCreated(data.session)
    +                is RegistrationResult.Success -> onSessionCreated(data.session)
                     is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
                 }
             }
    @@ -337,7 +358,7 @@ class LoginViewModel @AssistedInject constructor(
                         )
                     }
                 }
    -            LoginAction.ResetHomeServerUrl  -> {
    +            LoginAction.ResetHomeServerUrl -> {
                     authenticationService.reset()
     
                     setState {
    @@ -350,7 +371,7 @@ class LoginViewModel @AssistedInject constructor(
                         )
                     }
                 }
    -            LoginAction.ResetSignMode       -> {
    +            LoginAction.ResetSignMode -> {
                     setState {
                         copy(
                                 asyncHomeServerLoginFlowRequest = Uninitialized,
    @@ -360,7 +381,7 @@ class LoginViewModel @AssistedInject constructor(
                         )
                     }
                 }
    -            LoginAction.ResetLogin          -> {
    +            LoginAction.ResetLogin -> {
                     authenticationService.cancelPendingLoginOrRegistration()
     
                     setState {
    @@ -370,7 +391,7 @@ class LoginViewModel @AssistedInject constructor(
                         )
                     }
                 }
    -            LoginAction.ResetResetPassword  -> {
    +            LoginAction.ResetResetPassword -> {
                     setState {
                         copy(
                                 asyncResetPassword = Uninitialized,
    @@ -390,10 +411,10 @@ class LoginViewModel @AssistedInject constructor(
             }
     
             when (action.signMode) {
    -            SignMode.SignUp             -> startRegistrationFlow()
    -            SignMode.SignIn             -> startAuthenticationFlow()
    +            SignMode.SignUp -> startRegistrationFlow()
    +            SignMode.SignIn -> startAuthenticationFlow()
                 SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId))
    -            SignMode.Unknown            -> Unit
    +            SignMode.Unknown -> Unit
             }
         }
     
    @@ -405,12 +426,12 @@ class LoginViewModel @AssistedInject constructor(
             }
     
             when (action.serverType) {
    -            ServerType.Unknown   -> Unit /* Should not happen */
    +            ServerType.Unknown -> Unit /* Should not happen */
                 ServerType.MatrixOrg ->
                     // Request login flow here
                     handle(LoginAction.UpdateHomeServer(matrixOrgUrl))
                 ServerType.EMS,
    -            ServerType.Other     -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
    +            ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
             }.exhaustive
         }
     
    @@ -514,9 +535,9 @@ class LoginViewModel @AssistedInject constructor(
     
         private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
             when (state.signMode) {
    -            SignMode.Unknown            -> error("Developer error, invalid sign mode")
    -            SignMode.SignIn             -> handleLogin(action)
    -            SignMode.SignUp             -> handleRegisterWith(action)
    +            SignMode.Unknown -> error("Developer error, invalid sign mode")
    +            SignMode.SignIn -> handleLogin(action)
    +            SignMode.SignUp -> handleRegisterWith(action)
                 SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
             }.exhaustive
         }
    @@ -713,11 +734,11 @@ class LoginViewModel @AssistedInject constructor(
     
         private fun handleUpdateHomeserver(action: LoginAction.UpdateHomeServer) {
             val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl)
    -
             if (homeServerConnectionConfig == null) {
                 // This is invalid
                 _viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
             } else {
    +            rememberHomeServer(action.homeServerUrl)
                 getLoginFlow(homeServerConnectionConfig)
             }
         }
    @@ -755,9 +776,11 @@ class LoginViewModel @AssistedInject constructor(
                         is LoginFlowResult.Success -> {
                             val loginMode = when {
                                 // SSO login is taken first
    -                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)      -> LoginMode.Sso
    -                            data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
    -                            else                                                       -> LoginMode.Unsupported
    +                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
    +                                    && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
    +                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)                 -> LoginMode.Sso(data.ssoIdentityProviders)
    +                            data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD)            -> LoginMode.Password
    +                            else                                                                  -> LoginMode.Unsupported
                             }
     
                             // FIXME We should post a view event here normally?
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt
    index 194ed668bc..383fd4a54e 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt
    @@ -51,7 +51,8 @@ data class LoginViewState(
             val loginMode: LoginMode = LoginMode.Unknown,
             @PersistState
             // Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable
    -        val loginModeSupportedTypes: List = emptyList()
    +        val loginModeSupportedTypes: List = emptyList(),
    +        val knownCustomHomeServersUrls: List = emptyList()
     ) : MvRxState {
     
         fun isLoading(): Boolean {
    @@ -68,10 +69,13 @@ data class LoginViewState(
             return asyncLoginAction is Success
         }
     
    -    fun getSsoUrl(): String {
    +    fun getSsoUrl(providerId: String?): String {
             return buildString {
                 append(homeServerUrl?.trim { it == '/' })
                 append(SSO_REDIRECT_PATH)
    +            if (providerId != null) {
    +                append("/$providerId")
    +            }
                 // Set a redirect url we will intercept later
                 appendParamToUrl(SSO_REDIRECT_URL_PARAM, VECTOR_REDIRECT_URL)
                 deviceId?.takeIf { it.isNotBlank() }?.let {
    diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
    new file mode 100644
    index 0000000000..1d323c0fbc
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
    @@ -0,0 +1,149 @@
    +/*
    + * Copyright (c) 2020 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.login
    +
    +import android.annotation.SuppressLint
    +import android.content.Context
    +import android.util.AttributeSet
    +import android.util.TypedValue
    +import android.view.Gravity
    +import android.view.View
    +import android.widget.LinearLayout
    +import androidx.core.view.children
    +import com.google.android.material.button.MaterialButton
    +import im.vector.app.R
    +import org.matrix.android.sdk.api.auth.data.IdentityProvider
    +
    +class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
    +    : LinearLayout(context, attrs, defStyle) {
    +
    +    interface InteractionListener {
    +        fun onProviderSelected(id: IdentityProvider)
    +    }
    +
    +    enum class Mode {
    +        MODE_SIGN_IN,
    +        MODE_SIGN_UP,
    +        MODE_CONTINUE,
    +    }
    +
    +    var identityProviders: List? = null
    +        set(newProviders) {
    +            if (newProviders != identityProviders) {
    +                field = newProviders
    +                update()
    +            }
    +        }
    +
    +    var mode: Mode = Mode.MODE_CONTINUE
    +        set(value) {
    +            if (value != mode) {
    +                field = value
    +                update()
    +            }
    +        }
    +
    +    var listener: InteractionListener? = null
    +
    +    private fun update() {
    +        val cachedViews = emptyMap().toMutableMap()
    +        children.filterIsInstance().forEach {
    +            cachedViews[it.getTag(R.id.loginSignupSigninSocialLoginButtons)?.toString() ?: ""] = it
    +        }
    +        removeAllViews()
    +        if (identityProviders.isNullOrEmpty()) {
    +            return
    +        }
    +
    +        identityProviders?.forEach { identityProvider ->
    +            // Use some heuristic to render buttons according to branding guidelines
    +            val cached = cachedViews[identityProvider.id]
    +            val button: MaterialButton = if (cached != null) {
    +                cached
    +            } else {
    +               when (identityProvider.id) {
    +                    "google" -> {
    +                        MaterialButton(context, null, R.attr.vctr_social_login_button_google_style)
    +                    }
    +                    "github" -> {
    +                        MaterialButton(context, null, R.attr.vctr_social_login_button_github_style)
    +                    }
    +                    "apple" -> {
    +                        MaterialButton(context, null, R.attr.vctr_social_login_button_apple_style)
    +                    }
    +                    "facebook" -> {
    +                        MaterialButton(context, null, R.attr.vctr_social_login_button_facebook_style)
    +                    }
    +                    "twitter" -> {
    +                        MaterialButton(context, null, R.attr.vctr_social_login_button_twitter_style)
    +                    }
    +                    else       -> {
    +                        MaterialButton(context, null, R.attr.materialButtonStyle).apply {
    +                            transformationMethod = null
    +                            textAlignment = View.TEXT_ALIGNMENT_CENTER
    +                        }
    +                    }
    +                }
    +            }
    +            button.text = getButtonTitle(identityProvider)
    +            button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
    +            button.setOnClickListener {
    +                listener?.onProviderSelected(identityProvider)
    +            }
    +            addView(button)
    +        }
    +    }
    +
    +    private fun getButtonTitle(provider: IdentityProvider): String {
    +        return when (mode) {
    +            Mode.MODE_SIGN_IN -> context.getString(R.string.login_social_signin_with, provider.name)
    +            Mode.MODE_SIGN_UP -> context.getString(R.string.login_social_signup_with, provider.name)
    +            Mode.MODE_CONTINUE -> context.getString(R.string.login_social_continue_with, provider.name)
    +        }
    +    }
    +
    +    init {
    +        this.orientation = VERTICAL
    +        gravity = Gravity.CENTER
    +        clipToPadding = false
    +        clipChildren = false
    +        @SuppressLint("SetTextI18n")
    +        if (isInEditMode) {
    +            identityProviders = listOf(
    +                    IdentityProvider("google", "Google", null),
    +                    IdentityProvider("facebook", "Facebook", null),
    +                    IdentityProvider("apple", "Apple", null),
    +                    IdentityProvider("github", "Github", null),
    +                    IdentityProvider("twitter", "Twitter", null),
    +                    IdentityProvider("Custom_pro", "SSO", null)
    +            )
    +        }
    +        val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SocialLoginButtonsView, 0, 0)
    +        val modeAttr = typedArray.getInt(R.styleable.SocialLoginButtonsView_signMode, 2)
    +        mode = when (modeAttr) {
    +            0 -> Mode.MODE_SIGN_IN
    +            1 -> Mode.MODE_SIGN_UP
    +            else     -> Mode.MODE_CONTINUE
    +        }
    +        typedArray.recycle()
    +    }
    +
    +    fun dpToPx(dp: Int): Int {
    +        val resources = context.resources
    +        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).toInt()
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt
    index 989fc0aadb..89fa4a982a 100644
    --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt
    +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt
    @@ -107,7 +107,7 @@ class SoftLogoutController @Inject constructor(
                 }
                 is Success    -> {
                     when (state.asyncHomeServerLoginFlowRequest.invoke()) {
    -                    LoginMode.Password    -> {
    +                    LoginMode.Password          -> {
                             loginPasswordFormItem {
                                 id("passwordForm")
                                 stringProvider(stringProvider)
    @@ -120,21 +120,23 @@ class SoftLogoutController @Inject constructor(
                                 submitClickListener { password -> listener?.signinSubmit(password) }
                             }
                         }
    -                    LoginMode.Sso         -> {
    +                    is LoginMode.Sso            -> {
                             loginCenterButtonItem {
                                 id("sso")
                                 text(stringProvider.getString(R.string.login_signin_sso))
                                 listener { listener?.signinFallbackSubmit() }
                             }
                         }
    -                    LoginMode.Unsupported -> {
    +                    is LoginMode.SsoAndPassword -> {
    +                    }
    +                    LoginMode.Unsupported       -> {
                             loginCenterButtonItem {
                                 id("fallback")
                                 text(stringProvider.getString(R.string.login_signin))
                                 listener { listener?.signinFallbackSubmit() }
                             }
                         }
    -                    LoginMode.Unknown     -> Unit // Should not happen
    +                    LoginMode.Unknown           -> Unit // Should not happen
                     }
                 }
             }
    diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt
    index dbd5028401..0fc1076f1d 100644
    --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt
    @@ -54,14 +54,27 @@ class SoftLogoutFragment @Inject constructor(
     
             softLogoutViewModel.subscribe(this) { softLogoutViewState ->
                 softLogoutController.update(softLogoutViewState)
    -
    -            when (softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) {
    -                LoginMode.Sso,
    +            when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) {
    +                is LoginMode.SsoAndPassword -> {
    +                    loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
    +                            softLogoutViewState.homeServerUrl,
    +                            softLogoutViewState.deviceId,
    +                            mode.identityProviders
    +                    ))
    +                }
    +                is LoginMode.Sso -> {
    +                    loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
    +                            softLogoutViewState.homeServerUrl,
    +                            softLogoutViewState.deviceId,
    +                            mode.identityProviders
    +                    ))
    +                }
                     LoginMode.Unsupported -> {
                         // Prepare the loginViewModel for a SSO/login fallback recovery
                         loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
                                 softLogoutViewState.homeServerUrl,
    -                            softLogoutViewState.deviceId
    +                            softLogoutViewState.deviceId,
    +                            null
                         ))
                     }
                     else                  -> Unit
    diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
    index 4f6110aab1..f1d9a66342 100644
    --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
    @@ -105,9 +105,11 @@ class SoftLogoutViewModel @AssistedInject constructor(
                         is LoginFlowResult.Success -> {
                             val loginMode = when {
                                 // SSO login is taken first
    -                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)      -> LoginMode.Sso
    -                            data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
    -                            else                                                       -> LoginMode.Unsupported
    +                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
    +                                    && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
    +                            data.supportedLoginTypes.contains(LoginFlowTypes.SSO)                 -> LoginMode.Sso(data.ssoIdentityProviders)
    +                            data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD)            -> LoginMode.Password
    +                            else                                                                  -> LoginMode.Unsupported
                             }
     
                             setState {
    diff --git a/vector/src/main/res/color/button_social_google_background_selector_dark.xml b/vector/src/main/res/color/button_social_google_background_selector_dark.xml
    new file mode 100644
    index 0000000000..1369414e58
    --- /dev/null
    +++ b/vector/src/main/res/color/button_social_google_background_selector_dark.xml
    @@ -0,0 +1,7 @@
    +
    +
    +    
    +    
    +    
    +    
    +
    \ No newline at end of file
    diff --git a/vector/src/main/res/color/button_social_google_background_selector_light.xml b/vector/src/main/res/color/button_social_google_background_selector_light.xml
    new file mode 100644
    index 0000000000..bcac13885b
    --- /dev/null
    +++ b/vector/src/main/res/color/button_social_google_background_selector_light.xml
    @@ -0,0 +1,6 @@
    +
    +
    +    
    +    
    +    
    +
    \ No newline at end of file
    diff --git a/vector/src/main/res/drawable/ic_social_apple.xml b/vector/src/main/res/drawable/ic_social_apple.xml
    new file mode 100644
    index 0000000000..5d745a12a3
    --- /dev/null
    +++ b/vector/src/main/res/drawable/ic_social_apple.xml
    @@ -0,0 +1,12 @@
    +
    +  
    +
    diff --git a/vector/src/main/res/drawable/ic_social_facebook.xml b/vector/src/main/res/drawable/ic_social_facebook.xml
    new file mode 100644
    index 0000000000..77cb8be760
    --- /dev/null
    +++ b/vector/src/main/res/drawable/ic_social_facebook.xml
    @@ -0,0 +1,12 @@
    +
    +  
    +
    diff --git a/vector/src/main/res/drawable/ic_social_github.xml b/vector/src/main/res/drawable/ic_social_github.xml
    new file mode 100644
    index 0000000000..602f16aa60
    --- /dev/null
    +++ b/vector/src/main/res/drawable/ic_social_github.xml
    @@ -0,0 +1,12 @@
    +
    +  
    +
    diff --git a/vector/src/main/res/drawable/ic_social_google.xml b/vector/src/main/res/drawable/ic_social_google.xml
    new file mode 100644
    index 0000000000..1518c4cb29
    --- /dev/null
    +++ b/vector/src/main/res/drawable/ic_social_google.xml
    @@ -0,0 +1,36 @@
    +
    +  
    +  
    +  
    +  
    +  
    +
    diff --git a/vector/src/main/res/drawable/ic_social_twitter.xml b/vector/src/main/res/drawable/ic_social_twitter.xml
    new file mode 100644
    index 0000000000..7139c3c9bc
    --- /dev/null
    +++ b/vector/src/main/res/drawable/ic_social_twitter.xml
    @@ -0,0 +1,12 @@
    +
    +  
    +
    diff --git a/vector/src/main/res/layout/fragment_login.xml b/vector/src/main/res/layout/fragment_login.xml
    index ca7d267c80..2559e6d177 100644
    --- a/vector/src/main/res/layout/fragment_login.xml
    +++ b/vector/src/main/res/layout/fragment_login.xml
    @@ -19,9 +19,9 @@
                     android:id="@+id/loginServerIcon"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
    -                tools:src="@drawable/ic_logo_matrix_org"
                     app:tint="?riotx_text_primary"
    -                tools:ignore="MissingPrefix" />
    +                tools:ignore="MissingPrefix"
    +                tools:src="@drawable/ic_logo_matrix_org" />
     
                 
     
                 
    @@ -136,6 +136,37 @@
     
                 
     
    +            
    +            
    +
    +                
    +
    +                
    +
    +            
    +
             
     
         
    diff --git a/vector/src/main/res/layout/fragment_login_server_url_form.xml b/vector/src/main/res/layout/fragment_login_server_url_form.xml
    index 37122e8e43..3c223f19cc 100644
    --- a/vector/src/main/res/layout/fragment_login_server_url_form.xml
    +++ b/vector/src/main/res/layout/fragment_login_server_url_form.xml
    @@ -53,14 +53,14 @@
     
                 
     
    -                
     
    +            
    +            
    +
    +                
    +
    +                
    +
    +            
    +
             
     
         
    diff --git a/vector/src/main/res/values/attrs.xml b/vector/src/main/res/values/attrs.xml
    index 55a4f9a84e..b97a8f3837 100644
    --- a/vector/src/main/res/values/attrs.xml
    +++ b/vector/src/main/res/values/attrs.xml
    @@ -41,6 +41,12 @@
             
             
     
    +        
    +        
    +        
    +        
    +        
    +
         
     
         
    @@ -66,4 +72,12 @@
             
             
         
    +
    +    
    +        
    +            
    +            
    +            
    +        
    +    
     
    diff --git a/vector/src/main/res/values/colors_riotx.xml b/vector/src/main/res/values/colors_riotx.xml
    index 647ada65be..957d30398b 100644
    --- a/vector/src/main/res/values/colors_riotx.xml
    +++ b/vector/src/main/res/values/colors_riotx.xml
    @@ -41,6 +41,7 @@
         #FF000000
         #FFFFFFFF
         #55000000
    +    #8A000000
     
         
         Connect to %1$s
    diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml
    index 2dff0ab39c..b84dc1e138 100644
    --- a/vector/src/main/res/values/styles_riot.xml
    +++ b/vector/src/main/res/values/styles_riot.xml
    @@ -370,5 +370,103 @@
             @drawable/vector_tabbar_background
             @drawable/vector_tabbar_background
         
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +
    +    
    +
    +    
    +
    +    
    +
     
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml
    index 6ebf8e2b9b..6be0adb907 100644
    --- a/vector/src/main/res/values/theme_dark.xml
    +++ b/vector/src/main/res/values/theme_dark.xml
    @@ -194,6 +194,12 @@
             
             @transition/image_preview_transition
             @transition/image_preview_transition
    +
    +        @style/WidgetButtonSocialLogin.Google.Dark
    +        @style/WidgetButtonSocialLogin.Github.Dark
    +        @style/WidgetButtonSocialLogin.Facebook.Dark
    +        @style/WidgetButtonSocialLogin.Twitter.Dark
    +        @style/WidgetButtonSocialLogin.Apple.Dark
         
     
         
     
         
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -
    -    
    -
    -    
    -
    -    
    -
    -
    -    
    -
    -    
    -
    -    
    -
    -
    -    
    -
    -    
    -
    -    
    -
     
     
    \ No newline at end of file
    diff --git a/vector/src/main/res/values/styles_social_login.xml b/vector/src/main/res/values/styles_social_login.xml
    new file mode 100644
    index 0000000000..796965cee1
    --- /dev/null
    +++ b/vector/src/main/res/values/styles_social_login.xml
    @@ -0,0 +1,102 @@
    +
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +    
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +
    +    
    +
    +    
    +
    +    
    +
    +
    \ No newline at end of file
    
    From 09040b70954efc20e5b28e80af9939945bca2bfa Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Mon, 14 Dec 2020 16:20:36 +0100
    Subject: [PATCH 162/218] Clear history (#1933)
    
    ---
     CHANGES.md                                           |  1 +
     .../org/matrix/android/sdk/internal/raw/RawModule.kt |  3 +--
     .../java/im/vector/app/features/login/LoginAction.kt |  3 +++
     .../app/features/login/LoginServerUrlFormFragment.kt | 11 ++++++++++-
     .../im/vector/app/features/login/LoginViewModel.kt   | 12 +++++++-----
     .../res/layout/fragment_login_server_url_form.xml    | 11 +++++++++++
     vector/src/main/res/values/strings.xml               |  1 +
     7 files changed, 34 insertions(+), 8 deletions(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 78cea417aa..62bd92006e 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -13,6 +13,7 @@ Improvements 🙌:
      - Add Setting Item to Change PIN (#2462)
      - Improve room history visibility setting UX (#1579)
      - Matrix.to deeplink custom scheme support
    + - Homeserver history (#1933)
     
     Bugfix 🐛:
      - Fix cancellation of sending event (#2438)
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
    index 6ed14ddb3c..50721b809a 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
    @@ -24,9 +24,7 @@ import dagger.Module
     import dagger.Provides
     import io.realm.RealmConfiguration
     import okhttp3.OkHttpClient
    -import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.raw.RawService
    -import org.matrix.android.sdk.internal.auth.DefaultHomeServerHistoryService
     import org.matrix.android.sdk.internal.database.RealmKeysUtils
     import org.matrix.android.sdk.internal.di.GlobalDatabase
     import org.matrix.android.sdk.internal.di.MatrixScope
    @@ -61,6 +59,7 @@ internal abstract class RawModule {
                         .name("matrix-sdk-global.realm")
                         .schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
                         .migration(GlobalRealmMigration)
    +                    .allowWritesOnUiThread(true)
                         .modules(GlobalRealmModule())
                         .build()
             }
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
    index 9ab02711b5..2b4e3d6be0 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
    @@ -60,6 +60,9 @@ sealed class LoginAction : VectorViewModelAction {
         object ResetLogin : ResetAction()
         object ResetResetPassword : ResetAction()
     
    +    // Homeserver history
    +    object ClearHomeServerHistory : LoginAction()
    +
         // For the soft logout case
         data class SetupSsoForSessionRecovery(val homeServerUrl: String,
                                               val deviceId: String,
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
    index 716c1cda88..1915cdd204 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
    @@ -21,10 +21,12 @@ import android.os.Bundle
     import android.view.View
     import android.view.inputmethod.EditorInfo
     import android.widget.ArrayAdapter
    +import androidx.core.view.isInvisible
     import androidx.core.view.isVisible
     import butterknife.OnClick
     import com.google.android.material.textfield.TextInputLayout
     import com.jakewharton.rxbinding3.widget.textChanges
    +import im.vector.app.BuildConfig
     import im.vector.app.R
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.utils.ensureProtocol
    @@ -84,7 +86,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
                     loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
                 }
             }
    -        val completions =  state.knownCustomHomeServersUrls
    +        val completions =  state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
             loginServerUrlFormHomeServerUrl.setAdapter(ArrayAdapter(
                     requireContext(),
                     R.layout.item_completion_homeserver,
    @@ -100,6 +102,11 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
             openUrlInChromeCustomTab(requireActivity(), null, EMS_LINK)
         }
     
    +    @OnClick(R.id.loginServerUrlFormClearHistory)
    +    fun clearHistory() {
    +        loginViewModel.handle(LoginAction.ClearHomeServerHistory)
    +    }
    +
         override fun resetViewModel() {
             loginViewModel.handle(LoginAction.ResetHomeServerUrl)
         }
    @@ -141,6 +148,8 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
         override fun updateWithState(state: LoginViewState) {
             setupUi(state)
     
    +        loginServerUrlFormClearHistory.isInvisible = state.knownCustomHomeServersUrls.isEmpty()
    +
             if (state.loginMode != LoginMode.Unknown) {
                 // The home server url is valid
                 loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved))
    diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    index 2f84c3081d..0a6dbcaae2 100644
    --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
    @@ -28,7 +28,6 @@ import com.airbnb.mvrx.Uninitialized
     import com.airbnb.mvrx.ViewModelContext
     import com.squareup.inject.assisted.Assisted
     import com.squareup.inject.assisted.AssistedInject
    -import im.vector.app.BuildConfig
     import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.extensions.configureAndStart
    @@ -80,10 +79,7 @@ class LoginViewModel @AssistedInject constructor(
     
         private fun getKnownCustomHomeServersUrls() {
             setState {
    -            copy(
    -                    knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls()
    -                            + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
    -            )
    +            copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls())
             }
         }
     
    @@ -137,6 +133,7 @@ class LoginViewModel @AssistedInject constructor(
                 is LoginAction.ResetAction                -> handleResetAction(action)
                 is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
                 is LoginAction.UserAcceptCertificate      -> handleUserAcceptCertificate(action)
    +            LoginAction.ClearHomeServerHistory        -> handleClearHomeServerHistory()
                 is LoginAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
             }.exhaustive
         }
    @@ -167,6 +164,11 @@ class LoginViewModel @AssistedInject constructor(
             getKnownCustomHomeServersUrls()
         }
     
    +    private fun handleClearHomeServerHistory() {
    +        homeServerHistoryService.clearHistory()
    +        getKnownCustomHomeServersUrls()
    +    }
    +
         private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
             val safeLoginWizard = loginWizard
     
    diff --git a/vector/src/main/res/layout/fragment_login_server_url_form.xml b/vector/src/main/res/layout/fragment_login_server_url_form.xml
    index 3c223f19cc..a441fee3be 100644
    --- a/vector/src/main/res/layout/fragment_login_server_url_form.xml
    +++ b/vector/src/main/res/layout/fragment_login_server_url_form.xml
    @@ -70,6 +70,17 @@
     
                 
     
    +            
    +
                 Sign Up
         Sign In
         Continue with SSO
    +    Clear history
     
         Element Matrix Services Address
         Address
    
    From 995ec2599025eb398068d8a0ba2a9af45151869a Mon Sep 17 00:00:00 2001
    From: Besnik Bleta 
    Date: Sun, 13 Dec 2020 15:13:58 +0000
    Subject: [PATCH 163/218] Translated using Weblate (Albanian)
    
    Currently translated at 99.4% (1975 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/
    ---
     vector/src/main/res/values-sq/strings.xml | 6 ++++--
     1 file changed, 4 insertions(+), 2 deletions(-)
    
    diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
    index 93e82a7cde..9be2118399 100644
    --- a/vector/src/main/res/values-sq/strings.xml
    +++ b/vector/src/main/res/values-sq/strings.xml
    @@ -1099,7 +1099,8 @@
         Na ndjeni, thirrjet konferencë me Jitsi-n nuk mbulohen në pajisje të vjetra (pajisje me Android OS nën 5.0)
         Verifiko sesion
         ip e panjohur
    -    Një sesion i ri po kërkon emër keys.ession fshehtëzimi: %1$s
    +    Një sesion i ri po kërkon emër kyçe fshehtëzimi.
    +\nEmër sesioni: %1$s
     \nParë së fundi më: %2$s
     \nNëse s’keni bërë hyrje në një tjetër sesion, shpërfilleni këtë kërkesë.
         Një sesion i paverifikuar po kërkon kyçe fshehtëzimi. 
    @@ -1615,7 +1616,7 @@
         E dërgon emote-n e dhënë të ngjyrosur si ylber
         Rrjedhë kohore
         Përpunues mesazhesh
    -    Aktivizoni fshehtëzim skaj-më-skaj
    +    Aktivizoni fshehtëzim skaj-më-skaj…
         Pasi të aktivizohet, fshehtëzimi s’mund të çaktivizohet më.
         Të aktivizohet fshehtëzimi\?
         Pasi të aktivizohet, fshehtëzimi për një dhomë nuk mund të çaktivizohet. Mesazhet e dërguar në një dhomë të fshehtëzuar s’mund të shihen nga shërbyesi, vetëm nga pjesëmarrësit te dhoma. Aktivizimi i fshehtëzimit mund të pengojë funksionimin si duhet të mjaft robotëve dhe urave.
    @@ -2234,4 +2235,5 @@
         Kërkoni sipas emri ose ID-je
         Që të skanoni një kod QR, lypset të lejoni përdorim kamere.
         Filloni të Llafoseni
    +    Jepni leje për hyrje te kontaktet tuaja.
     
    \ No newline at end of file
    
    From 545e13c8433f22ddbd8d979669f85a8cdb773506 Mon Sep 17 00:00:00 2001
    From: Ihor Hordiichuk 
    Date: Sun, 13 Dec 2020 23:22:42 +0000
    Subject: [PATCH 164/218] Translated using Weblate (Ukrainian)
    
    Currently translated at 48.0% (955 of 1986 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
    ---
     vector/src/main/res/values-uk/strings.xml | 10 +++++-----
     1 file changed, 5 insertions(+), 5 deletions(-)
    
    diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
    index 186bf81a9b..ff949f88b5 100644
    --- a/vector/src/main/res/values-uk/strings.xml
    +++ b/vector/src/main/res/values-uk/strings.xml
    @@ -80,11 +80,11 @@
         Запрошення
         Низький пріоритет
         
    -    Діалоги
    +    Бесіди
         Локальні контакти
         Каталог користувачів
         Лише Matrix-контакти
    -    Немає діалогів
    +    Немає бесід
         Ви не надали Element доступу до контактів
         Немає результатів
         
    @@ -1107,9 +1107,9 @@
         Перевірка… (%1$d з %2$d)
         Перевірити
         Сповіщення усунення несправностей
    -    Захистити приватність діалогів шифруванням
    -    Фільтрувати діалоги…
    -    Діалоги
    +    Захистити приватність бесід шифруванням
    +    Фільтрувати бесіди…
    +    Бесіди
         У вас більше немає непрочитаних повідомлень
         Все прочитано!
         Встановти важливість сповіщень за подіями
    
    From 6687a74f5c47a7e36ef6eccd98333bf5939020c0 Mon Sep 17 00:00:00 2001
    From: Besnik Bleta 
    Date: Sun, 13 Dec 2020 15:28:10 +0000
    Subject: [PATCH 165/218] Translated using Weblate (Albanian)
    
    Currently translated at 98.0% (206 of 210 strings)
    
    Translation: Element Android/Element Android Sdk
    Translate-URL: https://translate.element.io/projects/element-android/element-sdk/sq/
    ---
     matrix-sdk-android/src/main/res/values-sq/strings.xml | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/matrix-sdk-android/src/main/res/values-sq/strings.xml b/matrix-sdk-android/src/main/res/values-sq/strings.xml
    index 6387ebb3bd..58ba8877bb 100644
    --- a/matrix-sdk-android/src/main/res/values-sq/strings.xml
    +++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml
    @@ -226,4 +226,10 @@
         %s ndryshoi ACL-ra shërbyesi për këtë dhomë.
         Ujdisët ACL-ra shërbyesi për këtë dhomë.
         %s ujdisi ACL-ra shërbyesi për këtë dhomë.
    +    • Shërbyes që kanë përputhje me %s u hoqën nga lista e të lejuarve.
    +    • Shërbyesit që kanë përputhje me %s tani janë të lejuar.
    +    • Shërbyesit që kanë përputhje me %s u hoqën nga lista e ndalimeve.
    +    • Shërbyesit që kanë përputhje me %s tani janë të ndaluar.
    +    • Shërbyesit që kanë përputhje me %s janë të ndaluar.
    +    • Shërbyesit që kanë përputhje me %s janë të ndaluar.
     
    \ No newline at end of file
    
    From 2d4eeb64c5c3a8feab7370bb01382723f1600deb Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Mon, 14 Dec 2020 16:28:17 +0000
    Subject: [PATCH 166/218] Translated using Weblate (French)
    
    Currently translated at 98.2% (1982 of 2018 strings)
    
    Translation: Element Android/Element Android App
    Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/
    ---
     vector/src/main/res/values-fr/strings.xml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
    index a38ba7ceb9..7eb188b070 100644
    --- a/vector/src/main/res/values-fr/strings.xml
    +++ b/vector/src/main/res/values-fr/strings.xml
    @@ -2200,7 +2200,7 @@
         Plus aucun résultat
         Exporter le rapport d\'audit
         %s pour permettre aux gens de connaître le sujet de ce salon.
    -    Ceci est le début de l\'historique de vos message direct avec %s.
    +    Ceci est le début de l\'historique de vos messages directs avec %s.
         Ceci est le début de cette conversation.
         Ceci est le début de %s.
         Vous n\'avez pas le droit d\'activer le chiffrement dans ce salon.
    
    From 211c158e23d82b0ec0c2313816a252453c7f171c Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Mon, 14 Dec 2020 17:42:52 +0100
    Subject: [PATCH 167/218] Remove bad translation
    
    ---
     matrix-sdk-android/src/main/res/values-fa/strings.xml | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/matrix-sdk-android/src/main/res/values-fa/strings.xml b/matrix-sdk-android/src/main/res/values-fa/strings.xml
    index 37aa2629a0..8f8059067e 100644
    --- a/matrix-sdk-android/src/main/res/values-fa/strings.xml
    +++ b/matrix-sdk-android/src/main/res/values-fa/strings.xml
    @@ -217,6 +217,5 @@
             %1$s، %2$s، %3$s و %4$d نفر دیگر
             %1$s، %2$s، %3$s و %4$d نفر دیگر
         
    -    %1$s، %2$s و %3$s
         %1$s، %2$s و %3$s
     
    \ No newline at end of file
    
    From 7da8b13cde95e1ef9c94232237aa0593354ecaef Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Wed, 9 Dec 2020 12:34:22 +0100
    Subject: [PATCH 168/218] Chat Effects
    
    ---
     CHANGES.md                                    |   1 +
     build.gradle                                  |   4 +
     .../session/room/model/message/MessageType.kt |   3 +
     vector/build.gradle                           |   3 +
     .../src/main/assets/open_source_licenses.html |  14 ++
     .../im/vector/app/features/command/Command.kt |   3 +-
     .../app/features/command/CommandParser.kt     |   4 +
     .../app/features/command/ParsedCommand.kt     |   1 +
     .../home/room/detail/ChatEffectManager.kt     | 125 ++++++++++++++
     .../home/room/detail/RoomDetailFragment.kt    |  36 ++++
     .../home/room/detail/RoomDetailViewEvents.kt  |   3 +
     .../home/room/detail/RoomDetailViewModel.kt   | 159 ++++++++++--------
     .../features/settings/VectorPreferences.kt    |   5 +
     .../src/main/res/drawable-anydpi-v26/snow.png | Bin 0 -> 1986 bytes
     vector/src/main/res/drawable/ic_snow.xml      |  13 ++
     .../main/res/layout/fragment_room_detail.xml  |  15 ++
     vector/src/main/res/values/attrs.xml          |   1 +
     vector/src/main/res/values/strings.xml        |   4 +
     vector/src/main/res/values/theme_dark.xml     |   3 +
     vector/src/main/res/values/theme_light.xml    |   4 +-
     .../res/xml/vector_settings_preferences.xml   |   6 +
     21 files changed, 339 insertions(+), 68 deletions(-)
     create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt
     create mode 100644 vector/src/main/res/drawable-anydpi-v26/snow.png
     create mode 100644 vector/src/main/res/drawable/ic_snow.xml
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 62bd92006e..d18a18e1c3 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -8,6 +8,7 @@ Features ✨:
      - Store encrypted file in cache and cleanup decrypted file at each app start (#2512)
      - Emoji Keyboard (#2520)
      - Social login (#2452)
    + - Support for chat effects in timeline (confetti, snow) (#2535)
     
     Improvements 🙌:
      - Add Setting Item to Change PIN (#2462)
    diff --git a/build.gradle b/build.gradle
    index 6dd61a720c..7531dee61e 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -43,6 +43,10 @@ allprojects {
                     includeGroupByRegex 'com\\.github\\.chrisbanes'
                     // PFLockScreen-Android
                     includeGroupByRegex 'com\\.github\\.vector-im'
    +
    +                //Chat effects
    +                includeGroupByRegex 'com\\.github\\.jetradarmobile'
    +                includeGroupByRegex 'nl\\.dionsegijn'
                 }
             }
             maven {
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
    index 0f133323b0..a2b4e135d1 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
    +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
    @@ -33,4 +33,7 @@ object MessageType {
         // Add, in local, a fake message type in order to StickerMessage can inherit Message class
         // Because sticker isn't a message type but a event type without msgtype field
         const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
    +
    +    const val MSGTYPE_CONFETTI = "nic.custom.confetti"
    +    const val MSGTYPE_SNOW = "nic.custom.snow"
     }
    diff --git a/vector/build.gradle b/vector/build.gradle
    index 28d8fe5c1b..7bb8ca187c 100644
    --- a/vector/build.gradle
    +++ b/vector/build.gradle
    @@ -410,6 +410,9 @@ dependencies {
         // Badge for compatibility
         implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
     
    +    // Chat effects
    +    implementation 'nl.dionsegijn:konfetti:1.2.5'
    +    implementation 'com.github.jetradarmobile:android-snowfall:1.2.0'
         // DI
         implementation "com.google.dagger:dagger:$daggerVersion"
         kapt "com.google.dagger:dagger-compiler:$daggerVersion"
    diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html
    index acf0bec14f..bf341e38b7 100755
    --- a/vector/src/main/assets/open_source_licenses.html
    +++ b/vector/src/main/assets/open_source_licenses.html
    @@ -390,6 +390,11 @@ SOFTWARE.
             
    Copyright (C) 2016 - Niklas Baudy, Ruben Gees, Mario Đanić and contributors +
  • + JetradarMobile / android-snowfall +
    + Copyright 2016 JetRadar +
  •  Apache License
    @@ -576,5 +581,14 @@ Apache License
         
     
    +
    +    ISC License
    +    
  • + DanielMartinus / Konfetti +
    + Copyright (c) 2017 Dion Segijn +
  • +
    + diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index db429f9e58..9702f917c1 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -44,7 +44,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll), SHRUG("/shrug", "", R.string.command_description_shrug), PLAIN("/plain", "", R.string.command_description_plain), - DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session); + DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session), + CONFETTI("/confetti", "", R.string.command_confetti); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 94de6bf265..0710faa47d 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -291,6 +291,10 @@ object CommandParser { Command.DISCARD_SESSION.command -> { ParsedCommand.DiscardSession } + Command.CONFETTI.command -> { + val message = textMessage.substring(Command.CONFETTI.command.length).trim() + ParsedCommand.Confetti(message) + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index 2f8531929a..72c0ac496e 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -55,4 +55,5 @@ sealed class ParsedCommand { class SendShrug(val message: CharSequence) : ParsedCommand() class SendPoll(val question: String, val options: List) : ParsedCommand() object DiscardSession : ParsedCommand() + class Confetti(val message: String) : ParsedCommand() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt new file mode 100644 index 0000000000..71d751b494 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail + +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import java.util.Timer +import java.util.TimerTask +import javax.inject.Inject + +enum class ChatEffect { + CONFETTI, + SNOW +} + +/** + * A simple chat effect manager helper class + * Used by the view model to know if an event that become visible should trigger a chat effect. + * It also manages effect duration and some cool down, for example if an effect is currently playing, + * any other trigger will be ignored + * For now it uses visibility callback to check for an effect (that means that a fail to decrypt event - more + * precisly an event decrypted with a few delay won't trigger an effect; it's acceptable) + * Events that are more that 10s old won't trigger effects + */ +class ChatEffectManager @Inject constructor() { + + interface Delegate { + fun stopEffects() + fun shouldStartEffect(effect: ChatEffect) + } + + var delegate: Delegate? = null + + private var stopTimer: Timer? = null + + // an in memory store to avoid trigger twice for an event (quick close/open timeline) + private val alreadyPlayed = emptyList().toMutableList() + + fun checkForEffect(event: TimelineEvent) { + val age = event.root.ageLocalTs ?: 0 + val now = System.currentTimeMillis() + // messages older than 10s should not trigger any effect + if ((now - age) >= 10_000) return + val content = event.root.getClearContent()?.toModel() ?: return + val effect = findEffect(content, event) + if (effect != null) { + synchronized(this) { + if (hasAlreadyPlayed(event)) return + markAsAlreadyPlayed(event) + // there is already an effect playing, so ignore + if (stopTimer != null) return + delegate?.shouldStartEffect(effect) + stopTimer = Timer().apply { + schedule(object : TimerTask() { + override fun run() { + stopEffect() + } + }, 6_000) + } + } + } + } + + fun dispose() { + stopTimer?.cancel() + stopTimer = null + delegate = null + alreadyPlayed.clear() + } + + @Synchronized + private fun stopEffect() { + stopTimer = null + delegate?.stopEffects() + } + + private fun markAsAlreadyPlayed(event: TimelineEvent) { + alreadyPlayed.add(event.eventId) + // also put the tx id as fast way to deal with local echo + event.root.unsignedData?.transactionId?.let { + alreadyPlayed.add(it) + } + } + + private fun hasAlreadyPlayed(event: TimelineEvent) : Boolean { + return alreadyPlayed.contains(event.eventId) + || (event.root.unsignedData?.transactionId?.let { alreadyPlayed.contains(it) } ?: false) + } + + private fun findEffect(content: MessageContent, event: TimelineEvent): ChatEffect? { + return when (content.msgType) { + MessageType.MSGTYPE_CONFETTI -> ChatEffect.CONFETTI + MessageType.MSGTYPE_SNOW -> ChatEffect.SNOW + + MessageType.MSGTYPE_TEXT -> { + val text = event.root.getClearContent().toModel()?.body ?: "" + if (text.contains("🎉") + || text.contains("🎊")) { + ChatEffect.CONFETTI + } else if (text.contains("⛄️") + || text.contains("☃️") + || text.contains("❄️")) { + ChatEffect.SNOW + } else null + } + else -> null + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index aff29bb7a3..4dd9c62a41 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -21,6 +21,7 @@ import android.app.Activity import android.content.DialogInterface import android.content.Intent import android.content.res.Configuration +import android.graphics.Color import android.graphics.Typeface import android.net.Uri import android.os.Build @@ -48,11 +49,14 @@ import androidx.core.text.toSpannable import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.core.view.forEach +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.transition.TransitionManager +import butterknife.BindView import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.addGlidePreloader @@ -168,6 +172,8 @@ import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.composer_layout.view.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* +import nl.dionsegijn.konfetti.models.Shape +import nl.dionsegijn.konfetti.models.Size import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.commonmark.parser.Parser import org.matrix.android.sdk.api.MatrixCallback @@ -378,6 +384,8 @@ class RoomDetailFragment @Inject constructor( is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item -> navigator.openBigImageViewer(requireActivity(), it.view, item) } + is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type) + RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() }.exhaustive } @@ -386,6 +394,34 @@ class RoomDetailFragment @Inject constructor( } } + private fun handleChatEffect(chatEffect: ChatEffect) { + when (chatEffect) { + ChatEffect.CONFETTI -> { + viewKonfetti.isVisible = true + viewKonfetti.build() + .addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA) + .setDirection(0.0, 359.0) + .setSpeed(2f, 5f) + .setFadeOutEnabled(true) + .setTimeToLive(2000L) + .addShapes(Shape.Square, Shape.Circle) + .addSizes(Size(12)) + .setPosition(-50f, viewKonfetti.width + 50f, -50f, -50f) + .streamFor(150, 3000L) + } + ChatEffect.SNOW -> { + viewSnowFall.isVisible = true + viewSnowFall.restartFalling() + } + } + } + private fun handleStopChatEffects() { + TransitionManager.beginDelayedTransition(rootConstraintLayout) + viewSnowFall.isVisible = false + // when gone the effect is a bit buggy + viewKonfetti.isInvisible = true + } + override fun onImageReady(uri: Uri?) { uri ?: return roomDetailViewModel.handle( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index d5d94a0ca5..81d3d622e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -95,4 +95,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { // TODO Remove object SlashCommandNotImplemented : SendMessageResult() + + data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents() + object StopChatEffects : RoomDetailViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 21858438b9..75423dabfb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -98,7 +98,6 @@ import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap import timber.log.Timber import java.io.File -import java.lang.Exception import java.util.UUID import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -115,8 +114,9 @@ class RoomDetailViewModel @AssistedInject constructor( private val roomSummaryHolder: RoomSummaryHolder, private val typingHelper: TypingHelper, private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, + private val chatEffectManager: ChatEffectManager, timelineSettingsFactory: TimelineSettingsFactory -) : VectorViewModel(initialState), Timeline.Listener { +) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate { private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId @@ -171,6 +171,7 @@ class RoomDetailViewModel @AssistedInject constructor( room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) + chatEffectManager.delegate = this } private fun observePowerLevel() { @@ -225,58 +226,58 @@ class RoomDetailViewModel @AssistedInject constructor( override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) - is RoomDetailAction.SaveDraft -> handleSaveDraft(action) - is RoomDetailAction.SendMessage -> handleSendMessage(action) - is RoomDetailAction.SendMedia -> handleSendMedia(action) - is RoomDetailAction.SendSticker -> handleSendSticker(action) - is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) - is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) - is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) - is RoomDetailAction.SendReaction -> handleSendReaction(action) - is RoomDetailAction.AcceptInvite -> handleAcceptInvite() - is RoomDetailAction.RejectInvite -> handleRejectInvite() - is RoomDetailAction.RedactAction -> handleRedactEvent(action) - is RoomDetailAction.UndoReaction -> handleUndoReact(action) - is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) - is RoomDetailAction.EnterEditMode -> handleEditAction(action) - is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) - is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) - is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) - is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) - is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) - is RoomDetailAction.ResendMessage -> handleResendEvent(action) - is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) - is RoomDetailAction.ResendAll -> handleResendAll() - is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() - is RoomDetailAction.ReportContent -> handleReportContent(action) - is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) + is RoomDetailAction.SaveDraft -> handleSaveDraft(action) + is RoomDetailAction.SendMessage -> handleSendMessage(action) + is RoomDetailAction.SendMedia -> handleSendMedia(action) + is RoomDetailAction.SendSticker -> handleSendSticker(action) + is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) + is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) + is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) + is RoomDetailAction.SendReaction -> handleSendReaction(action) + is RoomDetailAction.AcceptInvite -> handleAcceptInvite() + is RoomDetailAction.RejectInvite -> handleRejectInvite() + is RoomDetailAction.RedactAction -> handleRedactEvent(action) + is RoomDetailAction.UndoReaction -> handleUndoReact(action) + is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) + is RoomDetailAction.EnterEditMode -> handleEditAction(action) + is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) + is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) + is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) + is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) + is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) + is RoomDetailAction.ResendMessage -> handleResendEvent(action) + is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) + is RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailAction.ReportContent -> handleReportContent(action) + is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() - is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) - is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) - is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) - is RoomDetailAction.RequestVerification -> handleRequestVerification(action) - is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) - is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) - is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) - is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() - is RoomDetailAction.StartCall -> handleStartCall(action) - is RoomDetailAction.EndCall -> handleEndCall() - is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() - is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) - is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) - is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) - is RoomDetailAction.CancelSend -> handleCancel(action) - is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) - is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) - RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() - RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() - is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) - RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) - is RoomDetailAction.ShowRoomAvatarFullScreen -> { + is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() + is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) + is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) + is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() + is RoomDetailAction.StartCall -> handleStartCall(action) + is RoomDetailAction.EndCall -> handleEndCall() + is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() + is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) + is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) + is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) + is RoomDetailAction.CancelSend -> handleCancel(action) + is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) + is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) + RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() + RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() + is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) + RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) + is RoomDetailAction.ShowRoomAvatarFullScreen -> { _viewEvents.post( RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) ) @@ -549,7 +550,7 @@ class RoomDetailViewModel @AssistedInject constructor( SendMode.EDIT(timelineEvent, currentDraft.text) } } - else -> null + else -> null } ?: SendMode.REGULAR("", fromSharing = false) ) } @@ -592,16 +593,16 @@ class RoomDetailViewModel @AssistedInject constructor( return@withState false } when (itemId) { - R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true - R.id.timeline_setting -> true - R.id.invite -> state.canInvite - R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true - R.id.open_matrix_apps -> true + R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.timeline_setting -> true + R.id.invite -> state.canInvite + R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.open_matrix_apps -> true R.id.voice_call, - R.id.video_call -> true // always show for discoverability - R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null - R.id.search -> true - else -> false + R.id.video_call -> true // always show for discoverability + R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null + R.id.search -> true + else -> false } } @@ -714,6 +715,11 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } + is ParsedCommand.Confetti -> { + room.sendTextMessage(slashCommandResult.message, MessageType.MSGTYPE_CONFETTI) + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) + popDraft() + } is ParsedCommand.SendPoll -> { room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") }) _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) @@ -742,7 +748,7 @@ class RoomDetailViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId @@ -768,7 +774,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel() @@ -791,7 +797,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(RoomDetailViewEvents.MessageSent) @@ -983,9 +989,29 @@ class RoomDetailViewModel @AssistedInject constructor( visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event)) } } + + // handle chat effects here + if (vectorPreferences.chatEffectsEnabled()) { + chatEffectManager.checkForEffect(action.event) + } } } + override fun shouldStartEffect(effect: ChatEffect) { + when (effect) { + ChatEffect.CONFETTI -> { + _viewEvents.post(RoomDetailViewEvents.StartChatEffect(ChatEffect.CONFETTI)) + } + ChatEffect.SNOW -> { + _viewEvents.post(RoomDetailViewEvents.StartChatEffect(ChatEffect.SNOW)) + } + } + } + + override fun stopEffects() { + _viewEvents.post(RoomDetailViewEvents.StopChatEffects) + } + private fun handleLoadMore(action: RoomDetailAction.LoadMoreTimelineEvents) { timeline.paginate(action.direction, PAGINATION_COUNT) } @@ -1387,6 +1413,7 @@ class RoomDetailViewModel @AssistedInject constructor( if (vectorPreferences.sendTypingNotifs()) { room.userStopsTyping() } + chatEffectManager.dispose() super.onCleared() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index c50692df82..16be2b1552 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -97,6 +97,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY = "SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY" private const val SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY" private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER" + private const val SETTINGS_ENABLE_CHAT_EFFECTS = "SETTINGS_ENABLE_CHAT_EFFECTS" // Help private const val SETTINGS_SHOULD_SHOW_HELP_ON_ROOM_LIST_KEY = "SETTINGS_SHOULD_SHOW_HELP_ON_ROOM_LIST_KEY" @@ -869,6 +870,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG, true) } + fun chatEffectsEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_ENABLE_CHAT_EFFECTS, true) + } + /** * Return true if Pin code is disabled, or if user set the settings to see full notification content */ diff --git a/vector/src/main/res/drawable-anydpi-v26/snow.png b/vector/src/main/res/drawable-anydpi-v26/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..0142772cedb2cfe82fe2855d7ef9b0bc6febbafc GIT binary patch literal 1986 zcmV;z2R-Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91HlPCl1ONa40RR91HUIzs0P{mGqyPX1EJ;K`RA>d&n$2reXB5V3&3BU; zO>0mrLB&!^OQAHx3Q=?sn*IY_xKIlH3$h8tg&?$U6kU{B-MGj?sGDr6NF#P3wxrcU zg{~B#mT1zLhI}WJq|fj6ea3gjj58C*WahxhdB4x|+;i?b_r5n9UMa0?2+P@0jmxpO z<*4GaZPc~8VlJn}ti~E%$K}|vmixV~31G98F>dqtqHVUrLKpD7n82%=OdT6*JcjiR z8oQLGZi~lw4z^&aDtP46ficjSz(x&WJ#4Hd%sj-PA+?yLsZfszGaj-1zn15(o#h#p zbjO4!8$4vtYP7DduKMD|iyyVOw;yV1YI>`-wsvbxO%3{{`j_YD=l_uo&&|yZPfSew zH8?nU{piu7zs}CiE~wuML6d~MFNUp}N+S^A%0s3`S}$#qwn(>0+pb)>a^&H|hu<$O zEQ~A{Da>ImYp_;WleO`IFMQ(L(O2=s#iWQzt@JhNW@)Rmt*@`|v+?oq--_gtb2w|U zCTj;D_`)Z?!B7%%PC3%7PRbR0L%QR{i4%uLMn-G;4GK7(&CnB1x&XHtZo9A`mNZ|9vmcfJy$R9;l2 z8<4^$z5@oZfT`%Tj7f2IOlsGI2M_v6hqt(u@GUI6q+DP}t|)SZm>lM2C9g@powsk_ z{<=6Qr5_CjumntC1EY6K6>|&GVj$ZqI}uxM+vUrbzYxr|Out-LB?T5R1#DpSPXX9- z3eoa?#WuU+*s)_>Yq3$}Vv^ks7BGQL3dZ<;Q}tPKw48$E@}5VJ9$n8~e7);n0-F?! zaZ0A6MuaRh`u-xkbr&vN=xA+iJ?H`DV!;G9FiM?@U{4b>RvNrvu;cKU^v>AW*zfBd zom~FG2F8FD%-%>6a*h}klsflV&81714m3A6f1q+%3fRCXa6~NL_A`h9-f75_Mr}t& zM>mb-EW-GS@DV96XKeeWKdBw!^CeUGIh1_3IL z*v9($dS2rzLIPGuJwq&EsE#x_4d!LCA|znVASK`#sR*$|bs)pTZ$(JJn#L~Bl#}!` zfw*(ojTKl&d%MrTucNRh84YDz$%Qp02J=Lxr>95Dm`etnSO#1k zNqCUtZ8UwDqw>efEp+bf1~?zQsuG`uz*Qm zrlnv6E11(8hsqfeM~y>B@^+83{p{JZy-Q0=i`iFht?OU_3zz~nFs^uDs`d=wp5twl zW8>tATlQV)zN=TSe!JF0vi*SpECEx&N8B2p{9tph`Nul>D53iM(oTJ+>d$tzR&{)X zAz*2f1zW)%B{VjDM8HNun3K;)vK*f7*9Y)(Ynhh#!Y951hMlrtic>J&-PN_SaIZNf zIU8q}B$szdA=i2O^yx44FAWnfJ6ia_7e0e;Fn|S2VB@v{E0_zCm>l<;$#Jzv%YBCr zA3k#b{{34oiyUj?17E=>zD+9rbW|`Y%}>PSs#SC3NCEPj4TYWQpuSc03=IwaSt^38 z#hR=geBcY8_;#eg^xR10V{39O!luH4+9t}@V`JmyhK7dZ6-cl0W0RAUV|p!mGCDdsq>bU1lP6E!)tAcT zF;4410)-H5sDFfcCSfXUdJ6c@pBS;xQtnoZ6mA&h#A=h&+2`BDrquVj!| z?merc=!n|-CbutxbJPspB(d~aJsw-I*jd37!a&c%T1~)2ZQbuVF;IiIrH^tv2A4&V zVuGDfUac57^IeXu+vD{OG$!COk6TswKR+Ea U8gR6@i~s-t07*qoM6N<$f@hzt8UO$Q literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable/ic_snow.xml b/vector/src/main/res/drawable/ic_snow.xml new file mode 100644 index 0000000000..4149adab9f --- /dev/null +++ b/vector/src/main/res/drawable/ic_snow.xml @@ -0,0 +1,13 @@ + + + diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 33f462c0d1..d33f47872d 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -6,6 +6,21 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 46873accfc..f474af84c8 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -877,6 +877,8 @@ Show read receipts Click on the read receipts for a detailed list. Show room member state events + Show chat effects + Use /confetti command or send a message containing ❄️ or 🎉 Includes invite/join/left/kick/ban events and avatar/display name changes. Show join and leave events Invites, kicks, and bans are unaffected. @@ -2568,6 +2570,8 @@ Show %d devices you can verify with now
    + Sends the given message with confetti + Unencrypted Encrypted by an unverified device Review where you’re logged in diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index 6be0adb907..86fbb57608 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -200,6 +200,9 @@ @style/WidgetButtonSocialLogin.Facebook.Dark @style/WidgetButtonSocialLogin.Twitter.Dark @style/WidgetButtonSocialLogin.Apple.Dark + + + @android:color/transparent