diff --git a/CHANGES.md b/CHANGES.md index ad3f82af9a..e264facd8e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,24 @@ +Changes in Riot.imX 0.91.4 (2020-XX-XX) +=================================================== + +Features ✨: + - Re-activate Wellknown support with updated UI (#1614) + +Improvements 🙌: + - Upload device keys only once to the homeserver and fix crash when no network (#1629) + +Bugfix 🐛: + - Fix crash when coming from a notification (#1601) + - Fix Exception when importing keys (#1576) + - File isn't downloaded when another file with the same name already exists (#1578) + - saved images don't show up in gallery (#1324) + - Fix reply fallback leaking sender locale (#429) + +Build 🧱: + - Fix lint false-positive about WorkManager (#1012) + - Upgrade build-tools from 3.5.3 to 3.6.6 + - Upgrade gradle from 5.4.1 to 5.6.4 + Changes in Riot.imX 0.91.3 (2020-07-01) =================================================== diff --git a/build.gradle b/build.gradle index 74a62f0d17..5f1fa78620 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.3.2' classpath "com.airbnb.okreplay:gradle-plugin:1.5.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 51b92600a0..4da2435f42 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Sep 27 10:10:35 CEST 2019 +#Thu Jul 02 12:33:07 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/matrix-sdk-android/proguard-rules.pro b/matrix-sdk-android/proguard-rules.pro index 08a20cbf0a..fa860d8049 100644 --- a/matrix-sdk-android/proguard-rules.pro +++ b/matrix-sdk-android/proguard-rules.pro @@ -64,3 +64,19 @@ ### Webrtc -keep class org.webrtc.** { *; } + +### Serializable persisted classes +# https://www.guardsquare.com/en/products/proguard/manual/examples#serializable +-keepnames class * implements java.io.Serializable + +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + !static !transient ; + !private ; + !private ; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml index 94b2db2bf1..c02f34f1c9 100644 --- a/matrix-sdk-android/src/main/AndroidManifest.xml +++ b/matrix-sdk-android/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ @@ -8,11 +7,6 @@ - Einladung von %s Raumeinladung diff --git a/matrix-sdk-android/src/main/res/values-el/strings.xml b/matrix-sdk-android/src/main/res/values-el/strings.xml index 37eac5351e..9db4e91849 100644 --- a/matrix-sdk-android/src/main/res/values-el/strings.xml +++ b/matrix-sdk-android/src/main/res/values-el/strings.xml @@ -40,8 +40,6 @@ ** Αδυναμία αποκρυπτογράφησης: %s ** Η συσκευή του/της αποστολέα δεν μας έχει στείλει τα κλειδιά για αυτό το μήνυμα. - Προς απάντηση στο - Αποτυχία αποστολής μηνύματος Αποτυχία αναφόρτωσης εικόνας @@ -56,10 +54,6 @@ Η VoIP διάσκεψη έληξε Ο/Η %1$s εισήλθε στο δωμάτιο - έστειλε μία εικόνα. - έστειλε ένα βίντεο. - έστειλε ένα αρχείο ήχου. - έστειλε ένα αρχείο. Πρόσκληση από %s Πρόσκληση στο δωμάτιο diff --git a/matrix-sdk-android/src/main/res/values-eo/strings.xml b/matrix-sdk-android/src/main/res/values-eo/strings.xml index 69600394ac..4a1e2c4c65 100644 --- a/matrix-sdk-android/src/main/res/values-eo/strings.xml +++ b/matrix-sdk-android/src/main/res/values-eo/strings.xml @@ -17,8 +17,6 @@ ** Ne eblas malĉifri: %s ** La aparato de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo. - Responde al - %1$s: %2$s %1$s ŝanĝis sian vidigan nomon al %2$s %1$s ŝanĝis sian vidigan nomon de %2$s al %3$s @@ -62,11 +60,6 @@ Retpoŝtadreso Telefonnumero - sendis bildon. - sendis filmon. - sendis sondosieron. - sendis dosieron. - Invito de %s Ĉambra invito diff --git a/matrix-sdk-android/src/main/res/values-es-rMX/strings.xml b/matrix-sdk-android/src/main/res/values-es-rMX/strings.xml index 56cbe6ace5..35b7bfc829 100644 --- a/matrix-sdk-android/src/main/res/values-es-rMX/strings.xml +++ b/matrix-sdk-android/src/main/res/values-es-rMX/strings.xml @@ -73,13 +73,6 @@ %1$s envió una calcomanía. - En respuesta a - - envió una imagen. - envió un video. - envió un archivo de audio. - envió un archivo. - Invitación de %s Invitación de Sala diff --git a/matrix-sdk-android/src/main/res/values-es/strings.xml b/matrix-sdk-android/src/main/res/values-es/strings.xml index 69f02d2ef4..3c019b3b80 100644 --- a/matrix-sdk-android/src/main/res/values-es/strings.xml +++ b/matrix-sdk-android/src/main/res/values-es/strings.xml @@ -73,13 +73,6 @@ %1$s envió una pegatina. - En respuesta a - - envió una imagen. - envió un vídeo. - envió un archivo de audio. - envió un archivo. - Invitación de %s Invitación a Sala 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 3a5a1ff767..2536500247 100644 --- a/matrix-sdk-android/src/main/res/values-et/strings.xml +++ b/matrix-sdk-android/src/main/res/values-et/strings.xml @@ -50,8 +50,6 @@ ** Ei õnnestu dekrüptida: %s ** Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid. - Vastuseks kasutajale - Ei saanud muuta sõnumit Sõnumi saatmine ei õnnestunud @@ -67,11 +65,6 @@ E-posti aadress Telefoninumber - saatis pildi. - saatis video. - saatis helifaili. - saatis faili. - Kutse kasutajalt %s Kutse jututuppa diff --git a/matrix-sdk-android/src/main/res/values-eu/strings.xml b/matrix-sdk-android/src/main/res/values-eu/strings.xml index 7938db3cbd..1a5c81fe5e 100644 --- a/matrix-sdk-android/src/main/res/values-eu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-eu/strings.xml @@ -63,13 +63,6 @@ %1$s erabiltzaileak eranskailu bat bidali du. - Honi erantzunez - - irudi bat bidali du. - bideo bat bidali du. - audio fitxategi bat bidali du. - fitxategi bat bidali du. - %s gelarako gonbidapena Gela gonbidapena %1$s eta %2$s 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 7addf22ca8..18d8578e54 100644 --- a/matrix-sdk-android/src/main/res/values-fa/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fa/strings.xml @@ -51,8 +51,6 @@ ** ناتوان در رمزگشایی: %s ** دستگاه فرستنده، کلیدهای این پیام را برایمان نفرستاده است. - در پاسخ به - ناتوان در فرستادن پیام شکست در بارگذاری تصویر @@ -67,11 +65,6 @@ نشانی رایانامه شماره تلفن - تصویری فرستاد. - ویدیویی فرستاد. - پرونده‌ای صوتی فرستاد. - پرونده‌ای فرستاد. - دعوت از %s دعوت اتاق 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 06820183fd..078769942c 100644 --- a/matrix-sdk-android/src/main/res/values-fi/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fi/strings.xml @@ -70,13 +70,6 @@ %1$s lähetti tarran. - Vastauksena käyttäjälle - - oli lähettänyt kuvan. - lähetti videon. - lähetti äänitiedoston. - lähetti tiedoston. - %1$s ja yksi muu %1$s ja %2$d muuta 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 a744c35b99..aad3bd1afb 100644 --- a/matrix-sdk-android/src/main/res/values-fr/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fr/strings.xml @@ -63,13 +63,6 @@ %1$s a envoyé un sticker. - En réponse à - - a envoyé une image. - a envoyé une vidéo. - a envoyé un fichier audio. - a envoyé un fichier. - Invitation de %s Invitation au salon Salon vide diff --git a/matrix-sdk-android/src/main/res/values-gl/strings.xml b/matrix-sdk-android/src/main/res/values-gl/strings.xml index 907730f154..77868e7df3 100644 --- a/matrix-sdk-android/src/main/res/values-gl/strings.xml +++ b/matrix-sdk-android/src/main/res/values-gl/strings.xml @@ -50,8 +50,6 @@ ** Imposíbel descifrar: %s ** O dispositivo do que envía non enviou as chaves desta mensaxe. - Respondéndolle a - Non se puido redactar Non foi posíbel enviar a mensaxe @@ -64,11 +62,6 @@ Número de teléfono - Responder a - enviar un vídeo. - enviar un ficheiro de son. - enviar un ficheiro. - %1$s e %2$s diff --git a/matrix-sdk-android/src/main/res/values-hu/strings.xml b/matrix-sdk-android/src/main/res/values-hu/strings.xml index 03d52bef44..35f35eaecd 100644 --- a/matrix-sdk-android/src/main/res/values-hu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-hu/strings.xml @@ -62,13 +62,6 @@ %1$s küldött egy matricát. - Válasz erre: - - képet küldött. - videót küldött. - hangfájlt küldött. - fájlt küldött. - Meghívó tőle: %s Meghívó egy szobába %1$s és %2$s diff --git a/matrix-sdk-android/src/main/res/values-is/strings.xml b/matrix-sdk-android/src/main/res/values-is/strings.xml index 01954ae2b4..ecf19edb8a 100644 --- a/matrix-sdk-android/src/main/res/values-is/strings.xml +++ b/matrix-sdk-android/src/main/res/values-is/strings.xml @@ -24,7 +24,6 @@ (einnig var skipt um auðkennismynd) ** Mistókst að afkóða: %s ** - Sem svar til Gat ekki sent skilaboð 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 ec86122313..42328b836f 100644 --- a/matrix-sdk-android/src/main/res/values-it/strings.xml +++ b/matrix-sdk-android/src/main/res/values-it/strings.xml @@ -62,13 +62,6 @@ %1$s ha inviato un adesivo. - In risposta a - - inviata un\'immagine. - inviato un video. - inviato un file audio. - inviato un file. - Invito da %s Invito nella stanza diff --git a/matrix-sdk-android/src/main/res/values-ja/strings.xml b/matrix-sdk-android/src/main/res/values-ja/strings.xml index b72d1a13ca..366c743494 100644 --- a/matrix-sdk-android/src/main/res/values-ja/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ja/strings.xml @@ -56,8 +56,6 @@ ** 解読できません: %s ** 送信者の端末からこのメッセージのキーが送信されていません。 - に返信 - 修正できませんでした メッセージを送信できません @@ -73,9 +71,4 @@ メールアドレス 電話番号 - 画像を送信しました。 - 動画を送りました。 - 音声ファイルを送信しました。 - ファイルを送信しました。 - diff --git a/matrix-sdk-android/src/main/res/values-ko/strings.xml b/matrix-sdk-android/src/main/res/values-ko/strings.xml index 68e94bb641..88c5e7d618 100644 --- a/matrix-sdk-android/src/main/res/values-ko/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ko/strings.xml @@ -52,8 +52,6 @@ ** 암호를 복호화할 수 없음: %s ** 발신인의 기기에서 이 메시지의 키를 보내지 않았습니다. - 관련 대화 - 검열할 수 없습니다 메시지를 보낼 수 없습니다 @@ -69,11 +67,6 @@ 이메일 주소 전화번호 - 사진을 보냈습니다. - 동영상을 보냈습니다. - 오디오 파일을 보냈습니다. - 파일을 보냈습니다. - %s에서 초대함 방 초대 diff --git a/matrix-sdk-android/src/main/res/values-nl/strings.xml b/matrix-sdk-android/src/main/res/values-nl/strings.xml index d08b3c7845..22eb61f109 100644 --- a/matrix-sdk-android/src/main/res/values-nl/strings.xml +++ b/matrix-sdk-android/src/main/res/values-nl/strings.xml @@ -71,13 +71,6 @@ %1$s heeft een sticker gestuurd. - Als antwoord op - - heeft een afbeelding gestuurd. - heeft een video gestuurd. - heeft een audiobestand gestuurd. - heeft een bestand gestuurd. - Uitnodiging van %s Gespreksuitnodiging diff --git a/matrix-sdk-android/src/main/res/values-nn/strings.xml b/matrix-sdk-android/src/main/res/values-nn/strings.xml index edc50a5a0e..601cf4c9df 100644 --- a/matrix-sdk-android/src/main/res/values-nn/strings.xml +++ b/matrix-sdk-android/src/main/res/values-nn/strings.xml @@ -49,8 +49,6 @@ ** Fekk ikkje til å dekryptera: %s ** Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi. - Som svar til - Kunde ikkje gjera um Fekk ikkje å senda meldingi @@ -64,11 +62,6 @@ Epostadresse Telefonnummer - sende eit bilæte. - sende ein video. - sende ei ljodfil. - sende ei fil. - Innbjoding frå %s Rominnbjoding %1$s og %2$s diff --git a/matrix-sdk-android/src/main/res/values-pl/strings.xml b/matrix-sdk-android/src/main/res/values-pl/strings.xml index 4e1d256788..dc380516b7 100644 --- a/matrix-sdk-android/src/main/res/values-pl/strings.xml +++ b/matrix-sdk-android/src/main/res/values-pl/strings.xml @@ -42,7 +42,6 @@ %1$s wycofał(a) zaproszenie %2$s %s odebrał(a) połączenie. (awatar też został zmieniony) - W odpowiedzi do Zaproszenie od %s Zaproszenie do pokoju @@ -76,11 +75,6 @@ Nie można zredagować Obecnie nie jest możliwe ponowne dołączenie do pustego pokoju. - wyślij zdjęcie. - wyślij wideo. - wyślij plik audio. - wyślij plik. - Wiadomość usunięta Wiadomość usunięta przez %1$s Wiadomość usunięta [powód: %1$s] 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 17e7ae33a1..a573c659a6 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 @@ -74,13 +74,6 @@ %1$s enviou um sticker. - Em resposta a - - enviou uma imagem. - enviou um vídeo. - enviou um arquivo de áudio. - enviou um arquivo. - Convite de %s Convite para sala 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 3e20353b7d..bd0dcef3dd 100644 --- a/matrix-sdk-android/src/main/res/values-ru/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml @@ -73,13 +73,6 @@ %1$s отправил стикер. - В ответ на - - отправил изображение. - отправил видео. - отправил аудиофайл. - отправил файл. - Приглашение от %s Приглашение в комнату diff --git a/matrix-sdk-android/src/main/res/values-sk/strings.xml b/matrix-sdk-android/src/main/res/values-sk/strings.xml index c6eb6b896b..8aec8fccf9 100644 --- a/matrix-sdk-android/src/main/res/values-sk/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sk/strings.xml @@ -62,13 +62,6 @@ %1$s poslal nálepku. - Odpoveď na - - odoslal obrázok. - odoslal video. - odoslal zvukový súbor. - Odoslal súbor. - Pozvanie od %s Pozvanie do miestnosti %1$s a %2$s 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 853d4729af..e63e28288f 100644 --- a/matrix-sdk-android/src/main/res/values-sq/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml @@ -34,8 +34,6 @@ ** S’arrihet të shfshehtëzohet: %s ** Pajisja e dërguesit nuk na ka dërguar kyçet për këtë mesazh. - Në përgjigje të - S’u redaktua dot S’arrihet të dërgohet mesazh @@ -51,11 +49,6 @@ Adresë email Numër telefoni - dërgoi një figurë. - dërgoi një video. - dërgoi një kartelë audio. - dërgoi një kartelë. - Ftesë nga %s Ftesë Dhome 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 bf83e39d72..eb5071f190 100644 --- a/matrix-sdk-android/src/main/res/values-uk/strings.xml +++ b/matrix-sdk-android/src/main/res/values-uk/strings.xml @@ -56,8 +56,6 @@ ** Неможливо розшифрувати: %s ** Пристрій відправника не надіслав нам ключ для цього повідомлення. - У відповідь на - Неможливо відредагувати Не вдалося надіслати повідомлення @@ -71,11 +69,6 @@ Адреса електронної пошти Номер телефону - надіслав зображення. - надіслав відео. - надіслав аудіо файл. - надіслав файл. - %1$s та 1 інший %1$s та %2$d інші diff --git a/matrix-sdk-android/src/main/res/values-vls/strings.xml b/matrix-sdk-android/src/main/res/values-vls/strings.xml index dad88788e4..5c9132ed35 100644 --- a/matrix-sdk-android/src/main/res/values-vls/strings.xml +++ b/matrix-sdk-android/src/main/res/values-vls/strings.xml @@ -50,8 +50,6 @@ ** Kun nie ountsleuteln: %s ** ’t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd. - Als antwoord ip - Kosteg nie verwyderd wordn Kosteg ’t bericht nie verzendn @@ -67,11 +65,6 @@ E-mailadresse Telefongnumero - èt e fotootje gesteurd. - èt e filmtje gesteurd. - èt e geluudsfragment gesteurd. - èt e bestand gesteurd. - Uutnodigienge van %s Gespreksuutnodigienge diff --git a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml index 0c0953c92d..48dbd27a1b 100644 --- a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml +++ b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml @@ -63,13 +63,6 @@ %1$s:%2$s %1$s 发送了一张贴纸。 - 发送了一张图片。 - 发送了一个视频。 - 发送了一段音频。 - 发送了一个文件。 - - 回复 - 空聊天室 来自 %s 的邀请 聊天室邀请 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 ca2fd79aa2..f3da62dccc 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 @@ -62,13 +62,6 @@ %1$s 傳送了一張貼圖。 - 回覆 - - 傳送了圖片。 - 傳送了影片。 - 傳送了音訊檔案。 - 傳送了檔案。 - 來自%s 的邀請 聊天室邀請 %1$s 和 %2$s diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 9b7fa01eaf..0dc64c1b4b 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -112,7 +112,6 @@ The sender\'s device has not sent us the keys for this message. - In reply to Could not redact @@ -139,12 +138,6 @@ Email address Phone number - - sent an image. - sent a video. - sent an audio file. - sent a file. - Invite from %s Room Invite diff --git a/vector/build.gradle b/vector/build.gradle index 3c74ccda97..f966f441b2 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -17,7 +17,7 @@ androidExtensions { // Note: 2 digits max for each value ext.versionMajor = 0 ext.versionMinor = 91 -ext.versionPatch = 3 +ext.versionPatch = 4 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -106,6 +106,11 @@ def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0 android { compileSdkVersion 29 + + // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use + // Ref: https://issuetracker.google.com/issues/144111441 + ndkVersion "21.3.6528147" + defaultConfig { applicationId "im.vector.app" // Set to API 21: see #405 @@ -232,8 +237,7 @@ android { lintOptions { lintConfig file("lint.xml") - // TODO Restore true once pb with WorkManager is fixed - abortOnError false + abortOnError true } compileOptions { diff --git a/vector/proguard-rules.pro b/vector/proguard-rules.pro index 56d3b95510..bc27767d8a 100644 --- a/vector/proguard-rules.pro +++ b/vector/proguard-rules.pro @@ -20,4 +20,7 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class im.vector.riotx.features.** { *; } \ No newline at end of file +-keep class im.vector.riotx.features.** { *; } + +## print all the rules in a file +# -printconfiguration ../proguard_files/full-r8-config.txt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 3ed0d95b71..f9b78db17c 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -247,6 +247,12 @@ + + + = Build.VERSION_CODES.Q) { - val externalContentUri: Uri - val values = ContentValues() - when { - mediaMimeType?.startsWith("image/") == true -> { - externalContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI - values.put(MediaStore.Images.Media.TITLE, title) - values.put(MediaStore.Images.Media.DISPLAY_NAME, title) - values.put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType) - values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis()) - values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) - } - mediaMimeType?.startsWith("video/") == true -> { - externalContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - values.put(MediaStore.Video.Media.TITLE, title) - values.put(MediaStore.Video.Media.DISPLAY_NAME, title) - values.put(MediaStore.Video.Media.MIME_TYPE, mediaMimeType) - values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis()) - values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis()) - } - mediaMimeType?.startsWith("audio/") == true -> { - externalContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - values.put(MediaStore.Audio.Media.TITLE, title) - values.put(MediaStore.Audio.Media.DISPLAY_NAME, title) - values.put(MediaStore.Audio.Media.MIME_TYPE, mediaMimeType) - values.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis()) - values.put(MediaStore.Audio.Media.DATE_TAKEN, System.currentTimeMillis()) - } - else -> { - externalContentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI - values.put(MediaStore.Downloads.TITLE, title) - values.put(MediaStore.Downloads.DISPLAY_NAME, title) - values.put(MediaStore.Downloads.MIME_TYPE, mediaMimeType) - values.put(MediaStore.Downloads.DATE_ADDED, System.currentTimeMillis()) - values.put(MediaStore.Downloads.DATE_TAKEN, System.currentTimeMillis()) - } + val values = ContentValues().apply { + put(MediaStore.Images.Media.TITLE, title) + put(MediaStore.Images.Media.DISPLAY_NAME, title) + put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType) + put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis()) + 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 + } + val uri = context.contentResolver.insert(externalContentUri, values) if (uri == null) { Toast.makeText(context, R.string.error_saving_media_file, Toast.LENGTH_LONG).show() @@ -357,16 +345,70 @@ fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification) } } - // TODO add notification? } else { - @Suppress("DEPRECATION") - Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent -> - mediaScanIntent.data = Uri.fromFile(file) - context.sendBroadcast(mediaScanIntent) + saveMediaLegacy(context, mediaMimeType, title, file) + } +} + +@Suppress("DEPRECATION") +private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: String, file: File) { + val state = Environment.getExternalStorageState() + if (Environment.MEDIA_MOUNTED != state) { + context.toast(context.getString(R.string.error_saving_media_file)) + return + } + + 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 + } + val downloadDir = Environment.getExternalStoragePublicDirectory(dest) + try { + val outputFilename = if (title.substringAfterLast('.', "").isEmpty()) { + val extension = mediaMimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } + "$title.$extension" + } else { + title + } + val savedFile = saveFileIntoLegacy(file, downloadDir, outputFilename) + if (savedFile != null) { + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager + downloadManager?.addCompletedDownload( + savedFile.name, + title, + true, + mediaMimeType ?: "application/octet-stream", + savedFile.absolutePath, + savedFile.length(), + true) + addToGallery(savedFile, mediaMimeType, context) + } + } catch (error: Throwable) { + GlobalScope.launch(Dispatchers.Main) { + context.toast(context.getString(R.string.error_saving_media_file)) + } } } } +private fun addToGallery(savedFile: File, mediaMimeType: String?, context: Context) { + // MediaScannerConnection provides a way for applications to pass a newly created or downloaded media file to the media scanner service. + var mediaConnection: MediaScannerConnection? = null + val mediaScannerConnectionClient: MediaScannerConnection.MediaScannerConnectionClient = object : MediaScannerConnection.MediaScannerConnectionClient { + override fun onMediaScannerConnected() { + mediaConnection?.scanFile(savedFile.path, mediaMimeType) + } + + override fun onScanCompleted(path: String, uri: Uri?) { + if (path == savedFile.path) mediaConnection?.disconnect() + } + } + mediaConnection = MediaScannerConnection(context, mediaScannerConnectionClient).apply { connect() } +} + /** * Open the play store to the provided application Id, default to this app */ @@ -381,3 +423,76 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID } } } + +// ============================================================================================================== +// Media utils +// ============================================================================================================== +/** + * Copy a file into a dstPath directory. + * The output filename can be provided. + * The output file is not overridden if it is already exist. + * + * ~~ This is copied from the old matrix sdk ~~ + * + * @param sourceFile the file source path + * @param dstDirPath the dst path + * @param outputFilename optional the output filename + * @param callback the asynchronous callback + */ +@Suppress("DEPRECATION") +fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: String?): File? { + // defines another name for the external media + val dstFileName: String + + // build a filename is not provided + if (null == outputFilename) { + // extract the file extension from the uri + val dotPos = sourceFile.name.lastIndexOf(".") + var fileExt = "" + if (dotPos > 0) { + fileExt = sourceFile.name.substring(dotPos) + } + dstFileName = "vector_" + System.currentTimeMillis() + fileExt + } else { + dstFileName = outputFilename + } + + var dstFile = File(dstDirPath, dstFileName) + + // if the file already exists, append a marker + if (dstFile.exists()) { + var baseFileName = dstFileName + var fileExt = "" + val lastDotPos = dstFileName.lastIndexOf(".") + if (lastDotPos > 0) { + baseFileName = dstFileName.substring(0, lastDotPos) + fileExt = dstFileName.substring(lastDotPos) + } + var counter = 1 + while (dstFile.exists()) { + dstFile = File(dstDirPath, "$baseFileName($counter)$fileExt") + counter++ + } + } + + // Copy source file to destination + var inputStream: FileInputStream? = null + var outputStream: FileOutputStream? = null + try { + dstFile.createNewFile() + inputStream = FileInputStream(sourceFile) + outputStream = FileOutputStream(dstFile) + val buffer = ByteArray(1024 * 10) + var len: Int + while (inputStream.read(buffer).also { len = it } != -1) { + outputStream.write(buffer, 0, len) + } + return dstFile + } catch (failure: Throwable) { + return null + } finally { + // Close resources + tryThis { inputStream?.close() } + tryThis { outputStream?.close() } + } +} diff --git a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt index 0361fc9d71..c26cd85dc7 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt @@ -37,3 +37,11 @@ internal fun String.ensureProtocol(): String { else -> this } } + +internal fun String.ensureTrailingSlash(): String { + return when { + isEmpty() -> this + !endsWith("/") -> "$this/" + else -> this + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/form/FormEditTextWithButtonItem.kt b/vector/src/main/java/im/vector/riotx/features/form/FormEditTextWithButtonItem.kt index 0650c0f55c..799f6abe00 100644 --- a/vector/src/main/java/im/vector/riotx/features/form/FormEditTextWithButtonItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/form/FormEditTextWithButtonItem.kt @@ -56,6 +56,7 @@ abstract class FormEditTextWithButtonItem : VectorEpoxyModel, grantResults: IntArray) { if (allGranted(grantResults)) { when (requestCode) { -// PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> { -// val action = roomDetailViewModel.pendingAction -// if (action != null) { -// (action as? RoomDetailAction.DownloadFile) -// ?.messageFileContent -// ?.getFileName() -// ?.let { showSnackWithMessage(getString(R.string.downloading_file, it)) } -// roomDetailViewModel.pendingAction = null -// roomDetailViewModel.handle(action) -// } -// } + SAVE_ATTACHEMENT_REQUEST_CODE -> { + sharedActionViewModel.pendingAction?.let { + handleActions(it) + sharedActionViewModel.pendingAction = null + } + } PERMISSION_REQUEST_CODE_INCOMING_URI -> { val pendingUri = roomDetailViewModel.pendingUri if (pendingUri != null) { @@ -1357,6 +1354,11 @@ class RoomDetailFragment @Inject constructor( } private fun onSaveActionClicked(action: EventSharedAction.Save) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q + && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, SAVE_ATTACHEMENT_REQUEST_CODE)) { + sharedActionViewModel.pendingAction = action + return + } session.fileService().downloadFile( downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = action.eventId, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt index 2e041fd2ea..ec5c49f814 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt @@ -21,4 +21,6 @@ import javax.inject.Inject /** * Activity shared view model to handle message actions */ -class MessageSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() +class MessageSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() { + var pendingAction : EventSharedAction? = null +} diff --git a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt index 5927e5b117..90be21b6be 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt @@ -73,6 +73,9 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { override fun showFailure(throwable: Throwable) { when (throwable) { + is Failure.Cancelled -> + /* Ignore this error, user has cancelled the action */ + Unit is Failure.ServerError -> if (throwable.error.code == MatrixError.M_FORBIDDEN && throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index 86be00702c..f71b0ecba4 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -151,8 +151,8 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) - is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone() - is LoginViewEvents.OnSignModeSelected -> onSignModeSelected() + is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents) + is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents) is LoginViewEvents.OnLoginFlowRetrieved -> addFragmentToBackstack(R.id.loginFragmentContainer, if (loginViewEvents.isSso) { @@ -228,18 +228,20 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { .show() } - private fun onServerSelectionDone() = withState(loginViewModel) { state -> - when (state.serverType) { + private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) { + when (loginViewEvents.serverType) { ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.Modular, ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerUrlFormFragment::class.java, option = commonOption) + ServerType.Unknown -> Unit /* Should not happen */ } } - private fun onSignModeSelected() = withState(loginViewModel) { state -> - when (state.signMode) { + 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 -> { // This is managed by the LoginViewEvents diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt index eaf0a3cc78..ef8281fa89 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt @@ -54,6 +54,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { private var passwordShown = false private var isSignupMode = false + // Temporary patch for https://github.com/vector-im/riotX-android/issues/1410, // waiting for https://github.com/matrix-org/synapse/issues/7576 private var isNumericOnlyUserIdForbidden = false @@ -138,6 +139,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { loginServerIcon.isVisible = false loginTitle.text = getString(R.string.login_signin_matrix_id_title) loginNotice.text = getString(R.string.login_signin_matrix_id_notice) + loginPasswordNotice.isVisible = true } else { val resId = when (state.signMode) { SignMode.Unknown -> error("developer error") @@ -164,7 +166,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl()) loginNotice.text = getString(R.string.login_server_other_text) } + ServerType.Unknown -> Unit /* Should not happen */ } + loginPasswordNotice.isVisible = false } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt index 79c5c7d024..3a1bba7f11 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.login import android.os.Bundle import android.view.View import butterknife.OnClick -import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.utils.openUrlInChromeCustomTab import kotlinx.android.synthetic.main.fragment_login_server_selection.* @@ -40,11 +39,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment } private fun updateSelectedChoice(state: LoginViewState) { - state.serverType.let { - loginServerChoiceMatrixOrg.isChecked = it == ServerType.MatrixOrg - loginServerChoiceModular.isChecked = it == ServerType.Modular - loginServerChoiceOther.isChecked = it == ServerType.Other - } + loginServerChoiceMatrixOrg.isChecked = state.serverType == ServerType.MatrixOrg } private fun initTextViews() { @@ -61,42 +56,17 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment @OnClick(R.id.loginServerChoiceMatrixOrg) fun selectMatrixOrg() { - if (loginServerChoiceMatrixOrg.isChecked) { - // Consider this is a submit - submit() - } else { - loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg)) - } + loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg)) } @OnClick(R.id.loginServerChoiceModular) fun selectModular() { - if (loginServerChoiceModular.isChecked) { - // Consider this is a submit - submit() - } else { - loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular)) - } + loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular)) } @OnClick(R.id.loginServerChoiceOther) fun selectOther() { - if (loginServerChoiceOther.isChecked) { - // Consider this is a submit - submit() - } else { - loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other)) - } - } - - @OnClick(R.id.loginServerSubmit) - fun submit() = withState(loginViewModel) { state -> - if (state.serverType == ServerType.MatrixOrg) { - // Request login flow here - loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url))) - } else { - loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnServerSelectionDone)) - } + loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other)) } @OnClick(R.id.loginServerIKnowMyIdSubmit) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt index cb90ef2397..28331bc99e 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt @@ -70,7 +70,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint) loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_modular_notice) } - ServerType.Other -> { + else -> { loginServerUrlFormIcon.isVisible = false loginServerUrlFormTitle.text = getString(R.string.login_server_other_title) loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server) @@ -78,7 +78,6 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint) loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_other_notice) } - else -> error("This fragment should not be displayed in matrix.org mode") } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt index 427ad99b41..6ac5993f30 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt @@ -49,6 +49,7 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo loginSignupSigninTitle.text = getString(R.string.login_server_other_title) loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl()) } + ServerType.Unknown -> Unit /* Should not happen */ } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt index 9b69ba8a4f..fe5c00399b 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt @@ -33,9 +33,9 @@ sealed class LoginViewEvents : VectorViewEvents { // Navigation event object OpenServerSelection : LoginViewEvents() - object OnServerSelectionDone : LoginViewEvents() + data class OnServerSelectionDone(val serverType: ServerType) : LoginViewEvents() data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents() - object OnSignModeSelected : LoginViewEvents() + data class OnSignModeSelected(val signMode: SignMode) : LoginViewEvents() object OnForgetPasswordClicked : LoginViewEvents() object OnResetPasswordSendThreePidDone : LoginViewEvents() object OnResetPasswordMailConfirmationSuccess : LoginViewEvents() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index fc970297d1..7edc674b11 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -39,6 +39,7 @@ import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.Stage import im.vector.matrix.android.api.auth.wellknown.WellknownResult +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable import im.vector.riotx.R @@ -47,6 +48,7 @@ import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.utils.ensureTrailingSlash import im.vector.riotx.features.call.WebRtcPeerConnectionManager import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener @@ -87,8 +89,12 @@ class LoginViewModel @AssistedInject constructor( } } + // Store the last action, to redo it after user has trusted the untrusted certificate + private var lastAction: LoginAction? = null private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null + private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() + val currentThreePid: String? get() = registrationWizard?.currentThreePid @@ -111,8 +117,8 @@ class LoginViewModel @AssistedInject constructor( is LoginAction.UpdateServerType -> handleUpdateServerType(action) is LoginAction.UpdateSignMode -> handleUpdateSignMode(action) is LoginAction.InitWith -> handleInitWith(action) - is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action) - is LoginAction.LoginOrRegister -> handleLoginOrRegister(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) @@ -126,10 +132,23 @@ class LoginViewModel @AssistedInject constructor( } private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) { - // It happen when we get the login flow, so alter the homeserver config and retrieve again the login flow - currentHomeServerConnectionConfig - ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { getLoginFlow(it) } + // 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 -> + currentHomeServerConnectionConfig + ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } + ?.let { getLoginFlow(it) } + is LoginAction.LoginOrRegister -> + handleDirectLogin( + finalLastAction, + HomeServerConnectionConfig.Builder() + // Will be replaced by the task + .withHomeServerUri("https://dummy.org") + .withAllowedFingerPrints(listOf(action.fingerprint)) + .build() + ) + } } private fun handleLoginWithToken(action: LoginAction.LoginWithToken) { @@ -321,7 +340,7 @@ class LoginViewModel @AssistedInject constructor( LoginAction.ResetHomeServerType -> { setState { copy( - serverType = ServerType.MatrixOrg + serverType = ServerType.Unknown ) } } @@ -333,6 +352,7 @@ class LoginViewModel @AssistedInject constructor( asyncHomeServerLoginFlowRequest = Uninitialized, homeServerUrl = null, loginMode = LoginMode.Unknown, + serverType = ServerType.Unknown, loginModeSupportedTypes = emptyList() ) } @@ -379,9 +399,9 @@ class LoginViewModel @AssistedInject constructor( when (action.signMode) { SignMode.SignUp -> startRegistrationFlow() SignMode.SignIn -> startAuthenticationFlow() - SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected) + SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) SignMode.Unknown -> Unit - }.exhaustive + } } private fun handleUpdateServerType(action: LoginAction.UpdateServerType) { @@ -390,6 +410,15 @@ class LoginViewModel @AssistedInject constructor( serverType = action.serverType ) } + + when (action.serverType) { + ServerType.Unknown -> Unit /* Should not happen */ + ServerType.MatrixOrg -> + // Request login flow here + handle(LoginAction.UpdateHomeServer(matrixOrgUrl)) + ServerType.Modular, + ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType)) + }.exhaustive } private fun handleInitWith(action: LoginAction.InitWith) { @@ -427,7 +456,6 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException setState { copy( asyncResetPassword = Fail(failure) @@ -469,7 +497,6 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException setState { copy( asyncResetMailConfirmed = Fail(failure) @@ -485,23 +512,22 @@ class LoginViewModel @AssistedInject constructor( SignMode.Unknown -> error("Developer error, invalid sign mode") SignMode.SignIn -> handleLogin(action) SignMode.SignUp -> handleRegisterWith(action) - SignMode.SignInWithMatrixId -> handleDirectLogin(action) + SignMode.SignInWithMatrixId -> handleDirectLogin(action, null) }.exhaustive } - private fun handleDirectLogin(action: LoginAction.LoginOrRegister) { + private fun handleDirectLogin(action: LoginAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { setState { copy( asyncLoginAction = Loading() ) } - // TODO Handle certificate error in this case. Direct login is deactivated now, so we will handle that later - authenticationService.getWellKnownData(action.username, null, object : MatrixCallback { + authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback { override fun onSuccess(data: WellknownResult) { when (data) { is WellknownResult.Prompt -> - onWellknownSuccess(action, data) + onWellknownSuccess(action, data, homeServerConnectionConfig) is WellknownResult.InvalidMatrixId -> { setState { copy( @@ -522,23 +548,26 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } + onDirectLoginError(failure) } }) } - private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) { - val homeServerConnectionConfig = HomeServerConnectionConfig( - homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) + private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, + wellKnownPrompt: WellknownResult.Prompt, + homeServerConnectionConfig: HomeServerConnectionConfig?) { + val alteredHomeServerConnectionConfig = homeServerConnectionConfig + ?.copy( + homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + ?: HomeServerConnectionConfig( + homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) authenticationService.directAuthentication( - homeServerConnectionConfig, + alteredHomeServerConnectionConfig, action.username, action.password, action.initialDeviceName, @@ -548,15 +577,29 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } + onDirectLoginError(failure) } }) } + private fun onDirectLoginError(failure: Throwable) { + if (failure is Failure.UnrecognizedCertificateFailure) { + // Display this error in a dialog + _viewEvents.post(LoginViewEvents.Failure(failure)) + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + } else { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + } + } + private fun handleLogin(action: LoginAction.LoginOrRegister) { val safeLoginWizard = loginWizard @@ -584,7 +627,6 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException setState { copy( asyncLoginAction = Fail(failure) @@ -609,7 +651,7 @@ class LoginViewModel @AssistedInject constructor( // Ensure Wizard is ready loginWizard - _viewEvents.post(LoginViewEvents.OnSignModeSelected) + _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignIn)) } private fun onFlowResponse(flowResult: FlowResult) { @@ -673,7 +715,10 @@ class LoginViewModel @AssistedInject constructor( setState { copy( - asyncHomeServerLoginFlowRequest = Loading() + asyncHomeServerLoginFlowRequest = Loading(), + // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg + // It is also useful to set the value again in the case of a certificate error on matrix.org + serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType ) } @@ -682,7 +727,9 @@ class LoginViewModel @AssistedInject constructor( _viewEvents.post(LoginViewEvents.Failure(failure)) setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized + asyncHomeServerLoginFlowRequest = Uninitialized, + // If we were trying to retrieve matrix.org login flow, also reset the serverType + serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt index 944d1f7d82..655966ce25 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt @@ -35,7 +35,7 @@ data class LoginViewState( // User choices @PersistState - val serverType: ServerType = ServerType.MatrixOrg, + val serverType: ServerType = ServerType.Unknown, @PersistState val signMode: SignMode = SignMode.Unknown, @PersistState diff --git a/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt b/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt index 4c7007c137..50dfee19f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.login enum class ServerType { + Unknown, MatrixOrg, Modular, Other diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt index d7dabd0778..36874d5782 100755 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationUtils.kt @@ -205,7 +205,7 @@ class NotificationUtils @Inject constructor(private val context: Context, @SuppressLint("NewApi") fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification { // build the pending intent go to the home screen if this is clicked. - val i = Intent(context, HomeActivity::class.java) + val i = HomeActivity.newIntent(context) i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP val pi = PendingIntent.getActivity(context, 0, i, 0) @@ -307,7 +307,7 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) val answerCallPendingIntent = TaskStackBuilder.create(context) - .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(VectorCallActivity.newIntent( context = context, callId = callId, @@ -459,7 +459,7 @@ class NotificationUtils @Inject constructor(private val context: Context, ) val contentPendingIntent = TaskStackBuilder.create(context) - .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context)) // TODO other userId .addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null)) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) @@ -651,7 +651,7 @@ class NotificationUtils @Inject constructor(private val context: Context, stringProvider.getString(R.string.join), joinIntentPendingIntent) - val contentIntent = Intent(context, HomeActivity::class.java) + val contentIntent = HomeActivity.newIntent(context) contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that contentIntent.data = Uri.parse("foobar://" + inviteNotifiableEvent.eventId) @@ -689,7 +689,7 @@ class NotificationUtils @Inject constructor(private val context: Context, .setColor(accentColor) .setAutoCancel(true) .apply { - val contentIntent = Intent(context, HomeActivity::class.java) + val contentIntent = HomeActivity.newIntent(context) contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that contentIntent.data = Uri.parse("foobar://" + simpleNotifiableEvent.eventId) @@ -718,7 +718,7 @@ class NotificationUtils @Inject constructor(private val context: Context, // Recreate the back stack return TaskStackBuilder.create(context) - .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java)) + .addNextIntentWithParentStack(HomeActivity.newIntent(context)) .addNextIntent(roomIntentTap) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) } diff --git a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt index 13b90f26e8..fadcaa8055 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt @@ -93,8 +93,9 @@ class SoftLogoutFragment @Inject constructor( softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password)) } - override fun signinFallbackSubmit() { - loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected)) + override fun signinFallbackSubmit() = withState(loginViewModel) { state -> + // The loginViewModel has been prepared for a SSO/login fallback recovery (above) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected(state.signMode))) } override fun clearData() { diff --git a/vector/src/main/res/drawable/bg_login_server_selector.xml b/vector/src/main/res/drawable/bg_login_server_selector.xml index 57be1e5d54..3fcc4e006a 100644 --- a/vector/src/main/res/drawable/bg_login_server_selector.xml +++ b/vector/src/main/res/drawable/bg_login_server_selector.xml @@ -2,6 +2,7 @@ + diff --git a/vector/src/main/res/layout/fragment_login.xml b/vector/src/main/res/layout/fragment_login.xml index a35a60104d..8e7fc9e418 100644 --- a/vector/src/main/res/layout/fragment_login.xml +++ b/vector/src/main/res/layout/fragment_login.xml @@ -106,6 +106,16 @@ + + + - - + - - - - - - + + app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d8aad6f539..c3f0e9df41 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1995,10 +1995,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method: - Sign in with my Matrix identifier - Sign in - Enter your identifier and your password - User identifier + Sign in with Matrix ID + Sign in with Matrix ID + If you set up an account on a homeserver, use your Matrix ID (e.g. @user:domain.com) and password below. + Matrix ID + If you don’t know your password, go back to reset it. This is not a valid user identifier. Expected format: \'@user:homeserver.org\' Unable to find a valid homeserver. Please check your identifier