diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md
new file mode 100644
index 0000000000..af6bbe2190
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/release.md
@@ -0,0 +1,84 @@
+---
+name: Release
+about: Checklist for each release. To be used by the core team only.
+title: "[Release] Element Android v"
+labels: "\U0001F680 Release"
+assignees: bmarty
+
+---
+
+For the example, we are releasing the version 1.1.10
+
+### Before the release
+
+- [ ] Weblate sync, fix lint issue if any (in a dedicated PR)
+- [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores.
+
+### Do the release
+
+- [ ] Create release with gitflow, branch name `release/1.1.10`
+- [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect.
+- [ ] Run `./tools/import_emojis.py` and commit the change if any.
+- [ ] Run `./tools/import_sas_strings.py` and commit the change if any. If there is no change since a while, ping Travis
+- [ ] Check the crashes from the PlayStore
+- [ ] Check the rageshake with the current dev version. For instance https://github.com/matrix-org/element-android-rageshakes/labels/1.1.10-dev
+- [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()`
+- [ ] Create an account on matrix.org
+- [ ] Run towncrier: `./towncrier --version v1.1.10` (add `--draft` for a preview)
+- [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs
+- [ ] Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes.
+- [ ] Finish release with gitflow, delete the draft PR
+- [ ] Push `main` and the new tag `v1.1.10` to origin
+- [ ] Checkout `develop`
+- [ ] Increase version in `./vector/build.gradle`
+- [ ] Commit and push `develop`
+- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
+- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
+- [ ] Install the APK on your phone to check that the upgrade went well (no init sync, etc.)
+- [ ] Create a new beta release on the GooglePlay console and upload the 4 signed Apks.
+- [ ] Check that the version codes are correct
+- [ ] Copy the fastlane change to the GooglePlay console in the section en-GB.
+- [ ] Push to beta release to 100% of the users
+- [ ] Create the release on gitHub [from the tag](https://github.com/vector-im/element-android/tags), copy paste the block from the file CHANGES.md
+- [ ] Add the 4 signed APKs to the GitHub release
+- [ ] Ping the Android Internal room
+- [ ] Add an entry in the internal diary
+
+### Once Live on PlayStore
+
+- [ ] Ping the Android public room and update its topic
+
+### After at least 2 days
+
+- [ ] Check the [rageshakes](https://github.com/matrix-org/element-android-rageshakes/issues)
+- [ ] Check the crash reports on the GooglePlay console
+- [ ] Check the Android Element room for any reported issues on the new version
+- [ ] If all is OK, push to production and notify Markus (Bubu) to release the F-Droid version
+- [ ] Ping the Android public room and update its topic with the new available version
+
+### Android SDK2
+
+- [ ] Checkout the `main` branch on Element Android project
+
+#### On the SDK2 project
+
+https://github.com/matrix-org/matrix-android-sdk2
+
+- [ ] Create a release with GitFlow
+- [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project.
+- [ ] Run the script `./tools/import_from_element.sh`
+- [ ] Update the version in `./matrix-sdk-android/build.gradle` and let the script finish to build the library
+- [ ] Update the file `CHANGES.md`
+- [ ] Finish the release using GitFlow
+- [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags)
+- [ ] Upload the AAR on the GitHub release
+
+### Android SDK2 sample
+
+https://github.com/matrix-org/matrix-android-sdk2-sample
+
+- [ ] Update the dependency to the new version of the SDK2. Jitpack will have to build the AAR, it can take a few minutes. You can check status on https://jitpack.io/#matrix-org/matrix-android-sdk2
+- [ ] Build and run the sample, you may have to fix some API break
+- [ ] Commit and push directly on `main`
+
+
diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index 8299f1c4de..e143720aa9 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -38,6 +38,7 @@
unpublishunwedgingvctr
+ wellknown
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index 9947146a9e..e9a990f1b2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,20 @@
+Changes in Element 1.1.12 (2021-07-05)
+======================================
+
+Features ✨
+----------
+ - Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling. ([#3545](https://github.com/vector-im/element-android/issues/3545))
+ - Implements new design for Jump to unread and quick fix visibility issues. ([#3547](https://github.com/vector-im/element-android/issues/3547))
+
+Bugfixes 🐛
+----------
+ - Fix some issues with timeline cache invalidation and visibility. ([#3542](https://github.com/vector-im/element-android/issues/3542))
+ - Fix call invite processed after call is ended because of fastlane mode. ([#3564](https://github.com/vector-im/element-android/issues/3564))
+ - Fix crash after video call. ([#3577](https://github.com/vector-im/element-android/issues/3577))
+ - Fix crash out of memory ([#3583](https://github.com/vector-im/element-android/issues/3583))
+ - CryptoStore migration has to be object to avoid crash ([#3605](https://github.com/vector-im/element-android/issues/3605))
+
+
Changes in Element v1.1.11 (2021-06-22)
=======================================
diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle
index 4b95b20029..0ed883cc95 100644
--- a/attachment-viewer/build.gradle
+++ b/attachment-viewer/build.gradle
@@ -55,9 +55,9 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation 'androidx.core:core-ktx:1.5.0'
+ implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation "androidx.recyclerview:recyclerview:1.2.1"
- implementation 'com.google.android.material:material:1.3.0'
+ implementation 'com.google.android.material:material:1.4.0'
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index c07c0201e8..cba7fed3c8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.2.1'
+ classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.google.gms:google-services:4.3.8'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
diff --git a/changelog.d/2843.bugfix b/changelog.d/2843.bugfix
new file mode 100644
index 0000000000..881d6d4f90
--- /dev/null
+++ b/changelog.d/2843.bugfix
@@ -0,0 +1 @@
+Perform .well-known request first, even if the entered URL is a valid homeserver base url
\ No newline at end of file
diff --git a/changelog.d/3273.feature b/changelog.d/3273.feature
new file mode 100644
index 0000000000..0edc81796b
--- /dev/null
+++ b/changelog.d/3273.feature
@@ -0,0 +1 @@
+Remove redundant mimetype (vector-im/element-web#2547)
\ No newline at end of file
diff --git a/changelog.d/3545.feature b/changelog.d/3545.feature
deleted file mode 100644
index f582affbf2..0000000000
--- a/changelog.d/3545.feature
+++ /dev/null
@@ -1 +0,0 @@
-Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling.
\ No newline at end of file
diff --git a/changelog.d/3547.feature b/changelog.d/3547.feature
deleted file mode 100644
index 8921932067..0000000000
--- a/changelog.d/3547.feature
+++ /dev/null
@@ -1 +0,0 @@
-Implements new design for Jump to unread and quick fix visibility issues.
\ No newline at end of file
diff --git a/changelog.d/3551.feature b/changelog.d/3551.feature
new file mode 100644
index 0000000000..5575c6fb53
--- /dev/null
+++ b/changelog.d/3551.feature
@@ -0,0 +1 @@
+Room version capabilities and room upgrade support, better error feedback
\ No newline at end of file
diff --git a/changelog.d/3564.bugfix b/changelog.d/3564.bugfix
deleted file mode 100644
index 74f71ccded..0000000000
--- a/changelog.d/3564.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix call invite processed after call is ended because of fastlane mode.
\ No newline at end of file
diff --git a/changelog.d/3572.misc b/changelog.d/3572.misc
new file mode 100644
index 0000000000..384b8e69c7
--- /dev/null
+++ b/changelog.d/3572.misc
@@ -0,0 +1 @@
+RawService.getWellknown() now takes a domain instead of a matrixId as parameter
\ No newline at end of file
diff --git a/changelog.d/3577.bugfix b/changelog.d/3577.bugfix
deleted file mode 100644
index 6b3917292f..0000000000
--- a/changelog.d/3577.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix crash after video call.
\ No newline at end of file
diff --git a/changelog.d/3624.bugfix b/changelog.d/3624.bugfix
new file mode 100644
index 0000000000..31f0844d15
--- /dev/null
+++ b/changelog.d/3624.bugfix
@@ -0,0 +1 @@
+Use different copy for self verification.
diff --git a/changelog.d/3634.bugfix b/changelog.d/3634.bugfix
new file mode 100644
index 0000000000..0b1f007030
--- /dev/null
+++ b/changelog.d/3634.bugfix
@@ -0,0 +1 @@
+Crash when opening room addresses screen with no internet connection
\ No newline at end of file
diff --git a/changelog.d/3635.feature b/changelog.d/3635.feature
new file mode 100644
index 0000000000..30d2cc64b4
--- /dev/null
+++ b/changelog.d/3635.feature
@@ -0,0 +1 @@
+Add retry support in room addresses screen
\ No newline at end of file
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101070.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101070.txt
new file mode 100644
index 0000000000..2a6afb21a5
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101070.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: beta podpora pro Spaces. Komprimace videa před odesláním.
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.7
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101080.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101080.txt
new file mode 100644
index 0000000000..5ec173ebea
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101080.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: vylepšení pro Spaces
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.8
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101090.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101090.txt
new file mode 100644
index 0000000000..782a2fa8c5
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101090.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: doplněna podpora pro síť gitter.im
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.9
diff --git a/fastlane/metadata/android/en-US/changelogs/40101120.txt b/fastlane/metadata/android/en-US/changelogs/40101120.txt
new file mode 100644
index 0000000000..6118974777
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40101120.txt
@@ -0,0 +1,2 @@
+Main changes in this version: theme and style update and fix a crash after video call
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.12
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40101070.txt b/fastlane/metadata/android/fr-FR/changelogs/40101070.txt
new file mode 100644
index 0000000000..66cc5d1671
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40101070.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : prise en charge des espaces en bêta. Compression des vidéos avant envoi.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.7
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40101080.txt b/fastlane/metadata/android/fr-FR/changelogs/40101080.txt
new file mode 100644
index 0000000000..31a4b72363
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40101080.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des espaces.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.8
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40101090.txt b/fastlane/metadata/android/fr-FR/changelogs/40101090.txt
new file mode 100644
index 0000000000..99f02f1bb2
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40101090.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : ajout de la prise en charge de gitter.im
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.9
diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt
index 066b94868b..78fcdf5617 100644
--- a/fastlane/metadata/android/fr-FR/full_description.txt
+++ b/fastlane/metadata/android/fr-FR/full_description.txt
@@ -1,30 +1,39 @@
-Element est une nouvelle application de messagerie et de collaboration qui :
+Element est à la fois une messagerie sécurisée et une application de collaboration en équipe, idéale pour les conversations de groupe en télétravail. Cette application utilise le chiffrement de bout en bout. Elle permet de mettre en place des téléconférences vidéo, du partage de fichier et des appels vocaux.
-1. Vous permet de préserver votre vie privée
-2. Vous permet de communiquer avec n’importe qui sur réseau Matrix, et plus encore grâce aux intégrations d’autres applications telles que Slack ou Discord
-3. Vous protège de la publicité et de la collecte de données
-4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs
+Les fonctionnalités d’Element incluent :
+- Outils de communication en ligne avancés
+- Communication d’entreprise sécurisée par le chiffrement de bout en bout des messages, même pour les travailleurs à distance
+- Messagerie décentralisée basée sur le framework open source Matrix
+- Partage sécurisé de fichiers avec chiffrement des données lors de la gestion de projet
+- Conversations vidéo par voix sur IP et partage d’écran
+- Intégration facile avec vos outils de collaboration, de gestion de projet, services de VoIP et autres applications de messagerie
-Element est complètement différente des autres applications de messagerie et de collaboration puisque l’application est décentralisée et open-source.
+Element est complètement différente des autres applications de messagerie et de collaboration. Elle s’appuie sur Matrix, un réseau ouvert de communication décentralisée. Elle permet l’auto-hébergement pour que ses utilisateurs restent le plus en contrôle possible de leurs données et leurs messages.
-Element vous permet d’héberger vous-même ou de choisir un hôte vous permettant d’assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous donne accès à un réseau ouvert. Vous n’êtes donc pas condamné à parler à d’autres utilisateurs d’Element seulement. Et c'est très sécurisé.
+Confidentialité et messagerie chiffrée
+Element vous protège des publicités non désirées, du minage de données et des prisons dorées. Elle protègé vos données et vos communications vocales grâce au chiffrement de bout en bout et à la vérification de signature croisée entre appareils.
-Element peut faire tout ça car elle est basée sur Matrix, le protocole standard pour la communication ouverte et décentralisée.
+Element vous donne la main sur votre confidentialité en vous permettant de communiquer de manière sécurisée avec tout le réseau Matrix ou d’autres applications de communication d’entreprise au travers d’intégrations d’applications comme Slack.
-Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières :
+Element peut être auto-hébergé
+Pour une meilleure souveraineté sur vos données et conversations, Element peut être auto-hébergé ou vous pouvez choisir votre hôte Matrix - la norme open source pour les communications décentralisées. Element garantit votre confidentialité, conformité aux normes de sécurité, tout en proposant une intégration souple.
-1. Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parmi les milliers de serveurs public hébergés par des bénévoles
-2. Héberger vous-même votre compte en installant un serveur sur votre propre machine
-3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
+Vos données vous appartiennent
+Vous décidez où stocker vos données et messages. Aucun risque de minage de données où d’accès par des tierce parties.
-Pourquoi choisir Element ?
+Element vous place aux commandes de différente manières :
+1. Inscrivez vous sur le serveur public matrix.org hébergé par les développeurs de Matrix ou choisissez parmi des milliers de serveurs publics hébergés par des bénévoles
+2. Auto-hébergez votre compte sur un serveur de votre proper infrastructure informatique
+3. Inscrivez vous à la plateforme d’hébergement Element Matrix Services
-VOS DONNÉES VOUS APPARTIENNENT : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant.
+Messagerie et collaboration ouvertes
+Vous pouvez discuter avec tout le réseau Matrix, que vos interlocuteurs utilisent Element, une autre application Matrix, ou même s’ils utilisent une application complètement différente.
-MESSAGERIE ET COLLABORATION OUVERTES : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP.
+Ultra sécurisé
+Chiffrement de bout en bout (seules les personnes dans la conversation peuvent déchiffrer les messages) et vérification de signature croisée entre appareils.
-ULTRA SÉCURISÉ : chiffrement de bout en bout (seuls les membres d’une conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs.
+Communication et intégration parfaites
+Messagerie instantannée, appels audio et vidéo, partage de fichier, partage d’écran et bien d’autres intégrations, bots et widgets. Lancez des salons, des communautés, restez en contact et menez vos projets à bien.
-TOUTES VOS COMMUNICATIONS : messagerie, appels audio et vidéo, partage de fichier, partage d’écran et un grand nombre d’intégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets.
-
-PARTOUT AVEC VOUS : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.
+Reprenez où vous vous êtes arrêté
+Restez en contact où que vous soyez grâce à l’historique des messages synchronisé entre tous vos appareils et sur le web sur https://app.element.io
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 283500479a..17ba19021b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=a9e356a21595348b6f04b024ed0b08ac8aea6b2ac37e6c0ef58e51549cd7b9cb
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
+distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle
index 3a8851784e..09d57d9d9d 100644
--- a/library/ui-styles/build.gradle
+++ b/library/ui-styles/build.gradle
@@ -53,7 +53,7 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
- implementation 'com.google.android.material:material:1.3.0'
+ implementation 'com.google.android.material:material:1.4.0'
// Pref theme
implementation 'androidx.preference:preference-ktx:1.1.1'
// PFLockScreen attrs
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index cb00b0fd56..7d54012204 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -9,7 +9,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath "io.realm:realm-gradle-plugin:10.6.0"
+ classpath "io.realm:realm-gradle-plugin:10.6.1"
}
}
@@ -121,7 +121,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.3.0"
- implementation "androidx.core:core-ktx:1.5.0"
+ implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -169,30 +169,30 @@ dependencies {
implementation 'com.otaliastudios:transcoder:0.10.3'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
- testImplementation 'io.mockk:mockk:1.11.0'
+ testImplementation 'io.mockk:mockk:1.12.0'
testImplementation 'org.amshove.kluent:kluent-android:1.67'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
- androidTestImplementation 'androidx.test:core:1.3.0'
- androidTestImplementation 'androidx.test:runner:1.3.0'
- androidTestImplementation 'androidx.test:rules:1.3.0'
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ androidTestImplementation 'androidx.test:core:1.4.0'
+ androidTestImplementation 'androidx.test:runner:1.4.0'
+ androidTestImplementation 'androidx.test:rules:1.4.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.65'
- androidTestImplementation 'io.mockk:mockk-android:1.11.0'
+ androidTestImplementation 'io.mockk:mockk-android:1.12.0'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
- androidTestUtil 'androidx.test:orchestrator:1.3.0'
+ androidTestUtil 'androidx.test:orchestrator:1.4.0'
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
index 7f5f3f54ef..3af1a22779 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.api
+import org.matrix.android.sdk.BuildConfig
+
/**
* This class contains pattern to match the different Matrix ids
*/
@@ -154,7 +156,7 @@ object MatrixPatterns {
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~),
* or consist of more than 50 characters, are forbidden and the field should be ignored if received.
*/
- fun isValidOrderString(order: String?) : Boolean {
+ fun isValidOrderString(order: String?): Boolean {
return order != null && order.length < 50 && order matches ORDER_STRING_REGEX
}
@@ -163,4 +165,17 @@ object MatrixPatterns {
"[^a-z0-9._%#@=+-]".toRegex().replace(it, "")
}
}
+
+ /**
+ * Return the domain form a userId
+ * Examples:
+ * - "@alice:domain.org".getDomain() will return "domain.org"
+ * - "@bob:domain.org:3455".getDomain() will return "domain.org:3455"
+ */
+ fun String.getDomain(): String {
+ if (BuildConfig.DEBUG) {
+ assert(isUserId(this))
+ }
+ return substringAfter(":")
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
index e1c5171bfc..dfe899ea4a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
@@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data
import android.net.Uri
import com.squareup.moshi.JsonClass
+import okhttp3.CipherSuite
+import okhttp3.TlsVersion
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
-import okhttp3.CipherSuite
-import okhttp3.TlsVersion
/**
* This data class holds how to connect to a specific Homeserver.
@@ -31,7 +31,12 @@ import okhttp3.TlsVersion
*/
@JsonClass(generateAdapter = true)
data class HomeServerConnectionConfig(
+ // This is the homeserver URL entered by the user
val homeServerUri: Uri,
+ // This is the homeserver base URL for the client-server API. Default to homeServerUri,
+ // but can be updated with data from .Well-Known before login, and/or with the data
+ // included in the login response
+ val homeServerUriBase: Uri = homeServerUri,
val identityServerUri: Uri? = null,
val antiVirusServerUri: Uri? = null,
val allowedFingerprints: List = emptyList(),
@@ -47,7 +52,6 @@ data class HomeServerConnectionConfig(
* This builder should be use to create a [HomeServerConnectionConfig] instance.
*/
class Builder {
-
private lateinit var homeServerUri: Uri
private var identityServerUri: Uri? = null
private var antiVirusServerUri: Uri? = null
@@ -234,16 +238,16 @@ data class HomeServerConnectionConfig(
*/
fun build(): HomeServerConnectionConfig {
return HomeServerConnectionConfig(
- homeServerUri,
- identityServerUri,
- antiVirusServerUri,
- allowedFingerprints,
- shouldPin,
- tlsVersions,
- tlsCipherSuites,
- shouldAcceptTlsExtensions,
- allowHttpExtension,
- forceUsageTlsVersions
+ homeServerUri = homeServerUri,
+ identityServerUri = identityServerUri,
+ antiVirusServerUri = antiVirusServerUri,
+ allowedFingerprints = allowedFingerprints,
+ shouldPin = shouldPin,
+ tlsVersions = tlsVersions,
+ tlsCipherSuites = tlsCipherSuites,
+ shouldAcceptTlsExtensions = shouldAcceptTlsExtensions,
+ allowHttpExtension = allowHttpExtension,
+ forceUsageTlsVersions = forceUsageTlsVersions
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
index b2a57c7f5c..b490ac877e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
@@ -51,13 +51,18 @@ data class SessionParams(
val deviceId = credentials.deviceId
/**
- * The current homeserver Url. It can be different that the homeserver url entered
- * during login phase, because a redirection may have occurred
+ * The homeserver Url entered by the user during the login phase.
*/
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
/**
- * The current homeserver host
+ * The current homeserver Url for client-server API. It can be different that the homeserver url entered
+ * during login phase, because a redirection may have occurred
+ */
+ val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString()
+
+ /**
+ * The current homeserver host, using what has been entered by the user during login phase
*/
val homeServerHost = homeServerConnectionConfig.homeServerUri.host
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
index c68a9e9699..56257db79c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
@@ -22,11 +22,6 @@ import org.matrix.android.sdk.api.auth.data.WellKnown
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
*/
sealed class WellknownResult {
- /**
- * The provided matrixId is no valid. Unable to extract a domain name.
- */
- object InvalidMatrixId : WellknownResult()
-
/**
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt
new file mode 100644
index 0000000000..8f7bca803d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2021 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.failure
+
+sealed class MatrixIdFailure : Failure.FeatureFailure() {
+ object InvalidMatrixId : MatrixIdFailure()
+}
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 f1722b2189..3366d040f7 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
@@ -29,8 +29,10 @@ interface RawService {
/**
* Specific case for the well-known file. Cache validity is 8 hours
+ * @param domain the domain to get the .well-known file, for instance "matrix.org".
+ * The URL will be "https://{domain}/.well-known/matrix/client"
*/
- suspend fun getWellknown(userId: String): String
+ suspend fun getWellknown(domain: String): String
/**
* Clear all the cache data
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index da99ab8d54..10c2db4535 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -32,7 +32,13 @@ data class HomeServerCapabilities(
/**
* Default identity server url, provided in Wellknown
*/
- val defaultIdentityServerUrl: String? = null
+ val defaultIdentityServerUrl: String? = null,
+ /**
+ * Room versions supported by the server
+ * This capability describes the default and available room versions a server supports, and at what level of stability.
+ * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
+ */
+ val roomVersions: RoomVersionCapabilities? = null
) {
companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
new file mode 100644
index 0000000000..7798b4cc63
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.homeserver
+
+data class RoomVersionCapabilities(
+ val defaultRoomVersion: String,
+ val supportedVersion: List
+)
+
+data class RoomVersionInfo(
+ val version: String,
+ val status: RoomVersionStatus
+)
+
+enum class RoomVersionStatus {
+ STABLE,
+ UNSTABLE
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index cb04b05a74..ebe96b6382 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
+import org.matrix.android.sdk.api.session.room.version.RoomVersionService
import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional
@@ -57,7 +58,8 @@ interface Room :
RelationService,
RoomCryptoService,
RoomPushRuleService,
- RoomAccountDataService {
+ RoomAccountDataService,
+ RoomVersionService {
/**
* The roomId of this room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
index 74ad26f449..1bcb10d88c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
@@ -65,5 +65,5 @@ data class MessageAudioContent(
) : MessageWithAttachmentContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType
+ get() = audioInfo?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
index d93f115322..96877b4d9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
@@ -60,8 +60,7 @@ data class MessageFileContent(
) : MessageWithAttachmentContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype
- ?: info?.mimeType
+ get() = info?.mimeType
?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension ->
MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
}
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 73e27b64e3..73fd1eab56 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,7 +20,6 @@ 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)
@@ -55,5 +54,5 @@ data class MessageImageContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images
+ get() = info?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
index 280316d4b5..8e1d4d3d75 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
@@ -55,5 +55,5 @@ data class MessageStickerContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: info?.mimeType
+ get() = info?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
index b7581c9fbf..3f5d2dab2e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
@@ -53,5 +53,5 @@ data class MessageVideoContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageWithAttachmentContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType
+ get() = videoInfo?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
new file mode 100644
index 0000000000..ea67b55174
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 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.version
+
+interface RoomVersionService {
+ /**
+ * Return the room version of this room
+ */
+ fun getRoomVersion(): String
+
+ /**
+ * Upgrade to the given room version
+ * @return the replacement room id
+ */
+ suspend fun upgradeToVersion(version: String): String
+
+ /**
+ * Get the recommended room version for the current homeserver
+ */
+ fun getRecommendedVersion() : String
+
+ /**
+ * Ask if the user has enough power level to upgrade the room
+ */
+ fun userMayUpgradeRoom(userId: String): Boolean
+
+ /**
+ * Return true if the current room version is declared unstable by the homeserver
+ */
+ fun isUsingUnstableRoomVersion(): Boolean
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
index db25762c2f..3bae6126e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
interface Space {
@@ -38,6 +39,8 @@ interface Space {
autoJoin: Boolean = false,
suggested: Boolean? = false)
+ fun getChildInfo(roomId: String): SpaceChildContent?
+
suspend fun removeChildren(roomId: String)
@Throws
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
index 5a9fa9edf6..50d9e5a06c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
@@ -21,8 +21,8 @@ import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.auth.data.Availability
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
-import org.matrix.android.sdk.internal.auth.data.RiotConfig
import org.matrix.android.sdk.internal.auth.data.TokenLoginParams
+import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
@@ -44,16 +44,16 @@ import retrofit2.http.Url
*/
internal interface AuthAPI {
/**
- * Get a Riot config file, using the name including the domain
+ * Get a Web client config file, using the name including the domain
*/
@GET("config.{domain}.json")
- suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig
+ suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig
/**
- * Get a Riot config file
+ * Get a Web client default config file
*/
@GET("config.json")
- suspend fun getRiotConfig(): RiotConfig
+ suspend fun getWebClientConfig(): WebClientConfig
/**
* Get the version information of the homeserver
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 20ce438d8e..e76dc28734 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
@@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri
import dagger.Lazy
import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
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
@@ -28,10 +30,11 @@ 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
import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.auth.data.RiotConfig
+import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
@@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor(
private fun getHomeServerUrlBase(): String? {
return pendingSessionData
?.homeServerConnectionConfig
- ?.homeServerUri
+ ?.homeServerUriBase
?.toString()
?.trim { it == '/' }
}
@@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor(
return result.fold(
{
// The homeserver exists and up to date, keep the config
- // Homeserver url may have been changed, if it was a Riot url
+ // Homeserver url may have been changed, if it was a Web client url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(it.homeServerUrl)
+ homeServerUriBase = Uri.parse(it.homeServerUrl)
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
@@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor(
},
{
if (it is UnrecognizedCertificateException) {
- throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
+ throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint)
} else {
throw it
}
@@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor(
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
- // First check the homeserver version
- return runCatching {
- executeRequest(null) {
- authAPI.versions()
+ // First check if there is a well-known file
+ return try {
+ getWellknownLoginFlowInternal(homeServerConnectionConfig)
+ } catch (failure: Throwable) {
+ if (failure is Failure.OtherServerError
+ && failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
+ // 404, no well-known data, try direct access to the API
+ // First check the homeserver version
+ return runCatching {
+ executeRequest(null) {
+ authAPI.versions()
+ }
+ }
+ .map { versions ->
+ // Ok, it seems that the homeserver url is valid
+ getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUriBase.toString())
+ }
+ .fold(
+ {
+ it
+ },
+ {
+ if (it is Failure.OtherServerError
+ && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
+ // It's maybe a Web client url?
+ getWebClientDomainLoginFlowInternal(homeServerConnectionConfig)
+ } else {
+ throw it
+ }
+ }
+ )
+ } else {
+ throw failure
}
}
- .map { versions ->
- // Ok, it seems that the homeserver url is valid
- getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
- }
- .fold(
- {
- it
- },
- {
- if (it is Failure.OtherServerError
- && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
- // It's maybe a Riot url?
- getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
- } else {
- throw it
- }
- }
- )
}
- private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
+ private suspend fun getWebClientDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val domain = homeServerConnectionConfig.homeServerUri.host
- ?: return getRiotLoginFlowInternal(homeServerConnectionConfig)
+ ?: return getWebClientLoginFlowInternal(homeServerConnectionConfig)
- // Ok, try to get the config.domain.json file of a RiotWeb client
+ // Ok, try to get the config.domain.json file of a Web client
return runCatching {
executeRequest(null) {
- authAPI.getRiotConfigDomain(domain)
+ authAPI.getWebClientConfigDomain(domain)
}
}
- .map { riotConfig ->
- onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig)
+ .map { webClientConfig ->
+ onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
}
.fold(
{
@@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor(
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Try with config.json
- getRiotLoginFlowInternal(homeServerConnectionConfig)
+ getWebClientLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
@@ -222,40 +236,24 @@ internal class DefaultAuthenticationService @Inject constructor(
)
}
- private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
+ private suspend fun getWebClientLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
- // Ok, try to get the config.json file of a RiotWeb client
- return runCatching {
- executeRequest(null) {
- authAPI.getRiotConfig()
- }
+ // Ok, try to get the config.json file of a Web client
+ return executeRequest(null) {
+ authAPI.getWebClientConfig()
}
- .map { riotConfig ->
- onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig)
+ .let { webClientConfig ->
+ onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
}
- .fold(
- {
- it
- },
- {
- if (it is Failure.OtherServerError
- && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
- // Try with wellknown
- getWellknownLoginFlowInternal(homeServerConnectionConfig)
- } else {
- throw it
- }
- }
- )
}
- private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult {
- val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl()
+ private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult {
+ val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl()
if (defaultHomeServerUrl?.isNotEmpty() == true) {
// Ok, good sign, we got a default hs url
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(defaultHomeServerUrl)
+ homeServerUriBase = Uri.parse(defaultHomeServerUrl)
)
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor(
val domain = homeServerConnectionConfig.homeServerUri.host
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
- // Create a fake userId, for the getWellknown task
- val fakeUserId = "@alice:$domain"
- val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig))
+ val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig))
return when (wellknownResult) {
is WellknownResult.Prompt -> {
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(wellknownResult.homeServerUrl),
- identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) }
+ homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl),
+ identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri
)
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor(
override suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
- return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig))
+ if (!MatrixPatterns.isUserId(matrixId)) {
+ throw MatrixIdFailure.InvalidMatrixId
+ }
+
+ return getWellknownTask.execute(GetWellknownTask.Params(
+ domain = matrixId.getDomain(),
+ homeServerConnectionConfig = homeServerConnectionConfig)
+ )
}
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
@@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
- val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString())
+ val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString())
return retrofit.create(AuthAPI::class.java)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
index 867cf46b8d..bc3d887000 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
@@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
val client = buildClient(params.homeServerConnectionConfig)
- val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
+ val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
index 7c4a0c38ec..51ecc68d62 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
@@ -56,7 +56,7 @@ internal class DefaultSessionCreator @Inject constructor(
tryOrNull {
isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params(
- homeServerConnectionConfig.copy(homeServerUri = it)
+ homeServerConnectionConfig.copy(homeServerUriBase = it)
)
)
.also { Timber.d("Overriding homeserver url: $it") }
@@ -66,7 +66,7 @@ internal class DefaultSessionCreator @Inject constructor(
val sessionParams = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri,
+ homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase,
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
similarity index 84%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
index e61358a67b..65c3dc64a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
-internal data class RiotConfig(
+internal data class WebClientConfig(
/**
* This is now deprecated, but still used first, rather than value from "default_server_config"
*/
@@ -28,7 +28,7 @@ internal data class RiotConfig(
val defaultHomeServerUrl: String?,
@Json(name = "default_server_config")
- val defaultServerConfig: RiotConfigDefaultServerConfig?
+ val defaultServerConfig: WebClientConfigDefaultServerConfig?
) {
fun getPreferredHomeServerUrl(): String? {
return defaultHomeServerUrl
@@ -38,13 +38,13 @@ internal data class RiotConfig(
}
@JsonClass(generateAdapter = true)
-internal data class RiotConfigDefaultServerConfig(
+internal data class WebClientConfigDefaultServerConfig(
@Json(name = "m.homeserver")
- val homeServer: RiotConfigBaseConfig? = null
+ val homeServer: WebClientConfigBaseConfig? = null
)
@JsonClass(generateAdapter = true)
-internal data class RiotConfigBaseConfig(
+internal data class WebClientConfigBaseConfig(
@Json(name = "base_url")
val baseURL: String? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
index bb2667228b..c2104690b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
@@ -16,17 +16,19 @@
package org.matrix.android.sdk.internal.auth.db
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.auth.data.sessionId
-import org.matrix.android.sdk.internal.di.MoshiProvider
+import android.net.Uri
import io.realm.DynamicRealm
import io.realm.RealmMigration
+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.sessionId
+import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
internal object AuthRealmMigration : RealmMigration {
// Current schema version
- const val SCHEMA_VERSION = 3L
+ const val SCHEMA_VERSION = 4L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
@@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration {
if (oldVersion <= 0) migrateTo1(realm)
if (oldVersion <= 1) migrateTo2(realm)
if (oldVersion <= 2) migrateTo3(realm)
+ if (oldVersion <= 3) migrateTo4(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration {
}
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
}
+
+ private fun migrateTo4(realm: DynamicRealm) {
+ Timber.d("Step 3 -> 4")
+ Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
+
+ val adapter = MoshiProvider.providesMoshi()
+ .adapter(HomeServerConnectionConfig::class.java)
+
+ realm.schema.get("SessionParamsEntity")
+ ?.transform {
+ val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
+
+ val homeserverConnectionConfig = adapter
+ .fromJson(homeserverConnectionConfigJson)
+
+ val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
+ // Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
+ // "https://matrix-client.matrix.org". So fix that here
+ val alteredHomeserverConnectionConfig =
+ if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
+ homeserverConnectionConfig.copy(
+ homeServerUri = Uri.parse("https://matrix.org"),
+ homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
+ )
+ } else {
+ homeserverConnectionConfig
+ }
+ it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
index 77bbb8096f..3888633723 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
@@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
override suspend fun execute(params: DirectLoginTask.Params): Session {
val client = buildClient(params.homeServerConnectionConfig)
- val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
+ val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
index e114f86a99..84d4fef5af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
@@ -112,7 +112,6 @@ internal abstract class CryptoModule {
@SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
- realmCryptoStoreMigration: RealmCryptoStoreMigration,
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
@@ -123,7 +122,7 @@ internal abstract class CryptoModule {
.modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true)
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
- .migration(realmCryptoStoreMigration)
+ .migration(RealmCryptoStoreMigration)
.build()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index 5a9852b6db..70730326da 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -39,7 +39,9 @@ internal object MXEncryptedAttachments {
private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
- fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
+ fun encrypt(clearStream: InputStream,
+ outputFile: File,
+ progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom()
val initVectorBytes = ByteArray(16) { 0.toByte() }
@@ -86,7 +88,6 @@ internal object MXEncryptedAttachments {
return EncryptedFileInfo(
url = null,
- mimetype = mimetype,
key = EncryptedFileKey(
alg = "A256CTR",
ext = true,
@@ -155,10 +156,9 @@ internal object MXEncryptedAttachments {
* Encrypt an attachment stream.
* DO NOT USE for big files, it will load all in memory
* @param attachmentStream the attachment stream. Will be closed after this method call.
- * @param mimetype the mime type
* @return the encryption file info
*/
- fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult {
+ fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom()
@@ -207,7 +207,6 @@ internal object MXEncryptedAttachments {
return EncryptionResult(
encryptedFileInfo = EncryptedFileInfo(
url = null,
- mimetype = mimetype,
key = EncryptedFileKey(
alg = "A256CTR",
ext = true,
@@ -232,7 +231,9 @@ internal object MXEncryptedAttachments {
* @param outputStream the outputStream where the decrypted attachment will be write.
* @return true in case of success, false in case of error
*/
- fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean {
+ fun decryptAttachment(attachmentStream: InputStream?,
+ elementToDecrypt: ElementToDecrypt?,
+ outputStream: OutputStream): Boolean {
// sanity checks
if (null == attachmentStream || elementToDecrypt == null) {
Timber.e("## decryptAttachment() : null stream")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
index 0ed6c0dac9..4fc3adb42c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
@@ -29,12 +29,6 @@ data class EncryptedFileInfo(
@Json(name = "url")
val url: String? = null,
- /**
- * Not documented
- */
- @Json(name = "mimetype")
- val mimetype: String? = null,
-
/**
* Required. A JSON Web Key object.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index bca7914388..8e77f5b823 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
+import io.realm.DynamicRealm
+import io.realm.RealmMigration
+import io.realm.RealmObjectSchema
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
@@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SerializeNulls
-import io.realm.DynamicRealm
-import io.realm.RealmMigration
-import io.realm.RealmObjectSchema
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber
-import javax.inject.Inject
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
-internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration {
+internal object RealmCryptoStoreMigration : RealmMigration {
- companion object {
- // 0, 1, 2: legacy Riot-Android
- // 3: migrate to RiotX schema
- // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
- const val CRYPTO_STORE_SCHEMA_VERSION = 12L
- }
+ // 0, 1, 2: legacy Riot-Android
+ // 3: migrate to RiotX schema
+ // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
+ const val CRYPTO_STORE_SCHEMA_VERSION = 12L
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
if (!hasField(fieldName)) {
@@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
private fun migrateTo7(realm: DynamicRealm) {
Timber.d("Step 6 -> 7")
Timber.d("Updating KeyInfoEntity table")
+ val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
+
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try {
keyInfoEntities.forEach {
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 2b3c3b28ee..28ae4d8bfd 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
@@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm
import io.realm.FieldAttribute
import io.realm.RealmMigration
-import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
@@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.query.process
import timber.log.Timber
-import javax.inject.Inject
-class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
+internal object RealmSessionStoreMigration : RealmMigration {
- companion object {
- const val SESSION_STORE_SCHEMA_VERSION = 14L
- }
+ const val SESSION_STORE_SCHEMA_VERSION = 16L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@@ -66,6 +65,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm)
if (oldVersion <= 13) migrateTo14(realm)
+ if (oldVersion <= 14) migrateTo15(realm)
+ if (oldVersion <= 15) migrateTo16(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@@ -292,7 +293,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
Timber.d("Step 13 -> 14")
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
- .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
+ .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
realm.schema.get("RoomEntity")
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
@@ -306,4 +307,27 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
roomAccountDataSchema.isEmbedded = true
}
+
+ private fun migrateTo15(realm: DynamicRealm) {
+ Timber.d("Step 14 -> 15")
+ // fix issue with flattenParentIds on DM that kept growing with duplicate
+ // so we reset it, will be updated next sync
+ realm.where("RoomSummaryEntity")
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .findAll()
+ .onEach {
+ it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
+ }
+ }
+
+ private fun migrateTo16(realm: DynamicRealm) {
+ Timber.d("Step 15 -> 16")
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
+ ?.transform { obj ->
+ // Schedule a refresh of the capabilities
+ obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
index 244fe3432a..1771c5b202 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
@@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
@SessionFilesDirectory val directory: File,
@SessionId val sessionId: String,
@UserMd5 val userMd5: String,
- val migration: RealmSessionStoreMigration,
context: Context) {
// Keep legacy preferences name for compatibility reason
@@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
.allowWritesOnUiThread(true)
.modules(SessionRealmModule())
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
- .migration(migration)
+ .migration(RealmSessionStoreMigration)
.build()
// Try creating a realm instance and if it succeeds we can clear the flag
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index b18c67294f..2575cdef26 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -16,8 +16,15 @@
package org.matrix.android.sdk.internal.database.mapper
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.session.homeserver.RoomVersions
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
/**
* HomeServerCapabilitiesEntity -> HomeSeverCapabilities
@@ -29,7 +36,30 @@ internal object HomeServerCapabilitiesMapper {
canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize,
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
- defaultIdentityServerUrl = entity.defaultIdentityServerUrl
+ defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
+ roomVersions = mapRoomVersion(entity.roomVersionsJson)
)
}
+
+ private fun mapRoomVersion(roomVersionsJson: String?): RoomVersionCapabilities? {
+ roomVersionsJson ?: return null
+
+ return tryOrNull {
+ MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let {
+ RoomVersionCapabilities(
+ defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
+ supportedVersion = it.available.entries.map { entry ->
+ RoomVersionInfo(
+ version = entry.key,
+ status = if (entry.value == "stable") {
+ RoomVersionStatus.STABLE
+ } else {
+ RoomVersionStatus.UNSTABLE
+ }
+ )
+ }
+ )
+ }
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 763dcf80a2..980449ddfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true,
+ var roomVersionsJson: String? = null,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
index 320bf1d445..a4eef80c58 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
@@ -37,7 +37,8 @@ internal abstract class FederationModule {
fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): FederationAPI {
- return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java)
+ return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase)
+ .create(FederationAPI::class.java)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
index 4586cfea1e..ad2aff4c9d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
@@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
internal class DefaultLegacySessionImporter @Inject constructor(
private val context: Context,
private val sessionParamsStore: SessionParamsStore,
- private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
private val realmKeysUtils: RealmKeysUtils
) : LegacySessionImporter {
@@ -172,7 +171,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
- .migration(realmCryptoStoreMigration)
+ .migration(RealmCryptoStoreMigration)
.build()
Timber.d("Migration: copy DB to encrypted DB")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
index 9d7263f56a..976751446b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
@@ -253,7 +253,7 @@ internal object CertUtil {
val list = ArrayList()
list.add(builder.build())
// TODO: we should display a warning if user enter an http url
- if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) {
+ if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) {
list.add(ConnectionSpec.CLEARTEXT)
}
return list
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 42b826de16..bca1e498de 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
@@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor(
return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy))
}
- override suspend fun getWellknown(userId: String): String {
- val homeServerDomain = userId.substringAfter(":")
+ override suspend fun getWellknown(domain: String): String {
return getUrl(
- "https://$homeServerDomain/.well-known/matrix/client",
+ "https://$domain/.well-known/matrix/client",
CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
)
}
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 1cf99bbbd7..c2bd1e24ed 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
@@ -313,7 +313,7 @@ internal class DefaultSession @Inject constructor(
override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
val hsBas = sessionParams.homeServerConnectionConfig
- .homeServerUri
+ .homeServerUriBase
.toString()
.trim { it == '/' }
return buildString {
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 e6da21315b..cb29cb4819 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
@@ -261,7 +261,7 @@ internal abstract class SessionModule {
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory
- .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
+ .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString())
}
@JvmStatic
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
index f765056496..e4efdaa254 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
@@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
- private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash()
+ private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
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 237411db53..f14c85cf80 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
@@ -234,7 +234,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
.also { filesToDelete.add(it) }
uploadedFileEncryptedFileInfo =
- MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total ->
+ MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
notifyTracker(params) {
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
}
@@ -315,7 +315,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
if (params.isEncrypted) {
Timber.v("Encrypt thumbnail")
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
- val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
+ val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
val contentUploadResponse = fileUploader.uploadByteArray(
byteArray = encryptionResult.encryptedByteArray,
filename = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
index 0ed690d972..4c755b54b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
@@ -16,18 +16,12 @@
package org.matrix.android.sdk.internal.session.homeserver
-import com.zhuinden.monarchy.Monarchy
-import io.realm.Realm
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
-import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
-import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
-import org.matrix.android.sdk.internal.database.query.get
-import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject
internal class DefaultHomeServerCapabilitiesService @Inject constructor(
- @SessionDatabase private val monarchy: Monarchy,
+ private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask
) : HomeServerCapabilitiesService {
@@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
}
override fun getHomeServerCapabilities(): HomeServerCapabilities {
- return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
- HomeServerCapabilitiesEntity.get(realm)?.let {
- HomeServerCapabilitiesMapper.map(it)
- }
- }
+ return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
?: HomeServerCapabilities()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
index ab029a0fce..c4bc09a233 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.orTrue
+import org.matrix.android.sdk.api.util.JsonDict
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities
@@ -38,9 +39,14 @@ internal data class Capabilities(
* Capability to indicate if the user can change their password.
*/
@Json(name = "m.change_password")
- val changePassword: ChangePassword? = null
+ val changePassword: ChangePassword? = null,
- // No need for m.room_versions for the moment
+ /**
+ * This capability describes the default and available room versions a server supports, and at what level of stability.
+ * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
+ */
+ @Json(name = "m.room_versions")
+ val roomVersions: RoomVersions? = null
)
@JsonClass(generateAdapter = true)
@@ -52,6 +58,21 @@ internal data class ChangePassword(
val enabled: Boolean?
)
+@JsonClass(generateAdapter = true)
+internal data class RoomVersions(
+ /**
+ * Required. The default room version the server is using for new rooms.
+ */
+ @Json(name = "default")
+ val default: String?,
+
+ /**
+ * Required. A detailed description of the room versions the server supports.
+ */
+ @Json(name = "available")
+ val available: JsonDict
+)
+
// The spec says: If not present, the client should assume that password changes are possible via the API
internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
return capabilities?.changePassword?.enabled.orTrue()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index 740370123f..612b98f863 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
+import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
}.getOrNull()
val wellknownResult = runCatching {
- getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig))
+ getWellknownTask.execute(GetWellknownTask.Params(
+ domain = userId.getDomain(),
+ homeServerConnectionConfig = homeServerConnectionConfig
+ ))
}.getOrNull()
insertInDb(capabilities, mediaConfig, versions, wellknownResult)
@@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getCapabilitiesResult != null) {
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
+
+ homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let {
+ MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
+ }
}
if (getMediaConfigResult != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt
new file mode 100644
index 0000000000..6c913fa41e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 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.homeserver
+
+import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
+import org.matrix.android.sdk.internal.database.query.get
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import javax.inject.Inject
+
+internal class HomeServerCapabilitiesDataSource @Inject constructor(
+ @SessionDatabase private val monarchy: Monarchy
+) {
+ fun getHomeServerCapabilities(): HomeServerCapabilities? {
+ return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
+ HomeServerCapabilitiesEntity.get(realm)?.let {
+ HomeServerCapabilitiesMapper.map(it)
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
index 7a39a333a5..4d664b76be 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
@@ -60,7 +60,6 @@ internal abstract class IdentityModule {
@SessionScope
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
@SessionFilesDirectory directory: File,
- migration: RealmIdentityStoreMigration,
@UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
@@ -69,7 +68,7 @@ internal abstract class IdentityModule {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
}
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
- .migration(migration)
+ .migration(RealmIdentityStoreMigration)
.allowWritesOnUiThread(true)
.modules(IdentityRealmModule())
.build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
index 6081dbab12..21c0f8eb9e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
@@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db
import io.realm.DynamicRealm
import io.realm.RealmMigration
import timber.log.Timber
-import javax.inject.Inject
-internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
+internal object RealmIdentityStoreMigration : RealmMigration {
- companion object {
- const val IDENTITY_STORE_SCHEMA_VERSION = 1L
- }
+ const val IDENTITY_STORE_SCHEMA_VERSION = 1L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
index 72fbfcced5..82565d8118 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.permalinks
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.di.UserId
@@ -47,9 +48,9 @@ internal class ViaParameterFinder @Inject constructor(
}
fun computeViaParams(userId: String, roomId: String, max: Int): List {
- val userHomeserver = userId.substringAfter(":")
+ val userHomeserver = userId.getDomain()
return getUserIdsOfJoinedMembers(roomId)
- .map { it.substringAfter(":") }
+ .map { it.getDomain() }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 0d9c106d41..8afd690f64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
+import org.matrix.android.sdk.api.session.room.version.RoomVersionService
import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional
@@ -67,9 +68,11 @@ internal class DefaultRoom(override val roomId: String,
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService,
private val roomAccountDataService: RoomAccountDataService,
+ private val roomVersionService: RoomVersionService,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
- private val searchTask: SearchTask) :
+ private val searchTask: SearchTask
+) :
Room,
TimelineService by timelineService,
SendService by sendService,
@@ -85,7 +88,8 @@ internal class DefaultRoom(override val roomId: String,
RelationService by relationService,
MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService,
- RoomAccountDataService by roomAccountDataService {
+ RoomAccountDataService by roomAccountDataService,
+ RoomVersionService by roomVersionService {
override fun getRoomSummaryLive(): LiveData> {
return roomSummaryDataSource.getRoomSummaryLive(roomId)
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 4f12604039..18ece60629 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
@@ -369,4 +369,15 @@ internal interface RoomAPI {
@Path("roomId") roomId: String,
@Path("type") type: String,
@Body content: JsonDict)
+
+ /**
+ * Upgrades the given room to a particular room version.
+ * Errors:
+ * 400, The request was invalid. One way this can happen is if the room version requested is not supported by the homeserver
+ * (M_UNSUPPORTED_ROOM_VERSION)
+ * 403: The user is not permitted to upgrade the room.(M_FORBIDDEN)
+ */
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
+ suspend fun upgradeRoom(@Path("roomId") roomId: String,
+ @Body body: RoomUpgradeBody): RoomUpgradeResponse
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 9ddb8f1177..d44eb32529 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
import org.matrix.android.sdk.internal.session.search.SearchTask
import javax.inject.Inject
@@ -61,6 +62,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
+ private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
@@ -87,6 +89,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
+ roomVersionService = roomVersionServiceFactory.create(roomId),
sendStateTask = sendStateTask,
searchTask = searchTask,
viaParameterFinder = viaParameterFinder
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 d88c195056..c04c899e18 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
@@ -92,6 +92,8 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUpgradeTask
+import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
import retrofit2.Retrofit
@@ -243,4 +245,7 @@ internal abstract class RoomModule {
@Binds
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
+
+ @Binds
+ abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt
new file mode 100644
index 0000000000..4629f6e409
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class RoomUpgradeBody(
+ @Json(name = "new_version")
+ val newVersion: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt
new file mode 100644
index 0000000000..1cca2c572b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class RoomUpgradeResponse(
+ @Json(name = "replacement_room")
+ val replacementRoomId: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
index 66164c5280..7c137a8102 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.room.alias
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.internal.di.UserId
@@ -64,6 +65,6 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
}
companion object {
- internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":")
+ internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain()
}
}
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 a07df63691..a64b903947 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
@@ -142,7 +142,7 @@ internal class DefaultSendService @AssistedInject constructor(
// The image has not yet been sent
val attachmentData = ContentAttachmentData(
size = messageContent.info!!.size,
- mimeType = messageContent.info.mimeType!!,
+ mimeType = messageContent.mimeType,
width = messageContent.info.width.toLong(),
height = messageContent.info.height.toLong(),
name = messageContent.body,
@@ -169,7 +169,7 @@ internal class DefaultSendService @AssistedInject constructor(
is MessageFileContent -> {
val attachmentData = ContentAttachmentData(
size = messageContent.info!!.size,
- mimeType = messageContent.info.mimeType!!,
+ mimeType = messageContent.mimeType,
name = messageContent.getFileName(),
queryUri = Uri.parse(messageContent.url),
type = ContentAttachmentData.Type.FILE
@@ -181,7 +181,7 @@ internal class DefaultSendService @AssistedInject constructor(
val attachmentData = ContentAttachmentData(
size = messageContent.audioInfo?.size ?: 0,
duration = messageContent.audioInfo?.duration?.toLong() ?: 0L,
- mimeType = messageContent.audioInfo?.mimeType,
+ mimeType = messageContent.mimeType,
name = messageContent.body,
queryUri = Uri.parse(messageContent.url),
type = ContentAttachmentData.Type.AUDIO,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
index 2d53699917..1d7ce587f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
@@ -26,8 +26,8 @@ import java.net.Socket
internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) {
fun check(): Boolean {
- val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false
- val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80
+ val host = sessionParams.homeServerConnectionConfig.homeServerUriBase.host ?: return false
+ val port = sessionParams.homeServerConnectionConfig.homeServerUriBase.port.takeIf { it != -1 } ?: 80
val timeout = 30_000
try {
Socket().use { socket ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 7cbcfee713..842c9d3aba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -199,7 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor(
measureTimeMillis {
val lookupMap = realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
- .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
// we order by roomID to be consistent when breaking parent/child cycles
.sort(RoomSummaryEntityFields.ROOM_ID)
.findAll().map {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt
new file mode 100644
index 0000000000..dc12c3209b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.version
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import org.matrix.android.sdk.api.query.QueryStringValue
+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.homeserver.RoomVersionStatus
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.api.session.room.version.RoomVersionService
+import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+
+internal class DefaultRoomVersionService @AssistedInject constructor(
+ @Assisted private val roomId: String,
+ private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
+ private val stateEventDataSource: StateEventDataSource,
+ private val roomVersionUpgradeTask: RoomVersionUpgradeTask
+) : RoomVersionService {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(roomId: String): DefaultRoomVersionService
+ }
+
+ override fun getRoomVersion(): String {
+ return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)
+ ?.content
+ ?.toModel()
+ ?.roomVersion
+ // as per spec -> Defaults to "1" if the key does not exist.
+ ?: DEFAULT_ROOM_VERSION
+ }
+
+ override suspend fun upgradeToVersion(version: String): String {
+ return roomVersionUpgradeTask.execute(
+ RoomVersionUpgradeTask.Params(
+ roomId = roomId,
+ newVersion = version
+ )
+ )
+ }
+
+ override fun getRecommendedVersion(): String {
+ return homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions?.defaultRoomVersion ?: DEFAULT_ROOM_VERSION
+ }
+
+ override fun isUsingUnstableRoomVersion(): Boolean {
+ val versionCaps = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions
+ val currentVersion = getRoomVersion()
+ return versionCaps?.supportedVersion?.firstOrNull { it.version == currentVersion }?.status == RoomVersionStatus.UNSTABLE
+ }
+
+ override fun userMayUpgradeRoom(userId: String): Boolean {
+ val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
+ ?.content?.toModel()
+ ?.let { PowerLevelsHelper(it) }
+
+ return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false
+ }
+
+ companion object {
+ const val DEFAULT_ROOM_VERSION = "1"
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt
new file mode 100644
index 0000000000..457bb3e948
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.version
+
+import io.realm.RealmConfiguration
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.session.room.RoomUpgradeBody
+import org.matrix.android.sdk.internal.task.Task
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+internal interface RoomVersionUpgradeTask : Task {
+ data class Params(
+ val roomId: String,
+ val newVersion: String
+ )
+}
+
+internal class DefaultRoomVersionUpgradeTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val globalErrorReceiver: GlobalErrorReceiver,
+ @SessionDatabase
+ private val realmConfiguration: RealmConfiguration
+) : RoomVersionUpgradeTask {
+
+ override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
+ val replacementRoomId = executeRequest(globalErrorReceiver) {
+ roomAPI.upgradeRoom(
+ roomId = params.roomId,
+ body = RoomUpgradeBody(params.newVersion)
+ )
+ }.replacementRoomId
+
+ // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
+ tryOrNull {
+ awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
+ realm.where(RoomSummaryEntity::class.java)
+ .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
+ .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
+ }
+ }
+ return replacementRoomId
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
index 70c52bf4ae..233eef45f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -86,6 +86,12 @@ internal class DefaultSpace(
)
}
+ override fun getChildInfo(roomId: String): SpaceChildContent? {
+ return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+ .firstOrNull()
+ ?.content.toModel()
+ }
+
override suspend fun setChildrenOrder(roomId: String, order: String?) {
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
index 7a9beac8c0..fdeb8c1ca5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.wellknown
import android.util.MalformedJsonException
import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.WellKnown
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
@@ -39,7 +38,11 @@ import javax.net.ssl.HttpsURLConnection
internal interface GetWellknownTask : Task {
data class Params(
- val matrixId: String,
+ /**
+ * domain, for instance "matrix.org"
+ * the URL will be https://{domain}/.well-known/matrix/client
+ */
+ val domain: String,
val homeServerConnectionConfig: HomeServerConnectionConfig?
)
}
@@ -54,14 +57,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
) : GetWellknownTask {
override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
- if (!MatrixPatterns.isUserId(params.matrixId)) {
- return WellknownResult.InvalidMatrixId
- }
-
- val homeServerDomain = params.matrixId.substringAfter(":")
-
val client = buildClient(params.homeServerConnectionConfig)
- return findClientConfig(homeServerDomain, client)
+ return findClientConfig(params.domain, client)
}
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient {
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index e0645e00b3..ba9dec0877 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -162,7 +162,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===101
+enum class===102
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/vector/build.gradle b/vector/build.gradle
index 82ae3d609f..f1b71741aa 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 1
-ext.versionPatch = 12
+ext.versionPatch = 13
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@@ -323,8 +323,8 @@ dependencies {
// Tests
def kluent_version = '1.67'
- def androidxTest_version = '1.3.0'
- def espresso_version = '3.3.0'
+ def androidxTest_version = '1.4.0'
+ def espresso_version = '3.4.0'
implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx")
@@ -343,7 +343,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.0-beta02'
implementation "androidx.sharetarget:sharetarget:1.1.0"
- implementation 'androidx.core:core-ktx:1.5.0'
+ implementation 'androidx.core:core-ktx:1.6.0'
implementation "androidx.media:media:1.3.1"
implementation "androidx.transition:transition:1.4.1"
@@ -362,7 +362,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
@@ -393,7 +393,7 @@ dependencies {
// UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
- implementation 'com.google.android.material:material:1.3.0'
+ implementation 'com.google.android.material:material:1.4.0'
implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
@@ -501,7 +501,7 @@ dependencies {
androidTestImplementation "androidx.test:core:$androidxTest_version"
androidTestImplementation "androidx.test:runner:$androidxTest_version"
androidTestImplementation "androidx.test:rules:$androidxTest_version"
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt
index 4791c2e499..be8447d409 100644
--- a/vector/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/app/VectorApplication.kt
@@ -162,7 +162,6 @@ class VectorApplication :
// Do not display the name change popup
doNotShowDisclaimerDialog(this)
}
-
if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
activeSessionHolder.setActiveSession(lastAuthenticatedSession)
diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
index 38edb771bb..2a2c8908b0 100644
--- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
@@ -40,12 +40,14 @@ import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.HomeModule
+import im.vector.app.features.home.room.detail.JoinReplacementRoomBottomSheet
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.search.SearchActivity
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
+import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.home.room.list.RoomListModule
@@ -193,6 +195,8 @@ interface ScreenComponent {
fun inject(bottomSheet: SpaceSettingsMenuBottomSheet)
fun inject(bottomSheet: InviteRoomSpaceChooserBottomSheet)
fun inject(bottomSheet: SpaceInviteBottomSheet)
+ fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
+ fun inject(bottomSheet: MigrateRoomBottomSheet)
/* ==========================================================================================
* Others
diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
index ca92e6aa75..39458a054a 100644
--- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
+++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
@@ -50,7 +50,15 @@ class UnrecognizedCertificateDialog @Inject constructor(
val userId = activeSessionHolder.getSafeActiveSession()?.myUserId
val hsConfig = activeSessionHolder.getSafeActiveSession()?.sessionParams?.homeServerConnectionConfig ?: return
- internalShow(activity, unrecognizedFingerprint, true, callback, userId, hsConfig.homeServerUri.toString(), hsConfig.allowedFingerprints.isNotEmpty())
+ internalShow(
+ activity = activity,
+ unrecognizedFingerprint = unrecognizedFingerprint,
+ existing = true,
+ callback = callback,
+ userId = userId,
+ homeServerUrl = hsConfig.homeServerUriBase.toString(),
+ homeServerConnectionConfigHasFingerprints = hsConfig.allowedFingerprints.isNotEmpty()
+ )
}
/**
@@ -60,7 +68,15 @@ class UnrecognizedCertificateDialog @Inject constructor(
unrecognizedFingerprint: Fingerprint,
homeServerUrl: String,
callback: Callback) {
- internalShow(activity, unrecognizedFingerprint, false, callback, null, homeServerUrl, false)
+ internalShow(
+ activity = activity,
+ unrecognizedFingerprint = unrecognizedFingerprint,
+ existing = false,
+ callback = callback,
+ userId = null,
+ homeServerUrl = homeServerUrl,
+ homeServerConnectionConfigHasFingerprints = false
+ )
}
/**
diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
index c0219b26e2..e7602e5cfe 100644
--- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
+++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
@@ -21,6 +21,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.call.dialpad.DialPadLookup
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection
@@ -39,9 +40,9 @@ class DefaultErrorFormatter @Inject constructor(
override fun toHumanReadable(throwable: Throwable?): String {
return when (throwable) {
- null -> null
- is IdentityServiceError -> identityServerError(throwable)
- is Failure.NetworkConnection -> {
+ null -> null
+ is IdentityServiceError -> identityServerError(throwable)
+ is Failure.NetworkConnection -> {
when (throwable.ioException) {
is SocketTimeoutException ->
stringProvider.getString(R.string.error_network_timeout)
@@ -54,7 +55,7 @@ class DefaultErrorFormatter @Inject constructor(
stringProvider.getString(R.string.error_no_network)
}
}
- is Failure.ServerError -> {
+ is Failure.ServerError -> {
when {
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
// Special case for terms and conditions
@@ -104,23 +105,25 @@ class DefaultErrorFormatter @Inject constructor(
}
}
}
- is Failure.OtherServerError -> {
+ is Failure.OtherServerError -> {
when (throwable.httpCode) {
- HttpURLConnection.HTTP_NOT_FOUND ->
+ HttpURLConnection.HTTP_NOT_FOUND ->
// homeserver not found
stringProvider.getString(R.string.login_error_no_homeserver_found)
HttpURLConnection.HTTP_UNAUTHORIZED ->
// uia errors?
stringProvider.getString(R.string.error_unauthorized)
- else ->
+ else ->
throwable.localizedMessage
}
}
- is DialPadLookup.Failure.NumberIsYours ->
+ is DialPadLookup.Failure.NumberIsYours ->
stringProvider.getString(R.string.cannot_call_yourself)
- is DialPadLookup.Failure.NoResult ->
+ is DialPadLookup.Failure.NoResult ->
stringProvider.getString(R.string.call_dial_pad_lookup_error)
- else -> throwable.localizedMessage
+ is MatrixIdFailure.InvalidMatrixId ->
+ stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id)
+ else -> throwable.localizedMessage
}
?: stringProvider.getString(R.string.unknown_error)
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
index 759e130e29..92dc76670f 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
@@ -41,7 +41,7 @@ fun SearchView.withoutLeftMargin() {
}
fun EditText.hidePassword() {
- inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
fun View.getMeasurements(): Pair {
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
index d6d4d07500..b9b5bc8ca5 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
@@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit
/**
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
*/
-abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView {
+abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView {
private val mvrxViewIdProperty = MvRxViewId()
final override val mvrxViewId: String by mvrxViewIdProperty
@@ -168,6 +168,10 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShee
@CallSuper
override fun invalidate() {
+ forceExpandState()
+ }
+
+ protected fun forceExpandState() {
if (showExpanded) {
// Force the bottom sheet to be expanded
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED
diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericProgressBarItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericProgressBarItem.kt
new file mode 100644
index 0000000000..e392c0bdf2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericProgressBarItem.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 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.list
+
+import android.widget.ProgressBar
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+
+/**
+ * A generic progress bar item.
+ */
+@EpoxyModelClass(layout = R.layout.item_generic_progress)
+abstract class GenericProgressBarItem : VectorEpoxyModel() {
+
+ @EpoxyAttribute
+ var progress: Int = 0
+
+ @EpoxyAttribute
+ var total: Int = 100
+
+ @EpoxyAttribute
+ var indeterminate: Boolean = false
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.progressbar.progress = progress
+ holder.progressbar.max = total
+ holder.progressbar.isIndeterminate = indeterminate
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val progressbar by bind(R.id.genericProgressBar)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
index ad2a4b8e0c..463d94b288 100644
--- a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
@@ -21,11 +21,12 @@ import android.graphics.Color
import android.text.method.LinkMovementMethod
import android.util.AttributeSet
import android.view.View
-import android.widget.RelativeLayout
+import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.text.italic
import im.vector.app.R
import im.vector.app.core.error.ResourceLimitErrorFormatter
+import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewNotificationAreaBinding
import im.vector.app.features.themes.ThemeUtils
@@ -44,7 +45,7 @@ class NotificationAreaView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
-) : RelativeLayout(context, attrs, defStyleAttr) {
+) : LinearLayout(context, attrs, defStyleAttr) {
var delegate: Delegate? = null
private var state: State = State.Initial
@@ -69,12 +70,13 @@ class NotificationAreaView @JvmOverloads constructor(
cleanUp()
state = newState
when (newState) {
+ State.Initial -> Unit
is State.Default -> renderDefault()
is State.Hidden -> renderHidden()
is State.NoPermissionToPost -> renderNoPermissionToPost()
- is State.Tombstone -> renderTombstone(newState)
+ is State.Tombstone -> renderTombstone()
is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
- }
+ }.exhaustive
}
// PRIVATE METHODS ****************************************************************************************************************************************
@@ -125,15 +127,15 @@ class NotificationAreaView @JvmOverloads constructor(
setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
}
- private fun renderTombstone(state: State.Tombstone) {
+ private fun renderTombstone() {
visibility = View.VISIBLE
- views.roomNotificationIcon.setImageResource(R.drawable.error)
+ views.roomNotificationIcon.setImageResource(R.drawable.ic_warning_badge)
val message = span {
+resources.getString(R.string.room_tombstone_versioned_description)
+"\n"
span(resources.getString(R.string.room_tombstone_continuation_link)) {
textDecorationLine = "underline"
- onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) }
+ onClick = { delegate?.onTombstoneEventClicked() }
}
}
views.roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance()
@@ -177,6 +179,6 @@ class NotificationAreaView @JvmOverloads constructor(
* An interface to delegate some actions to another object
*/
interface Delegate {
- fun onTombstoneEventClicked(tombstoneEvent: Event)
+ fun onTombstoneEventClicked()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
index c9fb8fbccd..7b01824c6c 100644
--- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
+++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
@@ -56,7 +56,7 @@ class JitsiService @Inject constructor(
// Build data for a jitsi widget
val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis()
val preferredJitsiDomain = tryOrNull {
- rawService.getElementWellknown(session.myUserId)
+ rawService.getElementWellknown(session.sessionParams)
?.jitsiServer
?.preferredDomain
}
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 61d39857cc..3719618d31 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
@@ -50,7 +50,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
CREATE_SPACE("/createspace", "*", R.string.command_description_create_space, true),
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true),
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true),
- LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true);
+ LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true),
+ UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true);
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 3de00f4d0c..adba6e4a18 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
@@ -312,24 +312,32 @@ object CommandParser {
)
}
}
- Command.ADD_TO_SPACE.command -> {
+ Command.ADD_TO_SPACE.command -> {
val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim()
ParsedCommand.AddToSpace(
rawCommand
)
}
- Command.JOIN_SPACE.command -> {
+ Command.JOIN_SPACE.command -> {
val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim()
ParsedCommand.JoinSpace(
spaceIdOrAlias
)
}
- Command.LEAVE_ROOM.command -> {
+ Command.LEAVE_ROOM.command -> {
val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim()
ParsedCommand.LeaveRoom(
spaceIdOrAlias
)
}
+ Command.UPGRADE_ROOM.command -> {
+ val newVersion = textMessage.substring(Command.UPGRADE_ROOM.command.length).trim()
+ if (newVersion.isEmpty()) {
+ ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
+ } else {
+ ParsedCommand.UpgradeRoom(newVersion)
+ }
+ }
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 d67caac60a..123f1d3a36 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
@@ -61,4 +61,5 @@ sealed class ParsedCommand {
class AddToSpace(val spaceId: String) : ParsedCommand()
class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand()
class LeaveRoom(val roomId: String) : ParsedCommand()
+ class UpgradeRoom(val newVersion: String) : ParsedCommand()
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
index 2f0b6e5ec9..88e8d68155 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
@@ -86,7 +86,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
setState { copy(createAndInviteState = Loading()) }
viewModelScope.launch(Dispatchers.IO) {
- val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+ val adminE2EByDefault = rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
index bfa56cfb9e..cf75bdf1b6 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
@@ -35,7 +35,7 @@ class DirectRoomHelper @Inject constructor(
if (existingRoomId != null) {
roomId = existingRoomId
} else {
- val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+ val adminE2EByDefault = rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
index 1202a22042..7025343fc6 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
@@ -45,9 +45,22 @@ class VerificationChooseMethodController @Inject constructor(
val host = this
if (state.otherCanScanQrCode || state.otherCanShowQrCode) {
+ val scanCodeInstructions: String
+ val scanOtherCodeTitle: String
+ val compareEmojiSubtitle: String
+ if (state.isMe) {
+ scanCodeInstructions = stringProvider.getString(R.string.verification_scan_self_notice)
+ scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_with_this_device)
+ compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_self_emoji_subtitle)
+ } else {
+ scanCodeInstructions = stringProvider.getString(R.string.verification_scan_notice)
+ scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_their_code)
+ compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_emoji_subtitle)
+ }
+
bottomSheetVerificationNoticeItem {
id("notice")
- notice(host.stringProvider.getString(R.string.verification_scan_notice))
+ notice(scanCodeInstructions)
}
if (state.otherCanScanQrCode && !state.qrCodeText.isNullOrBlank()) {
@@ -64,7 +77,7 @@ class VerificationChooseMethodController @Inject constructor(
if (state.otherCanShowQrCode) {
bottomSheetVerificationActionItem {
id("openCamera")
- title(host.stringProvider.getString(R.string.verification_scan_their_code))
+ title(scanOtherCodeTitle)
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
iconRes(R.drawable.ic_camera)
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
@@ -80,7 +93,7 @@ class VerificationChooseMethodController @Inject constructor(
id("openEmoji")
title(host.stringProvider.getString(R.string.verification_scan_emoji_title))
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
- subTitle(host.stringProvider.getString(R.string.verification_scan_emoji_subtitle))
+ subTitle(compareEmojiSubtitle)
iconRes(R.drawable.ic_arrow_right)
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
listener { host.listener?.doVerifyBySas() }
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
index dda3df3881..13809bcef3 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
@@ -327,7 +327,7 @@ class HomeDetailFragment @Inject constructor(
private fun setupBottomNavigationView() {
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
- views.bottomNavigationView.setOnNavigationItemSelectedListener {
+ views.bottomNavigationView.setOnItemSelectedListener {
val tab = when (it.itemId) {
R.id.bottom_action_people -> HomeTab.RoomList(RoomListDisplayMode.PEOPLE)
R.id.bottom_action_rooms -> HomeTab.RoomList(RoomListDisplayMode.ROOMS)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
new file mode 100644
index 0000000000..54681366e0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021 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 android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.parentFragmentViewModel
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.epoxy.ClickListener
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.platform.ButtonStateView
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.databinding.BottomSheetTombstoneJoinBinding
+import javax.inject.Inject
+
+class JoinReplacementRoomBottomSheet :
+ VectorBaseBottomSheetDialogFragment() {
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ BottomSheetTombstoneJoinBinding.inflate(inflater, container, false)
+
+ @Inject
+ lateinit var errorFormatter: ErrorFormatter
+
+ override fun injectWith(injector: ScreenComponent) {
+ injector.inject(this)
+ }
+
+ private val viewModel: RoomDetailViewModel by parentFragmentViewModel()
+
+ override val showExpanded: Boolean
+ get() = true
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.roomUpgradeButton.retryClicked = object : ClickListener {
+ override fun invoke(view: View) {
+ viewModel.handle(RoomDetailAction.JoinAndOpenReplacementRoom)
+ }
+ }
+
+ viewModel.selectSubscribe(this, RoomDetailViewState::joinUpgradedRoomAsync) { joinState ->
+ when (joinState) {
+ // it should never be Uninitialized
+ Uninitialized,
+ is Loading -> {
+ views.roomUpgradeButton.render(ButtonStateView.State.Loading)
+ views.descriptionText.setText(R.string.it_may_take_some_time)
+ }
+ is Success -> {
+ views.roomUpgradeButton.render(ButtonStateView.State.Loaded)
+ dismiss()
+ }
+ is Fail -> {
+ // display the error message
+ views.descriptionText.text = errorFormatter.toHumanReadable(joinState.error)
+ views.roomUpgradeButton.render(ButtonStateView.State.Error)
+ }
+ }
+ }
+ }
+}
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 ac28f63850..6b031159b8 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
@@ -20,7 +20,6 @@ import android.net.Uri
import android.view.View
import im.vector.app.core.platform.VectorViewModelAction
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.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
@@ -45,7 +44,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class NavigateToEvent(val eventId: String, val highlight: Boolean) : RoomDetailAction()
object MarkAllAsRead : RoomDetailAction()
data class DownloadOrOpen(val eventId: String, val senderId: String?, val messageFileContent: MessageWithAttachmentContent) : RoomDetailAction()
- data class HandleTombstoneEvent(val event: Event) : RoomDetailAction()
+ object JoinAndOpenReplacementRoom : RoomDetailAction()
object AcceptInvite : RoomDetailAction()
object RejectInvite : RoomDetailAction()
@@ -110,6 +109,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
// Failed messages
object RemoveAllFailedMessages : RoomDetailAction()
+ data class RoomUpgradeSuccess(val replacementRoomId: String): RoomDetailAction()
+
// Voice Message
object StartRecordingVoiceMessage : RoomDetailAction()
data class EndRecordingVoiceMessage(val isCancelled: Boolean) : 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 49fbae4ecf..74be3ed5ab 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
@@ -50,6 +50,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.forEach
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
+import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@@ -59,11 +60,7 @@ import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.epoxy.addGlidePreloader
import com.airbnb.epoxy.glidePreloader
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Fail
-import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
-import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@@ -148,6 +145,7 @@ 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.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
@@ -181,7 +179,6 @@ import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser
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.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -308,6 +305,15 @@ class RoomDetailFragment @Inject constructor(
private lateinit var emojiPopup: EmojiPopup
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
+ bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
+ roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
+ }
+ }
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
@@ -355,10 +361,6 @@ class RoomDetailFragment @Inject constructor(
invalidateOptionsMenu()
})
- roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) {
- renderTombstoneEventHandling(it)
- }
-
roomDetailViewModel.selectSubscribe(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ ->
updateJumpToReadMarkerViewVisibility()
}
@@ -412,6 +414,8 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type)
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
+ RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
+ is RoomDetailViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it)
}.exhaustive
}
@@ -430,6 +434,19 @@ class RoomDetailFragment @Inject constructor(
startActivity(intent)
}
+ private fun handleRoomReplacement() {
+ // this will join a new room, it can take time and might fail
+ // so we need to report progress and retry
+ val tag = JoinReplacementRoomBottomSheet::javaClass.name
+ JoinReplacementRoomBottomSheet().show(childFragmentManager, tag)
+ }
+
+ private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: RoomDetailViewEvents.ShowRoomUpgradeDialog) {
+ val tag = MigrateRoomBottomSheet::javaClass.name
+ MigrateRoomBottomSheet.newInstance(roomDetailArgs.roomId, roomDetailViewEvents.newVersion)
+ .show(parentFragmentManager, tag)
+ }
+
private fun handleChatEffect(chatEffect: ChatEffect) {
when (chatEffect) {
ChatEffect.CONFETTI -> {
@@ -479,6 +496,9 @@ class RoomDetailFragment @Inject constructor(
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
navigator.openRoom(requireContext(), openRoom.roomId, null)
+ if (openRoom.closeCurrentRoom) {
+ requireActivity().finish()
+ }
}
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
@@ -813,8 +833,8 @@ class RoomDetailFragment @Inject constructor(
private fun setupNotificationView() {
views.notificationAreaView.delegate = object : NotificationAreaView.Delegate {
- override fun onTombstoneEventClicked(tombstoneEvent: Event) {
- roomDetailViewModel.handle(RoomDetailAction.HandleTombstoneEvent(tombstoneEvent))
+ override fun onTombstoneEventClicked() {
+ roomDetailViewModel.handle(RoomDetailAction.JoinAndOpenReplacementRoom)
}
}
}
@@ -1004,6 +1024,8 @@ class RoomDetailFragment @Inject constructor(
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm ->
roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
+ is RoomDetailPendingAction.OpenRoom ->
+ handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
}.exhaustive
}
@@ -1365,23 +1387,6 @@ class RoomDetailFragment @Inject constructor(
}
}
- private fun renderTombstoneEventHandling(async: Async) {
- when (async) {
- is Loading -> {
- // TODO Better handling progress
- vectorBaseActivity.showWaitingView(getString(R.string.joining_room))
- }
- is Success -> {
- navigator.openRoom(vectorBaseActivity, async())
- vectorBaseActivity.finish()
- }
- is Fail -> {
- vectorBaseActivity.hideWaitingView()
- vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
- }
- }
- }
-
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) {
is RoomDetailViewEvents.SlashCommandHandled -> {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
index 598ab9d056..fccab500c5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
@@ -20,4 +20,5 @@ sealed class RoomDetailPendingAction {
data class OpenOrCreateDm(val userId: String) : RoomDetailPendingAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction()
data class MentionUser(val userId: String) : RoomDetailPendingAction()
+ data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailPendingAction()
}
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 4d1e62da7e..d62c5f6003 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
@@ -41,7 +41,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class ShowInfoOkDialog(val message: String) : RoomDetailViewEvents()
data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents()
- data class OpenRoom(val roomId: String) : RoomDetailViewEvents()
+ data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailViewEvents()
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
@@ -94,4 +94,6 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
object StopChatEffects : RoomDetailViewEvents()
+ object RoomReplacementStarted : RoomDetailViewEvents()
+ data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean): 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 74017aa5b7..a2f23f1df9 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
@@ -284,7 +284,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
- is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
+ is RoomDetailAction.JoinAndOpenReplacementRoom -> handleJoinAndOpenReplacementRoom()
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
@@ -329,6 +329,12 @@ class RoomDetailViewModel @AssistedInject constructor(
RoomDetailAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
RoomDetailAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
RoomDetailAction.EndAllVoiceActions -> handleEndAllVoiceActions()
+ is RoomDetailAction.RoomUpgradeSuccess -> {
+ setState {
+ copy(joinUpgradedRoomAsync = Success(action.replacementRoomId))
+ }
+ _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
+ }
}.exhaustive
}
@@ -582,24 +588,33 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
- private fun handleTombstoneEvent(action: RoomDetailAction.HandleTombstoneEvent) {
- val tombstoneContent = action.event.getClearContent().toModel() ?: return
+ private fun handleJoinAndOpenReplacementRoom() = withState { state ->
+ val tombstoneContent = state.tombstoneEvent?.getClearContent()?.toModel() ?: return@withState
val roomId = tombstoneContent.replacementRoomId ?: ""
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
if (isRoomJoined) {
- setState { copy(tombstoneEventHandling = Success(roomId)) }
+ setState { copy(joinUpgradedRoomAsync = Success(roomId)) }
+ _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
} else {
- val viaServers = MatrixPatterns.extractServerNameFromId(action.event.senderId)
+ val viaServers = MatrixPatterns.extractServerNameFromId(state.tombstoneEvent.senderId)
?.let { listOf(it) }
.orEmpty()
+ // need to provide feedback as joining could take some time
+ _viewEvents.post(RoomDetailViewEvents.RoomReplacementStarted)
+ setState {
+ copy(joinUpgradedRoomAsync = Loading())
+ }
viewModelScope.launch {
val result = runCatchingToAsync {
session.joinRoom(roomId, viaServers = viaServers)
roomId
}
setState {
- copy(tombstoneEventHandling = result)
+ copy(joinUpgradedRoomAsync = result)
+ }
+ if (result is Success) {
+ _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
}
}
}
@@ -862,6 +877,16 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft()
}
+ is ParsedCommand.UpgradeRoom -> {
+ _viewEvents.post(
+ RoomDetailViewEvents.ShowRoomUpgradeDialog(
+ slashCommandResult.newVersion,
+ room.roomSummary()?.isPublic ?: false
+ )
+ )
+ _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
+ popDraft()
+ }
}.exhaustive
}
is SendMode.EDIT -> {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
index 1ead34fadd..d10456b7c2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
@@ -65,7 +65,7 @@ data class RoomDetailViewState(
val typingMessage: String? = null,
val sendMode: SendMode = SendMode.REGULAR("", false),
val tombstoneEvent: Event? = null,
- val tombstoneEventHandling: Async = Uninitialized,
+ val joinUpgradedRoomAsync: Async = Uninitialized,
val syncState: SyncState = SyncState.Idle,
val highlightedEventId: String? = null,
val unreadState: UnreadState = UnreadState.Unknown,
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 54008c6da3..e1dae11c1c 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
@@ -39,13 +39,13 @@ import im.vector.app.features.home.room.detail.UnreadState
import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
+import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
-import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
@@ -166,10 +166,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
override fun onChanged(position: Int, count: Int, payload: Any?) {
synchronized(modelCache) {
assertUpdateCallbacksAllowed()
- (position until (position + count)).forEach {
+ (position until position + count).forEach {
// Invalidate cache
modelCache[it] = null
}
+ // Also invalidate the first previous displayable event if
+ // it's sent by the same user so we are sure we have up to date information.
+ val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
+ val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
+ timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
+ }
+ if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
+ modelCache[prevDisplayableEventIndex] = null
+ }
requestModelBuild()
}
}
@@ -343,10 +352,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val event = currentSnapshot[position]
val nextEvent = currentSnapshot.nextOrNull(position)
val prevEvent = currentSnapshot.prevOrNull(position)
+ val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
+ timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
+ }
val params = TimelineItemFactoryParams(
event = event,
prevEvent = prevEvent,
nextEvent = nextEvent,
+ nextDisplayableEvent = nextDisplayableEvent,
highlightedEventId = eventIdToHighlight,
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
callback = callback
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 c89753b3cc..6ab9f83c32 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
@@ -365,7 +365,7 @@ class MessageItemFactory @Inject constructor(
.leftGuideline(avatarSizeProvider.leftGuideline)
.imageContentRenderer(imageContentRenderer)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
- .playable(messageContent.info?.mimeType == MimeTypes.Gif)
+ .playable(messageContent.mimeType == MimeTypes.Gif)
.highlighted(highlight)
.mediaData(data)
.apply {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
index f92cd2800a..0e595ba30e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
@@ -23,6 +23,7 @@ data class TimelineItemFactoryParams(
val event: TimelineEvent,
val prevEvent: TimelineEvent? = null,
val nextEvent: TimelineEvent? = null,
+ val nextDisplayableEvent: TimelineEvent? = null,
val highlightedEventId: String? = null,
val lastSentEventIdWithoutReadReceipts: String? = null,
val callback: TimelineEventController.Callback? = null
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 124b196f72..221149aced 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
@@ -50,27 +50,28 @@ import javax.inject.Inject
class MessageInformationDataFactory @Inject constructor(private val session: Session,
private val roomSummariesHolder: RoomSummariesHolder,
private val dateFormatter: VectorDateFormatter,
+ private val visibilityHelper: TimelineEventVisibilityHelper,
private val vectorPreferences: VectorPreferences) {
fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event
- val nextEvent = params.nextEvent
+ val nextDisplayableEvent = params.nextDisplayableEvent
val eventId = event.eventId
val date = event.root.localDateTime()
- val nextDate = nextEvent?.root?.localDateTime()
+ val nextDate = nextDisplayableEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false
val showInformation =
addDaySeparator
- || event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl
- || event.senderInfo.disambiguatedDisplayName != nextEvent?.senderInfo?.disambiguatedDisplayName
- || nextEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
+ || event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl
+ || event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName
+ || nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo
- || isTileTypeMessage(nextEvent)
- || nextEvent.isEdition()
+ || isTileTypeMessage(nextDisplayableEvent)
+ || nextDisplayableEvent.isEdition()
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
val e2eDecoration = getE2EDecoration(event)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomAction.kt
new file mode 100644
index 0000000000..cb65be7e28
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomAction.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 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.upgrade
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class MigrateRoomAction : VectorViewModelAction {
+ data class SetAutoInvite(val autoInvite: Boolean) : MigrateRoomAction()
+ data class SetUpdateKnownParentSpace(val update: Boolean) : MigrateRoomAction()
+ object UpgradeRoom : MigrateRoomAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt
new file mode 100644
index 0000000000..6e0aabb0f2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2021 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.upgrade
+
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.fragment.app.setFragmentResult
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.databinding.BottomSheetRoomUpgradeBinding
+import kotlinx.parcelize.Parcelize
+import javax.inject.Inject
+
+class MigrateRoomBottomSheet :
+ VectorBaseBottomSheetDialogFragment(),
+ MigrateRoomViewModel.Factory {
+
+ @Parcelize
+ data class Args(
+ val roomId: String,
+ val newVersion: String
+ ) : Parcelable
+
+ @Inject
+ lateinit var viewModelFactory: MigrateRoomViewModel.Factory
+
+ override val showExpanded = true
+
+ @Inject
+ lateinit var errorFormatter: ErrorFormatter
+
+ val viewModel: MigrateRoomViewModel by fragmentViewModel()
+
+ override fun injectWith(injector: ScreenComponent) {
+ injector.inject(this)
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ views.headerText.setText(if (state.isPublic) R.string.upgrade_public_room else R.string.upgrade_private_room)
+ views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion)
+
+ views.autoInviteSwitch.isVisible = !state.isPublic && state.otherMemberCount > 0
+
+ views.autoUpdateParent.isVisible = state.knownParents.isNotEmpty()
+
+ when (state.upgradingStatus) {
+ is Loading -> {
+ views.progressBar.isVisible = true
+ views.progressBar.isIndeterminate = state.upgradingProgressIndeterminate
+ views.progressBar.progress = state.upgradingProgress
+ views.progressBar.max = state.upgradingProgressTotal
+ views.inlineError.setTextOrHide(null)
+ views.button.isVisible = false
+ }
+ is Success -> {
+ views.progressBar.isVisible = false
+ when (val result = state.upgradingStatus.invoke()) {
+ is UpgradeRoomViewModelTask.Result.Failure -> {
+ val errorText = when (result) {
+ is UpgradeRoomViewModelTask.Result.UnknownRoom -> {
+ // should not happen
+ getString(R.string.unknown_error)
+ }
+ is UpgradeRoomViewModelTask.Result.NotAllowed -> {
+ getString(R.string.upgrade_room_no_power_to_manage)
+ }
+ is UpgradeRoomViewModelTask.Result.ErrorFailure -> {
+ errorFormatter.toHumanReadable(result.throwable)
+ }
+ else -> null
+ }
+ views.inlineError.setTextOrHide(errorText)
+ views.button.isVisible = true
+ views.button.text = getString(R.string.global_retry)
+ }
+ is UpgradeRoomViewModelTask.Result.Success -> {
+ setFragmentResult(REQUEST_KEY, Bundle().apply {
+ putString(BUNDLE_KEY_REPLACEMENT_ROOM, result.replacementRoomId)
+ })
+ dismiss()
+ }
+ }
+ }
+ else -> {
+ views.button.isVisible = true
+ views.button.text = getString(R.string.upgrade)
+ }
+ }
+
+ super.invalidate()
+ }
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ BottomSheetRoomUpgradeBinding.inflate(inflater, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.button.debouncedClicks {
+ viewModel.handle(MigrateRoomAction.UpgradeRoom)
+ }
+
+ views.autoInviteSwitch.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(MigrateRoomAction.SetAutoInvite(isChecked))
+ }
+
+ views.autoUpdateParent.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(MigrateRoomAction.SetUpdateKnownParentSpace(isChecked))
+ }
+ }
+
+ override fun create(initialState: MigrateRoomViewState): MigrateRoomViewModel {
+ return viewModelFactory.create(initialState)
+ }
+
+ companion object {
+
+ const val REQUEST_KEY = "MigrateRoomBottomSheetRequest"
+ const val BUNDLE_KEY_REPLACEMENT_ROOM = "BUNDLE_KEY_REPLACEMENT_ROOM"
+
+ fun newInstance(roomId: String, newVersion: String): MigrateRoomBottomSheet {
+ return MigrateRoomBottomSheet().apply {
+ setArguments(Args(roomId, newVersion))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
new file mode 100644
index 0000000000..231bb319f0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2021 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.upgrade
+
+import com.airbnb.mvrx.ActivityViewModelContext
+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 dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.session.coroutineScope
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.session.Session
+
+class MigrateRoomViewModel @AssistedInject constructor(
+ @Assisted initialState: MigrateRoomViewState,
+ private val session: Session,
+ private val upgradeRoomViewModelTask: UpgradeRoomViewModelTask)
+ : VectorViewModel(initialState) {
+
+ init {
+ val room = session.getRoom(initialState.roomId)
+ val summary = session.getRoomSummary(initialState.roomId)
+ setState {
+ copy(
+ currentVersion = room?.getRoomVersion(),
+ isPublic = summary?.isPublic ?: false,
+ otherMemberCount = summary?.otherMemberIds?.count() ?: 0,
+ knownParents = summary?.flattenParentIds ?: emptyList()
+ )
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(initialState: MigrateRoomViewState): MigrateRoomViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ override fun create(viewModelContext: ViewModelContext, state: MigrateRoomViewState): MigrateRoomViewModel? {
+ val factory = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+ is ActivityViewModelContext -> viewModelContext.activity as? Factory
+ }
+ return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
+ }
+ }
+
+ override fun handle(action: MigrateRoomAction) {
+ when (action) {
+ is MigrateRoomAction.SetAutoInvite -> {
+ setState {
+ copy(shouldIssueInvites = action.autoInvite)
+ }
+ }
+ is MigrateRoomAction.SetUpdateKnownParentSpace -> {
+ setState {
+ copy(shouldUpdateKnownParents = action.update)
+ }
+ }
+ MigrateRoomAction.UpgradeRoom -> {
+ handleUpgradeRoom()
+ }
+ }
+ }
+
+ private fun handleUpgradeRoom() = withState { state ->
+ val summary = session.getRoomSummary(state.roomId)
+ setState {
+ copy(upgradingStatus = Loading())
+ }
+ session.coroutineScope.launch {
+ val result = upgradeRoomViewModelTask.execute(UpgradeRoomViewModelTask.Params(
+ roomId = state.roomId,
+ newVersion = state.newVersion,
+ userIdsToAutoInvite = summary?.otherMemberIds?.takeIf { state.shouldIssueInvites } ?: emptyList(),
+ parentSpaceToUpdate = summary?.flattenParentIds?.takeIf { state.shouldUpdateKnownParents } ?: emptyList(),
+ progressReporter = { indeterminate, progress, total ->
+ setState {
+ copy(
+ upgradingProgress = progress,
+ upgradingProgressTotal = total,
+ upgradingProgressIndeterminate = indeterminate
+ )
+ }
+ }
+ ))
+
+ setState {
+ copy(upgradingStatus = Success(result))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt
new file mode 100644
index 0000000000..e3936de42f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021 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.upgrade
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+
+data class MigrateRoomViewState(
+ val roomId: String,
+ val newVersion: String,
+ val currentVersion: String? = null,
+ val isPublic: Boolean = false,
+ val shouldIssueInvites: Boolean = false,
+ val shouldUpdateKnownParents: Boolean = true,
+ val otherMemberCount: Int = 0,
+ val knownParents: List = emptyList(),
+ val upgradingStatus: Async = Uninitialized,
+ val upgradingProgress: Int = 0,
+ val upgradingProgressTotal: Int = 0,
+ val upgradingProgressIndeterminate: Boolean = true
+) : MvRxState {
+ constructor(args: MigrateRoomBottomSheet.Args) : this(
+ roomId = args.roomId,
+ newVersion = args.newVersion
+ )
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
new file mode 100644
index 0000000000..32c8e6ee92
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2021 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.upgrade
+
+import im.vector.app.core.platform.ViewModelTask
+import im.vector.app.core.resources.StringProvider
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.Session
+import timber.log.Timber
+import javax.inject.Inject
+
+class UpgradeRoomViewModelTask @Inject constructor(
+ val session: Session,
+ val stringProvider: StringProvider
+) : ViewModelTask {
+
+ sealed class Result {
+ data class Success(val replacementRoomId: String) : Result()
+ abstract class Failure(val throwable: Throwable?) : Result()
+ object UnknownRoom : Failure(null)
+ object NotAllowed : Failure(null)
+ class ErrorFailure(throwable: Throwable) : Failure(throwable)
+ }
+
+ data class Params(
+ val roomId: String,
+ val newVersion: String,
+ val userIdsToAutoInvite: List = emptyList(),
+ val parentSpaceToUpdate: List = emptyList(),
+ val progressReporter: ((indeterminate: Boolean, progress: Int, total: Int) -> Unit)? = null
+ )
+
+ override suspend fun execute(params: Params): Result {
+ params.progressReporter?.invoke(true, 0, 0)
+
+ val room = session.getRoom(params.roomId)
+ ?: return Result.UnknownRoom
+ if (!room.userMayUpgradeRoom(session.myUserId)) {
+ return Result.NotAllowed
+ }
+
+ val updatedRoomId = try {
+ room.upgradeToVersion(params.newVersion)
+ } catch (failure: Throwable) {
+ return Result.ErrorFailure(failure)
+ }
+
+ val totalStep = params.userIdsToAutoInvite.size + params.parentSpaceToUpdate.size
+ var currentStep = 0
+ params.userIdsToAutoInvite.forEach {
+ params.progressReporter?.invoke(false, currentStep, totalStep)
+ tryOrNull {
+ session.getRoom(updatedRoomId)?.invite(it)
+ }
+ currentStep++
+ }
+
+ params.parentSpaceToUpdate.forEach { parentId ->
+ params.progressReporter?.invoke(false, currentStep, totalStep)
+ // we try and silently fail
+ try {
+ session.getRoom(parentId)?.asSpace()?.let { parentSpace ->
+ val currentInfo = parentSpace.getChildInfo(params.roomId)
+ if (currentInfo != null) {
+ parentSpace.addChildren(
+ roomId = updatedRoomId,
+ viaServers = currentInfo.via,
+ order = currentInfo.order,
+ autoJoin = currentInfo.autoJoin ?: false,
+ suggested = currentInfo.suggested
+ )
+
+ parentSpace.removeChildren(params.roomId)
+ }
+ }
+ } catch (failure: Throwable) {
+ Timber.d("## Migrate: Failed to update space parent. cause: ${failure.localizedMessage}")
+ } finally {
+ currentStep++
+ }
+ }
+
+ return Result.Success(updatedRoomId)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt
index 9c28faa942..5e45004579 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt
@@ -23,13 +23,12 @@ import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
-import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
@EpoxyModelClass(layout = R.layout.item_room_filter_footer)
abstract class FilteredRoomFooterItem : VectorEpoxyModel() {
@EpoxyAttribute
- var listener: FilteredRoomFooterItemListener? = null
+ var listener: Listener? = null
@EpoxyAttribute
var currentFilter: String = ""
@@ -47,7 +46,9 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel(R.id.roomFilterFooterOpenRoomDirectory)
}
- interface FilteredRoomFooterItemListener : NotifsFabMenuView.Listener {
+ interface Listener {
fun createRoom(initialName: String)
+ fun createDirectChat()
+ fun openRoomDirectory(initialFilter: String)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt
index cef8fa2d26..22cd0ae639 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt
@@ -22,6 +22,7 @@ import im.vector.app.core.epoxy.helpFooterItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
import javax.inject.Inject
@@ -30,7 +31,7 @@ class RoomListFooterController @Inject constructor(
private val userPreferencesProvider: UserPreferencesProvider
) : TypedEpoxyController() {
- var listener: RoomListListener? = null
+ var listener: FilteredRoomFooterItem.Listener? = null
override fun buildModels(data: RoomListViewState?) {
val host = this
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
index 3c08793fa4..fdfe171439 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
@@ -42,6 +42,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
@@ -70,6 +71,7 @@ class RoomListFragment @Inject constructor(
) : VectorBaseFragment(),
RoomListListener,
OnBackPressed,
+ FilteredRoomFooterItem.Listener,
NotifsFabMenuView.Listener {
private var modelBuildListener: OnModelBuildFinishedListener? = null
@@ -178,16 +180,16 @@ class RoomListFragment @Inject constructor(
private fun setupCreateRoomButton() {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true
- RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
- RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
+ RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
+ RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
}
views.createChatRoomButton.debouncedClicks {
- createDirectChat()
+ fabCreateDirectChat()
}
views.createGroupRoomButton.debouncedClicks {
- openRoomDirectory()
+ fabOpenRoomDirectory()
}
// Hide FAB when list is scrolling
@@ -221,14 +223,28 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.FilterWith(filter))
}
- override fun openRoomDirectory(initialFilter: String) {
- navigator.openRoomDirectory(requireActivity(), initialFilter)
+ // FilteredRoomFooterItem.Listener
+ override fun createRoom(initialName: String) {
+ navigator.openCreateRoom(requireActivity(), initialName)
}
override fun createDirectChat() {
navigator.openCreateDirectRoom(requireActivity())
}
+ override fun openRoomDirectory(initialFilter: String) {
+ navigator.openRoomDirectory(requireActivity(), initialFilter)
+ }
+
+ // NotifsFabMenuView.Listener
+ override fun fabCreateDirectChat() {
+ navigator.openCreateDirectRoom(requireActivity())
+ }
+
+ override fun fabOpenRoomDirectory() {
+ navigator.openRoomDirectory(requireActivity(), "")
+ }
+
private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
@@ -339,8 +355,8 @@ class RoomListFragment @Inject constructor(
if (isAdded) {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show()
- RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
- RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
+ RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
+ RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
else -> Unit
}
}
@@ -409,14 +425,14 @@ class RoomListFragment @Inject constructor(
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper),
message = getString(R.string.room_list_catchup_empty_body))
}
- RoomListDisplayMode.PEOPLE ->
+ RoomListDisplayMode.PEOPLE ->
StateView.State.Empty(
title = getString(R.string.room_list_people_empty_title),
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
isBigImage = true,
message = getString(R.string.room_list_people_empty_body)
)
- RoomListDisplayMode.ROOMS ->
+ RoomListDisplayMode.ROOMS ->
StateView.State.Empty(
title = getString(R.string.room_list_rooms_empty_title),
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),
@@ -480,8 +496,4 @@ class RoomListFragment @Inject constructor(
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
}
-
- override fun createRoom(initialName: String) {
- navigator.openCreateRoom(requireActivity(), initialName)
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt
index cf619ce435..b0bb6706dd 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt
@@ -16,11 +16,10 @@
package im.vector.app.features.home.room.list
-import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
-interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
+interface RoomListListener {
fun onRoomClicked(room: RoomSummary)
fun onRoomLongClicked(room: RoomSummary): Boolean
fun onRejectRoomInvitation(room: RoomSummary)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt b/vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt
index 51191e03f2..72b58fc7ca 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt
@@ -43,14 +43,14 @@ class NotifsFabMenuView @JvmOverloads constructor(context: Context, attrs: Attri
.forEach {
it.setOnClickListener {
closeFabMenu()
- listener?.createDirectChat()
+ listener?.fabCreateDirectChat()
}
}
listOf(views.createRoomItemGroup, views.createRoomItemGroupLabel)
.forEach {
it.setOnClickListener {
closeFabMenu()
- listener?.openRoomDirectory()
+ listener?.fabOpenRoomDirectory()
}
}
@@ -99,7 +99,7 @@ class NotifsFabMenuView @JvmOverloads constructor(context: Context, attrs: Attri
}
interface Listener {
- fun createDirectChat()
- fun openRoomDirectory(initialFilter: String = "")
+ fun fabCreateDirectChat()
+ fun fabOpenRoomDirectory()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
index 05fd825558..083b843b33 100644
--- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
@@ -70,7 +70,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor(
private fun initAdminE2eByDefault() {
viewModelScope.launch(Dispatchers.IO) {
val adminE2EByDefault = tryOrNull {
- rawService.getElementWellknown(session.myUserId)
+ rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
} ?: true
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 cf799681ac..016e4ca0c7 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
@@ -174,7 +174,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment {
views.loginServerIcon.isVisible = true
views.loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
- views.loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
+ views.loginTitle.text = getString(resId, state.homeServerUrlFromUser.toReducedUrl())
views.loginNotice.text = getString(R.string.login_server_matrix_org_text)
}
ServerType.EMS -> {
@@ -185,7 +185,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment {
views.loginServerIcon.isVisible = false
- views.loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
+ views.loginTitle.text = getString(resId, state.homeServerUrlFromUser.toReducedUrl())
views.loginNotice.text = getString(R.string.login_server_other_text)
}
ServerType.Unknown -> Unit /* Should not happen */
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
index d08d81d98a..07c27ab296 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
@@ -55,7 +55,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment {
views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
views.loginSignupSigninServerIcon.isVisible = true
- views.loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
+ views.loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl())
views.loginSignupSigninText.text = getString(R.string.login_server_matrix_org_text)
}
ServerType.EMS -> {
views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services)
views.loginSignupSigninServerIcon.isVisible = true
views.loginSignupSigninTitle.text = getString(R.string.login_connect_to_modular)
- views.loginSignupSigninText.text = state.homeServerUrl.toReducedUrl()
+ views.loginSignupSigninText.text = state.homeServerUrlFromUser.toReducedUrl()
}
ServerType.Other -> {
views.loginSignupSigninServerIcon.isVisible = false
views.loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
- views.loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
+ views.loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl())
}
ServerType.Unknown -> Unit /* Should not happen */
}
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 ce68ff4f38..2c4ad8b40d 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
@@ -40,6 +40,7 @@ import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.signout.soft.SoftLogoutActivity
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
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
@@ -51,6 +52,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import java.util.concurrent.CancellationException
@@ -213,6 +215,7 @@ class LoginViewModel @AssistedInject constructor(
copy(
signMode = SignMode.SignIn,
loginMode = LoginMode.Sso(action.ssoIdentityProviders),
+ homeServerUrlFromUser = action.homeServerUrl,
homeServerUrl = action.homeServerUrl,
deviceId = action.deviceId
)
@@ -364,6 +367,7 @@ class LoginViewModel @AssistedInject constructor(
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
+ homeServerUrlFromUser = null,
homeServerUrl = null,
loginMode = LoginMode.Unknown,
serverType = ServerType.Unknown,
@@ -560,24 +564,16 @@ class LoginViewModel @AssistedInject constructor(
return@launch
}
when (data) {
- is WellknownResult.Prompt ->
+ is WellknownResult.Prompt ->
onWellknownSuccess(action, data, homeServerConnectionConfig)
- is WellknownResult.FailPrompt ->
+ is WellknownResult.FailPrompt ->
// Relax on IS discovery if home server is valid
if (data.homeServerUrl != null && data.wellKnown != null) {
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
} else {
onWellKnownError()
}
- is WellknownResult.InvalidMatrixId -> {
- setState {
- copy(
- asyncLoginAction = Uninitialized
- )
- }
- _viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
- }
- else -> {
+ else -> {
onWellKnownError()
}
}.exhaustive
@@ -598,11 +594,12 @@ class LoginViewModel @AssistedInject constructor(
homeServerConnectionConfig: HomeServerConnectionConfig?) {
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
?.copy(
- homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
+ homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl),
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
)
?: HomeServerConnectionConfig(
- homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
+ homeServerUri = Uri.parse("https://${action.username.getDomain()}"),
+ homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl),
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
)
@@ -620,19 +617,23 @@ class LoginViewModel @AssistedInject constructor(
}
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
- )
+ when (failure) {
+ is MatrixIdFailure.InvalidMatrixId,
+ is Failure.UnrecognizedCertificateFailure -> {
+ // Display this error in a dialog
+ _viewEvents.post(LoginViewEvents.Failure(failure))
+ setState {
+ copy(
+ asyncLoginAction = Uninitialized
+ )
+ }
}
- } else {
- setState {
- copy(
- asyncLoginAction = Fail(failure)
- )
+ else -> {
+ setState {
+ copy(
+ asyncLoginAction = Fail(failure)
+ )
+ }
}
}
}
@@ -775,7 +776,7 @@ class LoginViewModel @AssistedInject constructor(
data ?: return@launch
// Valid Homeserver, add it to the history.
- // Note: we add what the user has input, data.homeServerUrl can be different
+ // Note: we add what the user has input, data.homeServerUrlBase can be different
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
val loginMode = when {
@@ -791,6 +792,7 @@ class LoginViewModel @AssistedInject constructor(
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
+ homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(),
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
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 37ac89794f..187d39d9eb 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
@@ -38,7 +38,12 @@ data class LoginViewState(
@PersistState
val resetPasswordEmail: String? = null,
@PersistState
+ val homeServerUrlFromUser: String? = null,
+
+ // Can be modified after a Wellknown request
+ @PersistState
val homeServerUrl: String? = null,
+
// For SSO session recovery
@PersistState
val deviceId: String? = null,
diff --git a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt
index 3227bb7371..6f033b0294 100755
--- a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt
@@ -112,7 +112,7 @@ class LoginTermsFragment @Inject constructor(
}
override fun updateWithState(state: LoginViewState) {
- policyController.homeServer = state.homeServerUrl.toReducedUrl()
+ policyController.homeServer = state.homeServerUrlFromUser.toReducedUrl()
renderState()
}
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt
index 2e457551dc..13d819c284 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt
@@ -41,6 +41,7 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
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
@@ -595,10 +596,6 @@ class LoginViewModel2 @AssistedInject constructor(
} else {
onWellKnownError()
}
- is WellknownResult.InvalidMatrixId -> {
- setState { copy(isLoading = false) }
- _viewEvents.post(LoginViewEvents2.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
- }
else -> {
onWellKnownError()
}
@@ -616,7 +613,7 @@ class LoginViewModel2 @AssistedInject constructor(
homeServerConnectionConfig: HomeServerConnectionConfig?) {
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
?.copy(
- homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
+ homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl),
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
)
?: HomeServerConnectionConfig(
@@ -655,7 +652,7 @@ class LoginViewModel2 @AssistedInject constructor(
}
viewEvent?.let { _viewEvents.post(it) }
- val urlFromUser = action.username.substringAfter(":")
+ val urlFromUser = action.username.getDomain()
setState {
copy(
isLoading = false,
@@ -756,7 +753,7 @@ class LoginViewModel2 @AssistedInject constructor(
} ?: return@launch
// Valid Homeserver, add it to the history.
- // Note: we add what the user has input, data.homeServerUrl can be different
+ // Note: we add what the user has input, data.homeServerUrlBase can be different
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
val loginMode = when {
diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt
index c1118e40cb..4dd5a68673 100644
--- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt
+++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt
@@ -16,11 +16,15 @@
package im.vector.app.features.raw.wellknown
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
+import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService
-suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? {
- return tryOrNull { getWellknown(userId) }
+suspend fun RawService.getElementWellknown(sessionParams: SessionParams): ElementWellKnown? {
+ // By default we use the domain of the userId to retrieve the .well-known data
+ val domain = sessionParams.userId.getDomain()
+ return tryOrNull { getWellknown(domain) }
?.let { ElementWellKnownMapper.from(it) }
}
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt
index 8c8d17dd46..ff62136267 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt
@@ -34,6 +34,7 @@ import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
@@ -62,7 +63,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
private fun initHomeServerName() {
setState {
copy(
- homeServerName = session.myUserId.substringAfter(":")
+ homeServerName = session.myUserId.getDomain()
)
}
}
@@ -72,7 +73,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
private fun initAdminE2eByDefault() {
viewModelScope.launch(Dispatchers.IO) {
adminE2EByDefault = tryOrNull {
- rawService.getElementWellknown(session.myUserId)
+ rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
} ?: true
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt
index 65d8f2d1cb..90283de77c 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryListCreator.kt
@@ -20,6 +20,7 @@ import im.vector.app.R
import im.vector.app.core.resources.StringArrayProvider
import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.RoomDirectoryServer
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import javax.inject.Inject
@@ -36,7 +37,7 @@ class RoomDirectoryListCreator @Inject constructor(
val protocols = ArrayList()
// Add user homeserver name
- val userHsName = session.myUserId.substringAfter(":")
+ val userHsName = session.myUserId.getDomain()
// Add default protocol
protocols.add(
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt
index aa2db348c8..be026894b6 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt
@@ -22,8 +22,10 @@ import im.vector.app.R
import im.vector.app.core.epoxy.expandableTextItem
import im.vector.app.core.epoxy.profiles.buildProfileAction
import im.vector.app.core.epoxy.profiles.buildProfileSection
+import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
+import im.vector.app.core.ui.list.genericPositiveButtonItem
import im.vector.app.features.home.ShortcutCreator
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
@@ -34,6 +36,7 @@ import javax.inject.Inject
class RoomProfileController @Inject constructor(
private val stringProvider: StringProvider,
+ private val colorProvider: ColorProvider,
private val vectorPreferences: VectorPreferences,
private val shortcutCreator: ShortcutCreator
) : TypedEpoxyController() {
@@ -55,6 +58,7 @@ class RoomProfileController @Inject constructor(
fun onRoomIdClicked()
fun onRoomDevToolsClicked()
fun onUrlInTopicLongClicked(url: String)
+ fun doMigrateToVersion(newVersion: String)
}
override fun buildModels(data: RoomProfileViewState?) {
@@ -87,6 +91,28 @@ class RoomProfileController @Inject constructor(
// Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
+
+ // Upgrade warning
+ val roomVersion = data.roomCreateContent()?.roomVersion
+ if (data.canUpgradeRoom
+ && !data.isTombstoned
+ && roomVersion != null
+ && data.isUsingUnstableRoomVersion
+ && data.recommendedRoomVersion != null) {
+ genericFooterItem {
+ id("version_warning")
+ text(host.stringProvider.getString(R.string.room_using_unstable_room_version, roomVersion))
+ textColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
+ centered(false)
+ }
+
+ genericPositiveButtonItem {
+ id("migrate_button")
+ text(host.stringProvider.getString(R.string.room_upgrade_to_recommended_version))
+ buttonClickAction { host.callback?.doMigrateToVersion(data.recommendedRoomVersion) }
+ }
+ }
+
val learnMoreSubtitle = if (roomSummary.isEncrypted) {
if (roomSummary.isDirect) R.string.direct_room_profile_encrypted_subtitle else R.string.room_profile_encrypted_subtitle
} else {
@@ -194,7 +220,7 @@ class RoomProfileController @Inject constructor(
editable = false,
action = { callback?.onRoomIdClicked() }
)
- data.roomCreateContent()?.roomVersion?.let {
+ roomVersion?.let {
buildProfileAction(
id = "roomVersion",
title = stringProvider.getString(R.string.room_settings_room_version_title),
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
index 9ebca6164a..14ddf896ca 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
@@ -25,6 +25,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.isVisible
+import androidx.fragment.app.setFragmentResultListener
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@@ -43,6 +44,9 @@ import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentMatrixProfileBinding
import im.vector.app.databinding.ViewStubRoomProfileHeaderBinding
import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.home.room.detail.RoomDetailPendingAction
+import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
+import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
@@ -61,6 +65,7 @@ data class RoomProfileArgs(
class RoomProfileFragment @Inject constructor(
private val roomProfileController: RoomProfileController,
private val avatarRenderer: AvatarRenderer,
+ private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
val roomProfileViewModelFactory: RoomProfileViewModel.Factory
) :
VectorBaseFragment(),
@@ -81,6 +86,16 @@ class RoomProfileFragment @Inject constructor(
override fun getMenuRes() = R.menu.vector_room_profile
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
+ bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
+ roomDetailPendingActionStore.data = RoomDetailPendingAction.OpenRoom(replacementRoomId, closeCurrentRoom = true)
+ vectorBaseActivity.finish()
+ }
+ }
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
@@ -296,6 +311,11 @@ class RoomProfileFragment @Inject constructor(
copyToClipboard(requireContext(), url, true)
}
+ override fun doMigrateToVersion(newVersion: String) {
+ MigrateRoomBottomSheet.newInstance(roomProfileArgs.roomId, newVersion)
+ .show(parentFragmentManager, "migrate")
+ }
+
private fun onShareRoomProfile(permalink: String) {
startSharePlainTextIntent(
fragment = this,
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt
index 209ebcc35b..d35e8f3ad5 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt
@@ -22,8 +22,8 @@ import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
@@ -81,8 +81,15 @@ class RoomProfileViewModel @AssistedInject constructor(
rxRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition)
.mapOptional { it.content.toModel() }
.unwrap()
- .execute {
- copy(roomCreateContent = it)
+ .execute { async ->
+ copy(
+ roomCreateContent = async,
+ // This is a shortcut, we should do the next lines elsewhere, but keep it like that for the moment.
+ recommendedRoomVersion = room.getRecommendedVersion(),
+ isUsingUnstableRoomVersion = room.isUsingUnstableRoomVersion(),
+ canUpgradeRoom = room.userMayUpgradeRoom(session.myUserId),
+ isTombstoned = room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE) != null
+ )
}
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt
index bf7cd732ef..999b6540bd 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt
@@ -30,7 +30,11 @@ data class RoomProfileViewState(
val roomCreateContent: Async = Uninitialized,
val bannedMembership: Async> = Uninitialized,
val actionPermissions: ActionPermissions = ActionPermissions(),
- val isLoading: Boolean = false
+ val isLoading: Boolean = false,
+ val isUsingUnstableRoomVersion: Boolean = false,
+ val recommendedRoomVersion: String? = null,
+ val canUpgradeRoom: Boolean = false,
+ val isTombstoned: Boolean = false
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt
index 80e1603453..e83f9f4aa2 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt
@@ -36,4 +36,7 @@ sealed class RoomAliasAction : VectorViewModelAction {
object ToggleAddLocalAliasForm : RoomAliasAction()
data class SetNewLocalAliasLocalPart(val aliasLocalPart: String) : RoomAliasAction()
object AddLocalAlias : RoomAliasAction()
+
+ // Retry to fetch data in error
+ object Retry : RoomAliasAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt
index 4a683b6292..a14bb61606 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt
@@ -57,6 +57,7 @@ class RoomAliasController @Inject constructor(
fun setNewLocalAliasLocalPart(aliasLocalPart: String)
fun addLocalAlias()
fun openAliasDetail(alias: String)
+ fun retry()
}
var callback: Callback? = null
@@ -99,8 +100,10 @@ class RoomAliasController @Inject constructor(
}
is Fail -> {
errorWithRetryItem {
+ id("rd_error")
text(host.stringProvider.getString(R.string.room_alias_publish_to_directory_error,
host.errorFormatter.toHumanReadable(data.roomDirectoryVisibility.error)))
+ listener { host.callback?.retry() }
}
}
}
@@ -119,7 +122,6 @@ class RoomAliasController @Inject constructor(
data.canonicalAlias
?.takeIf { it.isNotEmpty() }
?.let { canonicalAlias ->
-
profileActionItem {
id("canonical")
title(data.canonicalAlias)
@@ -224,6 +226,7 @@ class RoomAliasController @Inject constructor(
errorWithRetryItem {
id("alt_error")
text(host.errorFormatter.toHumanReadable(localAliases.error))
+ listener { host.callback?.retry() }
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt
index 3f429737f2..36dbf7bf8c 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt
@@ -181,6 +181,10 @@ class RoomAliasFragment @Inject constructor(
.show(childFragmentManager, "ROOM_ALIAS_ACTIONS")
}
+ override fun retry() {
+ viewModel.handle(RoomAliasAction.Retry)
+ }
+
private fun removeLocalAlias(alias: String) {
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
.setTitle(R.string.dialog_title_confirmation)
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 8832c9f7d8..aa9981997c 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
@@ -25,12 +25,13 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
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.MatrixPatterns.getDomain
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
@@ -101,7 +102,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo
private fun initHomeServerName() {
setState {
copy(
- homeServerName = session.myUserId.substringAfter(":")
+ homeServerName = session.myUserId.getDomain()
)
}
}
@@ -198,9 +199,19 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo
RoomAliasAction.AddLocalAlias -> handleAddLocalAlias()
is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action)
is RoomAliasAction.PublishAlias -> handlePublishAlias(action)
+ RoomAliasAction.Retry -> handleRetry()
}.exhaustive
}
+ private fun handleRetry() = withState { state ->
+ if (state.localAliases is Fail) {
+ fetchRoomAlias()
+ }
+ if (state.roomDirectoryVisibility is Fail) {
+ fetchRoomDirectoryVisibility()
+ }
+ }
+
private fun handleSetRoomDirectoryVisibility(action: RoomAliasAction.SetRoomDirectoryVisibility) {
postLoading(true)
viewModelScope.launch {
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 8701519d7c..922e8511c7 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
@@ -16,6 +16,7 @@
package im.vector.app.features.settings
import android.content.Context
+import android.content.SharedPreferences
import android.media.RingtoneManager
import android.net.Uri
import android.provider.MediaStore
@@ -255,6 +256,19 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private val defaultPrefs = DefaultSharedPreferences.getInstance(context)
+ /**
+ * Allow subscribing and unsubscribing to configuration changes. This is
+ * particularly useful when you need to be notified of a configuration change
+ * in a background service, e.g. for the P2P demos.
+ */
+ fun subscribeToChanges(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
+ defaultPrefs.registerOnSharedPreferenceChangeListener(listener)
+ }
+
+ fun unsubscribeToChanges(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
+ defaultPrefs.unregisterOnSharedPreferenceChangeListener(listener)
+ }
+
/**
* Clear the preferences.
*/
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt
index 0897e1c01d..0075be6e25 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt
@@ -157,7 +157,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
vectorActivity.getVectorComponent()
.rawService()
- .getElementWellknown(session.myUserId)
+ .getElementWellknown(session.sessionParams)
?.isE2EByDefault() == false
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt
index abd823fa99..87ad637ca5 100644
--- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt
@@ -23,7 +23,8 @@ import org.matrix.android.sdk.api.federation.FederationVersion
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
data class HomeServerSettingsViewState(
- val baseUrl: String = "",
+ val homeserverUrl: String = "",
+ val homeserverClientServerApiUrl: String = "",
val homeServerCapabilities: HomeServerCapabilities = HomeServerCapabilities(),
val federationVersion: Async = Uninitialized
) : MvRxState
diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt
index 3217756a82..cf623d9d9f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt
@@ -26,16 +26,20 @@ import im.vector.app.core.epoxy.errorWithRetryItem
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.genericWithValueItem
import im.vector.app.features.discovery.settingsCenteredImageItem
import im.vector.app.features.discovery.settingsInfoItem
import im.vector.app.features.discovery.settingsSectionTitleItem
+import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.federation.FederationVersion
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
import javax.inject.Inject
class HomeserverSettingsController @Inject constructor(
private val stringProvider: StringProvider,
- private val errorFormatter: ErrorFormatter
+ private val errorFormatter: ErrorFormatter,
+ private val vectorPreferences: VectorPreferences
) : TypedEpoxyController() {
var callback: Callback? = null
@@ -78,7 +82,17 @@ class HomeserverSettingsController @Inject constructor(
}
settingsInfoItem {
id("urlValue")
- helperText(state.baseUrl)
+ helperText(state.homeserverUrl)
+ }
+ if (vectorPreferences.developerMode()) {
+ settingsSectionTitleItem {
+ id("urlApiTitle")
+ titleResId(R.string.hs_client_url)
+ }
+ settingsInfoItem {
+ id("urlApiValue")
+ helperText(state.homeserverClientServerApiUrl)
+ }
}
}
@@ -118,5 +132,36 @@ class HomeserverSettingsController @Inject constructor(
helperText(host.stringProvider.getString(R.string.settings_server_upload_size_content, "${limit / 1048576L} MB"))
}
}
+
+ if (vectorPreferences.developerMode()) {
+ val roomCapabilities = data.homeServerCapabilities.roomVersions
+ if (roomCapabilities != null) {
+ settingsSectionTitleItem {
+ id("room_versions")
+ titleResId(R.string.settings_server_room_versions)
+ }
+
+ genericWithValueItem {
+ id("room_version_default")
+ title(host.stringProvider.getString(R.string.settings_server_default_room_version))
+ value(roomCapabilities.defaultRoomVersion)
+ }
+
+ roomCapabilities.supportedVersion.forEach {
+ genericWithValueItem {
+ id("room_version_${it.version}")
+ title(it.version)
+ value(
+ host.stringProvider.getString(
+ when (it.status) {
+ RoomVersionStatus.STABLE -> R.string.settings_server_room_version_stable
+ RoomVersionStatus.UNSTABLE -> R.string.settings_server_room_version_unstable
+ }
+ )
+ )
+ }
+ }
+ }
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt
index 17f03a3456..623ac37aa4 100644
--- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt
@@ -53,7 +53,8 @@ class HomeserverSettingsViewModel @AssistedInject constructor(
init {
setState {
copy(
- baseUrl = session.sessionParams.homeServerUrl,
+ homeserverUrl = session.sessionParams.homeServerUrl,
+ homeserverClientServerApiUrl = session.sessionParams.homeServerUrlBase,
homeServerCapabilities = session.getHomeServerCapabilities()
)
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
index 60110b7dd5..2537a3a592 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt
@@ -36,6 +36,7 @@ import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.AliasAvailabilityResult
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
@@ -51,7 +52,7 @@ class CreateSpaceViewModel @AssistedInject constructor(
init {
setState {
copy(
- homeServerName = session.myUserId.substringAfter(":")
+ homeServerName = session.myUserId.getDomain()
)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt
index f1731caf76..198e188723 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt
@@ -75,7 +75,7 @@ class CreateSpaceViewModelTask @Inject constructor(
val childIds = mutableListOf()
val e2eByDefault = tryOrNull {
- rawService.getElementWellknown(session.myUserId)
+ rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
} ?: true
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
index 13944adfc8..95cf5fb461 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
@@ -81,7 +81,7 @@ class SpacePeopleViewModel @AssistedInject constructor(
setState { copy(createAndInviteState = Loading()) }
viewModelScope.launch(Dispatchers.IO) {
- val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+ val adminE2EByDefault = rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml
index f9e290f29e..8943912ca4 100644
--- a/vector/src/main/res/layout/activity_bug_report.xml
+++ b/vector/src/main/res/layout/activity_bug_report.xml
@@ -39,7 +39,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/bottom_sheet_tombstone_join.xml b/vector/src/main/res/layout/bottom_sheet_tombstone_join.xml
new file mode 100644
index 0000000000..34f3c457a5
--- /dev/null
+++ b/vector/src/main/res/layout/bottom_sheet_tombstone_join.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_loading.xml b/vector/src/main/res/layout/fragment_loading.xml
index 27ae764a73..92f3bba078 100644
--- a/vector/src/main/res/layout/fragment_loading.xml
+++ b/vector/src/main/res/layout/fragment_loading.xml
@@ -17,7 +17,7 @@
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_timeline_event_create.xml b/vector/src/main/res/layout/item_timeline_event_create.xml
index ea881ccdd0..5c149a01d2 100644
--- a/vector/src/main/res/layout/item_timeline_event_create.xml
+++ b/vector/src/main/res/layout/item_timeline_event_create.xml
@@ -10,7 +10,6 @@
style="@style/Widget.Vector.TextView.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="?vctr_keys_backup_banner_accent_color"
@@ -18,7 +17,7 @@
android:gravity="center|start"
android:minHeight="80dp"
android:padding="16dp"
- app:drawableStartCompat="@drawable/error"
+ app:drawableStartCompat="@drawable/ic_warning_badge"
tools:text="This room is continuation…" />
\ No newline at end of file
diff --git a/vector/src/main/res/layout/media_upload_download_progress_layout.xml b/vector/src/main/res/layout/media_upload_download_progress_layout.xml
index d62bb65190..f9c7609dc5 100644
--- a/vector/src/main/res/layout/media_upload_download_progress_layout.xml
+++ b/vector/src/main/res/layout/media_upload_download_progress_layout.xml
@@ -17,7 +17,7 @@
-
-
-
-
+ android:orientation="horizontal"
+ android:padding="16dp">
+ tools:src="@drawable/ic_warning_badge" />
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 7523be029e..0b37b4e046 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -2859,4 +2859,9 @@
Zadejte název nového serveru, který chcete prozkoumat.Přidat nový serverVáš server
+ Přesto pokračovat
+ V současné době se lidé nemohou připojit k soukromým místnostem, které jste vytvořili.
+\n
+\nV rámci beta verze to zlepšíme, ale jen jsme vás chtěli informovat.
+ Prostory pro spolupracovníky nejsou ještě zcela připravené, ale přesto je můžete vyzkoušet
\ No newline at end of file
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index fa6d20d007..5b5fb0809d 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -511,7 +511,7 @@
VorschauAblehnen
- Zur ersten ungelesenen Nachricht springen.
+ Zur ersten ungelesenen NachrichtDu wurdest von %s in diesen Raum eingeladenDiese Einladung wurde an %s gesendet, welche nicht mit diesem Konto verknüpft ist.
@@ -650,9 +650,9 @@
Benachrichtigungen für diesen Account aktivierenBenachrichtigungen für diese Sitzung aktivierenBildschirm für 3 Sekunden aktivieren
- Nachrichten in direkten Chats
- Nachrichten in Gruppen-Chats
- Wenn ich in einen Raum eingeladen werde
+ Direktnachrichten
+ Gruppenchats
+ EinladungenAnrufeNachrichten von BotsHintergrundsynchronisierung
@@ -741,7 +741,7 @@
Wer kann den Chatverlauf lesen?Wer kann auf diesen Raum zugreifen?
- Jede*r
+ AlleNur Mitglieder (ab dem Zeitpunkt, an dem diese Option ausgewählt wurde)Nur Mitglieder (ab dem Zeitpunkt, an dem sie eingeladen wurden)Nur Mitglieder (ab dem Zeitpunkt, an dem sie beigetreten sind)
@@ -797,7 +797,7 @@
SitzungsschlüsselVerifizierungsstatusEd25519-Fingerabdruck
- Ende-zu-Ende-Verschlüsselungs-Raumschlüssel exportieren
+ Ende-zu-Ende-Raumschlüssel exportierenRaumschlüssel exportierenSchlüssel in lokale Datei exportierenExportieren
@@ -805,7 +805,7 @@
Passphrase bestätigenDie Ende-zu-Ende-Raumschlüssel wurden in \'%s\' gespeichert.
\n
-\nAchtung: Diese Datei wird vielleicht gelöscht, wenn die App deinstalliert wird.
+\nWarnung: Diese Datei wird möglicherweise gelöscht, wenn die App deinstalliert wird.Ende-zu-Ende-Raumschlüssel importierenRaumschlüssel importierenSchlüssel aus lokaler Datei importieren
@@ -877,7 +877,7 @@
Du hast keine Berechtigung, diese Aktion in diesem Raum auszuführen.Anfrage beinhaltet keine Raum-ID.Raum %s ist nicht sichtbar.
- Matrix-Apps hinzufügen
+ Integrationen hinzufügenBenachrichtigungstonAnfrage konnte nicht gesendet werden.Anfrage enthält keine user_id.
@@ -887,7 +887,7 @@
Synchronisiere…Auf Ereignisse lauschenNachrichten mit meinem Anzeigenamen
- Nachrichten, die meinen Benutzernamen enthalten
+ Nachrichten mit meinen BenutzernamenDu hast die neue Sitzung \'%s\' hinzugefügt, die jetzt Verschlüsselungs-Schlüssel anfordert.Deine bislang nicht verifiziertes Sitzung \'%s\' fordert Verschlüsselungs-Schlüssel an.Verifizierung beginnen
@@ -1028,7 +1028,7 @@
\n
\nMöchtest du welche hinzufügen\?Account deaktivieren
- Deaktiviere meinen Account
+ Meinen Account deaktivierenSende Analysedaten${app_name} sammelt anonyme Analysedaten um uns zu helfen, die App zu verbessern.Bitte aktive Analysedaten um uns zu helfen ${app_name} zu verbessern.
@@ -1150,11 +1150,11 @@
EntfernenGrundLinkvorschau im Chat aktivieren, falls dein Home-Server diese Funktion unterstützt.
- Sende Schreibbenachrichtigungen
+ Schreibbenachrichtigungen sendenLasse andere Benutzer wissen, dass du tippst.Markdown-Formatierung
- Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen, etwa Sternchen (*) um kursiven Text anzuzeigen.
- Zeige Lesebestätigungen
+ Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen (*), um kursiven Text anzuzeigen.
+ Lesebestätigungen zeigenKlicke auf die Lesebestätigungen für eine detailliertere Liste.Einladungen, Kicks und Banns bleiben unberührt.Passwort
@@ -1223,7 +1223,7 @@
Einschränkungen deaktivierenBatterieoptimierung${app_name} wird nicht von Batterieoptimierungen beeinflusst.
- Fehler bei Benachrichtigungen finden
+ Benachrichtigungsprobleme findenDiagnose von FehlernBasisdiagnose ist OK. Wenn du immer noch keine Benachrichtigungen bekommst, sende bitte einen Fehlerbericht, um uns beim Nachforschen zu helfen.Prüfung der Play-Dienste
@@ -1254,8 +1254,8 @@
Konto hinzufügenLaute Benachrichtigungen einstellenAnrufbenachrichtigung einstellen
- Lautlose Benachrichtigungen einstellen
- Wähle LED-Farbe, Vibration, Ton…
+ Stumme Benachrichtigungen einstellen
+ LED-Farbe, Vibration, Ton usw. wählenStummBitte eine Passphrase eingebenPassphrase ist zu schwach
@@ -1391,7 +1391,7 @@
Mit Single-Sign-On anmeldenDiese URL ist nicht erreichbar, bitte prüfenDein Gerät nutzt eine veraltetes TLS-Sicherheitsprotokoll, das anfällig für Angriffe ist. Zu deiner Sicherheit wirst du nicht in der Lage sein, dich zu verbinden
- Schicke Nachricht mit Eingabetaste
+ Nachricht mit Eingabetaste sendenEingabetaste der Bildschirmtastatur schickt die Nachricht ab, statt einen Zeilenumbruch zu erzeugenPasswort aktualisierenDas Passwort ist ungültig
@@ -1569,7 +1569,7 @@
Kannst du nicht finden, wonach du suchst\?Erstelle einen neuen RaumName oder ID (#beispiel:matrix.org)
- Aktiviere Wischen, um in der Zeitleiste zu antworten
+ Wischen, um in der Zeitleiste zu antwortenKein Ergebnis gefunden. Verwende \'Mit Matrix-ID hinzufügen\', um auf dem Server zu suchen.Beginne mit der Eingabe, um Ergebnisse zu erhaltenFiltern nach Benutzername oder ID…
@@ -1638,7 +1638,7 @@
IntegrationenBenutze einen Integrations-Manager um Bots, Brücken, Widgets und Sticker-Pakete zu verwalten.
\nIntegrations-Manager erhalten Konfigurationsdaten und können Widgets verändern, Raum-Einladungen senden und in deinem Namen Berechtigungslevel setzen.
- Erlaube Integrationen
+ Integrationen erlaubenWidgetWidget ladenDieses Widget wurde hinzugefügt von:
@@ -1659,7 +1659,7 @@
Mikrofon benutzenLese DRM-geschützte MedienDu wirst nicht über eingehende Nachrichten benachrichtigt, wenn die App im Hintergrund ist.
- Verwalte deine Erkennungseinstellungen.
+ Erkennungseinstellungen verwalten.Zugriff für mich zurückziehenSitzungsname:Format:
@@ -1989,7 +1989,7 @@
\nSchlüssel sind nicht vertrauenswürdigCross-Signing ist nicht aktiviertAktive Sitzungen
- Zeige alle Sitzungen
+ Alle Sitzungen anzeigenSitzungen verwaltenDiese Sitzung abmeldenKeine kryptografischen Informationen verfügbar
@@ -2101,8 +2101,8 @@
Speichere ihn auf einem USB-Stick oder auf einem SicherungslaufwerkImport der Schlüssel fehlgeschlagenBenachrichtigungskonfiguration
- Nachrichten, die @raum enthalten
- Verschlüsselte Nachrichten in Gruppenchats
+ Nachrichten mit \"@room\"
+ Verschlüsselte GruppenchatsSetze die Benachrichtigungspräferenz abhängig vom EreignistypSendet eine Nachricht als einfachen Text, ohne sie als Markdown zu interpretierenInkorrekter Benutzername und/oder Passwort. Das eingegebene Passwort beginnt oder endet mit Leerzeichen, bitte kontrolliere es.
@@ -2113,7 +2113,7 @@
Kopier es in deinen persönlichen Cloud-SpeicherVerschlüsselung ist nicht aktiviertDies kann nicht von einem mobilen Gerät erfolgen
- Wenn Räume verbessert werden
+ RaumupgradesVerschlüsselung aktiviertNachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr & verifiziere Benutzer in deren Profil.Die Verschlüsselung in diesem Raum wird nicht unterstützt
@@ -2123,7 +2123,7 @@
%s hat den Raum erstellt und konfiguriert.Fast geschafft! Zeigt das andere Gerät das gleiche Schild an\?Fast geschafft! Warte auf Bestätigung…
- Verschlüsselte Nachrichten in 1:1 Chats
+ Verschlüsselte DirektnachrichtenNachricht…Verifiziere dich & andere, um eure Chats zu schützenGib zum Fortfahren deinen %s ein
@@ -2150,7 +2150,7 @@
nutze deinen Schlüsselbackup-WiederherstellungsschlüsselWenn du dein Schlüsselbackup-Passwort nicht weißt, kannst du %s.Schlüsselbackup-Wiederherstellungsschlüssel
- Verhindere Screenshots innerhalb der Anwendung
+ Screenshots innerhalb der Anwendung verhindernDas Aktivieren dieser Einstellung setzt das FLAG_SECURE in allen Aktivitäten. Starte die Anwendung neu, damit die Änderung wirksam wird.Datei wurde der Galerie hinzugefügtDatei konnte nicht zur Galerie hinzugefügt werden
@@ -2381,7 +2381,7 @@
Aktiviere PINWenn du deine PIN zurücksetzen möchtest, tippe \"PIN vergessen\" um dich abzumelden und sie anschließend zurückzusetzen.Bestätige PIN um die PIN zu deaktivieren
- Verhindere versehentliche Anrufe
+ Versehentliche Anrufe verhindernBitte um Bestätigung, bevor du einen Anruf tätigstEinrichtenDir fehlt die Berechtigung in diesem Raum eine Konferenz zu starten
@@ -2397,7 +2397,7 @@
%1$d/%2$d Schlüssel erfolgreich importiert.%1$d/%2$d Schlüssel erfolgreich importiert.
- Verwalte Integrationen
+ Integrationen verwaltenKeine aktiven WidgetsDer Raum wurde erstellt, aber manche Einladungen wurden aus folgendem Grund nicht versendet:
\n
@@ -2456,7 +2456,7 @@
Aktiviere Gerät-spezifische Biometrie wie Fingerabdrücke und Gesichtserkennung.Biometrie aktivierenSchutz konfigurieren
- Zugriff schützen
+ ZugriffsschutzSchütze den Zugriff mit PIN und Biometrie.Zeigen das Gerät, mit dem du jetzt überprüfen kannst
@@ -2556,7 +2556,7 @@
Hey, schreibe mit mir auf ${app_name}: %sFreunde einladenLeute hinzufügen
- "Thema "
+ "Thema: "Füge ein Thema hinzu%s, um zu zeigen um was es in diesem Raum geht.Das ist der Anfang deiner Direktnachrichten mit %s.
@@ -2579,7 +2579,7 @@
Der Raum ist gerade nicht zugänglich.
\nVersuche es später nochmal, oder bitte einen Raum-Admin um Hilfe.Eine neue Adresse veröffentlichen
- Mit einer öffentlichen Adresse kann jede*r mit jedem Server deinem Raum beitreten. Um eine Adresse zu veröffentlichen, muss sie zuerst als lokale Adresse gesetzt sein.
+ Mit einer öffentlichen Adresse kann jeder den Raum betreten. Um eine Adresse zu veröffentlichen, muss sie zuerst als lokale Adresse gesetzt sein.Diesen Raum im Verzeichnis von %1$s veröffentlichen\?Die Adresse \"%1$s\" nicht mehr veröffentlichen\?Diese Adresse nicht mehr veröffentlichen
@@ -2629,7 +2629,7 @@
Knopf zum Nachrichteneditor hinzufügen, der die Emoji-Tastatur öffnetEmoji-Tastatur anzeigenNutze /confetti Kommando oder sende Nachrichten, die ❄️ oder 🎉 enthalten
- Chat-Effekte zeigen
+ ChateffekteThema ändernRaum aktualisierenRollen, die zum Ändern verschiedener Teile des Raums erforderlich sind, auswählen
@@ -2642,8 +2642,8 @@
Cross-Signing konnte nicht eingerichtet werdenNicht autorisierte, fehlende gültige AuthentifizierungsdatenNutzer
- Beim Übertragen des Anrufs ist ein Fehler aufgetreten
- Übertragen
+ Beim Weiterleiten des Anrufs ist ein Fehler aufgetreten
+ WeiterleitenVerbinden1 aktiver Anruf (%1$s) · 1 pausierter Anruf
@@ -2735,7 +2735,7 @@
Senden der Nachricht gescheitertWird gesendetNachricht gesendet
- Zuerst nachfragen
+ Zuerst anfragenPrivatÖffentlichDu kannst dies später ändern
@@ -2755,9 +2755,9 @@
Diese werden kein Teil von %s seinTritt meinem Space %1$s %2$s beiWillkommen zu %1$s, %2$s.
- Warnung benötigt Server Unterstützung und eine experimentelle Raumversion
+ Warnung: benötigt Server-Unterstützung und eine experimentelle RaumversionExperimenteller Space - Zugangsbeschränkter Raum.
- Mit Spaces hast du die Möglichkeit Personen und Räume zu gruppieren.
+ Mit Spaces kannst du Personen und Räume zu gruppieren.Sag hallo zu Spaces!Füge bereits existierende Räume und Spaces hinzuVorübergehend überspringen
@@ -2791,7 +2791,7 @@
Meine Teamkameraden und ichEin privater Space um deine Räume zu organisierenUm einem bereits existierenden Space beizutreten, benötigst du eine Einladung.
- Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt.
+ Wir haben Spaces entwickelt, damit ihr eure Räume besser organisieren könntDein privater SpaceDein öffentlicher SpaceBetrete einen Space mit der angegebenen ID
@@ -2866,4 +2866,11 @@
Spaces FeedbackDieser Server ist schon in der Liste vorhandenServer oder Raumliste kann nicht gefunden werden
+ Momentan kann es sein, dass einige Leute deinen privaten Räumen nicht beitreten können.
+\n
+\nDies werden wir demnächst als Teil der Beta verbessern, wir wollten aber sicherstellen, dass du bescheid weißt.
+ Team-Spaces sind noch nicht fertig entwickelt, du kannst sie aber schon testen
+ Trotzdem fortfahren
+ Bei %1$s anfragen
+ Zu %1$s weiterleiten
\ No newline at end of file
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index 88081c59c5..49761f6d88 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -2806,4 +2806,9 @@
Sisesta serveri nimi, mille sisu sa soovid uurida.Lisa uus serverSinu server
+ Kaasteeliste kogukonnakeskused pole veel päris valmis, aga sa võid neid juba proovida
+ Jätka ikkagi
+ Hetkel teiste kasutajate liitumine sinu poolt tehtud privaatsete jututubadega ei pruugi õnnestuda.
+\n
+\nKuna tegemist on beetaversiooniga, siis me veel parandame seda funktsionaalsust, aga lihtsalt tahtsime sind teavitada.
\ No newline at end of file
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index 2d5cad33f3..c33afe70d1 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -2813,4 +2813,9 @@
Ajouter un nouveau serveurVotre serveurConsultation de %1$s
+ Pour l’instant, vos coéquipiers pourraient ne pas pouvoir rejoindre les salons privés que vous créez.
+\n
+\nNous allons améliorer ceci dans la bêta, mais nous voulions vous en faire part.
+ Les espaces pour les équipes ne sont pas tout à fait prêts, mais vous pouvez quand même les essayer
+ Poursuivre malgré tout
\ No newline at end of file
diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml
index 12a1df9012..5bdcc26d1f 100644
--- a/vector/src/main/res/values-fy/strings.xml
+++ b/vector/src/main/res/values-fy/strings.xml
@@ -630,4 +630,39 @@
IepenbierEltsenien kin by disse keamer oankopje, leden kinne don akseptearje as ôfslaanEltsenien dyt in link nei disse keamer hat, sels gasten
+ Do hast gjin brûkers negearre
+ Negearre brûkers
+ Stim & Fideo
+ Avansearre ynstellingen
+ Oanpaste en avansearre ynstellingen
+ Akkount Tafoegje
+ Oanpaste Ynstellingen.
+ Ynskeakelje
+ Ynskeakelje
+ Notifikaasjes binne ynskeakele foar dyn akkount.
+ Akkount Ynstellingen.
+ Ynstellingen Iepenje
+ Notifikaasjes binne ynskeakele yn de systeem ynstellingen.
+ Systeem Ynstellingen.
+ Tests Útfiere
+ Telefoannûmers
+ E-mailadressen
+ Wachtwurd befêstigje
+ Applikaasje informaasje yn de systeem ynstellingen sjen litte.
+ Applikaasje informaasje
+ Telefoannûmer tafoegje
+ Der is gjin telefoannûmer tafoege oan syn akkount
+ E-mailadres tafoegje
+ Email
+ Ynstellingen
+ Petear Ferlitte
+ Direkt Petear
+ Alle berjochten
+ Alle berjochten (lûd)
+ Ynstellingen
+ Ynstellingen feroarje
+ Keamer rjochten
+ Do hast de tsjinner ACLs foar dizze keamer feroare.
+ %s hat de tsjinner ACLs foar dizze keamer feroare.
+ Do hast dyn profyl ôfbylding feroare
\ No newline at end of file
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 797ea4f036..ba1e3a9e8e 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -2811,4 +2811,9 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
Add meg a felfedezni kívánt új szerver nevét.Új szerver hozzáadásaMatrix szervered
+ Az emberek jelen pillanatban nem fognak tudni csatlakozni egyetlen olyan privát szobához sem amit készítettél.
+\n
+\nEzt folyamatosan fejlesztjük a béta program keretében, csak szerettünk volna tájékoztatni róla.
+ A csoporttárs terek még nem igazán vannak készen de már tehetsz velük egy próbát
+ Mindenképpen folytatás
\ No newline at end of file
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index 2bffefc310..1792d09cae 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -1430,10 +1430,7 @@
\nNome sessione: %1$s
\nUltimo accesso: %2$s
\nSe non hai effettuato l\'accesso da un\'altra sessione, ignora questa richiesta.
- Una sessione non verificata sta chiedendo le chiavi crittografiche.
-\nNome sessione: %1$s
-\nUltimo accesso: %2$s
-\nSe non hai effettuato l\'accesso da un\'altra sessione, ignora questa richiesta.
+ Una sessione non verificata sta chiedendo le chiavi crittografiche.\nNome sessione: %1$s\nUltimo accesso: %2$s\nSe non hai effettuato l\'accesso da un\'altra sessione, ignora questa richiesta.VerificaCondividiRichiesta condivisione chiavi crittografiche
@@ -1897,12 +1894,7 @@
Per avere la massima certezza effettuate l\'operazione tramite un mezzo di comunicazione già fidato oppure fatelo di persona.Il badge verde garantisce che l\'identità dell\'utente è verificata. Verifica tutti gli utenti in una stanza per assicurarti che sia sicura.Non sicuro
- Uno dei seguenti potrebbe essere compromesso:
-\n
-\n - Il tuo Home Server
-\n - L\'Home Server al quale è connesso l\'utente che stai verificando
-\n - La tua connessione internet o quella dell\'altro utente
-\n - Il tuo dispositivo o quello dell\'altro utente
+ Uno dei seguenti potrebbe essere compromesso:\n\n - Il tuo Home Server\n - L\'Home Server al quale è connesso l\'utente che stai verificando\n - La tua connessione internet o quella dell\'altro utente\n - Il tuo dispositivo o quello dell\'altro utenteVideo.Immagine.Audio
@@ -2868,4 +2860,9 @@
Inserisci il nome di un nuovo server che vuoi esplorare.Aggiungi un nuovo serverIl tuo server
+ Al momento le persone potrebbero non poter entrare nelle stanze private che crei.
+\n
+\nMiglioreremo questa cosa come parte della beta, ma volevamo almeno fartelo sapere.
+ Gli spazi dei compagni non sono ancora pronti del tutto, ma puoi comunque provarli
+ Continua comunque
\ No newline at end of file
diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml
index 827c7ceaae..bf220237da 100644
--- a/vector/src/main/res/values-nb-rNO/strings.xml
+++ b/vector/src/main/res/values-nb-rNO/strings.xml
@@ -140,7 +140,7 @@
IgnorerFolkFiler
- Instillinger
+ InnstillingerBLE MEDAvbryt opplastningAvbryt nedlasting
@@ -318,7 +318,7 @@
LydGalleriDemp
- Instillinger
+ InnstillingerLær merAnnetFortsett
@@ -349,7 +349,7 @@
Logg påLogg påPassord
- Instillinger
+ InnstillingerGjeldende øktFøyer til ¯\\_(ツ)_/¯ på en råtekstmeldingVideo.
@@ -897,13 +897,13 @@
Denne hjemmetjeneren vil vite om du er en robotRegistrering med e-post og telefonummer samtidig fungerer ikke enda. Bare telefonummeret kommer til å bli registrert.
\n
-\nDu kan legge e-postadressen din til i instillinger.
- Bruk egendefinerte tjenerinstillinger (avansert)
+\nDu kan legge e-postadressen din til i innstillinger.
+ Bruk egendefinerte tjenerinnstillinger (avansert)Klarte ikke å starte en sanntidskopling.
\nVennligst be hjemmetjeneradministratoren din om å sette opp en TURN server så samtaler blir mer stabile.Vennligst be administratoren for hjemmetjeneren din (%1$s) til å sette opp en TURN tjener for at telefonsamtaler skal fungere ordentlig.
\n
-\nAlternativt kan du prøve å bruke den offentlige tjeneren på %2$s, men dette vil ikke være like stabilt, og det vil dele IP-adressen din med den serveren. Du kan styre dette i instillinger.
+\nAlternativt kan du prøve å bruke den offentlige tjeneren på %2$s, men dette vil ikke være like stabilt, og det vil dele IP-adressen din med den serveren. Du kan styre dette i innstillinger.E-postkoblingen som ikke er klikket på ennåDette brukernavnet er allerede bruktInneholdt ikke gyldig JSON
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index b2c1aff6ec..a76c94cc05 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -1477,4 +1477,66 @@
%1$s heeft de kamer aangemaaktJouw uitnodigingJe hebt een afbeelding gestuurd.
+ U heeft %1$s verbannen. Reden: %2$s
+ U heeft de verbanning van %1$s opgeheven. Reden: %2$s
+ U heeft %1$s eruit getrapt. Reden: %2$s
+ U heeft de uitnodiging geweigerd. Reden: %1$s
+ U bent vertrokken. Reden: %1$s
+ %1$s is vertrokken. Reden: %2$s
+ U heeft de ruimte verlaten. Reden: %1$s
+ U heeft zich aangesloten. Reden: %1$s
+ %1$s heeft zich aangesloten. Reden: %2$s
+ U heeft zich aangesloten bij de kamer. Reden: %1$s
+
+ %1$s, %2$s, %3$s en %4$d andere
+ %1$s, %2$s, %3$s en %4$d anderen
+
+ %1$s, %2$s, %3$s en %4$s
+ %1$s, %2$s en %3$s
+ %1$s van %2$s naar %3$s
+ %1$s heeft het machtigingsniveau van %2$s aangepast.
+ U heeft het machtigingsniveau van %1$s aangepast.
+ Speciaal
+ Speciaal (%1$d)
+ Standaardlid
+ Beheerder
+ U heeft de videoconferentie aangepast
+ Videoconferentie aangepast door %1$s
+ U heeft de videoconferentie beëindigd
+ Videoconferentie beëindigd door %1$s
+ U heeft een videoconferentie gestart
+ Videoconferentie gestart door %1$s
+ U heeft de widget %1$s aangepast
+ %1$s heeft de widget %2$s aangepast
+ U heeft de widget %1$s verwijderd
+ %1$s heeft de widget %2$s verwijderd
+ U heeft de widget %1$s toegevoegd
+ %1$s heeft de widget %2$s toegevoegd
+ U heeft de uitnodiging voor %1$s geaccepteerd
+ U heeft de uitnodiging voor %1$s ingetrokken
+ %1$s heeft de uitnodiging voor %2$s ingetrokken
+ U heeft de uitnodiging voor %1$s ingetrokken om zich bij de kamer aan te sluiten
+ U heeft een uitnodiging gestuurd naar %1$s om zich bij de kamer aan te sluiten
+ U heeft de kameravatar verwijderd
+ %1$s heeft de kameravatar verwijderd
+ U heeft het kameronderwerp verwijderd
+ U heeft de kamernaam verwijderd
+ U heeft de ruimte geüpgradet.
+ U verstuurde data om het gesprek op te zetten.
+ %s verstuurde data om het gesprek op te zetten.
+ U heeft een audiogesprek geopend.
+ U heeft een videogesprek geopend.
+ U heeft de ruimtenaam veranderd naar: %1$s
+ U heeft de ruimteavatar aangepast
+ %1$s heeft de ruimteavatar aangepast
+ U heeft het onderwerp gewijzigd naar: %1$s
+ U heeft uw weergavenaam verwijderd (voorheen %1$s)
+ U heeft de uitnodiging van %1$s ingetrokken
+ U heeft %1$s verbannen
+ U heeft de verbanning van %1$s opgeheven
+ U heeft %1$s eruit getrapt
+ U sloot zich aan
+ %1$s sluit aan
+ Je hebt de kamer betreden
+ Je hebt een sticker verzonden.
\ No newline at end of file
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index db7f940e38..f1aa5b6fb9 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -313,8 +313,8 @@
ConvidarFazer signout
- Chamada de Voz
- Chamada de Vídeo
+ Chamar por Voz
+ Chamar por VídeoPesquisa globalMarcar tudo como lidoHistórico
@@ -359,7 +359,7 @@
Enviar crash logsEnviar screenshotReportar bug
- Por favor descreva o bug. O que você fez\? O que você esperava que acontecese\? O que aconteceu na verdade\?
+ Por favor descreva o bug. O que você fez\? O que você esperava que acontecesse\? O que na verdade aconteceu\?Descreva seu problema aquiA fim de diagnosticar problemas, logs deste cliente vão ser enviados com este reporte de bug. Este reporte de bug, incluindo os logs e o screenshot, não será visível publicamente. Se você prefere somente enviar o texto acima, por favor desmarque:Você parece estar agitando o telefone em frustração. Você gostaria de abrir a tela de reporte de bug\?
@@ -429,7 +429,7 @@
Falha para verificar endereço de email: assegure-se que clicou no link no emailSua senha tem sido resettada.
\n
-\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para reativar notificações, faça re-login em cada dispositivo.
+\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para re-ativar notificações, re-faça login em cada dispositivo.URL deve começar com http[s]://Incapaz de fazer login: Erro de rede
@@ -640,7 +640,7 @@
VersãoTermos & condiçõesNotas de terceiros
- Direito de autor
+ CopyrightPolítica de privacidadeImagem de Perfil
@@ -667,7 +667,7 @@
versão de olmTermos & condiçõesNotas de terceiros
- Direitos de autor
+ CopyrightPolítica de privacidadeLimpar cache
@@ -894,7 +894,7 @@
Lista de GruposChamarBanir usuária(o) vai expulsá-la(o) desta sala e preveni-la(o) de se juntar de novo.
- Todas as mensagens (barulhentas)
+ Todas as mensagens (barulhento)Todas as mensagensMenções somenteMudo
@@ -1015,7 +1015,7 @@
Avatar de reciboAvatar de notaAvatar
- Agite com raiva para reportar bug
+ Agitar com raiva para reportar bugNormalPrivacidade reduzidaO app precisa de permissão para rodar no background
@@ -1077,7 +1077,7 @@
Privacidade de Notificação${app_name} pode rodar no background para gerenciar suas notificações seguramente e privadamente. Isto pode afetar uso de bateria.Conceder permissão
- Escolha um outra opção
+ Escolher uma outra opçãoEnviar dados de analítica${app_name} coleta analítica anônima para nos permitir melhorar o aplicativo.Por favor ative analítica para nos ajudar a melhorar ${app_name}.
@@ -1203,7 +1203,7 @@
Enviar notificações de digitaçãoDeixar outras(os) usuárias(os) saberem que você está digitando.Mostrar recibos de leitura
- Clicar nos recibos de leitura para uma lista detalhada.
+ Clique nos recibos de leitura para uma lista detalhada.Mostrar eventos de juntar-se e sairConvites, expulsões e bans são desafetados.Mostrar eventos de conta
@@ -1358,7 +1358,7 @@
Desbanir usuária(o)Desbanir usuária(o) vai permitir-lhe se juntar à sala de novo.Confirme sua senha
- Você não pode fazer isto de ${app_name} celular
+ Você não pode fazer isto desde ${app_name} mobileAutenticação é requeridaO app não precisa de se conectar ao ServidorCasa no background, isto deveria reduzir uso de bateriaModo Sinc no Background
@@ -1387,7 +1387,7 @@
Gere uma nova Chave de Segurança ou defina uma nova Frase de Segurança para seu backup existente.Isto vai substituir sua Chave ou Frase atual.Descoberta
- Gerencie suas configurações de descoberta.
+ Gerenciar suas configurações de descoberta.Modo de economia de dados aplica um filtro específico para que atualizações de presença e notificações de digitação sejam filtradas fora.Permitir integraçõesGerenciador de Integração
@@ -1600,9 +1600,9 @@
Você recebeu uma requisição de verificação entrante.Visualizar requisiçãoEsperando por parceira(o) confirmar…
- Verificada!
+ Verificada(o)!Você tem confirmado esta sessão com sucesso.
- Mensagens seguras com esta(e) usuária(o) estão encriptadas ponta-a-ponta e não são capazes de ser lidas por terceiros.
+ Mensagens seguras com esta(e) usuária(o) são encriptadas ponta-a-ponta e não são capazes de ser lidas por terceiros.EntendidoNada aparecendo\? Não todos os clientes suportam verificação interativa ainda. Use verificação legado.Usar verificação legado.
@@ -1637,10 +1637,10 @@
Convidada(o) por %sVocê está em dia!Você não tem mais nenhuma mensagem não-lida
- Boas-vindas!
+ Boas-vindas a casa!Fique em dia com suas mensagens não-lidas aquiConversas
- Suas conversas de mensagem direta vai ser exibidas aqui. Toque no + à direita fundo para começar algumas.
+ Suas conversas de mensagem direta vão ser exibidas aqui. Toque no + à direita fundo para começar algumas.SalasSuas salas vão ser exibidas aqui. Toque no + à direita fundo para encontrar umas existentes ou começar algumas propriamente suas.Reações
@@ -1669,7 +1669,7 @@
Nova SalaCRIARNome
- Público
+ PúblicaQualquer pessoa vai ser capaz de se juntar a esta salaDiretório de SalasPublicar esta sala no diretório de salas
@@ -1816,7 +1816,7 @@
\nPor favor permita acesso no próximo pop-up para ser capaz de exportar suas chaves manualmente.Não há nenhuma conexão de rede no momentoIgnorar usuária(o)
- Todas as mensagens (barulhentas)
+ Todas as mensagens (barulhento)Todas as mensagensMenções somenteMutar
@@ -1840,13 +1840,13 @@
Faça chat com pessoas diretamente ou em gruposMantenha conversas privadas com encriptaçãoExtenda & personalize sua experiência
- Comece agora
+ Começar agoraSelecione um servidorAssim como email, contas têm uma casa, embora você pode falar com qualquer pessoaJunte-se a milhões de graça no maior servidor públicoHospedagem premium para organizaçõesSaiba mais
- Outros
+ OutroConfigurações personalizadas & avançadasContinuarConectar-se a %1$s
@@ -1943,7 +1943,7 @@
Este não é um identificador de usuária(o) válido. Formato esperado: \'@usuarix:servidorcasa.org\'Incapaz de encontrar um servidorcasa válido. Por favor cheque seu identificadorVista por
- Você fez signout
+ Você está com signout feitoPode ser devido a várias razões:
\n
\n• Você tem mudado sua senha numa outra sessão.
@@ -1952,7 +1952,7 @@
\n
\n• O/a administrador(a) de seu servidor tem invalidado seu acesso por razão de segurança.Fazer signin de novo
- Você fez signout
+ Você está com signout feitoFazer signinA/o admin de seu servidorcasa (%1$s) fez seu signout de sua conta %2$s (%3$s).Faça signin para recuperar chaves de encriptação armazenadas exclusivamente neste dispositivo. Você precisa delas para ler todas suas mensagens seguras em qualquer dispositivo.
@@ -1995,7 +1995,7 @@
Eles correspondemEles não correspondemVerifique esta(e) usuária(o) ao confirmar que os seguintes emoji únicos aparecem na tela dela(e), na mesma ordem.
- Para máxima segurança, use um outro meio de comunicação confiado ou faça isto em pessoa.
+ Para segurança ótima, use um outro meio de comunicação confiado ou faça isto em pessoa.Procure pelo escudo verde para assegurar que um/uma usuário(a) é confiado. Confie em todos(as) os/as usuários(as) numa sala para assegurar que a sala é segura.Não seguroUm dos seguintes pode estar comprometido:
@@ -2010,7 +2010,7 @@
ArquivoStickerEsperando…
- %s cancelado
+ %s cancelouVocê cancelou%s aceitouVocê aceitou
@@ -2036,7 +2036,7 @@
Mensagens nesta sala não são encriptadas ponta-a-ponta.Mensagens nesta sala são encriptadas ponta-a-ponta.
\n
-\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipente têm as chaves únicas para os destrancar.
+\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipente têm as chaves únicas para as destrancar.SegurançaSaiba maisMais
@@ -2109,7 +2109,7 @@
Completar SegurançaUse uma sessão existente para verificar esta aqui, garantindo-lhe acesso a mensagens encriptadas.Verificar
- Verificada
+ Verificada(o)AvisoFalha para obter sessõesSessões
@@ -2207,7 +2207,7 @@
Configurando Backup de ChaveSuas %2$s & %1$s estão agora definidas.
\n
-\nMantenha-as seguras! Você vai precisar delas para destrancar mensagens encriptadas e informação segura se você perder todas as suas sessões ativas.
+\nMantenha-as seguras! Você vai precisar delas para destrancar mensagens encriptadas e informação segura se você perder todas suas sessões ativas.Imprima-a e armazene-a em algum lugar seguroSalve-a em uma chave USB ou drive de backupCopie-a para seu armazenamento nuvem pessoal
@@ -2361,7 +2361,7 @@
Você não pode acessar esta mensagem porque o/a enviador(a) propositalmente não enviou as chavesEsperando por histórico de encriptaçãoRiot agora é Element!
- Nós estamos animados em anunciar que nós mudamos de nome! Seu app está atualizado e o signin está feito a sua conta.
+ Nós estamos animados em anunciar que nós temos mudado de nome! Seu app está atualizado e você está com signin feito a sua conta.ENTENDISABER MAISSalvar chave de recuperação em
@@ -2475,7 +2475,7 @@
ConfiguraçõesMensagens aqui são encriptadas ponta-a-ponta.
\n
-\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipiente têm as chaves únicas para os destrancar.
+\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipiente têm as chaves únicas para as destrancar.Mensagens aqui não são encriptadas ponta-a-ponta.Este servidorcasa está rodando uma versão antiga. Peça à/ao admin de seu servidorcasa para fazer upgrade. Você pode continuar, mas algumas funcionalidades podem não funcionar corretamente.Mostrar histórico completo em salas encriptadas
@@ -2510,7 +2510,7 @@
Rotar e recortarConfigurações de salaTópico
- Tópico da sala (opcional)
+ Tópico de sala (opcional)Nome de salaEnviar histórico de requisições de compartilhamento de chavesMais nenhum resultado
@@ -2519,7 +2519,7 @@
Mostrar avançadasEsconder avançadasLink Matrix
- %s para deixar pessoas sabendo do que esta sala se trata.
+ %s para deixar pessoas saberem do que esta sala se trata.Por favor proveja um endereço de salaRecenteQR code não scannado!
@@ -2876,4 +2876,9 @@
Enviar vídeo com o tamanho originalEnviar vídeos com o tamanho original
+ No momento pessoas podem não ser capaz de se juntar a quaisquer salas privadas que você fizer.
+\n
+\nNós vamos melhorar isto como parte da beta, mas só queríamos deixar você saber.
+ Espaços de colegas de trabalho não estão bem prontos mas você ainda pode dar-lhes uma tentativa
+ Continuar Mesmo Assim
\ No newline at end of file
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 73512f103c..167d4a6354 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -2796,4 +2796,9 @@
Jepni emrin e e një shërbyesi të ri që doni të eksploroni.Shtoni shërbyes të riShërbyesi juaj
+ Hëpërhë, personat mund të mos jenë në gjendje të hyjnë në çfarëdo dhome private që krijoni.
+\n
+\nDo ta përmirësojmë këtë punë, si pjesë e versionit beta, thjesht donim t’ua bënim të ditur.
+ Hapësirat për anëtarë ekipi ende s’janë tërësisht gati, por mund t’i provoni
+ Vazhdo, Sido Qoftë
\ No newline at end of file
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index ce65fd9a5a..d9b58bb605 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2806,4 +2806,9 @@
Ange namnet för en ny server du vill utforska.Lägg till en ny serverDin server
+ För tillfället så kan folk kanske inte gå med i privata rum som du skapar.
+\n
+\nVi kommer att förbättra detta som en del av betan, men ville låta dig veta.
+ Lagkamratsutrymmen är inte riktigt färdiga men du kan ändå testa dem
+ Fortsätt ändå
\ No newline at end of file
diff --git a/vector/src/main/res/values-ta/strings.xml b/vector/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000000..a6b3daec93
--- /dev/null
+++ b/vector/src/main/res/values-ta/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index d3cc148943..190ea00a1e 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2763,4 +2763,9 @@
输入你想要探索的新服务器的名称。添加一个新的服务器你的服务器
+ 眼下,人们可能无法加入您设置的任何私人房间。
+\n
+\n作为测试版的一部分,我们将对此进行改进,只是想让你知道。
+ 队友空间还没有完全准备好,但你仍然可以尝试一下
+ 不论如何继续
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 1c67fdbbcd..e6577580b5 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2753,4 +2753,9 @@
輸入您想要探索的新伺服器名稱。加入新的伺服器您的伺服器
+ 目前,人們可能無法加入您開啟的任何私人聊天室。
+\n
+\n作為測試版的一部分,我們會對此進行改善,但想先讓您知道。
+ 隊友空間還沒有完全準備好,但您仍可以試試看
+ 無論如何都要繼續
\ No newline at end of file
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 551a89ca16..266c8aac01 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -538,6 +538,7 @@
Log inSign outHomeserver URL
+ Homeserver API URLIdentity Server URLSearch
@@ -926,7 +927,7 @@
Resend unsent messagesDelete unsent messagesFile not found
- You do not have permission to post to this room
+ You do not have permission to post to this room.%d new message%d new messages
@@ -1820,7 +1821,7 @@
Please enter a username.Please enter your password.
- This room has been replaced and is no longer active
+ This room has been replaced and is no longer active.The conversation continues hereThis room is a continuation of another conversationClick here to see older messages
@@ -2631,9 +2632,12 @@
YouScan the code with the other user\'s device to securely verify each other
+ Scan the code with your other device or switch and scan with this deviceScan their code
+ Scan with this deviceCan\'t scanIf you\'re not in person, compare emoji instead
+ Verify by comparing emoji insteadVerify by comparing emojis
@@ -2736,6 +2740,11 @@
Server file upload limitYour homeserver accepts attachments (files, media, etc.) with a size up to %s.The limit is unknown.
+
+ Room Versions 👓
+ Default Version
+ stable
+ unstableNo cryptographic information available
@@ -3296,6 +3305,7 @@
Create a SpaceJoin the Space with the given idLeave room with given id (or current room if null)
+ Upgrades a room to a new versionSendingSent
@@ -3407,6 +3417,22 @@
"Teammate spaces aren’t quite ready but you can still give them a try""At the moment people might not be able to join any private rooms you make.\n\nWe’ll be improving this as part of the beta, but just wanted to let you know."
+
+ Join replacement room
+ Please be patient, it may take some time.
+
+ Upgrade
+ Upgrade public room
+ Upgrade private room
+ Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.\nThis usually only affects how the room is processed on the server.
+ You\'ll upgrade this room from %s to %s.
+ Automatically invite users
+ Automatically update space parent
+ You need permission to upgrade a room
+
+ This room is running room version %s, which this homeserver has marked as unstable.
+ Upgrade to the recommended room version
+
Sorry, an error occurred while trying to join: %sStart Voice Message