diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml
new file mode 100644
index 0000000000..4a786a9339
--- /dev/null
+++ b/.github/workflows/update-gradle-wrapper.yml
@@ -0,0 +1,18 @@
+name: Update Gradle Wrapper
+
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+jobs:
+ update-gradle-wrapper:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Update Gradle Wrapper
+ uses: gradle-update/update-gradle-wrapper-action@v1
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ target-branch: develop
diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index 5c27da044e..d13e40248f 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -29,6 +29,7 @@
signout
signup
ssss
+ sygnal
threepid
unwedging
diff --git a/CHANGES.md b/CHANGES.md
index 6837fa8313..99058117b6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,62 @@
+Changes in Element 1.0.9 (2020-10-16)
+===================================================
+
+Features ✨:
+ - Search messages in a room - phase 1 (#2110)
+ - Hide encrypted history (before user is invited). Can be shown if wanted in developer settings
+ - Changed rainbow algorithm
+
+Improvements 🙌:
+ - Wording differentiation for direct rooms (#2176)
+ - PIN code: request PIN code if phone has been locked
+ - Small optimisation of scrolling experience in timeline (#2114)
+ - Allow user to reset cross signing if he has no way to recover (#2052)
+ - Ability to share text
+ - Create home shortcut for any room (#1525)
+ - Can't confirm email due to killing by Android (#2021)
+ - Add a menu item to open the setting in room list and in room (#2171)
+ - Add a menu item in the timeline as a shortcut to invite user (#2171)
+ - Drawer: move settings access and add sign out action (#2171)
+ - Filter room member (and banned users) by name (#2184)
+ - Implement "Jump to read receipt" and "Mention" actions on the room member profile screen
+ - Direct share (#2029)
+ - Add FAB to room members list (#2226)
+ - Add Sygnal API implementation to test is Push are correctly received
+ - Add PushGateway API implementation to test if Push are correctly received
+ - Cross signing: shouldn't offer to verify with other session when there is not. (#2227)
+
+Bugfix 🐛:
+ - Improve support for image/audio/video/file selection with intent changes (#1376)
+ - Fix Splash layout on small screens
+ - Invalid popup when pressing back (#1635)
+ - Simplifies draft management and should fix bunch of draft issues (#952, #683)
+ - Very long topic cannot be fully visible (#1957)
+ - Properly detect cross signing keys reset
+ - Don't set presence when handling a push notification or polling (#2156)
+ - Be robust against `StrandHogg` task injection
+ - Clear alerts if user sign out
+ - Fix rows are hidden in Textinput (#2234)
+ - Uploading a file to a room caused it to have a info.size of -1 (#2141)
+
+Translations 🗣:
+ - Move store data to `/fastlane/metadata/android` (#812)
+ - Weblate is now hosted at https://translate.element.io
+
+SDK API changes ⚠️:
+ - Search messages in a room by using Session.searchService() or Room.search()
+
+Build 🧱:
+ - Use Update Gradle Wrapper Action
+ - Updates Gradle Wrapper from 5.6.4 to 6.6.1. (#2193)
+ - Upgrade kotlin version from `1.3.72` to `1.4.10` and kotlin coroutines version from `1.3.8` to `1.3.9`
+ - Upgrade build tools from `3.5.3` to `4.0.1`, then to `4.1.0`
+ - Upgrade com.google.gms:google-services from `4.3.2` to `4.3.4`
+ - Upgrade Moshi to `1.11.0`, Dagger to `2.29.1`, Epoxy to `4.1.0`
+
+Other changes:
+ - Added registration/verification automated UI tests
+ - Create a script to help getting public information form any homeserver
+
Changes in Element 1.0.8 (2020-09-25)
===================================================
@@ -83,7 +142,7 @@ Bugfix 🐛:
- Replies to poll appears in timeline as unsupported events during sending (#1004)
Translations 🗣:
- - The SDK is now using SAS string translations from [Weblate Matrix-doc project](https://translate.riot.im/projects/matrix-doc/) (#1909)
+ - The SDK is now using SAS string translations from [Weblate Matrix-doc project](https://translate.element.io/projects/matrix-doc/) (#1909)
- New translation to kabyle
Build 🧱:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f10c87cdbe..3464fd9e76 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -40,7 +40,7 @@ For now, the Matrix SDK and the Element application are in the same project. So
## I want to help translating Element
If you want to fix an issue with an English string, please submit a PR.
-If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.riot.im/projects/element-android/).
+If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.element.io/projects/element-android/).
## I want to submit a PR to fix an issue
diff --git a/README.md b/README.md
index 64c6c9d04d..e89fb15010 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
[![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop)
-[![Weblate](https://translate.riot.im/widgets/element-android/-/svg-badge.svg)](https://translate.riot.im/engage/element-android/?utm_source=widget)
+[![Weblate](https://translate.element.io/widgets/element-android/-/svg-badge.svg)](https://translate.element.io/engage/element-android/?utm_source=widget)
[![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org)
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=alert_status)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle
index 3a5c3298d4..91ddd519df 100644
--- a/attachment-viewer/build.gradle
+++ b/attachment-viewer/build.gradle
@@ -58,21 +58,16 @@ android {
}
dependencies {
- implementation 'com.github.chrisbanes:PhotoView:2.0.0'
+ implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
- implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation 'androidx.core:core-ktx:1.3.0'
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'com.google.android.material:material:1.1.0'
- implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'
- implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation "androidx.fragment:fragment:1.3.0-beta01"
+ implementation "androidx.recyclerview:recyclerview:1.1.0"
+ implementation 'com.google.android.material:material:1.2.1'
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index f06d1859b5..05dcaa43ed 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.3.72'
+ // Ref: https://kotlinlang.org/releases.html
+ ext.kotlin_version = '1.4.10'
+ ext.kotlin_coroutines_version = "1.3.9"
repositories {
google()
jcenter()
@@ -10,10 +12,8 @@ buildscript {
}
}
dependencies {
- // Warning: 3.6.3 leads to infinite gradle builds. Stick to 3.5.3 for the moment
- classpath 'com.android.tools.build:gradle:3.5.3'
- classpath 'com.google.gms:google-services:4.3.2'
- classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
+ classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath 'com.google.gms:google-services:4.3.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
@@ -64,7 +64,8 @@ allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them
- kotlinOptions.allWarningsAsErrors = true
+ // You can override by passing `-PallWarningsAsErrors=false` in the command line
+ kotlinOptions.allWarningsAsErrors = project.properties['allWarningsAsErrors']?.toBoolean() ?: true
}
}
diff --git a/docs/ui-tests.md b/docs/ui-tests.md
new file mode 100644
index 0000000000..ff01da0b31
--- /dev/null
+++ b/docs/ui-tests.md
@@ -0,0 +1,107 @@
+# Automate user interface tests
+
+Element Android ensures that some fundamental flows are properly working by running automated user interface tests.
+Ui tests are using the android [Espresso](https://developer.android.com/training/testing/espresso) library.
+
+Tests can be run on a real device, or on a virtual device (such as the emulator in Android Studio).
+
+Currently the test are covering a small set of application flows:
+ - Registration
+ - Self verification via emoji
+ - Self verification via passphrase
+
+## Prerequisites:
+
+Out of the box, the tests use one of the homeservers (located at http://localhost:8080) of the "Demo Federation of Homeservers" (https://github.com/matrix-org/synapse#running-a-demo-federation-of-synapses).
+
+You first need to follow instructions to set up Synapse in development mode at https://github.com/matrix-org/synapse#synapse-development. If you have already installed all dependencies, the steps are:
+
+```shell script
+$ git clone https://github.com/matrix-org/synapse.git
+$ cd synapse
+$ virtualenv -p python3 env
+$ source env/bin/activate
+(env) $ python -m pip install --no-use-pep517 -e .
+```
+
+Every time you want to launch these test homeservers, type:
+
+```shell script
+$ virtualenv -p python3 env
+$ source env/bin/activate
+(env) $ demo/start.sh --no-rate-limit
+```
+
+**Emulator/Device set up**
+
+When running the test via android studio on a device, you have to disable system animations in order for the test to work properly.
+
+First, ensure developer mode is enabled:
+
+- To enable developer options, tap the **Build Number** option 7 times. You can find this option in one of the following locations, depending on your Android version:
+
+ - Android 9 (API level 28) and higher: **Settings > About Phone > Build Number**
+ - Android 8.0.0 (API level 26) and Android 8.1.0 (API level 26): **Settings > System > About Phone > Build Number**
+ - Android 7.1 (API level 25) and lower: **Settings > About Phone > Build Number**
+
+On your device, under **Settings > Developer options**, disable the following 3 settings:
+
+- Window animation scale
+- Transition animation scale
+- Animator duration scale
+
+## Run the tests
+
+Once Synapse is running, and an emulator is running, you can run the UI tests.
+
+### From the source code
+
+Click on the green arrow in front of each test. Clicking on the arrow in front of the test class, or from the package directory does not always work (Tests not found issue).
+
+### From command line
+
+````shell script
+./gradlew vector:connectedGplayDebugAndroidTest
+````
+
+To run all the tests from the `vector` module.
+
+In case of trouble, you can try to uninstall the previous installed test APK first with this command:
+
+```shell script
+adb uninstall im.vector.app.debug.test
+```
+## Recipes
+
+We added some specific Espresso IdlingResources, and other utilities for matrix related tests
+
+### Wait for initial sync
+
+```kotlin
+// Wait for initial sync and check room list is there
+withIdlingResource(initialSyncIdlingResource(uiSession)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+}
+```
+
+### Accessing current activity
+
+```kotlin
+ val activity = EspressoHelper.getCurrentActivity()!!
+ val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession()
+```
+
+### Interact with other session
+
+It's possible to create a session via the SDK, and then use this session to interact with the one that the emulator is using (to check verifications for example)
+
+```kotlin
+@Before
+fun initAccount() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val matrix = Matrix.getInstance(context)
+ val userName = "foobar_${System.currentTimeMillis()}"
+ existingSession = createAccountAndSync(matrix, userName, password, true)
+}
+```
diff --git a/vector/src/main/play/listings/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/de/full_description.txt
rename to fastlane/metadata/android/de/full_description.txt
diff --git a/vector/src/main/play/listings/de/short_description.txt b/fastlane/metadata/android/de/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/de/short_description.txt
rename to fastlane/metadata/android/de/short_description.txt
diff --git a/vector/src/main/play/listings/de/title.txt b/fastlane/metadata/android/de/title.txt
similarity index 100%
rename from vector/src/main/play/listings/de/title.txt
rename to fastlane/metadata/android/de/title.txt
diff --git a/vector/src/main/play/listings/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/en-US/full_description.txt
rename to fastlane/metadata/android/en-US/full_description.txt
diff --git a/vector/src/main/play/listings/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/en-US/short_description.txt
rename to fastlane/metadata/android/en-US/short_description.txt
diff --git a/vector/src/main/play/listings/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt
similarity index 100%
rename from vector/src/main/play/listings/en-US/title.txt
rename to fastlane/metadata/android/en-US/title.txt
diff --git a/vector/src/main/play/listings/es/full_description.txt b/fastlane/metadata/android/es/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/es/full_description.txt
rename to fastlane/metadata/android/es/full_description.txt
diff --git a/vector/src/main/play/listings/es/short_description.txt b/fastlane/metadata/android/es/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/es/short_description.txt
rename to fastlane/metadata/android/es/short_description.txt
diff --git a/vector/src/main/play/listings/es/title.txt b/fastlane/metadata/android/es/title.txt
similarity index 100%
rename from vector/src/main/play/listings/es/title.txt
rename to fastlane/metadata/android/es/title.txt
diff --git a/fastlane/metadata/android/et/short_description.txt b/fastlane/metadata/android/et/short_description.txt
new file mode 100644
index 0000000000..4075c1f7cf
--- /dev/null
+++ b/fastlane/metadata/android/et/short_description.txt
@@ -0,0 +1 @@
+Turvalised ning hajutatud vestlused ja VoIP-kõned. Sinu suhtlus on üliturvaline.
diff --git a/fastlane/metadata/android/et/title.txt b/fastlane/metadata/android/et/title.txt
new file mode 100644
index 0000000000..f74f9ff18f
--- /dev/null
+++ b/fastlane/metadata/android/et/title.txt
@@ -0,0 +1 @@
+Element (varem Riot.im)
diff --git a/vector/src/main/play/listings/fi/short_description.txt b/fastlane/metadata/android/fi/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/fi/short_description.txt
rename to fastlane/metadata/android/fi/short_description.txt
diff --git a/vector/src/main/play/listings/fi/title.txt b/fastlane/metadata/android/fi/title.txt
similarity index 100%
rename from vector/src/main/play/listings/fi/title.txt
rename to fastlane/metadata/android/fi/title.txt
diff --git a/vector/src/main/play/listings/fr/title.txt b/fastlane/metadata/android/fr/title.txt
similarity index 100%
rename from vector/src/main/play/listings/fr/title.txt
rename to fastlane/metadata/android/fr/title.txt
diff --git a/vector/src/main/play/listings/hu/full_description.txt b/fastlane/metadata/android/hu/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/hu/full_description.txt
rename to fastlane/metadata/android/hu/full_description.txt
diff --git a/vector/src/main/play/listings/hu/short_description.txt b/fastlane/metadata/android/hu/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/hu/short_description.txt
rename to fastlane/metadata/android/hu/short_description.txt
diff --git a/vector/src/main/play/listings/hu/title.txt b/fastlane/metadata/android/hu/title.txt
similarity index 100%
rename from vector/src/main/play/listings/hu/title.txt
rename to fastlane/metadata/android/hu/title.txt
diff --git a/fastlane/metadata/android/it/full_description.txt b/fastlane/metadata/android/it/full_description.txt
new file mode 100644
index 0000000000..b6f7cf449c
--- /dev/null
+++ b/fastlane/metadata/android/it/full_description.txt
@@ -0,0 +1,30 @@
+Element è un nuovo tipo di app di messaggistica e collaborazione che:
+
+1. Ti mette al controllo per preservare la tua privacy
+2. Ti lascia comunicare con chiunque nella rete Matrix e oltre, integrandosi con app come Slack
+3. Ti protegge da pubblicità, raccolta di dati e piattaforme chiuse
+4. Ti protegge con la crittografia end-to-end, con la firma incrociata per verificare gli altri
+
+Element è completamente diverso dalle altre app di messaggistica e collaborazione perchè è decentralizzato e open source.
+
+Element può essere gestito in locale - o puoi scegliere un host - in modo che tu abbia privacy, possesso e controllo dei tuoi dati e conversazioni. Ti dà accesso ad una rete aperta, quindi non sei limitato a parlare solo con altri utenti Element. Ed è molto sicuro.
+
+Element può fare tutto ciò perchè funziona su Matrix - lo standard per comunicazioni aperte e decentralizzate.
+
+Element ti mette al controllo lasciandoti scegliere chi gestisce il server delle tue conversazioni. Dall'app Element, hai diverse opzioni:
+
+1. Crea un account gratuito sul server pubblico matrix.org gestito dagli sviluppatori di Matrix, o scegli tra migliaia di server pubblici gestiti da volontari
+2. Gestisci autonomamente un account installando un server sul tuo hardware
+3. Registra un account su un server personalizzato iscrivendoti alla piattaforma Element Matrix Services
+
+Perchè scegliere Element?
+
+POSSIEDI I TUOI DATI: decidi dove tenere i tuoi dati e messaggi. Sono tuoi e li controlli tu, non qualche MEGADITTA che raccoglie i tuoi dati o ne dà l'accesso a terze parti.
+
+MESSAGGISTICA E COLLABORAZIONE APERTE: puoi chattare con chiunque nella rete Matrix, usando Element o un'altra app Matrix, o anche se si sta usando un sistema di messaggistica diverso come Slack, IRC o XMPP.
+
+SUPER SICURO: vera crittografia end-to-end (solo chi è nella conversazione può decifrare i messaggi) e firma incrociata per verificare i dispositivi dei partecipanti.
+
+COMUNICAZIONE COMPLETA: messaggi, chiamate audio e video, condivisione file e schermo, un vasto numero di integrazioni, bot e widget. Crea stanze, comunità, resta in contatto e porta a termine gli impegni.
+
+OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi sincronizzata tra tutti i tuoi dispositivi e in rete su https://app.element.io.
diff --git a/fastlane/metadata/android/it/short_description.txt b/fastlane/metadata/android/it/short_description.txt
new file mode 100644
index 0000000000..8c0c8fbee0
--- /dev/null
+++ b/fastlane/metadata/android/it/short_description.txt
@@ -0,0 +1 @@
+Chat e VoIP decentralizzati sicuri. Tieni lontani i tuoi dati dalle terze parti.
diff --git a/fastlane/metadata/android/it/title.txt b/fastlane/metadata/android/it/title.txt
new file mode 100644
index 0000000000..54e3b456c7
--- /dev/null
+++ b/fastlane/metadata/android/it/title.txt
@@ -0,0 +1 @@
+Element (ex Riot.im)
diff --git a/vector/src/main/play/listings/kab/full_description.txt b/fastlane/metadata/android/kab/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/kab/full_description.txt
rename to fastlane/metadata/android/kab/full_description.txt
diff --git a/vector/src/main/play/listings/kab/short_description.txt b/fastlane/metadata/android/kab/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/kab/short_description.txt
rename to fastlane/metadata/android/kab/short_description.txt
diff --git a/vector/src/main/play/listings/kab/title.txt b/fastlane/metadata/android/kab/title.txt
similarity index 100%
rename from vector/src/main/play/listings/kab/title.txt
rename to fastlane/metadata/android/kab/title.txt
diff --git a/vector/src/main/play/listings/pt_BR/full_description.txt b/fastlane/metadata/android/pt_BR/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/pt_BR/full_description.txt
rename to fastlane/metadata/android/pt_BR/full_description.txt
diff --git a/vector/src/main/play/listings/pt_BR/short_description.txt b/fastlane/metadata/android/pt_BR/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/pt_BR/short_description.txt
rename to fastlane/metadata/android/pt_BR/short_description.txt
diff --git a/fastlane/metadata/android/pt_BR/title.txt b/fastlane/metadata/android/pt_BR/title.txt
new file mode 100644
index 0000000000..5d2ae0c353
--- /dev/null
+++ b/fastlane/metadata/android/pt_BR/title.txt
@@ -0,0 +1 @@
+Element (o novo Riot.im)
diff --git a/vector/src/main/play/listings/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/ru/full_description.txt
rename to fastlane/metadata/android/ru/full_description.txt
diff --git a/vector/src/main/play/listings/ru/short_description.txt b/fastlane/metadata/android/ru/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/ru/short_description.txt
rename to fastlane/metadata/android/ru/short_description.txt
diff --git a/vector/src/main/play/listings/ru/title.txt b/fastlane/metadata/android/ru/title.txt
similarity index 100%
rename from vector/src/main/play/listings/ru/title.txt
rename to fastlane/metadata/android/ru/title.txt
diff --git a/vector/src/main/play/listings/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/sk/full_description.txt
rename to fastlane/metadata/android/sk/full_description.txt
diff --git a/vector/src/main/play/listings/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/sk/short_description.txt
rename to fastlane/metadata/android/sk/short_description.txt
diff --git a/vector/src/main/play/listings/sk/title.txt b/fastlane/metadata/android/sk/title.txt
similarity index 100%
rename from vector/src/main/play/listings/sk/title.txt
rename to fastlane/metadata/android/sk/title.txt
diff --git a/vector/src/main/play/listings/sv/full_description.txt b/fastlane/metadata/android/sv/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/sv/full_description.txt
rename to fastlane/metadata/android/sv/full_description.txt
diff --git a/vector/src/main/play/listings/sv/short_description.txt b/fastlane/metadata/android/sv/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/sv/short_description.txt
rename to fastlane/metadata/android/sv/short_description.txt
diff --git a/vector/src/main/play/listings/sv/title.txt b/fastlane/metadata/android/sv/title.txt
similarity index 100%
rename from vector/src/main/play/listings/sv/title.txt
rename to fastlane/metadata/android/sv/title.txt
diff --git a/vector/src/main/play/listings/th/short_description.txt b/fastlane/metadata/android/th/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/th/short_description.txt
rename to fastlane/metadata/android/th/short_description.txt
diff --git a/vector/src/main/play/listings/th/title.txt b/fastlane/metadata/android/th/title.txt
similarity index 100%
rename from vector/src/main/play/listings/th/title.txt
rename to fastlane/metadata/android/th/title.txt
diff --git a/vector/src/main/play/listings/tr/short_description.txt b/fastlane/metadata/android/tr/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/tr/short_description.txt
rename to fastlane/metadata/android/tr/short_description.txt
diff --git a/vector/src/main/play/listings/tr/title.txt b/fastlane/metadata/android/tr/title.txt
similarity index 100%
rename from vector/src/main/play/listings/tr/title.txt
rename to fastlane/metadata/android/tr/title.txt
diff --git a/vector/src/main/play/listings/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt
similarity index 95%
rename from vector/src/main/play/listings/uk/full_description.txt
rename to fastlane/metadata/android/uk/full_description.txt
index ca6b4c2ae1..64247581d2 100644
--- a/vector/src/main/play/listings/uk/full_description.txt
+++ b/fastlane/metadata/android/uk/full_description.txt
@@ -13,7 +13,7 @@ Element здатен забезпечити усе це завдяки тому,
Element надає вам повний контроль, дозволяючи обирати з-поміж надавачів послуг, що обслуговують сервери з вашими бесідами. Ви вільні обрати будь-який спосіб розміщення прямо з застосунку Element:
-1. Отримати безкоштовний обліковий запис на загальнодоступному сервері matrix.org
+1. Отримати безкоштовний обліковий запис на загальнодоступному сервері matrix.org, який обслуговують розробники Matrix, чи на одному з тисяч публічних серверів, які обслуговують волонтери
2. Розмістити свій обліковий запис на власному сервері
3. Зареєструватись на індивідуальному сервері, просто підписавшись на послуги платформи Element Matrix Services
diff --git a/vector/src/main/play/listings/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/uk/short_description.txt
rename to fastlane/metadata/android/uk/short_description.txt
diff --git a/vector/src/main/play/listings/uk/title.txt b/fastlane/metadata/android/uk/title.txt
similarity index 100%
rename from vector/src/main/play/listings/uk/title.txt
rename to fastlane/metadata/android/uk/title.txt
diff --git a/vector/src/main/play/listings/zh_Hans/full_description.txt b/fastlane/metadata/android/zh_Hans/full_description.txt
similarity index 100%
rename from vector/src/main/play/listings/zh_Hans/full_description.txt
rename to fastlane/metadata/android/zh_Hans/full_description.txt
diff --git a/vector/src/main/play/listings/zh_Hans/short_description.txt b/fastlane/metadata/android/zh_Hans/short_description.txt
similarity index 100%
rename from vector/src/main/play/listings/zh_Hans/short_description.txt
rename to fastlane/metadata/android/zh_Hans/short_description.txt
diff --git a/vector/src/main/play/listings/zh_Hans/title.txt b/fastlane/metadata/android/zh_Hans/title.txt
similarity index 100%
rename from vector/src/main/play/listings/zh_Hans/title.txt
rename to fastlane/metadata/android/zh_Hans/title.txt
diff --git a/gradle.properties b/gradle.properties
index 99fd9d64fd..b3f11e08a3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,6 +14,8 @@ org.gradle.jvmargs=-Xmx2048m
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+# Enable file system watch (https://docs.gradle.org/6.7/release-notes.html)
+org.gradle.vfs.watch=true
vector.debugPrivateData=false
vector.httpLogLevel=NONE
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 87b738cbd0..e708b1c023 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4da2435f42..99d667ccdc 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Jul 02 12:33:07 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionSha256Sum=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/gradlew b/gradlew
index af6708ff22..4f906e0c81 100755
--- a/gradlew
+++ b/gradlew
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
+
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m"'
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 0f8d5937c4..ac1b06f938 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m"
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle
index 70a05114c2..3d62758065 100644
--- a/matrix-sdk-android-rx/build.gradle
+++ b/matrix-sdk-android-rx/build.gradle
@@ -35,7 +35,8 @@ android {
dependencies {
implementation project(":matrix-sdk-android")
- implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging
@@ -43,8 +44,4 @@ dependencies {
// Logging
implementation 'com.jakewharton.timber:timber:4.7.1'
-
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/LiveDataObservable.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/LiveDataObservable.kt
index ec0d0cd288..2174c6f118 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/LiveDataObservable.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/LiveDataObservable.kt
@@ -1,5 +1,4 @@
/*
- * Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/OptionalRx.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/OptionalRx.kt
index 551e277353..ff4b0d755c 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/OptionalRx.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/OptionalRx.kt
@@ -1,5 +1,4 @@
/*
- * Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
index 793d4694ce..f6dbe3d160 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
@@ -1,5 +1,4 @@
/*
- * Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
index 0717a10d03..228e83faff 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
@@ -1,5 +1,4 @@
/*
- * Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -101,8 +100,11 @@ class RxRoom(private val room: Room) {
return room.getEventReadReceiptsLive(eventId).asObservable()
}
- fun liveDrafts(): Observable> {
- return room.getDraftsLive().asObservable()
+ fun liveDraft(): Observable> {
+ return room.getDraftLive().asObservable()
+ .startWithCallable {
+ room.getDraft().toOptional()
+ }
}
fun liveNotificationState(): Observable {
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
index 55ede52c0c..03df708c0c 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
@@ -1,5 +1,4 @@
/*
- * Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/SecretsSynchronisationInfo.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/SecretsSynchronisationInfo.kt
index e80bca1d7d..6da3217070 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/SecretsSynchronisationInfo.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/SecretsSynchronisationInfo.kt
@@ -1,5 +1,4 @@
/*
- * Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 2c20137647..e4a8b8884a 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -3,7 +3,6 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android'
-apply plugin: 'okreplay'
buildscript {
repositories {
@@ -36,6 +35,10 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
+ // Seems that the build tools 4.1.0 does not generate BuildConfig.VERSION_NAME anymore.
+ // Add it manually here. We may remove this trick in the future
+ buildConfigField "String", "VERSION_NAME", "\"0.0.1\""
+
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
@@ -109,21 +112,21 @@ static def gitRevisionDate() {
dependencies {
def arrow_version = "0.8.2"
- def moshi_version = '1.8.0'
+ def moshi_version = '1.11.0'
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
- def coroutines_version = "1.3.8"
def markwon_version = '3.1.0'
- def daggerVersion = '2.25.4'
+ def daggerVersion = '2.29.1'
def work_version = '2.4.0'
def retrofit_version = '2.6.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.2.0"
- implementation "androidx.core:core-ktx:1.3.1"
+ implementation "androidx.fragment:fragment:1.3.0-beta01"
+ implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -143,7 +146,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
- implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
+ implementation 'androidx.exifinterface:exifinterface:1.3.0'
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
@@ -181,33 +184,29 @@ dependencies {
// Use the same WebRTC library than the one used by Jitsi library
implementation('com.facebook.react:react-native-webrtc:1.84.0-jitsi-5112273@aar')
- debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
- releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
- androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
-
- testImplementation 'junit:junit:4.12'
+ testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.3'
//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.9.2.kotlin12'
- testImplementation 'org.amshove.kluent:kluent-android:1.44'
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+ testImplementation 'org.amshove.kluent:kluent-android:1.61'
+ 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.2.0'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test:rules:1.2.0'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
+ 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 'org.amshove.kluent:kluent-android:1.61'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
- androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_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.2.0'
+ androidTestUtil 'androidx.test:orchestrator:1.3.0'
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
index cb6e624bce..b784884363 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/LiveDataTestObserver.java b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/LiveDataTestObserver.java
index a09a655008..26920fbb35 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/LiveDataTestObserver.java
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/LiveDataTestObserver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/MainThreadExecutor.java b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/MainThreadExecutor.java
index d26782d538..7ef2534037 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/MainThreadExecutor.java
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/MainThreadExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt
index 4316b09b89..9942ea9db3 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt
index cbb5af5911..5dede9dcfd 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
index e2140328e6..ec5477f976 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
index 36d09fb497..a6fbfd9b7a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
index 751b2a708c..0d71af864b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
package org.matrix.android.sdk.api
import android.content.Context
+import android.os.Handler
+import android.os.Looper
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration
import androidx.work.WorkManager
@@ -48,13 +50,17 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager
+ private val uiHandler = Handler(Looper.getMainLooper())
+
init {
Monarchy.init(context)
DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
if (context.applicationContext !is Configuration.Provider) {
WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
}
- ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
+ uiHandler.post {
+ ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
+ }
}
fun getUserAgent() = userAgentHolder.userAgent
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index fdbfa57b5c..1c912b365f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -1,6 +1,5 @@
/*
- * Copyright 2016 OpenMarket Ltd
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -218,7 +217,7 @@ class CommonTestHelper(context: Context) {
.createAccount(userName, password, null, it)
}
- // Preform dummy step
+ // Perform dummy step
val registrationResult = doSync {
matrix.authenticationService
.getRegistrationWizard()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt
index 283ddd6fde..76e59d9a90 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
index 9765d35bc5..370b416f54 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt
index a9bd9403d2..e7978a9cb2 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/MockOkHttpInterceptor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/SessionTestParams.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/SessionTestParams.kt
index 287cafcdfd..428d44b666 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/SessionTestParams.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/SessionTestParams.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestAssertUtil.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestAssertUtil.kt
index d972ad621c..2253360adc 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestAssertUtil.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestAssertUtil.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt
index cbfc9bbbf6..8eb7e251e2 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt
index 800c6ae7e0..c2e1ec0f92 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixCallback.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt
index e2ab16cad3..33de345630 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt
index c3b11d65cc..3e4d5fe08e 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt
index 80467d91f4..4cd92ca272 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
index 1e109f11ae..b6cb7f9e79 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
index 261c0903f0..75ccce0db9 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.crypto
-import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
@@ -34,14 +33,8 @@ internal class CryptoStoreHelper {
.modules(RealmCryptoStoreModule())
.build(),
crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
- credentials = createCredential())
+ userId = "userId_" + Random.nextInt(),
+ deviceId = "deviceId_sample"
+ )
}
-
- fun createCredential() = Credentials(
- userId = "userId_" + Random.nextInt(),
- homeServer = "http://matrix.org",
- accessToken = "access_token",
- refreshToken = null,
- deviceId = "deviceId_sample"
- )
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
index 79477e3a4d..1d838b5c84 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt
index 0ee79c2e1e..17664c78aa 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index e9a1775ac3..0e3b29118c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt
index 9467e861db..9fa7458ea7 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
index 09f14032d0..38c57bd22a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.matrix.android.sdk.internal.crypto.crosssigning
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
new file mode 100644
index 0000000000..e42059c639
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.encryption
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.amshove.kluent.shouldBe
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.NoOpMatrixCallback
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.room.timeline.Timeline
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestHelper
+import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
+import java.util.concurrent.CountDownLatch
+
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EncryptionTest : InstrumentedTest {
+ private val mTestHelper = CommonTestHelper(context())
+ private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+
+ @Test
+ fun test_EncryptionEvent() {
+ performTest(roomShouldBeEncrypted = false) { room ->
+ // Send an encryption Event as an Event (and not as a state event)
+ room.sendEvent(
+ eventType = EventType.STATE_ROOM_ENCRYPTION,
+ content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
+ )
+ }
+ }
+
+ @Test
+ fun test_EncryptionStateEvent() {
+ performTest(roomShouldBeEncrypted = true) { room ->
+ // Send an encryption Event as a State Event
+ room.sendStateEvent(
+ eventType = EventType.STATE_ROOM_ENCRYPTION,
+ stateKey = null,
+ body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent(),
+ callback = NoOpMatrixCallback()
+ )
+ }
+ }
+
+ private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
+ val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
+
+ val aliceSession = cryptoTestData.firstSession
+ val room = aliceSession.getRoom(cryptoTestData.roomId)!!
+
+ room.isEncrypted() shouldBe false
+
+ val timeline = room.createTimeline(null, TimelineSettings(10))
+ val latch = CountDownLatch(1)
+ val timelineListener = object : Timeline.Listener {
+ override fun onTimelineFailure(throwable: Throwable) {
+ }
+
+ override fun onNewTimelineEvents(eventIds: List) {
+ // noop
+ }
+
+ override fun onTimelineUpdated(snapshot: List) {
+ val newMessages = snapshot
+ .filter { it.root.sendState == SendState.SYNCED }
+ .filter { it.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION }
+
+ if (newMessages.isNotEmpty()) {
+ timeline.removeListener(this)
+ latch.countDown()
+ }
+ }
+ }
+ timeline.start()
+ timeline.addListener(timelineListener)
+
+ action.invoke(room)
+
+ mTestHelper.await(latch)
+ timeline.dispose()
+
+ room.isEncrypted() shouldBe roomShouldBeEncrypted
+
+ cryptoTestData.cleanUp(mTestHelper)
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 7c1a88dc75..197e36df06 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 03f9de2894..80cc14fcb6 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt
index f38b55beba..cc71f88fc0 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPasswordTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt
index 29a0b5ffd6..864f3c12e4 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index aef97d5687..ca8993fb00 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestConstants.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestConstants.kt
index f31e67b0e8..1248c8f07d 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestConstants.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestConstants.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
index f84a90708c..944d1036d3 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt
index c28b7990e0..6aefe98f86 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/PrepareKeysBackupDataResult.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt
index 90d2fd7812..ff8ce43b55 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/StateObserver.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
index 42cee74334..0489ee179f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
index a6beeb123c..75c7ad4d53 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/HexParser.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/HexParser.kt
index cd5aa32d59..29d4e7850a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/HexParser.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/HexParser.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt
index 54a0f7e771..ee604fc9ab 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,14 @@
package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.matrix.android.sdk.InstrumentedTest
+import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeNull
-import org.amshove.kluent.shouldEqual
-import org.amshove.kluent.shouldEqualTo
import org.amshove.kluent.shouldNotBeNull
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@@ -66,32 +65,32 @@ class QrCodeTest : InstrumentedTest {
@Test
fun testEncoding1() {
- qrCode1.toEncodedString() shouldEqual value1
+ qrCode1.toEncodedString() shouldBeEqualTo value1
}
@Test
fun testEncoding2() {
- qrCode2.toEncodedString() shouldEqual value2
+ qrCode2.toEncodedString() shouldBeEqualTo value2
}
@Test
fun testEncoding3() {
- qrCode3.toEncodedString() shouldEqual value3
+ qrCode3.toEncodedString() shouldBeEqualTo value3
}
@Test
fun testSymmetry1() {
- qrCode1.toEncodedString().toQrCodeData() shouldEqual qrCode1
+ qrCode1.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode1
}
@Test
fun testSymmetry2() {
- qrCode2.toEncodedString().toQrCodeData() shouldEqual qrCode2
+ qrCode2.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode2
}
@Test
fun testSymmetry3() {
- qrCode3.toEncodedString().toQrCodeData() shouldEqual qrCode3
+ qrCode3.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode3
}
@Test
@@ -102,7 +101,7 @@ class QrCodeTest : InstrumentedTest {
checkHeader(byteArray)
// Mode
- byteArray[7] shouldEqualTo 0
+ byteArray[7] shouldBeEqualTo 0
checkSizeAndTransaction(byteArray)
@@ -120,7 +119,7 @@ class QrCodeTest : InstrumentedTest {
checkHeader(byteArray)
// Mode
- byteArray[7] shouldEqualTo 1
+ byteArray[7] shouldBeEqualTo 1
checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray)
@@ -137,7 +136,7 @@ class QrCodeTest : InstrumentedTest {
checkHeader(byteArray)
// Mode
- byteArray[7] shouldEqualTo 2
+ byteArray[7] shouldBeEqualTo 2
checkSizeAndTransaction(byteArray)
compareArray(byteArray.copyOfRange(23, 23 + 32), tlx_byteArray)
@@ -156,10 +155,10 @@ class QrCodeTest : InstrumentedTest {
val result = qrCode.toEncodedString()
val expected = value1.replace("\u0000\u000DMaTransaction", "\u0007\u00D0$longTransactionId")
- result shouldEqual expected
+ result shouldBeEqualTo expected
// Reverse operation
- expected.toQrCodeData() shouldEqual qrCode
+ expected.toQrCodeData() shouldBeEqualTo qrCode
}
@Test
@@ -170,7 +169,7 @@ class QrCodeTest : InstrumentedTest {
val qrCode = qrCode1.copy(transactionId = longTransactionId)
// Symmetric operation
- qrCode.toEncodedString().toQrCodeData() shouldEqual qrCode
+ qrCode.toEncodedString().toQrCodeData() shouldBeEqualTo qrCode
}
}
@@ -218,32 +217,32 @@ class QrCodeTest : InstrumentedTest {
}
private fun compareArray(actual: ByteArray, expected: ByteArray) {
- actual.size shouldEqual expected.size
+ actual.size shouldBeEqualTo expected.size
for (i in actual.indices) {
- actual[i] shouldEqualTo expected[i]
+ actual[i] shouldBeEqualTo expected[i]
}
}
private fun checkHeader(byteArray: ByteArray) {
// MATRIX
- byteArray[0] shouldEqualTo 'M'.toByte()
- byteArray[1] shouldEqualTo 'A'.toByte()
- byteArray[2] shouldEqualTo 'T'.toByte()
- byteArray[3] shouldEqualTo 'R'.toByte()
- byteArray[4] shouldEqualTo 'I'.toByte()
- byteArray[5] shouldEqualTo 'X'.toByte()
+ byteArray[0] shouldBeEqualTo 'M'.toByte()
+ byteArray[1] shouldBeEqualTo 'A'.toByte()
+ byteArray[2] shouldBeEqualTo 'T'.toByte()
+ byteArray[3] shouldBeEqualTo 'R'.toByte()
+ byteArray[4] shouldBeEqualTo 'I'.toByte()
+ byteArray[5] shouldBeEqualTo 'X'.toByte()
// Version
- byteArray[6] shouldEqualTo 2
+ byteArray[6] shouldBeEqualTo 2
}
private fun checkSizeAndTransaction(byteArray: ByteArray) {
// Size
- byteArray[8] shouldEqualTo 0
- byteArray[9] shouldEqualTo 13
+ byteArray[8] shouldBeEqualTo 0
+ byteArray[9] shouldBeEqualTo 13
// Transaction
- byteArray.copyOfRange(10, 10 + "MaTransaction".length).toString(Charsets.ISO_8859_1) shouldEqual "MaTransaction"
+ byteArray.copyOfRange(10, 10 + "MaTransaction".length).toString(Charsets.ISO_8859_1) shouldBeEqualTo "MaTransaction"
}
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecretTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecretTest.kt
index 4032890723..97b93dcf5a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecretTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecretTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
index 0c003215ee..1385dac1ec 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt
index eebaa93415..1713578932 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt
index 854d420a82..b5ab6589ff 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/util/JsonCanonicalizerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
index a2a1586864..69ae57e644 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,14 @@ package org.matrix.android.sdk.session.room.timeline
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import io.realm.kotlin.createObject
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldBeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.send.SendState
@@ -29,14 +37,6 @@ import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents
import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent
-import io.realm.Realm
-import io.realm.RealmConfiguration
-import io.realm.kotlin.createObject
-import org.amshove.kluent.shouldBeTrue
-import org.amshove.kluent.shouldEqual
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
internal class ChunkEntityTest : InstrumentedTest {
@@ -60,10 +60,10 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
- realm.copyToRealmOrUpdate(it)
+ realm.copyToRealm(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
- chunk.timelineEvents.size shouldEqual 1
+ chunk.timelineEvents.size shouldBeEqualTo 1
}
}
@@ -72,11 +72,11 @@ internal class ChunkEntityTest : InstrumentedTest {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
- realm.copyToRealmOrUpdate(it)
+ realm.copyToRealm(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
- chunk.timelineEvents.size shouldEqual 1
+ chunk.timelineEvents.size shouldBeEqualTo 1
}
}
@@ -88,7 +88,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
- chunk1.timelineEvents.size shouldEqual 60
+ chunk1.timelineEvents.size shouldBeEqualTo 60
}
}
@@ -104,7 +104,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
- chunk1.timelineEvents.size shouldEqual 40
+ chunk1.timelineEvents.size shouldBeEqualTo 40
chunk1.isLastForward.shouldBeTrue()
}
}
@@ -119,7 +119,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
- chunk1.prevToken shouldEqual prevToken
+ chunk1.prevToken shouldBeEqualTo prevToken
}
}
@@ -133,7 +133,7 @@ internal class ChunkEntityTest : InstrumentedTest {
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
- chunk1.nextToken shouldEqual nextToken
+ chunk1.nextToken shouldBeEqualTo nextToken
}
}
@@ -142,7 +142,7 @@ internal class ChunkEntityTest : InstrumentedTest {
direction: PaginationDirection) {
events.forEach { event ->
val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
- realm.copyToRealmOrUpdate(it)
+ realm.copyToRealm(it)
}
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
index 9a133032b6..b86c86c0c7 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
index 06828ef3d1..d09bfb18c6 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt
index 0301157d09..657f622c5b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeTokenChunkEvent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt
index a6fe675218..1adf31be5f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.Membership
-import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import kotlin.random.Random
@@ -41,11 +41,11 @@ object RoomDataHelper {
}
}
- fun createFakeEvent(type: String,
- content: Content? = null,
- prevContent: Content? = null,
- sender: String = FAKE_TEST_SENDER,
- stateKey: String = FAKE_TEST_SENDER
+ private fun createFakeEvent(type: String,
+ content: Content? = null,
+ prevContent: Content? = null,
+ sender: String = FAKE_TEST_SENDER,
+ stateKey: String = FAKE_TEST_SENDER
): Event {
return Event(
type = type,
@@ -62,8 +62,8 @@ object RoomDataHelper {
return createFakeEvent(EventType.MESSAGE, message)
}
- fun createFakeRoomMemberEvent(): Event {
- val roomMember = RoomMemberSummary(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
+ private fun createFakeRoomMemberEvent(): Event {
+ val roomMember = RoomMemberContent(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
}
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt
index 8c5e7f17f2..3774e6f513 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineBackToPreviousLastForwardTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
index facb905b35..34edf37733 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
index 28ce75c221..9ebac8766a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt
index b0da49cdbb..9be0a5d5af 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,7 +78,7 @@ internal class TimelineTest : InstrumentedTest {
// }
// }
// latch.await()
-// timelineEvents.size shouldEqual initialLoad + paginationCount
+// timelineEvents.size shouldBeEqualTo initialLoad + paginationCount
// timeline.dispose()
// }
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
new file mode 100644
index 0000000000..7db159cd0b
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.session.search
+
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.api.session.search.SearchResult
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestHelper
+import org.matrix.android.sdk.common.TestConstants
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class SearchMessagesTest : InstrumentedTest {
+
+ private val MESSAGE = "Lorem ipsum dolor sit amet"
+
+ private val commonTestHelper = CommonTestHelper(context())
+ private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
+
+ @Test
+ fun sendTextMessageAndSearchPartOfItUsingSession() {
+ val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
+ val aliceSession = cryptoTestData.firstSession
+ val aliceRoomId = cryptoTestData.roomId
+ aliceSession.cryptoService().setWarnOnUnknownDevices(false)
+ val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
+ val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
+ aliceTimeline.start()
+
+ commonTestHelper.sendTextMessage(
+ roomFromAlicePOV,
+ MESSAGE,
+ 2)
+
+ run {
+ var lock = CountDownLatch(1)
+
+ val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
+ snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2
+ }
+
+ aliceTimeline.addListener(eventListener)
+ commonTestHelper.await(lock)
+
+ lock = CountDownLatch(1)
+ aliceSession
+ .searchService()
+ .search(
+ searchTerm = "lore",
+ limit = 10,
+ includeProfile = true,
+ afterLimit = 0,
+ beforeLimit = 10,
+ orderByRecent = true,
+ nextBatch = null,
+ roomId = aliceRoomId,
+ callback = object : MatrixCallback {
+ override fun onSuccess(data: SearchResult) {
+ super.onSuccess(data)
+ assertTrue(data.results?.size == 2)
+ assertTrue(
+ data.results
+ ?.all {
+ (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
+ }.orFalse()
+ )
+ lock.countDown()
+ }
+
+ override fun onFailure(failure: Throwable) {
+ super.onFailure(failure)
+ fail(failure.localizedMessage)
+ lock.countDown()
+ }
+ }
+ )
+ lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
+
+ aliceTimeline.removeAllListeners()
+ cryptoTestData.cleanUp(commonTestHelper)
+ }
+
+ aliceSession.startSync(true)
+ }
+
+ @Test
+ fun sendTextMessageAndSearchPartOfItUsingRoom() {
+ val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
+ val aliceSession = cryptoTestData.firstSession
+ val aliceRoomId = cryptoTestData.roomId
+ aliceSession.cryptoService().setWarnOnUnknownDevices(false)
+ val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
+ val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
+ aliceTimeline.start()
+
+ commonTestHelper.sendTextMessage(
+ roomFromAlicePOV,
+ MESSAGE,
+ 2)
+
+ run {
+ var lock = CountDownLatch(1)
+
+ val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
+ snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2
+ }
+
+ aliceTimeline.addListener(eventListener)
+ commonTestHelper.await(lock)
+
+ lock = CountDownLatch(1)
+ roomFromAlicePOV
+ .search(
+ searchTerm = "lore",
+ limit = 10,
+ includeProfile = true,
+ afterLimit = 0,
+ beforeLimit = 10,
+ orderByRecent = true,
+ nextBatch = null,
+ callback = object : MatrixCallback {
+ override fun onSuccess(data: SearchResult) {
+ super.onSuccess(data)
+ assertTrue(data.results?.size == 2)
+ assertTrue(
+ data.results
+ ?.all {
+ (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
+ }.orFalse()
+ )
+ lock.countDown()
+ }
+
+ override fun onFailure(failure: Throwable) {
+ super.onFailure(failure)
+ fail(failure.localizedMessage)
+ lock.countDown()
+ }
+ }
+ )
+ lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
+
+ aliceTimeline.removeAllListeners()
+ cryptoTestData.cleanUp(commonTestHelper)
+ }
+
+ aliceSession.startSync(true)
+ }
+}
diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/database/RealmDebugTools.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/database/RealmDebugTools.kt
index 324a3c1062..e5f4af2377 100644
--- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/database/RealmDebugTools.kt
+++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/database/RealmDebugTools.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index ee2c6076cc..2103dc954d 100644
--- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2016 Jeff Gilfelt.
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt
index 349110aff8..5c03e8a855 100644
--- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt
+++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml
index 52238f824c..220a168f60 100644
--- a/matrix-sdk-android/src/main/AndroidManifest.xml
+++ b/matrix-sdk-android/src/main/AndroidManifest.xml
@@ -1,12 +1,24 @@
+
+
+
+
+
Entfernen nicht möglich
Nachricht kann nicht gesendet werden
-
Bild konnte nicht hochgeladen werden
-
Netzwerk-Fehler
Matrix-Fehler
-
-
-
-
Es ist aktuell nicht möglich, einen leeren Raum erneut zu betreten.
-
Verschlüsselte Nachricht
-
E-Mail-Adresse
Telefonnummer
-
%1$s sandte einen Sticker.
-
Einladung von %s
Raumeinladung
%1$s und %2$s
Leerer Raum
-
- %1$s und 1 andere(r)
- %1$s und %2$d andere
-
-
Nachricht entfernt
Nachricht entfernt von %1$s
Nachricht entfernt [Grund: %1$s]
Nachricht entfernt von %1$s [Grund: %2$s]
%s hat diesen Raum aufgewertet.
-
Sende eine Nachricht…
Sendewarteschlange leeren
-
Erste Synchronisation: Importiere Benutzerkonto…
Erste Synchronisation: Importiere Cryptoschlüssel
Erste Synchronisation: Importiere Räume
@@ -102,7 +81,6 @@
Erste Synchronisation: Importiere verlassene Räume
Erste Synchronisation: Importiere Gemeinschaften
Erste Synchronisation: Importiere Benutzerdaten
-
%1$s hat die Einladung an %2$s, den Raum zu betreten, zurückgezogen
%1$s\'s Einladung. Grund: %2$s
%1$s hat %2$s eingeladen. Grund: %3$s
@@ -117,33 +95,24 @@
%1$s hat Einladung an %2$s zu Betreten dieses Raumes zurückgezogen. Grund: %3$s
%1$s hat die Einladung für %2$s angenommen. Grund: %3$s
%1$s hat Einladung für %2$s verworfen. Grund: %3$s
-
- %1$s fügt %2$s als eine Adresse für diesen Raum hinzu.
- %1$s fügt %2$s als Adressen für diesen Raum hinzu.
-
- %1$s entfernt %2$s als eine Adresse für diesen Raum.
- %1$s entfernt %2$s als Adressen für diesen Raum.
-
%1$s fügt %2$s als Adresse für diesen Raum hinzu und entfernt %3$s.
-
%1$s legt die Hauptadresse fest für diesen Raum als %2$s fest.
%1$s entfernt die Hauptadresse für diesen Raum.
-
%1$s hat Gästen erlaubt den Raum zu betreten.
%1$s hat Gäste unterbunden den Raum zu betreten.
-
%1$s aktivierte Ende-zu-Ende-Verschlüsselung.
%1$s aktivierte Ende-zu-Ende-Verschlüsselung (unbekannter Algorithmus %2$s).
-
%s fordert zur Überprüfung deines Schlüssels auf, jedoch unterstützt dein Client nicht die Schlüsselüberprüfung im Chat. Du musst die herkömmliche Schlüsselüberprüfung verwenden, um die Schlüssel zu überprüfen.
-
Du hast ein Bild gesendet.
Du hast einen Sticker gesendet.
-
Deine Einladung
%1$s hat den Raum erstellt
Du hast den Raum erstellt
@@ -170,7 +139,6 @@
Du hast den zukünftigen Nachrichtenverlauf für %1$s sichtbar gemacht
Du hast Ende-zu-Ende-Verschlüsselung aktiviert (%1$s)
Du hast den Raum aufgwertet.
-
Du hast eine VoIP-Konferenz angefordert
Du hast den Raumnamen entfernt
Du hast das Raumthema entfernt
@@ -180,24 +148,20 @@
Du hast %1$s in den Raum eingeladen
Du hast die Einladung für %1$s zurückgenommen
Du hast die Einladung für %1$s akzeptiert
-
%1$s hat das %2$s Widget hinzugefügt
Du hast das %1$s Widget hinzugefügt
%1$s hat das %2$s Widget entfernt
Du hast das %1$s Widget entfernt
%1$s hat das %2$s Widget modifiziert
Du hast das %1$s Widget modifiziert
-
Administrator
Moderator
Standard
Benutzerdefiniert (%1$d)
Benutzerdefiniert
-
Du hast die Berechtigungsstufe von %1$s geändert.
%1$s hat die Berechtigungsstufe von %2$s geändert.
%1$s von %2$s zu %3$s
-
Deine Einladung. Grund: %1$s
Du hast %1$s eingeladen. Grund: %2$s
Du bist dem Raum beigetreten. Grund: %1$s
@@ -210,28 +174,37 @@
Du hast die Einladung für %1$s zurückgenommen. Grund: %2$s
Du hast die Einladung von %1$s angenommen. Grund: %2$s
Du hast die Einladung von %1$s abgelehnt. Grund: %2$s
-
- Du hast die Raumaddresse %1$s hinzugefügt.
- Du hast die Raumaddressen %1$s hinzugefügt.
-
- - Du hast die Raumaddresse %1$s vom Raum entfernt.
- - Du hast die Raumaddressen %1$s vom Raum entfernt.
+ - Du hast die Raum-Adresse %1$s vom Raum entfernt.
+ - Du hast die Raum-Adressen %1$s vom Raum entfernt.
-
Du hast den Raumaddressen %1$s hinzugefügt und %2$s entfernt.
-
Du hast die Hauptaddresse für diesen Raum auf %1$s gesetzt.
Du hast die Hauptaddresse des Raums entfernt.
-
Du hast Gästen erlaubt dem Raum beizutreten.
Du hast Gästen untersagt dem Raum beizutreten.
-
Du hast Ende-zu-Ende-Verschlüsselung aktiviert.
Du hast Ende-zu-Ende-Verschlüsselung aktiviert (unbekannter Algorithmus %1$s).
-
%s hat Daten gesendet, um einen Anruf zu starten.
Du hast Daten geschickt, um eine Anruf zu starten.
-
+ Du hast Gästen erlaubt hier beizutreten.
+ %1$s hat Gästen erlaubt hier beizutreten.
+ Du bist beigetreten. Grund: %1$s
+ %1$s ist beigetreten. Grund: %2$s
+ Du hast die Einladung für %1$s zurückgezogen
+ %1$s hat die Einladung für %2$s zurückgezogen
+ Du hast %1$s eingeladen
+ %1$s hat %2$s eingeladen
+ Du hast zukünftige Nachrichten für %2$s sichtbar gemacht
+ %1$s hat zukünftige Nachrichten für %2$s sichtbar gemacht
+ Du hast den Raum verlassen
+ %1$s hat den Raum verlassen
+ Du bist beigetreten
+ %1$s ist beigetreten
+ Du hast eine Diskussion erstellt
+ %1$s hat eine Diskussion erstellt
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-et/strings.xml b/matrix-sdk-android/src/main/res/values-et/strings.xml
index 2fbe263464..71ee50f075 100644
--- a/matrix-sdk-android/src/main/res/values-et/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-et/strings.xml
@@ -1,9 +1,8 @@
-
+
%1$s: %2$s
%1$s saatis pildi.
%1$s saatis kleepsu.
-
Kasutaja %s kutse
%1$s kutsus kasutajat %2$s
%1$s kutsus sind
@@ -30,11 +29,9 @@
teadmata (%s).
%1$s lülitas sisse läbiva krüptimise (%2$s)
%s uuendas seda jututuba.
-
%1$s saatis VoIP konverentsi kutse
VoIP-konverents algas
VoIP-konverents lõppes
-
(samuti sai avatar muudetud)
%1$s eemaldas jututoa nime
%1$s eemaldas jututoa teema
@@ -46,37 +43,25 @@
%1$s saatis jututoaga liitumiseks kutse kasutajale %2$s
%1$s võttis tagasi jututoaga liitumise kutse kasutajalt %2$s
%1$s võttis vastu kutse %2$s nimel
-
** Ei õnnestu dekrüptida: %s **
Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.
-
Ei saanud muuta sõnumit
Sõnumi saatmine ei õnnestunud
-
Faili üles laadimine ei õnnestunud
-
Võrguühenduse viga
Matrix\'i viga
-
Hetkel ei ole võimalik uuesti liituda tühja jututoaga.
-
Krüptitud sõnum
-
E-posti aadress
Telefoninumber
-
Kutse kasutajalt %s
Kutse jututuppa
-
%1$s ja %2$s
-
- %1$s ja üks muu
- %1$s ja %2$d muud
-
Tühi jututuba
-
Alglaadimine:
\nImpordin kontot…
Alglaadimine:
@@ -93,10 +78,8 @@
\nImpordin kogukondi
Alglaadimine:
\nImpordin kontoandmeid
-
Saadan sõnumit…
Tühjenda saatmisjärjekord
-
Kasutaja %1$s kutse. Põhjus: %2$s
%1$s kutsus kasutajat %2$s. Põhjus: %3$s
%1$s kutsus sind. Põhjus: %2$s
@@ -108,34 +91,25 @@
%1$s tühistas kasutajale %2$s saadetud kutse jututoaga liitumiseks. Põhjus: %3$s
%1$s võttis vastu kutse %2$s jututoaga liitumiseks. Põhjus: %3$s
%1$s võttis tagasi kasutajale %2$s saadetud kutse. Põhjus: %3$s
-
%1$s lülitas sisse läbiva krüptimise.
%1$s lülitas sisse läbiva krüptimise (tundmatu algoritm %2$s).
-
- %1$s lisas %2$s selle jututoa aadressiks.
- %1$s lisas %2$s selle jututoa aadressideks.
-
- %1$s eemaldas %2$s kui selle jututoa aadressi.
- %1$s eemaldas %2$s selle jututoa aadresside hulgast.
-
%1$s lisas %2$s ja eemaldas %3$s selle jututoa aadresside loendist.
-
%1$s seadistas selle jututoa põhiaadressiks %2$s.
%1$s eemaldas selle jututoa põhiaadressi.
-
%1$s lubas külalistel selle jututoaga liituda.
%1$s seadistas, et külalised ei või selle jututoaga liituda.
-
%s soovib verifitseerida sinu võtmeid, kuid sinu kasutatav klient ei oska vestluse-sisest verifitseerimist teha. Sa pead kasutama traditsioonilist verifitseerimislahendust.
-
Kasutaja %1$s lõi jututoa
Sina saatsid pildi.
Sina saatsid kleepsu.
-
Sinu kutse
Sa lõid jututoa
Sina kutsusid kasutajat %1$s
@@ -145,7 +119,7 @@
Sina müksasid %1$s välja
%1$s taastas %2$s ligipääsu
Sina taastasid %1$s ligipääsu
- %1$s keelas %1$s ligipääsu
+ %1$s keelas %2$s ligipääsu
Sina keelasid %1$s ligipääsu
Sina võtsid tagasi %1$s kutse
Sina muutsid oma tunnuspilti
@@ -165,7 +139,6 @@
Sa seadistasid, et tulevane jututoa ajalugu on nähtav kasutajale %1$s
Sa lülitasid sisse läbiva krüptimise (%1$s)
Sa uuendasid seda jututuba.
-
Sa algatasid VoIP rühmakõne
Sa eemaldasid jututoa nime
Sa eemaldasid jututoa teema
@@ -175,26 +148,22 @@
Sina saatsid kasutajale %1$s kutse jututoaga liitumiseks
Sina võtsid tagasi jututoaga liitumise kutse kasutajalt %1$s
Sina võtsid vastu kutse %1$s nimel
-
%1$s lisas %2$s vidina
Sina lisasid %1$s vidina
%1$s eemaldas %2$s vidina
Sina eemdaldasid %1$s vidina
%1$s muutis %2$s vidinat
Sa muutsid %1$s vidinat
-
Peakasutaja
Moderaator
Tavakasutaja
- Kohandatud kasutajaõigused (%1$s)
+ Kohandatud kasutajaõigused (%1$d)
Kohandatud õigused
-
Sina muutsid kasutaja %1$s õigusi.
%1$s muutis kasutaja %2$s õigusi.
%1$s õiguste muutus %2$s -> %3$s
-
Sinu kutse. Põhjus %1$s
- Sina kutsusid kasutajat %1$s. Põhjus: %1$s
+ Sina kutsusid kasutajat %1$s. Põhjus: %2$s
Sina liitusid jututoaga. Põhjus: %1$s
Sina lahkusid jututoast. Põhjus: %1$s
Sina lükkasid kutse tagasi. Põhjus: %1$s
@@ -207,26 +176,41 @@
Sina võtsid tagasi jututoaga liitumise kutse kasutajalt %1$s. Põhjus: %2$s
Sina võtsid vastu kutse %1$s nimel. Põhjus: %2$s
Sina võtsid tagasi kasutaja %1$s kutse. Põhjus: %2$s
-
- Sina lisasid %1$s selle jututoa aadressiks.
- Sina lisasid %1$s selle jututoa aadressideks.
-
- Sina eemaldasid %1$s, kui selle jututoa aadressi.
- Sina eemaldasid %1$s selle jututoa aadresside hulgast.
-
Sina lisasid %1$s selle jututoa aadressiks ning eemaldasid %2$s aadresside hulgast.
-
Sina seadistasid selle jututoa põhiaadressiks %1$s.
Sina eemaldasid selle jututoa põhiaadressi.
-
Sina lubasid külalistel selle jututoaga liituda.
Sina seadistasid, et külalised ei või selle jututoaga liituda.
-
Sa lülitasid sisse läbiva krüptimise.
Sa lülitasid sisse läbiva krüptimise (kasutusel on tundmatu algoritm %1$s).
-
-
+ Sina seadistasid, et külalised ei või selle jututoaga liituda.
+ %1$s seadistas, et külalised ei või selle jututoaga liituda.
+ Sina lubasid külalistel selle jututoaga liituda.
+ %1$s lubas külalistel selle jututoaga liituda.
+ Sina lahkusid jututoast. Põhjus: %1$s
+ %1$s lahkus jututoast. Põhjus: %2$s
+ Sina liitusid jututoaga. Põhjus: %1$s
+ %1$s liitus jututoaga. Põhjus: %2$s
+ Sina võtsid tagasi jututoaga liitumise kutse kasutajalt %1$s
+ %1$s võttis tagasi jututoaga liitumise kutse kasutajalt %2$s
+ Sina kutsusid kasutajat %1$s
+ %1$s kutsus kasutajat %2$s
+ Sa uuendasid seda jututuba.
+ %s uuendas seda jututuba.
+ Sina seadistasid, et tulevased jututoa sõnumid on nähtavad kasutajale %1$s
+ %1$s seadistas, et tulevased jututoa sõnumid on nähtavad kasutajale %2$s
+ Sina lahkusid jututoast
+ %1$s lahkus jututoast
+ Sina liitusid
+ %1$s liitus
+ Sina alustasid vestlust
+ %1$s alustas vestlust
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-fa/strings.xml b/matrix-sdk-android/src/main/res/values-fa/strings.xml
index b88a98459d..25d92b4abe 100644
--- a/matrix-sdk-android/src/main/res/values-fa/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-fa/strings.xml
@@ -40,10 +40,10 @@
(تصویر هم عوض شد)
%1$s نام اتاق را پاک کرد
%1$s موضوع اتاق را پاک کرد
- پیام پاک شد
- پیام به دست %1$s پاک شد
- پیام پاک شد [دلیل: %1$s]
- پیام به دست %1$s پاک شد [دلیل: %2$s]
+ پیام برداشته شد
+ پیام به دست %1$s برداشته شد
+ پیام برداشته شد [دلیل: %1$s]
+ پیام به دست %1$s برداشته شد [دلیل: %2$s]
%1$s دعوتی برای پیوستن %2$s به اتاق فرستاد
%1$s دعوت پیوستن به اتاق %2$s را باطل کرد
%1$s دعوت برای %2$s را پذیرفت
@@ -56,7 +56,7 @@
شکست در بارگذاری تصویر
خطای شبکه
- خطای ماتریس
+ خطای ماتریکس
در حال حاضر امکان بازپیوست به اتاقی خالی وجود ندارد.
@@ -135,6 +135,98 @@
%s درخواست تأیید کلیدتان را دارد، ولی کارخواهتان تأیید کلید درون گپ را پشتیبانی نمیکند. برای تأیید کلیدها لازم است از تأییدیهٔ کلید قدیمی استفاده کنید.
%1$s اتاق را ایجاد کرد
- %1$s نمایه خود را بهروز کرد %2$s
+ %1$s نمایهاش را بهروز کرد %2$s
نمیتوان ویرایش کرد
+ تصویری فرستادید.
+ برچسبی فرستادید.
+
+ دعوتتان
+ اتاق را ایجاد کردید
+ از %1$s دعوت کردید
+ به اتاق پیوستید
+ اتاق را ترک کردید
+ دعوت را رد کردید
+ %1$s را اخراج کردید
+ تحریم %1$s را برداشتید
+ %1$s را تحریم کردید
+ دعوت %1$s را پسگرفتید
+ آواتارتان را عوض کردید
+ نام نمایشیتان را به %1$s تغییر دادید
+ نام نمایشیتان را از %1$s به %2$s تغییر دادید
+ نام نمایشیتان را برداشتید (%1$s بود)
+ موضوع را به %1$s تغییر دادید
+ %1$s آواتار اتاق را تغییر داد
+ آواتار اتاق را تغییر دادید
+ نام اتاق را به %1$s تغییر دادید
+ تماس تصویری گرفتید.
+ تماس صوتی گرفتید.
+ %s برای برپایی تماس، داده فرستاد.
+ برای برپایی تماس، داده فرستادید.
+ تماس را پاسخ دادید.
+ به تماس پایان دادید.
+ تاریخچهٔ آتی اتاق را برای %1$s نمایان کردید
+ رمزنگاری سرتاسری را روشن کردید (%1$s)
+ این اتاق را ارتقا دادید.
+
+ دارخواست کنفرانس ویپ دادید
+ نام اتاق را برداشتید
+ موضوع اتاق را برداشتید
+ %1$s آواتار اتاق را برداشت
+ آواتار اتاق را برداشتید
+ نمایهتان را بهروز کردید %1$s
+ برای %1$s دعوت پیوستن به اتاق فرستادید
+ دعوت پیوستن %1$s به اتاق را پس گرفتید
+ دعوت برای %1$s را پذیرفتید
+
+ %1$s ابزارک %2$s را افزود
+ ابزارک %1$s را افزودید
+ %1$s ابزارک %2$s را برداشت
+ ابزارک %1$s را برداشتید
+ %1$s ابزارک %2$s را دستکاری کرد
+ ابزارک %1$s را دستکاری کردید
+
+ مدیر
+ ناظم
+ پیشگزیده
+ سفارشی (%1$d)
+ سفارشی
+
+ سطح قدرت %1$s را تغییر دادید.
+ %1$s سطح قدرت %2$s را تغییر داد.
+ %1$s از %2$s به %3$s
+
+ دعوتتان. دلیل: %1$s
+ %1$s را دعوت کردید. دلیل: %2$s
+ به اتاق پیوستید. دلیل: %1$s
+ اتاق را ترک کردید. دلیل: %1$s
+ دعوت را رد کردید. دلیل: %1$s
+ %1$s را اخراج کردید. دلیل: %2$s
+ تحریم %1$s را برداشتید. دلیل: %2$s
+ %1$s را تحریم کردید. دلیل: %2$s
+ دعوتی به %1$s برای پیوستن به اتاق فرستادید. دلیل: %2$s
+ دعوت %1$s برای پیوستن به اتاق را پس گرفتید. دلیل: %2$s
+ دعوت برای %1$s را پذیرفتید. دلیل: %2$s
+ دعوت %1$s را رد کردید. دلیل: %2$s
+
+
+ - نشانی %1$s را به این اتاق افزودید.
+ - نشانیهای %1$s را به این اتاق افزودید.
+
+
+
+ - نشانی %1$s ار از این اتاق برداشتید.
+ - نشانیهای %1$s ار از این اتاق برداشتید.
+
+
+ نشانی %1$s ار افزوده و %2$s را از این اتاق برداشتید.
+
+ نشانی اصلی این اتاق را به %1$s تنظیم کردید.
+ نشانی اصلی این اتاق را برداشتید.
+
+ به میهمانان اجازهٔ پیوستن به گروه دادید.
+ میمهانان را از پیوستن به گروه بازداشتید.
+
+ رمزنگاری سرتاسری را روشن کردید.
+ رمزنگاری سرتاسری را روشن کردید (الگوریتم ناشناخته %1$s).
+
diff --git a/matrix-sdk-android/src/main/res/values-it/strings.xml b/matrix-sdk-android/src/main/res/values-it/strings.xml
index cf081752a2..d4479be674 100644
--- a/matrix-sdk-android/src/main/res/values-it/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-it/strings.xml
@@ -1,8 +1,7 @@
-
+
%1$s: %2$s
%1$s ha inviato un\'immagine.
-
Invito di %s
%1$s ha invitato %2$s
%1$s ti ha invitato
@@ -30,55 +29,40 @@
chiunque.
sconosciuto (%s).
%1$s ha attivato la crittografia end-to-end (%2$s)
-
%1$s ha richiesto una conferenza VoIP
Conferenza VoIP iniziata
Conferenza VoIP terminata
-
(anche l\'avatar è cambiato)
%1$s ha rimosso il nome della stanza
%1$s ha rimosso l\'argomento della stanza
%1$s ha aggiornato il profilo %2$s
%1$s ha mandato un invito a %2$s per unirsi alla stanza
%1$s ha accettato l\'invito per %2$s
-
** Impossibile decriptare: %s **
Il dispositivo del mittente non ci ha inviato le chiavi per questo messaggio.
-
Impossibile revisionare
Impossibile inviare il messaggio
-
Invio dell\'immagine fallito
-
Errore di rete
Errore di Matrix
-
Al momento non è possibile rientrare in una stanza vuota.
-
Messaggio criptato
-
Indirizzo email
Numero di telefono
-
%1$s ha inviato un adesivo.
-
Invito da %s
Invito nella stanza
%1$s e %2$s
Stanza vuota
-
- %1$s e 1 altro
- %1$s e %2$d altri
-
-
Messaggio rimosso
Messaggio rimosso da %1$s
Messaggio rimosso [motivo: %1$s]
Messaggio rimosso da %1$s [motivo: %2$s]
-
Sync iniziale:
\nImportazione account…
Sync iniziale:
@@ -95,12 +79,9 @@
\nImportazione comunità
Sync iniziale:
\nImportazione dati account
-
%s ha aggiornato questa stanza.
-
Invio messaggio in corso …
Cancella la coda di invio
-
%1$s ha revocato l\'invito a %2$s di unirsi alla stanza
Invito di %1$s. Motivo: %2$s
%1$s ha invitato %2$s. Motivo: %3$s
@@ -115,34 +96,25 @@
%1$s ha revocato l\'invito a %2$s di unirsi alla stanza. Motivo: %3$s
%1$s ha accettato l\'invito per %2$s. Motivo: %3$s
%1$s ha rifiutato l\'invito di %2$s. Motivo: %3$s
-
- %1$s ha aggiunto %2$s come indirizzo per questa stanza.
- %1$s ha aggiunto %2$s come indirizzi per questa stanza.
-
- %1$s ha rimosso %2$s come indirizzo per questa stanza.
- %1$s ha rimosso %3$s come indirizzi per questa stanza.
-
%1$s ha aggiunto %2$s e rimosso %3$s come indirizzi per questa stanza.
-
%1$s ha impostato l\'indirizzo principale per questa stanza a %2$s.
%1$s ha rimosso l\'indirizzo principale per questa stanza.
-
%1$s ha permesso l\'accesso alla stanza per gli ospiti.
%1$s ha impedito l\'accesso alla stanza per gli ospiti.
-
%1$s ha attivato la cifratura end-to-end.
%1$s ha attivato la cifratura end-to-end (algoritmo %2$s non riconosciuto).
-
%s sta chiedendo di verificare la tua chiave, ma il tuo client non supporta la verifica in-chat. Dovrai usare il metodo di verifica obsoleto per verificare le chiavi.
-
%1$s ha creato la stanza
Hai inviato un\'immagine.
Hai inviato un adesivo.
-
Il tuo invito
Hai creato la stanza
Hai invitato %1$s
@@ -170,7 +142,6 @@
Hai reso visibile la futura cronologia della stanza a %1$s
Hai attivato la crittografia end-to-end (%1$s)
Hai aggiornato questa stanza.
-
Hai richiesto una conferenza VoIP
Hai rimosso il nome della stanza
Hai rimosso l\'argomento della stanza
@@ -180,24 +151,20 @@
Hai mandato un invito a %1$s a unirsi alla stanza
Hai revocato l\'invito per %1$s a unirsi alla stanza
Hai accettato l\'invito per %1$s
-
%1$s ha aggiunto il widget %2$s
Hai aggiunto il widget %1$s
%1$s ha rimosso il widget %2$s
Hai rimosso il widget %1$s
%1$s ha modificato il widget %2$s
Hai modificato il widget %1$s
-
Amministratore
Moderatore
Predefinito
Personalizzato (%1$d)
Personalizzato
-
Hai cambiato il livello di potere di %1$s.
%1$s ha cambiato il livello di potere di %2$s.
%1$s da %2$s a %3$s
-
Il tuo invito. Motivo: %1$s
Hai invitato %1$s. Motivo: %2$s
Sei entrato nella stanza. Motivo: %1$s
@@ -209,27 +176,42 @@
Hai mandato un invito a %1$s a unirsi alla stanza. Motivo: %2$s
Hai revocato l\'invito a %1$s a unirsi alla stanza. Motivo: %2$s
Hai accettato l\'invito per %1$s. Motivo: %2$s
- Hai ritirato l\'invito di %2$s. Motivo: %2$s
-
+ Hai ritirato l\'invito di %1$s. Motivo: %2$s
- Hai aggiunto %1$s come indirizzo per questa stanza.
- Hai aggiunto %1$s come indirizzi per questa stanza.
-
- Hai rimosso %1$s come indirizzo per questa stanza.
- Hai rimosso %2$s come indirizzi per questa stanza.
-
Hai aggiunto %1$s e rimosso %2$s come indirizzi per questa stanza.
-
Hai impostato l\'indirizzo principale per questa stanza a %1$s.
Hai rimosso l\'indirizzo principale per questa stanza.
-
Hai permesso l\'accesso alla stanza per gli ospiti.
Hai impedito l\'accesso alla stanza per gli ospiti.
-
Hai attivato la crittografia end-to-end.
Hai attivato la crittografia end-to-end (algoritmo %1$s sconosciuto).
-
-
+ Hai impedito l\'accesso alla stanza agli ospiti.
+ %1$s ha impedito l\'accesso alla stanza agli ospiti.
+ Hai permesso l\'accesso agli ospiti.
+ %1$s ha permesso l\'accesso agli ospiti.
+ Sei entrato. Motivo: %1$s
+ Sei uscito. Motivo: %1$s
+ %1$s è uscito. Motivo: %2$s
+ %1$s è entrato. Motivo: %2$s
+ Hai revocato l\'invito per %1$s
+ %1$s ha revocato l\'invito per %2$s
+ Hai invitato %1$s
+ %1$s ha invitato %2$s
+ Hai aggiornato la stanza.
+ %s ha aggiornato la stanza.
+ Hai reso visibili i messaggi futuri a %1$s
+ %1$s ha reso visibili i messaggi futuri a %2$s
+ Sei uscito dalla stanza
+ %1$s è uscito dalla stanza
+ Sei entrato
+ %1$s è entrato
+ Hai creato la discussione
+ %1$s ha creato la discussione
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-kab/strings.xml b/matrix-sdk-android/src/main/res/values-kab/strings.xml
index 0d1cad6550..e557a7c824 100644
--- a/matrix-sdk-android/src/main/res/values-kab/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-kab/strings.xml
@@ -1,4 +1,4 @@
-
+
%1$s: %2$s
%1$s t.yuzen tugna.
@@ -23,16 +23,11 @@
Aseɣyad
Amezwer
Sagen
-
%1$s seg %2$s ɣer %3$s
-
Tegguma ad d-tali tugna
-
Tansa n yimayl
-
%1$s azen astiker.
Tuzneḍ amenṭaḍ.
-
%1$s yekkes agdal i %2$s
Tekkseḍ agdal i %1$s
%1$s igdel %2$s
@@ -46,12 +41,12 @@
Tbeddleḍ isem-ik•im i d-ittuseknen seg %1$s ɣer %2$s
%1$s yekkes isem-is i d-ittuseknen (yella %2$s)
Tekkseḍ isem-ik·im yettwaskanen (d %1$s)
- %1$S isnifel asentel s: %2$S
- Tesnifleḍ asentel s: %2$S
+ %1$s isnifel asentel s: %2$s
+ Tesnifleḍ asentel s: %1$s
%1$s ibeddel avaṭar n texxamt
Tbeddleḍ avaṭar n texxamt
%1$s ibeddel isem n texxamt s: %2$s
- Tbeddleḍ isem n texxamt s: %2$s
+ Tbeddleḍ isem n texxamt s: %1$s
%s isɛedda siwel s tvidyut.
Tesɛeddaḍ siwel s tvidyut.
%s isɛedda asiwel s taɣect.
@@ -68,15 +63,13 @@
yal yiwen.
arussin (%s).
%1$s isermed awgelhen seg yixef ɣer yixef (%2$s)
- Tesremdeḍ awgelhen seg yixef ɣer yixef (%2$s)
+ Tesremdeḍ awgelhen seg yixef ɣer yixef (%1$s)
%s ileqqem taxxamt-a.
Tleqqmeḍ taxxamt-a.
-
%1$s isuter-d asarag VoIP
Tsutreḍ-d asarag VoIP
Asarag VoIP yebda
Asarag VoIP yekfa
-
(avatar daɣen ibeddel)
%1$s yekkes isem n texxamt
Tekkseḍ isem n texxamt
@@ -94,51 +87,37 @@
Tuzneḍ tinubga i %1$s akken ad yeddu ɣer texxamt
%1$s iqbel tinubga i %2$s
Tqebleḍ tinubga i %1$s
-
%1$s yerna awiǧit %2$s
Terniḍ awiǧit %1$s
%1$s yekkes awiǧit %2$s
Tekkseḍ awiǧit %1$s
%1$s ibeddel awiǧit %2$s
Tbeddleḍ awiǧit %1$s
-
- Sagen (%1$)
+ Sagen (%1$d)
Tbeddleḍ aswir n tezmert n %1$s.
%1$s ibeddel aswir n tezmert n %2$s.
** Awgelhen d awezɣi: %s **
Ibenk n umazan ur aɣ-d-yuzin ara tisura i yizen-a.
-
Tuzna n yizen d tawezɣit
-
Tuccḍa deg uẓeṭṭa
Tuccḍa deg Matrix
-
%1$s iga amazray n texxamyt i d-iteddun yettban i %2$s
Tgiḍ amazray n texxamyt i d-iteddun yettban i %1$s
%1$s issefsax tinubga i %2$s i wakken ad d-yekcem ɣer texxamt
Tesfesxeḍ tinubga i %1$s i wakken ad d-yernu ɣer texxamt
D awezɣi tura ad nales ad nuɣal ɣer texxamt tilemt.
-
Izen yettwawgelhen
-
Uṭṭun n tiliɣri
-
Tinubga sɣur %s
Tinubga ɣer texxamt
-
%1$s d %2$s
-
- %1$s d 1 wayeḍ
- %1$s d %2$d wiyaḍ
-
Tremdeḍ awgelhen seg yixef ɣer yixef (alguritm %1$s ur yettwassen ara).
-
%s isuter-d ad isenqed tasarut-ik·im, maca amsaɣ-ik·im ur issefrak ara asenqed n tsura deg yidiwenniyen. Ilaq-ak·am useqdec asenqed iqdim n tsura i usenqed n tsura.
-
Taxxamt tilemt
-
Amtawi n tazwara:
\nAktar n umiḍan…
Amtawi n tazwara:
@@ -155,7 +134,6 @@
\nAktar n tmezdagnutin
Amtawi n tazwara:
\nAktar n yisefka n umiḍan
-
Tuzzna n yizen…
Tinubga n %1$s. Tamentilt: %2$s
Tinubga-k•m. Tamentilt: %1$s
@@ -180,46 +158,37 @@
Tqebleḍ tinubga i %1$s. Tamentilt: %2$s
%1$s issefsex tinubga n %2$s. Tamentilt: %3$s
Tesfesxeḍ tinubga n %1$s. Tamentilt: %2$s
-
- %1$s yerna %2$s d tansa i texxamt-a.
- %1$s yerna %2$s d tansiwin i texxamt-a.
-
- Terniḍ %1$s d tansa i texxamt-a.
- Terniḍ %1$s d tansiwin i texxamt-a.
-
- %1$s yekkes %2$s am tansa i texxamt-a.
- %1$s yekkes %3$s am tansiwin i texxamt-a.
-
- Tekkseḍ %1$s am tansa i texxamt-a.
- Tekkseḍ %2$s am tansiwin i texxamt-a.
-
- %1$s yerna %2$s terniḍ tekkseḍ %3s am tansiwin i texxamt-a.
+ %1$s yerna %2$s terniḍ tekkseḍ %3$s am tansiwin i texxamt-a.
Terniḍ %1$s terniḍ tekkseḍ %2$s am tansiwin i texxamt-a.
-
%1$s isbadu %2$s am tansa tagejdant i texxamt-a.
Tesbaduḍ %1$s am tansa tagejdant i texxamt-a.
%1$s yekkes tansa tagejdant i texxamt-a.
Tekkseḍ tansa tagejdant i texxamt-a.
-
%1$s isireg inebgawen ad ddun ɣer texxamt.
Tsirgeḍ inebgawen ad ddun ɣer texxamt.
%1$s issewḥel inebgawen iwakken ur tteddun ara ɣer texxamt.
Tesweḥleḍ inebgawen iwakken ur tteddun ara ɣer texxamt.
-
%1$s yermed awgelhen seg yixef ɣer yixef.
Tremdeḍ awgelhen seg yixef ɣer yixef.
%1$s yermed awgelhen seg yixef ɣer yixef (alguritm %2$s ur yettwassen ara).
Sfeḍ tabdart n uraǧu n tuzzna
-
- %1$s issefsex tinubga n %2$s i tmerniwt ɣer texxamt. Tamentilt: %2$s
+ %1$s issefsex tinubga n %2$s i tmerniwt ɣer texxamt. Tamentilt: %3$s
Tesfesxeḍ tinubga n %1$s i tmerna ɣer texxamt. Tamentilt: %2$s
Yegguma ad yaru
-
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
index 52b935c097..4e62a21c0e 100644
--- a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
@@ -1,9 +1,7 @@
-
+
-
%1$s: %2$s
%1$s enviou uma foto.
-
convite de %s
%1$s convidou %2$s
%1$s convidou você
@@ -31,64 +29,45 @@
qualquer pessoa.
desconhecido (%s).
%1$s ativou a criptografia de ponta a ponta (%2$s)
-
%1$s deseja iniciar uma chamada em grupo
Chamada em grupo iniciada
Chamada em grupo encerrada
-
(a foto de perfil também foi alterada)
%1$s removeu o nome da sala
%1$s removeu a descrição da sala
%1$s atualizou o perfil %2$s
%1$s enviou um convite para %2$s entrar na sala
%1$s aceitou o convite para %2$s
-
** Não foi possível descriptografar: %s **
O aparelho do remetente não nos enviou as chaves para esta mensagem.
-
Não foi possível redigir
Não foi possível enviar a mensagem
-
O envio da imagem falhou
-
Erro de conexão à internet
Erro no servidor Matrix
-
-
-
-
Atualmente, não é possível entrar novamente em uma sala vazia.
-
Mensagem criptografada
-
Endereço de e-mail
Número de telefone
-
-
%1$s enviou uma figurinha.
-
Convite de %s
Convite para sala
%1$s e %2$s
Sala vazia
-
- %1$s e 1 outro
- %1$s e %2$d outros
-
-
Você enviou uma foto.
Você enviou uma figurinha.
-
Seu convite
%1$s criou a sala
Você criou a sala
@@ -118,7 +97,6 @@
Você ativou a criptografia de ponta a ponta (%1$s)
%s atualizou esta sala.
Você atualizou esta sala.
-
Você solicitou uma chamada em grupo
Você removeu o nome da sala
Você removeu a descrição da sala
@@ -133,24 +111,20 @@
%1$s cancelou o convite a %2$s para entrar na sala
Você cancelou o convite a %1$s para entrar na sala
Você aceitou o convite para %1$s
-
%1$s adicionou o widget %2$s
Você adicionou o widget %1$s
%1$s removeu o widget %2$s
Você removeu o widget %1$s
%1$s editou o widget %2$s
Você editou o widget %1$s
-
Administrador
Moderador
Padrão
Personalizado (%1$d)
Personalizado
-
Você alterou o nível de permissão de %1$s.
%1$s alterou o nível de permissão de %2$s.
%1$s de %2$s para %3$s
-
Primeira sincronização:
\nImportando a conta…
Primeira sincronização:
@@ -167,10 +141,8 @@
\nImportando as comunidades
Primeira sincronização:
\nImportando os dados da conta
-
Enviando mensagem…
Limpar a fila de envio
-
Convite de %1$s. Motivo: %2$s
O seu convite. Motivo: %1$s
%1$s convidou %2$s. Motivo: %3$s
@@ -196,45 +168,57 @@
Você aceitou o convite para %1$s. Motivo: %2$s
%1$s desfez o convite de %2$s. Motivo: %3$s
Você desfez o convite de %1$s. Motivo: %2$s
-
- %1$s adicionou %2$s como um endereço desta sala.
- %1$s adicionou %2$s como endereços desta sala.
-
- Você adicionou %1$s como um endereço desta sala.
- Você adicionou %1$s como endereços desta sala.
-
- %1$s removeu %2$s como um endereço desta sala.
- %1$s removeu %3$s como endereços desta sala.
-
- Você removeu %1$s como um endereço desta sala.
- Você removeu %2$s como endereços desta sala.
-
%1$s adicionou %2$s e removeu %3$s como endereços desta sala.
Você adicionou %1$s e removeu %2$s como endereços desta sala.
-
%1$s definiu o endereço principal desta sala como %2$s.
Você definiu o endereço principal desta sala como %1$s.
%1$s removeu o endereço principal desta sala.
Você removeu o endereço principal desta sala.
-
%1$s permitiu que convidados entrem na sala.
Você permitiu que convidados entrem na sala.
%1$s impediu que convidados entrassem na sala.
Você impediu que convidados entrassem na sala.
-
%1$s ativou a criptografia de ponta a ponta.
Você ativou a criptografia de ponta a ponta.
%1$s ativou a criptografia de ponta a ponta (algoritmo não reconhecido %2$s).
Você ativou a criptografia de ponta a ponta (algoritmo não reconhecido %1$s).
-
%s deseja confirmar a sua chave, mas o seu aplicativo não suporta a confirmação da chave da conversa. Você precisará usar a confirmação tradicional de chaves para confirmar chaves.
-
-
+ Você impediu que desconhecidos entrem na sala.
+ %1$s impediu que desconhecidos entrem na sala.
+ Você permitiu que desconhecidos entrem aqui.
+ %1$s permitiu que desconhecidos entrem aqui.
+ Você saiu. Motivo: %1$s
+ %1$s saiu. Motivo: %2$s
+ Você entrou. Motivo: %1$s
+ %1$s entrou. Motivo: %2$s
+ Você cancelou o convite para %1$s
+ %1$s cancelou o convite para %2$s
+ Você convidou %1$s
+ %1$s convidou %2$s
+ Você atualizou esta sala.
+ %s atualizou esta sala.
+ Você definiu que as mensagens enviadas a partir do presente momento estarão disponíveis para %1$s
+ %1$s definiu que as mensagens enviadas a partir do presente momento estarão disponíveis para %2$s
+ Você saiu da sala
+ %1$s saiu da sala
+ Você entrou
+ %1$s entrou
+ Você criou a sala
+ %1$s criou a sala
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-ru/strings.xml b/matrix-sdk-android/src/main/res/values-ru/strings.xml
index a4d752782e..97643a34fe 100644
--- a/matrix-sdk-android/src/main/res/values-ru/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml
@@ -1,9 +1,7 @@
-
+
-
%1$s: %2$s
%1$s отправил(а) изображение.
-
%s приглашение
%1$s пригласил(а) %2$s
%1$s пригласил(а) вас
@@ -31,67 +29,49 @@
всем.
неизвестно (%s).
%1$s включил(а) сквозное шифрование (%2$s)
-
%1$s запросил(а) VoIP конференцию
VoIP-конференция начата
VoIP-конференция завершена
-
(аватар также был изменен)
%1$s удалил(а) название комнаты
%1$s удалил(а) тему комнаты
%1$s обновил(а) свой профиль %2$s
%1$s отправил(а) приглашение %2$s присоединиться к комнате
%1$s принял(а) приглашение от %2$s
-
** Невозможно расшифровать: %s **
Устройство отправителя не предоставило нам ключ для расшифровки этого сообщения.
-
Не удалось изменить
Не удалось отправить сообщение
-
Не удалось загрузить изображение
-
Сетевая ошибка
Ошибка Matrix
-
-
-
-
В настоящее время невозможно вновь присоединиться к пустой комнате.
-
Зашифрованное сообщение
-
Адрес электронной почты
Номер телефона
-
%1$s отправил стикер.
-
Приглашение от %s
Приглашение в комнату
%1$s и %2$s
Пустая комната
-
- %1$s и 1 другой
- %1$s и %2$d другие
- %1$s и %2$d других
-
+
-
-
Сообщение удалено
%1$s удалил(а) сообщение
Сообщение удалено [причина: %1$s]
%1$s удалил(а) сообщение [причина: %2$s]
-
Начальная синхронизация:
\nИмпорт учетной записи…
Начальная синхронизация:
@@ -108,12 +88,9 @@
\nИмпорт сообществ
Начальная синхронизация:
\nИмпорт данных учетной записи
-
%s обновил эту комнату.
-
Отправка сообщения…
Очистить очередь отправки
-
%1$s отозвал приглашение %2$s присоединиться к комнате
Приглашение %1$s. Причина: %2$s
%1$s приглашен %2$s. Причина: %3$s
@@ -128,36 +105,27 @@
%1$s отозвал приглашение %2$s присоединиться к комнате. Причина: %3$s
%1$s принял приглашение для %2$s. Причина: %3$s
%1$s отозвал приглашение %2$s. Причина: %3$s
-
%1$s создал(а) комнату
- %1$s добавил(а) %2$s в качестве адреса для этой комнаты.
- %1$s добавил(а) %2$s в качестве адресов для этой комнаты.
- %1$s добавил(а) %2$s в качестве адресов для этой комнаты.
-
- %1$s удалил(а) адрес %2$s для комнаты.
- %1$s удалил(а) адреса %2$s для комнаты.
- %1$s удалил(а) адреса %2$s для комнаты.
-
%1$s добавил(а) адреса %2$s и удалил(а) %3$s для комнаты.
-
%1$s сделал(а) %2$s главным адресом комнаты.
%1$s удалил(а) главный адрес комнаты.
-
%1$s разрешил(а) гостям входить в комнату.
%1$s запретил(а) гостям входить в комнату.
-
%1$s включил(а) сквозное шифрование.
%1$s включил(а) сквозное шифрование (неизвестный алгоритм %2$s).
-
%s запрашивает подтверждение вашего ключа, но ваш клиент не поддерживает подтверждение в чате. Используйте устаревшую проверку для сверки ключей.
-
Вы отправили изображение.
Вы отправили стикер.
-
Ваше приглашение
Вы создали комнату
Вы пригласили %1$s
@@ -181,7 +149,6 @@
Вы сделали будущую историю комнаты видимой для %1$s
Вы включили сквозное шифрование (%1$s)
Вы обновили эту комнату.
-
Вы начали групповой звонок
Вы удалили название комнаты
Вы удалили тему комнаты
@@ -189,24 +156,20 @@
Вы отправили %1$s приглашение в эту комнату
Вы отозвали у %1$s приглашение в эту комнату
Вы приняли приглашение для %1$s
-
%1$s добавил(а) виджет %2$s
Вы добавили виджет %1$s
%1$s удалил(а) виджет %2$s
Вы удалили виджет %1$s
%1$s изменил(а) виджет %2$s
Вы изменили виджет %1$s
-
Администратор
Модератор
По умолчанию
Пользовательский (%1$d)
Пользовательский
-
Вы изменили уровни доступа %1$s.
%1$s изменил(а) уровни доступа %2$s.
%1$s с %2$s на %3$s
-
Ваше приглашение. Причина: %1$s
Вы пригласили %1$s. Причина: %2$s
Вы вошли в комнату. Причина: %1$s
@@ -219,35 +182,47 @@
Вы отозвали у %1$s приглашение в эту комнату. Причина: %2$s
Вы приняли приглашение для %1$s. Причина: %2$s
Вы отозвали приглашение %1$s. Причина: %2$s
-
- Вы добавили адрес %1$s для этой комнаты.
- Вы добавили %1$s в качестве адресов для этой комнаты.
- Вы добавили %1$s в качестве адресов для этой комнаты.
-
- Вы удалили адрес этой комнаты: %1$s.
- Вы удалили адреса этой комнаты: %1$s.
- Вы удалили адреса этой комнаты: %1$s.
-
Вы добавили адреса %1$s и удалили %2$s для этой комнаты.
-
Вы задали главный адрес этой комнаты %1$s.
Вы удалили главный адрес этой комнаты.
-
Вы разрешили гостям входить в комнату.
Вы запретили гостям входить в комнату.
-
Вы включили сквозное шифрование.
Вы включили сквозное шифрование (неизвестный алгоритм %1$s).
-
%1$s изменил(а) аватар комнаты
Вы изменили аватар комнаты
%s отправил(а) данные для начала звонка.
Вы отправили данные для начала звонка.
%1$s удалил(а) аватар комнаты
Вы удалили аватар комнаты
-
-
+ Вы запретили гостям входить в комнату.
+ %1$s запретил(а) гостям входить в комнату.
+ Вы разрешили гостям присоединяться сюда.
+ %1$s разрешил(а) гостям присоединиться сюда.
+ Вы вышли. Причина: %1$s
+ %1$s вышел(-ла). Причина: %2$s
+ Вы вошли. Причина: %1$s
+ %1$s вошел(-ла). Причина: %2$s
+ Вы отозвали приглашение %1$s
+ %1$s отозвал(а) приглашение %2$s
+ Вы пригласили %1$s
+ %1$s пригласил(а) %2$s
+ Вы сделали будущие сообщения видимыми для %1$s
+ %1$s сделал(а) будущие сообщения видимыми для %2$s
+ Вы покинули комнату
+ %1$s покинул(а) комнату
+ Вы вошли
+ %1$s вошел(ла)
+ Вы создали обсуждение
+ %1$s создал(а) обсуждение
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-sk/strings.xml b/matrix-sdk-android/src/main/res/values-sk/strings.xml
index da869eacc2..c75c8b4832 100644
--- a/matrix-sdk-android/src/main/res/values-sk/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sk/strings.xml
@@ -1,8 +1,7 @@
-
+
%1$s: %2$s
%1$s poslal/a obrázok.
-
Pozvanie od %s
%1$s pozval/a %2$s
%1$s vás pozval/a
@@ -30,58 +29,42 @@
pre každého.
neznámym (%s).
%1$s povolil/a E2E šifrovanie (%2$s)
-
%1$s požiadal/a o VoIP konferenciu
Začala sa VoIP konferencia
Skončila sa VoIP konferencia
-
(aj obrázok v profile)
%1$s odstránil/a názov miestnosti
%1$s odstránil/a tému miestnosti
%1$s aktualizoval/a svoj profil %2$s
%1$s pozval/a %2$s vstúpiť do miestnosti
%1$s prijal/a pozvanie pre %2$s
-
** Nie je možné dešifrovať: %s **
Zo zariadenia odosieľateľa nebolo možné získať kľúče potrebné na dešifrovanie tejto správy.
-
Nie je možné vymazať
Nie je možné odoslať správu
-
Nepodarilo sa nahrať obrázok
-
Chyba siete
Chyba Matrix
-
V súčasnosti nie je možné znovu vstúpiť do prázdnej miestnosti.
-
Šifrovaná správa
-
Emailová adresa
Telefónne číslo
-
%1$s poslal/a nálepku.
-
Pozvanie od %s
Pozvanie do miestnosti
%1$s a %2$s
Prázdna miestnosť
-
- %1$s a 1 ďalší
- %1$s a %2$d ďalší
- %1$s a %2$d ďalších
-
+
-
-
%s aktualizoval/a túto miestnosť.
-
Odstránená správa
Odstránená správa používateľom %1$s
Odstránená správa [dôvod: %1$s]
Odstránená správa používateľom %1$s [dôvod: %2$s]
-
Úvodná synchronizácia:
\nPrebieha import účtu…
Úvodná synchronizácia:
@@ -98,10 +81,8 @@
\nPrebieha import komunít
Úvodná synchronizácia:
\nPrebieha import údajov účtu
-
Odosielanie správy…
Vymazať správy na odoslanie
-
%1$s zamietol/a pozvanie používateľa %2$s vstúpiť do miestnosti
Pozvanie od %1$s. Dôvod: %2$s
%1$s pozval/a %2$s. Dôvod: %3$s
@@ -116,28 +97,22 @@
%1$s zamietol/a pozvanie používateľa %2$s vstúpiť do miestnosti. Dôvod: %3$s
%1$s prijal/a pozvanie pre %2$s. Dôvod: %3$s
%1$s vzal/a späť pozvanie %2$s. Dôvod: %3$s
-
- %1$s pridal/a adresu %2$s pre túto miestnosť.
- %1$s pridal/a adresy %2$s pre túto miestnosť.
- %1$s pridal/a adresy %2$s pre túto miestnosť.
-
- %1$s odstránil/a adresu %2$s pre túto miestnosť.
- %1$s odstránil/a adresy %3$s pre túto miestnosť.
- %1$s odstránil/a adresy %3$s pre túto miestnosť.
-
%1$s pridal/a adresy %2$s a odstránil/a adresy %3$s pre túto miestnosť.
-
%1$s nastavil/a hlavnú adresu tejto miestnosti %2$s.
%1$s odstránil/a hlavnú adresu tejto miestnosti.
-
%1$s povolil/a hosťom///návštevníkom prístup do tejto miestnosti.
Poslali ste obrázok.
Poslali ste nálepku.
-
Pozvanie od vás
%1$s vytvoril/a miestnosť
Vytvorili ste miestnosť
@@ -166,7 +141,6 @@
Sprístupnili ste budúcu históriu miestnosti %1$s
Povolili ste E2E šifrovanie (%1$s)
Aktualizovali ste túto miestnosť.
-
Požiadali ste o VoIP konferenciu
Odstránili ste názov miestnosti
Odstránili ste tému miestnosti
@@ -174,26 +148,22 @@
Odstránili ste obrázok miestnosti
Aktualizovali ste svoj profil %1$s
Pozvali ste %1$s vstúpiť do miestnosti
- Zamietli ste pozvanie používateľa %2$s vstúpiť do miestnosti
+ Zamietli ste pozvanie používateľa %1$s vstúpiť do miestnosti
Prijali ste pozvanie pre %1$s
-
%1$s pridal/a widget %2$s
Pridali ste widget %1$s
%1$s odstránil/a widget %2$s
Odstránili ste widget %1$s
%1$s upravil/a widget %2$s
Upravili ste widget %1$s
-
Správca
Moderátor
Predvolený
Vlastná úroveň (%1$d)
Vlastná úroveň
-
Zmenili ste úroveň moci používateľa %1$s.
%1$s zmenil úroveň moci používateľa %2$s.
%1$s z %2$s na %3$s
-
Pozvanie od vás. Dôvod: %1$s
Pozvali ste %1$s. Dôvod: %2$s
Vstúpili ste do miestnosti. Dôvod: %1$s
@@ -206,33 +176,25 @@
Zamietli ste pozvanie používateľa %1$s vstúpiť do miestnosti. Dôvod: %2$s
Prijali ste pozvanie pre %1$s. Dôvod: %2$s
Vzali ste späť pozvanie %1$s. Dôvod: %2$s
-
- Pridali ste adresu %1$s pre túto miestnosť.
- Pridali ste adresy %1$s pre túto miestnosť.
- Pridali ste adresy %1$s pre túto miestnosť.
-
- Odstránili ste adresu %1$s pre túto miestnosť.
- Odstránili ste adresy %2$s pre túto miestnosť.
- Odstránili ste adresy %2$s pre túto miestnosť.
-
Pridali ste %1$s a odstránili adresy %2$s pre túto miestnosť.
-
- Nastavili ste hlavnú adresu tejto miestnosti %2$s.
+ Nastavili ste hlavnú adresu tejto miestnosti %1$s.
Odstránili ste hlavnú adresu tejto miestnosti.
-
Povolili ste hosťom///návštevníkom prístup do tejto miestnosti.
%1$s zakázal/a hosťom///návštevníkom prístup do tejto miestnosti.
Zakázali ste hosťom///návštevníkom prístup do tejto miestnosti.
-
%1$s povolil/a E2E šifrovanie.
Povolili ste E2E šifrovanie.
%1$s povolil/a E2E šifrovanie (Nerozpoznaný algorytmus %2$s).
Povolili ste E2E šifrovanie (Nerozpoznaný algorytmus %1$s).
-
%s požaduje overenie vašich šifrovacích kľúčov, ale váš klient nepodporuje overenie kľúčov v konverzácii. Budete musieť použiť zastaralú metódu overenia.
-
-
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-sq/strings.xml b/matrix-sdk-android/src/main/res/values-sq/strings.xml
index 14a7c61bbc..9756a11762 100644
--- a/matrix-sdk-android/src/main/res/values-sq/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml
@@ -138,4 +138,96 @@
%s po kërkon të verifikojë kyçin tuaj, por klienti juaj nuk mbulon verifikim kyçesh brenda fjalosjeje. Që të verifikoni kyça, do t’ju duhet të përdorni verifikim të dikurshëm kyçesh.
%1$s krijo dhomën
+ Dërguat një figurë.
+ Dërguat një ngjitës.
+
+ Ftesa juaj
+ Krijuat dhomën
+ Ftuat %1$s
+ Hytë në dhomë
+ Dolët nga dhoma
+ Hodhët poshtë ftesën
+ Përzutë %1$s
+ Hoqët dëbimin për %1$s
+ Dëbuat %1$s
+ Tërhoqët mbrapsht ftesën për %1$s
+ Ndryshuat avatarin tuaj
+ Caktuat si emrin tuaj në ekran %1$s
+ E ndryshuat emrin tuaj në ekran nga %1$s në %2$s
+ Hoqët emrin tuaj në ekran (qe %1$s)
+ E ndryshuat temën në: %1$s
+ %1$s ndryshoi avatarin e dhomës
+ Ndryshuat avatarin e dhomës
+ Ndryshuat emrin e dhomës në: %1$s
+ Filluat një thirrje video.
+ Filluat një thirrje zanore.
+ %s dërgoi të dhëna për ujdisjen e thirrjes.
+ Dërguat të dhëna për ujdisjen e thirrjes.
+ Iu përgjigjët thirrjes.
+ E përfunduat thirrjen.
+ E bëtë historikun e ardhshëm të dhomë të dukshëm për %1$s
+ Aktivizuat fshehtëzim skaj-më-skaj (%1$s)
+ Përmirësuat këtë dhomë.
+
+ Kërkuat një konferencë VoIP
+ Hoqët emrin e dhomës
+ Hoqët temën e dhomës
+ %1$s hoqi avatarin e dhomës
+ Hoqët avatarin e dhomës
+ Përditësuat profilin tuaj %1$s
+ Dërguat një ftesë te %1$s për të ardhur te dhoma
+ Shfuqizuat ftesën për ardhjen në dhomë të %1$s
+ Pranuat ftesën për %1$s
+
+ %1$s shtoi widget-in %2$s
+ Shtuat widget-in %1$s
+ %1$s hoqi widget-in %2$s
+ Hoqët widget-in %1$s
+ %1$s ndryshoi widget-in %2$s
+ Ndryshuat widget-in %1$s
+
+ Përgjegjës
+ Moderator
+ Parazgjedhje
+ Vetjake (%1$d)
+ Vetjake
+
+ Ndryshuat shkallën e pushtetit për %1$s.
+ %1$s ndryshoi shkallën e pushtetit për %2$s.
+ %1$s nga %2$s në %3$s
+
+ Ftesa juaj. Arsye: %1$s
+ Ftuat %1$s. Arsye: %2$s
+ Erdhët në dhomë, Arsye: %1$s
+ Ikët nga dhoma. Arsye: %1$s
+ Hodhët poshtë ftesën. Arsye: %1$s
+ Përzutë %1$s. Arsye: %2$s
+ Hoqët dëbimin për %1$s. Arsye: %2$s
+ Dëbuat %1$s. Arsye: %2$s
+ Dërguat një ftesë për %1$s të vijë në dhomë. Arsye: %2$s
+ Shfuqizuat ftesën për ardhjen në dhomë të %1$s. Arsye: %2$s
+ Pranuat ftesën për %1$s. Arsye: %2$s
+ Tërhoqët mbrapsht ftesën për %1$s. Arsye: %2$s
+
+
+ - Shtuat %1$s si një adresë për këtë dhomë.
+ - Shtuat %1$s si adresa për këtë dhomë.
+
+
+
+ - Hoqët %1$s si një adresë për këtë dhomë.
+ - Hoqët %1$s si adresa për këtë dhomë.
+
+
+ Shtuat %1$s dhe hoqët %2$s si adresa për këtë dhomë.
+
+ Caktuat si adresë kryesore për këtë dhomë %1$s.
+ Hoqët adresën kryesore për këtë dhomë.
+
+ Keni lejuar të vijnë mysafirë në dhomë.
+ U keni penguar mysafirëve të vijnë në dhomë.
+
+ Aktivizuat fshehtëzimin skaj-më-skaj.
+ Aktivizuat fshehtëzimin skaj-më-skaj (algoritëm %1$s i panjohur).
+
diff --git a/matrix-sdk-android/src/main/res/values-sv/strings.xml b/matrix-sdk-android/src/main/res/values-sv/strings.xml
index 8f8ba5e306..25e51b69e5 100644
--- a/matrix-sdk-android/src/main/res/values-sv/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sv/strings.xml
@@ -1,11 +1,10 @@
-
+
%1$s: %2$s
%1$s skickade en bild.
Du skickade en bild.
%1$s skickade en dekal.
Du skickade en dekal.
-
Inbjudan från %s
Inbjudan från dig
%1$s skapade rummet
@@ -62,12 +61,10 @@
Du aktiverade totalsträckskryptering (%1$s)
%s uppgraderade det här rummet.
Du uppgraderade det här rummet.
-
%1$s begärde ett VoIP-gruppsamtal
Du begärde ett VoIP-gruppsamtal
VoIP-gruppsamtal startat
VoIP-gruppsamtal avslutat
-
(avataren blev även bytt)
%1$s tog bort rummets namn
Du tog bort rummets namn
@@ -87,54 +84,39 @@
Du drog tillbaka inbjudan för %1$s att gå med i rummet
%1$s accepterade inbjudan för %2$s
Du accepterade inbjudan för %1$s
-
%1$s lade till %2$s-widget
Du lade till %1$s-widget
%1$s tog bort %2$s-widget
Du tog bort %1$s-widget
%1$s modifierade %2$s-widget
Du modifierade %1$s-widget
-
Admin
Moderator
Standard
Anpassad (%1$d)
Anpassad
-
Du ändrade behörighetsnivå för %1$s.
%1$s ändrade behörighetsnivå för %2$s.
%1$s från %2$s till %3$s
-
** Kan inte avkryptera: %s **
Avsändarens enhet har inte gett oss nycklarna för det här meddelandet.
-
Kunde inte dölja
Kunde inte skicka meddelandet
-
Misslyckades att ladda upp bilden
-
Nätverksfel
Matrixfel
-
Det går för närvarande inte att gå med i ett tomt rum igen.
-
Krypterat meddelande
-
E-postadress
Telefonnummer
-
Inbjudan från %s
Rumsinbjudan
-
%1$s och %2$s
-
- %1$s och en till
- %1$s och %2$d till
-
Tomt rum
-
Inledande synk:
\nImporterar konto…
Inledande synk:
@@ -151,10 +133,8 @@
\nImporterar gemenskaper
Inledande synk:
\nImporterar kontodata
-
Skickar meddelande…
Rensa sändningskö
-
Inbjudan från %1$s. Anledning: %2$s
Inbjudan från dig. Anledning: %1$s
%1$s bjöd in %2$s. Anledning: %3$s
@@ -180,45 +160,57 @@
Du accepterade inbjudan för %1$s. Anledning: %2$s
%1$s drog tillbaka inbjudan för %2$s. Anledning: %3$s
Du drog tillbaka inbjudan för %1$s. Anledning: %2$s
-
- %1$s lade till %2$s som en adress för det här rummet.
- %1$s lade till %2$s som adresser för det här rummet.
-
- Du lade till %1$s som en adress för det här rummet.
- Du lade till %1$s som adresser för det här rummet.
-
- %1$s tog bort %2$s som en adress för det här rummet.
- %1$s tog bort %2$s som adresser för det här rummet.
-
- Du tog bort %1$s som en adress för det här rummet.
- Du tog bort %2$s som adresser för det här rummet.
-
%1$s lade till %2$s och tog bort %3$s som adresser för det här rummet.
Du lade till %1$s och tog bort %2$s som adresser för det här rummet.
-
%1$s satta huvudadressen för det här rummet till %2$s.
Du satta huvudadressen för det här rummet till %1$s.
%1$s tog bort huvudadressen för det här rummet.
Du tog bort huvudadressen för det här rummet.
-
%1$s tillät gäster att gå med i rummet.
Du tillät gäster att gå med i rummet.
%1$s hindrade gäster från att gå med i rummet.
Du hindrade gäster från att gå med i rummet.
-
%1$s aktiverade totalsträckskryptering.
Du aktiverade totalsträckskryptering.
%1$s aktiverade totalsträckskryptering (okänd algoritm %2$s).
Du aktiverade totalsträckskryptering (okänd algoritm %1$s).
-
%s begär att verifiera din nyckel, men din klient stöder inte nyckelverifiering i chatten. Du behöver använda legacynyckelverifiering för att verifiera nycklar.
-
-
+ Du hindrade gäster från att gå med i rummet.
+ %1$s hindrade gäster från att gå med i rummet.
+ Du tillät gäster att gå med här.
+ %1$s tillät gäster att gå med här.
+ Du lämnade. Anledning: %1$s
+ %1$s Lämnade. Anledning: %2$s
+ Du gick med. Anledning: %1$s
+ %1$s gick med. Anledning: %2$s
+ Du drog tillbaka inbjudan för %1$s
+ %1$s drog tillbaka inbjudan för %2$s
+ Du bjöd in %1$s
+ %1$s bjöd in %2$s
+ Du uppgraderade här.
+ %s uppgraderade här.
+ Du gjorde framtida meddelanden synliga för %1$s
+ %1$s gjorde framtida meddelanden synliga för %2$s
+ Du lämnade rummet
+ %1$s lämnade rummet
+ Du gick med
+ %1$s gick med
+ Du skapade diskussionen
+ %1$s skapade diskussionen
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
index 60322821d4..496bbe6bf8 100644
--- a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml
@@ -1,7 +1,6 @@
-
+
%1$s 发送了一张图片。
-
%s 的邀请
%1$s 邀请了 %2$s
%1$s 邀请了您
@@ -27,42 +26,30 @@
任何人。
未知(%s)。
%1$s 开启了端到端加密(%2$s)
-
%1$s 请求了一次 VoIP 会议
VoIP 会议已开始
VoIP 会议已结束
-
(头像也被更改)
%1$s 移除了聊天室名称
%1$s 移除了聊天室主题
** 无法解密:%s **
发送者的设备没有向我们发送此消息的密钥。
-
无法发送消息
-
上传图像失败
-
网络错误
Matrix 错误
-
目前无法重新加入一个空的聊天室。
-
已加密消息
-
电子邮箱地址
手机号码
-
%1$s 撤回了对 %2$s 的邀请
%1$s 让未来的聊天室历史记录对 %2$s 可见
%1$s 更新了他的个人档案 %2$s
%1$s 向 %2$s 发送了加入聊天室的邀请
%1$s 接受了 %2$s 的邀请
-
无法撤回
-
%1$s:%2$s
%1$s 发送了一张贴纸。
-
空聊天室
来自 %s 的邀请
聊天室邀请
@@ -70,7 +57,6 @@
- %1$s 与其他 %2$d 位
-
消息已被移除
消息已被 %1$s 移除
消息已被移除 [原因: %1$s]
@@ -91,14 +77,10 @@
\n正在导入社区
初始化同步:
\n正在导入账号数据
-
%s 升级了此聊天室。
-
正在发送消息…
清除正在发送队列
-
%1$s 撤回了对 %2$s 加入聊天室的邀请
-
%1$s 的邀请。理由:%2$s
%1$s 邀请了 %2$s。理由:%3$s
%1$s 邀请了您。理由:%2$s
@@ -112,32 +94,23 @@
%1$s 撤销了 %2$s 加入聊天室的邀請。理由:%3$s
%1$s 接受 %2$s 的邀請。理由:%3$s
%1$s 撤回了对 %2$s 的邀请。理由:%3$s
-
- %1$s 新增了 %2$s 为此聊天室的地址。
-
- %1$s 移除了此聊天室的 %3$s 地址。
-
%1$s 为此聊天室新增了 %2$s 并移除 %3$s 地址。
-
%1$s 将此聊天室的主地址设为了 %2$s。
%1$s 为此聊天室移除了主地址。
-
%1$s 已允许访客加入聊天室。
%1$s 已禁止访客加入聊天室。
-
%1$s 已开启端到端加密。
%1$s 已开启端到端加密(无法识别的演算法 %2$s)。
-
%s 正在请求验证您的密钥,但您的客户端不支援聊天中密钥验证。 您将必须使用旧版的密钥验证来验证金钥。
-
%1$s 创建了这个聊天室
您发送了一张图片。
您发送了一张贴纸。
-
您的邀请
您创建了这个聊天室
您邀请了 %1$s
@@ -165,7 +138,6 @@
您已让未来的聊天室记录对 %1$s 可见
您开启了端到端加密(%1$s)
您升级了此聊天室。
-
您请求了 VoIP 会议
您移除了聊天室名称
您移除了聊天室主题
@@ -175,24 +147,20 @@
您向 %1$s 发送了加入聊天室的邀请
您已撤回了对 %1$s 加入聊天室的邀请
您接受了 %1$s 的邀请
-
%1$s 添加了 %2$s 小部件
您添加了 %1$s 小部件
%1$s 移除了 %2$s 小部件
您移除了 %1$s 小部件
%1$s 修改了 %2$s 小部件
您修改了 %1$s 小部件
-
管理员
审核员
默认
自定义(%1$d)
自定义
-
您更改了%1$s 的权力等级。
%1$s 更改了 %2$s 的权力等级。
%1$s 从 %2$s 到 %3$s
-
您的邀请。理由:%1$s
您邀请了 %1$s。理由:%2$s
您加入了聊天室。理由:%1$s
@@ -205,24 +173,35 @@
您撤销了 %1$s 加入聊天室的邀请。理由:%2$s
您接受了 %1$s 的邀请。理由:%2$s
您撤回了 %1$s 的邀请。理由:%2$s
-
- 您新增了 %1$s 为此聊天室的地址。
-
- 您移除了此聊天室的 %2$s 地址。
-
您为此聊天室新增了 %1$s 并移除了 %2$s 地址。
-
您将此聊天室的主地址设为了 %1$s。
您移除了此聊天室的主地址。
-
您已允许访客加入聊天室。
您已禁止访客加入聊天室。
-
您已开启端到端加密。
您已开启端到端加密(无法识别的算法 %1$s)。
-
-
+ 您已离开。理由:%1$s
+ %1$s 已离开。理由:%2$s
+ 您已加入。理由:%1$s
+ %1$s 已加入。理由:%2$s
+ 您撤回了对 %1$s 的邀请
+ %1$s 撤回了对 %2$s 的邀请
+ 您邀请了 %1$s
+ %1$s 邀请了 %2$s
+ 您在此处升级。
+ %s 在此处升级。
+ 您使未来的消息对 %2$s 可见
+ %1$s 使未来的消息对 %2$s 可见
+ 您离开了聊天室
+ %1$s 离开了聊天室
+ 您已加入
+ %1$s 已加入
+ 您创建了讨论
+ %1$s 创建了讨论
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
index 355890923c..4a3293b195 100644
--- a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
@@ -1,8 +1,7 @@
-
+
%1$s:%2$s
%1$s 傳送了一張圖片。
-
%s 的邀請
%1$s 邀請了 %2$s
%1$s 邀請您
@@ -30,52 +29,38 @@
任何人。
未知 (%s)。
%1$s 開啟了端對端加密 (%2$s)
-
%1$s 請求了 VoIP 會議通話
VoIP 會議通話已開始
VoIP 會議通話已結束
-
(大頭貼也變更了)
%1$s 移除了房間名稱
%1$s 移除了房間主題
%1$s 更新了他們的基本資料 %2$s
%1$s 傳送加入房間的邀請給 %2$s
%1$s 接受 %2$s 的邀請
-
** 無法解密:%s **
傳送者的裝置並未在此訊息傳送他們的金鑰。
-
無法編輯
無法傳送訊息
-
上傳圖片失敗
-
網路錯誤
Matrix 錯誤
-
目前無法重新加入空房間。
-
已加密的訊息
-
電子郵件
電話號碼
-
%1$s 傳送了一張貼圖。
-
來自%s 的邀請
聊天室邀請
%1$s 和 %2$s
-
空聊天室
- %1$s 和 和其他 %2$d 個人
-
訊息已移除
訊息已被 %1$s 移除
訊息已移除 [理由:%1$s]
訊息已被 %1$s 移除 [理由:%2$s]
-
初始化同步:
\n正在匯入帳號……
初始化同步:
@@ -92,12 +77,9 @@
\n正在匯入社群
初始化同步:
\n正在匯入帳號資料
-
%s 已升級此聊天室。
-
正在傳送訊息……
清除傳送佇列
-
%1$s 撤銷了 %2$s 加入聊天室的邀請
%1$s 的邀請。理由:%2$s
%1$s 邀請了 %2$s。理由:%3$s
@@ -112,32 +94,23 @@
%1$s 撤銷了 %2$s 加入聊天室的邀請。理由:%3$s
%1$s 接受 %2$s 的邀請。理由:%3$s
%1$s 撤回了對 %2$s 的邀請。理由:%3$s
-
- %1$s 新增了 %2$s 為此聊天室的地址。
-
- %1$s 移除了此聊天室的 %3$s 地址。
-
%1$s 為此聊天室新增 %2$s 並移除 %3$s 地址。
-
%1$s 為此聊天室設定了 %2$s 為主地址。
%1$s 為此聊天室移除了主要地址。
-
%1$s 已允許訪客加入聊天室。
%1$s 已禁止訪客加入聊天室。
-
%1$s 已開啟端到端加密。
%1$s 已開啟端到端加密(無法識別的演算法 %2$s)。
-
%s 正在請求驗證您的金鑰,但您的客戶端不支援聊天中金鑰驗證。您將必須使用舊版的金鑰驗證來驗證金鑰。
-
%1$s 建立了聊天室
您傳送了圖片。
您傳送了貼圖。
-
您的邀請
您建立了聊天室
您邀請了 %1$s
@@ -165,7 +138,6 @@
您已將未來的聊天室歷史設定為對 %1$s 可見
您開啟了端到端加密 (%1$s)
您升級了此聊天室。
-
您請求了 VoIP 會議
您移除了聊天室名稱
您移除了聊天室主題
@@ -175,24 +147,20 @@
您傳送了邀請給 %1$s 以加入聊天室
您已撤銷對 %1$s 加入聊天室的邀請
您接受了 %1$s 的邀請
-
%1$s 新增了 %2$s 小工具
您新增了 %1$s 小工具
%1$s 移除了 %2$s 小工具
您移除了 %1$s 小工具
%1$s 修改了 %2$s 小工具
您修改了 %1$s 小工具
-
管理員
板主
預設
自訂 (%1$d)
自訂
-
您變更了 %1$s 的權力等級。
%1$s 變更了 %2$s 的權力等級。
%1$s 從 %2$s 到 %3$s
-
您的邀請。理由:%1$s
您邀請了 %1$s。理由:%2$s
您加入了聊天室。理由:%1$s
@@ -205,24 +173,39 @@
您撤銷了 %1$s 加入聊天室的邀請。理由:%2$s
您接受了 %1$s 的邀請。理由:%2$s
您撤回了 %1$s 的邀請。理由:%2$s
-
- 您為此聊天室新增了 %1$s 作為地址。
-
- 您為此聊天室移除了 %2$s 作為地址。
-
您為此聊天室新增了 %1$s 並移除了 %2$s 作為地址。
-
您將此聊天室的主要地址設定為 %1$s。
您將此聊天室的主要地址移除。
-
您已允許訪客加入聊天室。
您已阻止訪客加入聊天室。
-
您開啟了端到端加密。
您開啟了端到端加密(無法識別的演算法 %1$s)。
-
-
+ 您已避免訪客加入此聊天室。
+ %1$s 已避免訪客加入此聊天室。
+ 您已允許訪客加入這裡。
+ %1$s 已允許訪客加入這裡。
+ 您已離開。理由:%1$s
+ %1$s 已離開。理由:%2$s
+ 您已加入。理由:%1$s
+ %1$s 已加入。理由:%2$s
+ 您已撤銷對 %1$s 的邀請
+ %1$s 已撤銷對 %2$s 的邀請
+ 您已邀請了 %1$s
+ %1$s 邀請了 %2$s
+ 您已在此升級。
+ %s 已在此升級。
+ 您讓未來的訊息對 %1$s 可見
+ %1$s 已讓未來的訊息對 %2$s 可見
+ 您已離開聊天室
+ %1$s 已離開聊天室
+ 您已加入
+ %1$s 已加入
+ 您已建立此討論
+ %1$s 已建立此討論
+
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml
index f64ec9926e..3f75b715f6 100644
--- a/matrix-sdk-android/src/main/res/values/strings.xml
+++ b/matrix-sdk-android/src/main/res/values/strings.xml
@@ -10,13 +10,19 @@
Your invitation
%1$s created the room
You created the room
+ %1$s created the discussion
+ You created the discussion
%1$s invited %2$s
You invited %1$s
%1$s invited you
%1$s joined the room
You joined the room
+ %1$s joined
+ You joined
%1$s left the room
You left the room
+ %1$s left the room
+ You left the room
%1$s rejected the invitation
You rejected the invitation
%1$s kicked %2$s
@@ -53,6 +59,8 @@
You ended the call.
%1$s made future room history visible to %2$s
You made future room history visible to %1$s
+ %1$s made future messages visible to %2$s
+ You made future messages visible to %1$s
all room members, from the point they are invited.
all room members, from the point they joined.
all room members.
@@ -62,6 +70,8 @@
You turned on end-to-end encryption (%1$s)
%s upgraded this room.
You upgraded this room.
+ %s upgraded here.
+ You upgraded here.
%1$s requested a VoIP conference
You requested a VoIP conference
@@ -83,8 +93,12 @@
You updated your profile %1$s
%1$s sent an invitation to %2$s to join the room
You sent an invitation to %1$s to join the room
+ %1$s invited %2$s
+ You invited %1$s
%1$s revoked the invitation for %2$s to join the room
You revoked the invitation for %1$s to join the room
+ %1$s revoked the invitation for %2$s
+ You revoked the invitation for %1$s
%1$s accepted the invitation for %2$s
You accepted the invitation for %1$s
@@ -171,8 +185,12 @@
%1$s invited you. Reason: %2$s
%1$s joined the room. Reason: %2$s
You joined the room. Reason: %1$s
+ %1$s joined. Reason: %2$s
+ You joined. Reason: %1$s
%1$s left the room. Reason: %2$s
You left the room. Reason: %1$s
+ %1$s left. Reason: %2$s
+ You left. Reason: %1$s
%1$s rejected the invitation. Reason: %2$s
You rejected the invitation. Reason: %1$s
%1$s kicked %2$s. Reason: %3$s
@@ -220,8 +238,12 @@
"%1$s has allowed guests to join the room."
"You have allowed guests to join the room."
+ "%1$s has allowed guests to join here."
+ "You have allowed guests to join here."
"%1$s has prevented guests from joining the room."
"You have prevented guests from joining the room."
+ "%1$s has prevented guests from joining the room."
+ "You have prevented guests from joining the room."
%1$s turned on end-to-end encryption.
You turned on end-to-end encryption.
diff --git a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index c4fed36216..f46e2ca35b 100644
--- a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt
index 69b15a1fa5..a815cec353 100644
--- a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt
+++ b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/sharedTest/java/org/matrix/android/sdk/test/shared/TestRules.kt b/matrix-sdk-android/src/sharedTest/java/org/matrix/android/sdk/test/shared/TestRules.kt
index 52aa7ea0c7..662656ae5e 100644
--- a/matrix-sdk-android/src/sharedTest/java/org/matrix/android/sdk/test/shared/TestRules.kt
+++ b/matrix-sdk-android/src/sharedTest/java/org/matrix/android/sdk/test/shared/TestRules.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/MatrixTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/MatrixTest.kt
index b0933c7106..f6a7f525db 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/MatrixTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/MatrixTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt
index 69e2f12eb7..c413d9ccae 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/auth/data/VersionsKtTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
index f213e1b1c1..b734444990 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
index b2c8d3bda8..c0b869a90e 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt
index b2d10968b6..2f01a43a67 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58Test.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt
index 6b9d388623..64ffe52acd 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt
index cac2d1cba9..0bcc7983c5 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt
index 0f8fe58b7f..b04834f9f4 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/BinaryStringTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,13 @@
package org.matrix.android.sdk.internal.crypto.verification.qrcode
import org.matrix.android.sdk.MatrixTest
-import org.amshove.kluent.shouldEqualTo
+import org.amshove.kluent.shouldBeEqualTo
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runners.MethodSorters
@FixMethodOrder(MethodSorters.JVM)
-class BinaryStringTest: MatrixTest {
+class BinaryStringTest : MatrixTest {
/**
* I want to put bytes to a String, and vice versa
@@ -37,17 +37,17 @@ class BinaryStringTest: MatrixTest {
val str = byteArray.toString(Charsets.ISO_8859_1)
- str.length shouldEqualTo 256
+ str.length shouldBeEqualTo 256
// Ok convert back to bytearray
val result = str.toByteArray(Charsets.ISO_8859_1)
- result.size shouldEqualTo 256
+ result.size shouldBeEqualTo 256
for (i in 0..255) {
- result[i] shouldEqualTo i.toByte()
- result[i] shouldEqualTo byteArray[i]
+ result[i] shouldBeEqualTo i.toByte()
+ result[i] shouldBeEqualTo byteArray[i]
}
}
}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
index 7bef439417..667e0b2471 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/multipicker/build.gradle b/multipicker/build.gradle
index 8f2226e884..b6e500e493 100644
--- a/multipicker/build.gradle
+++ b/multipicker/build.gradle
@@ -41,15 +41,10 @@ android {
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.3.0'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
-
- implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation "androidx.fragment:fragment:1.3.0-beta01"
+ implementation 'androidx.exifinterface:exifinterface:1.3.0'
// Log
implementation 'com.jakewharton.timber:timber:4.7.1'
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt
index 17c01bc8e3..516022100d 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/AudioPicker.kt
@@ -16,7 +16,6 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.MediaMetadataRetriever
@@ -26,19 +25,13 @@ import im.vector.lib.multipicker.entity.MultiPickerAudioType
/**
* Audio file picker implementation
*/
-class AudioPicker(override val requestCode: Int) : Picker(requestCode) {
+class AudioPicker : Picker() {
/**
* Call this function from onActivityResult(int, int, Intent).
- * Returns selected audio files or empty list if request code is wrong
- * or result code is not Activity.RESULT_OK
- * or user did not select any files.
+ * Returns selected audio files or empty list if user did not select any files.
*/
- override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
- if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
- return emptyList()
- }
-
+ override fun getSelectedFiles(context: Context, data: Intent?): List {
val audioList = mutableListOf()
getSelectedUriList(data).forEach { selectedUri ->
@@ -84,7 +77,7 @@ class AudioPicker(override val requestCode: Int) : Picker(
}
override fun createIntent(): Intent {
- return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ return Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
type = "audio/*"
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt
index be6fdb5ee8..3f24a28c28 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt
@@ -16,13 +16,12 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
+import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.FileProvider
-import androidx.fragment.app.Fragment
import im.vector.lib.multipicker.entity.MultiPickerImageType
import im.vector.lib.multipicker.utils.ImageUtils
import java.io.File
@@ -33,33 +32,18 @@ import java.util.Locale
/**
* Implementation of taking a photo with Camera
*/
-class CameraPicker(val requestCode: Int) {
+class CameraPicker {
/**
- * Start camera by using an Activity
- * @param activity Activity to handle onActivityResult().
+ * Start camera by using a ActivityResultLauncher
* @return Uri of taken photo or null if the operation is cancelled.
*/
- fun startWithExpectingFile(activity: Activity): Uri? {
- val photoUri = createPhotoUri(activity)
+ fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri? {
+ val photoUri = createPhotoUri(context)
val intent = createIntent().apply {
putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
}
- activity.startActivityForResult(intent, requestCode)
- return photoUri
- }
-
- /**
- * Start camera by using a Fragment
- * @param fragment Fragment to handle onActivityResult().
- * @return Uri of taken photo or null if the operation is cancelled.
- */
- fun startWithExpectingFile(fragment: Fragment): Uri? {
- val photoUri = createPhotoUri(fragment.requireContext())
- val intent = createIntent().apply {
- putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
- }
- fragment.startActivityForResult(intent, requestCode)
+ activityResultLauncher.launch(intent)
return photoUri
}
@@ -69,40 +53,38 @@ class CameraPicker(val requestCode: Int) {
* or result code is not Activity.RESULT_OK
* or user cancelled the operation.
*/
- fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? {
- if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
- val projection = arrayOf(
- MediaStore.Images.Media.DISPLAY_NAME,
- MediaStore.Images.Media.SIZE
- )
+ fun getTakenPhoto(context: Context, photoUri: Uri): MultiPickerImageType? {
+ val projection = arrayOf(
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.SIZE
+ )
- context.contentResolver.query(
- photoUri,
- projection,
- null,
- null,
- null
- )?.use { cursor ->
- val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
- val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
+ context.contentResolver.query(
+ photoUri,
+ projection,
+ null,
+ null,
+ null
+ )?.use { cursor ->
+ val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+ val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
- if (cursor.moveToNext()) {
- val name = cursor.getString(nameColumn)
- val size = cursor.getLong(sizeColumn)
+ if (cursor.moveToNext()) {
+ val name = cursor.getString(nameColumn)
+ val size = cursor.getLong(sizeColumn)
- val bitmap = ImageUtils.getBitmap(context, photoUri)
- val orientation = ImageUtils.getOrientation(context, photoUri)
+ val bitmap = ImageUtils.getBitmap(context, photoUri)
+ val orientation = ImageUtils.getOrientation(context, photoUri)
- return MultiPickerImageType(
- name,
- size,
- context.contentResolver.getType(photoUri),
- photoUri,
- bitmap?.width ?: 0,
- bitmap?.height ?: 0,
- orientation
- )
- }
+ return MultiPickerImageType(
+ name,
+ size,
+ context.contentResolver.getType(photoUri),
+ photoUri,
+ bitmap?.width ?: 0,
+ bitmap?.height ?: 0,
+ orientation
+ )
}
}
return null
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt
index e9ae096174..315fe6cbf2 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/ContactPicker.kt
@@ -16,7 +16,6 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@@ -26,19 +25,13 @@ import im.vector.lib.multipicker.entity.MultiPickerContactType
/**
* Contact Picker implementation
*/
-class ContactPicker(override val requestCode: Int) : Picker(requestCode) {
+class ContactPicker : Picker() {
/**
* Call this function from onActivityResult(int, int, Intent).
- * Returns selected contact or empty list if request code is wrong
- * or result code is not Activity.RESULT_OK
- * or user did not select any files.
+ * Returns selected contact or empty list if user did not select any contacts.
*/
- override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
- if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
- return emptyList()
- }
-
+ override fun getSelectedFiles(context: Context, data: Intent?): List {
val contactList = mutableListOf()
data?.data?.let { selectedUri ->
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt
index 296685886d..39bd93d03e 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/FilePicker.kt
@@ -16,7 +16,6 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.provider.OpenableColumns
@@ -25,19 +24,13 @@ import im.vector.lib.multipicker.entity.MultiPickerFileType
/**
* Implementation of selecting any type of files
*/
-class FilePicker(override val requestCode: Int) : Picker(requestCode) {
+class FilePicker : Picker() {
/**
* Call this function from onActivityResult(int, int, Intent).
- * Returns selected files or empty list if request code is wrong
- * or result code is not Activity.RESULT_OK
- * or user did not select any files.
+ * Returns selected files or empty list if user did not select any files.
*/
- override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
- if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
- return emptyList()
- }
-
+ override fun getSelectedFiles(context: Context, data: Intent?): List {
val fileList = mutableListOf()
getSelectedUriList(data).forEach { selectedUri ->
@@ -64,7 +57,7 @@ class FilePicker(override val requestCode: Int) : Picker(re
}
override fun createIntent(): Intent {
- return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ return Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
type = "*/*"
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt
index 39bd0c27cc..ce73058039 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/ImagePicker.kt
@@ -16,7 +16,6 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.provider.MediaStore
@@ -26,19 +25,13 @@ import im.vector.lib.multipicker.utils.ImageUtils
/**
* Image Picker implementation
*/
-class ImagePicker(override val requestCode: Int) : Picker(requestCode) {
+class ImagePicker : Picker() {
/**
* Call this function from onActivityResult(int, int, Intent).
- * Returns selected image files or empty list if request code is wrong
- * or result code is not Activity.RESULT_OK
- * or user did not select any files.
+ * Returns selected image files or empty list if user did not select any files.
*/
- override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
- if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
- return emptyList()
- }
-
+ override fun getSelectedFiles(context: Context, data: Intent?): List {
val imageList = mutableListOf()
getSelectedUriList(data).forEach { selectedUri ->
@@ -82,7 +75,7 @@ class ImagePicker(override val requestCode: Int) : Picker(
}
override fun createIntent(): Intent {
- return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ return Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
type = "image/*"
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt
index d28dcf9586..7e639a9bd3 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt
@@ -26,23 +26,16 @@ class MultiPicker {
val CONTACT by lazy { MultiPicker() }
val CAMERA by lazy { MultiPicker() }
- const val REQUEST_CODE_PICK_IMAGE = 5000
- const val REQUEST_CODE_PICK_VIDEO = 5001
- const val REQUEST_CODE_PICK_FILE = 5002
- const val REQUEST_CODE_PICK_AUDIO = 5003
- const val REQUEST_CODE_PICK_CONTACT = 5004
- const val REQUEST_CODE_TAKE_PHOTO = 5005
-
@Suppress("UNCHECKED_CAST")
fun get(type: MultiPicker): T {
return when (type) {
- IMAGE -> ImagePicker(REQUEST_CODE_PICK_IMAGE) as T
- VIDEO -> VideoPicker(REQUEST_CODE_PICK_VIDEO) as T
- FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T
- AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T
- CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T
- CAMERA -> CameraPicker(REQUEST_CODE_TAKE_PHOTO) as T
- else -> throw IllegalArgumentException("Unsupported type $type")
+ IMAGE -> ImagePicker() as T
+ VIDEO -> VideoPicker() as T
+ FILE -> FilePicker() as T
+ AUDIO -> AudioPicker() as T
+ CONTACT -> ContactPicker() as T
+ CAMERA -> CameraPicker() as T
+ else -> throw IllegalArgumentException("Unsupported type $type")
}
}
}
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt
index 65ec77e02a..ba765a3b1d 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt
@@ -16,28 +16,25 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
-import androidx.fragment.app.Fragment
+import androidx.activity.result.ActivityResultLauncher
/**
* Abstract class to provide all types of Pickers
*/
-abstract class Picker(open val requestCode: Int) {
+abstract class Picker {
protected var single = false
/**
* Call this function from onActivityResult(int, int, Intent).
- * @return selected files or empty list if request code is wrong
- * or result code is not Activity.RESULT_OK
- * or user did not select any files.
+ * @return selected files or empty list if user did not select any files.
*/
- abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List
+ abstract fun getSelectedFiles(context: Context, data: Intent?): List
/**
* Use this function to retrieve files which are shared from another application or internally
@@ -61,7 +58,7 @@ abstract class Picker(open val requestCode: Int) {
context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
- return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data)
+ return getSelectedFiles(context, data)
}
/**
@@ -75,19 +72,11 @@ abstract class Picker(open val requestCode: Int) {
abstract fun createIntent(): Intent
/**
- * Start Storage Access Framework UI by using an Activity.
- * @param activity Activity to handle onActivityResult().
+ * Start Storage Access Framework UI by using a ActivityResultLauncher.
+ * @param activityResultLauncher to handle the result.
*/
- fun startWith(activity: Activity) {
- activity.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
- }
-
- /**
- * Start Storage Access Framework UI by using a Fragment.
- * @param fragment Fragment to handle onActivityResult().
- */
- fun startWith(fragment: Fragment) {
- fragment.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode)
+ fun startWith(activityResultLauncher: ActivityResultLauncher) {
+ activityResultLauncher.launch(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) })
}
protected fun getSelectedUriList(data: Intent?): List {
diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt
index 7127e9defd..c7c06f795f 100644
--- a/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt
+++ b/multipicker/src/main/java/im/vector/lib/multipicker/VideoPicker.kt
@@ -16,7 +16,6 @@
package im.vector.lib.multipicker
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.MediaMetadataRetriever
@@ -26,19 +25,13 @@ import im.vector.lib.multipicker.entity.MultiPickerVideoType
/**
* Video Picker implementation
*/
-class VideoPicker(override val requestCode: Int) : Picker(requestCode) {
+class VideoPicker : Picker() {
/**
* Call this function from onActivityResult(int, int, Intent).
- * Returns selected video files or empty list if request code is wrong
- * or result code is not Activity.RESULT_OK
- * or user did not select any files.
+ * Returns selected video files or empty list if user did not select any files.
*/
- override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List {
- if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) {
- return emptyList()
- }
-
+ override fun getSelectedFiles(context: Context, data: Intent?): List {
val videoList = mutableListOf()
getSelectedUriList(data).forEach { selectedUri ->
@@ -93,7 +86,7 @@ class VideoPicker(override val requestCode: Int) : Picker(
}
override fun createIntent(): Intent {
- return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ return Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single)
type = "video/*"
diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh
index 8f850734fc..e855440e81 100755
--- a/tools/check/check_code_quality.sh
+++ b/tools/check/check_code_quality.sh
@@ -75,6 +75,15 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code.txt \
resultForbiddenStringInCode=$?
+echo
+echo "Search for forbidden patterns specific for SDK code..."
+
+${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_sdk.txt \
+ ./matrix-sdk-android/src \
+ ./matrix-sdk-android-rx/src
+
+resultForbiddenStringInCodeSdk=$?
+
echo
echo "Search for forbidden patterns in resources..."
@@ -148,7 +157,7 @@ fi
echo
-if [[ ${resultNbOfDrawable} -eq 0 ]] && [[ ${resultForbiddenStringInCode} -eq 0 ]] && [[ ${resultForbiddenStringInResource} -eq 0 ]] && [[ ${resultLongFiles} -eq 0 ]] && [[ ${resultPngInDrawable} -eq 0 ]]; then
+if [[ ${resultNbOfDrawable} -eq 0 ]] && [[ ${resultForbiddenStringInCode} -eq 0 ]] && [[ ${resultForbiddenStringInCodeSdk} -eq 0 ]] && [[ ${resultForbiddenStringInResource} -eq 0 ]] && [[ ${resultLongFiles} -eq 0 ]] && [[ ${resultPngInDrawable} -eq 0 ]]; then
echo "MAIN OK"
else
echo "❌ MAIN ERROR"
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index 3ced7de7e2..6e879df7ab 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -163,8 +163,8 @@ Formatter\.formatShortFileSize===1
# DISABLED
# 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 it is ok, change the value in file forbidden_strings_in_code.txt
-enum class===78
+### 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===82
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/tools/check/forbidden_strings_in_code_sdk.txt b/tools/check/forbidden_strings_in_code_sdk.txt
new file mode 100644
index 0000000000..270efef459
--- /dev/null
+++ b/tools/check/forbidden_strings_in_code_sdk.txt
@@ -0,0 +1,28 @@
+#
+# Copyright 2020 New Vector Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This file list String which are not allowed in source code.
+# Use Perl regex to write forbidden strings
+# Note: line cannot start with a space. Use \s instead.
+# It is possible to specify an authorized number of occurrence with === suffix. Default is 0
+# Example:
+# AuthorizedStringThreeTimes===3
+
+# Extension:java
+# Extension:kt
+
+### "The Matrix.org Foundation C.I.C." copyright is required on this file, and not "New Vector Ltd"
+New Vector Ltd
diff --git a/tools/check/forbidden_strings_in_resources.txt b/tools/check/forbidden_strings_in_resources.txt
index 0bbe90b31f..6fb6b184ba 100644
--- a/tools/check/forbidden_strings_in_resources.txt
+++ b/tools/check/forbidden_strings_in_resources.txt
@@ -79,9 +79,5 @@ layout_constraintLeft_
### Use im.vector.app.core.preference.VectorPreference to support multiline of the title
&2
+ exit 1
+fi
+
+# Get the command line parameters
+SERVER_KEY=$1
+FCM_TOKEN=$2
+
+echo
+echo "Check validity of API key, InvalidRegistration error is OK"
+# https://developers.google.com/cloud-messaging/http
+
+curl -H "Authorization: key=$SERVER_KEY" \
+ -H Content-Type:"application/json" \
+ -d "{\"registration_ids\":[\"ABC\"]}" \
+ -s \
+ https://fcm.googleapis.com/fcm/send \
+ | python -m json.tool
+
+# should obtain something like this:
+# {"multicast_id":5978845027639121780,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
+
+# content of the notification
+DATA='{"event_id":"$THIS_IS_A_FAKE_EVENT_ID"}'
+
+echo
+echo
+echo "Send a push, you should see success:1..."
+
+curl -H "Authorization: key=$SERVER_KEY" \
+ -H Content-Type:"application/json" \
+ -d "{ \"data\" : $DATA, \"to\":\"$FCM_TOKEN\" }" \
+ -s \
+ https://fcm.googleapis.com/fcm/send \
+ | python -m json.tool
+
+echo
+echo
+
+# should obtain something like this:
+# {"multicast_id":7967233883611790812,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1472636210339069%84ac25d9f9fd7ecd"}]}
+
diff --git a/vector/build.gradle b/vector/build.gradle
index 5d14065479..754f61b3e9 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -17,7 +17,7 @@ androidExtensions {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 0
-ext.versionPatch = 8
+ext.versionPatch = 9
ext.scVersion = 23
@@ -176,6 +176,19 @@ android {
}
}
*/
+
+ // The following argument makes the Android Test Orchestrator run its
+ // "pm clear" command after each test invocation. This command ensures
+ // that the app's state is completely cleared between tests.
+ testInstrumentationRunnerArguments clearPackageData: 'true'
+ }
+
+ testOptions {
+ // Disables animations during instrumented tests you run from the command line…
+ // This property does not affect tests that you run using Android Studio.”
+ animationsDisabled = true
+
+ execution 'ANDROIDX_TEST_ORCHESTRATOR'
}
signingConfigs {
@@ -271,20 +284,24 @@ android {
dependencies {
- def epoxy_version = '3.11.0'
- def fragment_version = '1.2.5'
+ def epoxy_version = '4.1.0'
+ def fragment_version = '1.3.0-beta01'
def arrow_version = "0.8.2"
- def coroutines_version = "1.3.8"
def markwon_version = '4.1.2'
def big_image_viewer_version = '1.6.2'
def glide_version = '4.11.0'
- def moshi_version = '1.8.0'
- def daggerVersion = '2.25.4'
+ def moshi_version = '1.11.0'
+ def daggerVersion = '2.29.1'
def autofill_version = "1.0.0"
def work_version = '2.4.0'
def arch_version = '2.1.0'
def lifecycle_version = '2.2.0'
+ // Tests
+ def kluent_version = '1.61'
+ def androidxTest_version = '1.3.0'
+ def espresso_version = '3.3.0'
+
implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx")
implementation project(":diff-match-patch")
@@ -293,16 +310,16 @@ dependencies {
implementation 'com.android.support:multidex:1.0.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
- implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
+ implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
- // Keep at 2.0.0-beta4 at the moment, as updating is breaking some UI
- implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
- implementation 'androidx.core:core-ktx:1.3.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
+ implementation "androidx.sharetarget:sharetarget:1.0.0"
+ implementation 'androidx.core:core-ktx:1.3.2'
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0"
@@ -330,9 +347,10 @@ dependencies {
implementation 'com.jakewharton.rxbinding3:rxbinding-material:3.0.0'
implementation("com.airbnb.android:epoxy:$epoxy_version")
+ implementation "com.airbnb.android:epoxy-glide-preloading:$epoxy_version"
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
- implementation 'com.airbnb.android:mvrx:1.3.0'
+ implementation 'com.airbnb.android:mvrx:1.5.1'
// Work
implementation "androidx.work:work-runtime-ktx:$work_version"
@@ -348,7 +366,7 @@ dependencies {
// UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
- implementation 'com.google.android.material:material:1.3.0-alpha01'
+ implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
@@ -383,7 +401,7 @@ dependencies {
implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version"
// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2'
- implementation 'com.github.chrisbanes:PhotoView:2.0.0'
+ implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
@@ -400,7 +418,7 @@ dependencies {
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
// gplay flavor only
- gplayImplementation('com.google.firebase:firebase-messaging:20.2.4') {
+ gplayImplementation('com.google.firebase:firebase-messaging:20.3.0') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
@@ -424,20 +442,22 @@ dependencies {
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
// TESTS
- testImplementation 'junit:junit:4.12'
- testImplementation 'org.amshove.kluent:kluent-android:1.44'
+ testImplementation 'junit:junit:4.13'
+ testImplementation "org.amshove.kluent:kluent-android:$kluent_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
// Activate when you want to check for leaks, from time to time.
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
- androidTestImplementation 'androidx.test:core:1.2.0'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test:rules:1.2.0'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
+ 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.espresso:espresso-core:$espresso_version"
+ androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
+ androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
+ androidTestImplementation "org.amshove.kluent:kluent-android:$kluent_version"
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
// Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
diff --git a/vector/lint.xml b/vector/lint.xml
index bd49091a3f..4ac0f20e51 100644
--- a/vector/lint.xml
+++ b/vector/lint.xml
@@ -27,6 +27,7 @@
+
diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
new file mode 100644
index 0000000000..d247d88caa
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import android.app.Activity
+import android.view.View
+import androidx.lifecycle.Observer
+import androidx.test.espresso.IdlingRegistry
+import androidx.test.espresso.IdlingResource
+import androidx.test.espresso.PerformException
+import androidx.test.espresso.UiController
+import androidx.test.espresso.ViewAction
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.util.HumanReadables
+import androidx.test.espresso.util.TreeIterables
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.runner.lifecycle.ActivityLifecycleCallback
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
+import androidx.test.runner.lifecycle.Stage
+import org.hamcrest.Matcher
+import org.hamcrest.Matchers
+import org.hamcrest.StringDescription
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.sync.SyncState
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
+import java.util.concurrent.TimeoutException
+
+object EspressoHelper {
+ fun getCurrentActivity(): Activity? {
+ var currentActivity: Activity? = null
+ getInstrumentation().runOnMainSync {
+ currentActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED).elementAtOrNull(0)
+ }
+ return currentActivity
+ }
+}
+
+fun waitForView(viewMatcher: Matcher, timeout: Long = 10_000, waitForDisplayed: Boolean = true): ViewAction {
+ return object : ViewAction {
+ override fun getConstraints(): Matcher {
+ return Matchers.any(View::class.java)
+ }
+
+ override fun getDescription(): String {
+ val matcherDescription = StringDescription()
+ viewMatcher.describeTo(matcherDescription)
+ return "wait for a specific view <$matcherDescription> to be ${if (waitForDisplayed) "displayed" else "not displayed during $timeout millis."}"
+ }
+
+ override fun perform(uiController: UiController, view: View) {
+ println("*** waitForView 1 $view")
+ uiController.loopMainThreadUntilIdle()
+ val startTime = System.currentTimeMillis()
+ val endTime = startTime + timeout
+ val visibleMatcher = isDisplayed()
+
+ do {
+ println("*** waitForView loop $view end:$endTime current:${System.currentTimeMillis()}")
+ val viewVisible = TreeIterables.breadthFirstViewTraversal(view)
+ .any { viewMatcher.matches(it) && visibleMatcher.matches(it) }
+
+ println("*** waitForView loop viewVisible:$viewVisible")
+ if (viewVisible == waitForDisplayed) return
+ println("*** waitForView loop loopMainThreadForAtLeast...")
+ uiController.loopMainThreadForAtLeast(50)
+ println("*** waitForView loop ...loopMainThreadForAtLeast")
+ } while (System.currentTimeMillis() < endTime)
+
+ println("*** waitForView timeout $view")
+ // Timeout happens.
+ throw PerformException.Builder()
+ .withActionDescription(this.description)
+ .withViewDescription(HumanReadables.describe(view))
+ .withCause(TimeoutException())
+ .build()
+ }
+ }
+}
+
+fun initialSyncIdlingResource(session: Session): IdlingResource {
+ val res = object : IdlingResource, Observer {
+ private var callback: IdlingResource.ResourceCallback? = null
+
+ override fun getName() = "InitialSyncIdlingResource for ${session.myUserId}"
+
+ override fun isIdleNow(): Boolean {
+ val isIdle = session.hasAlreadySynced()
+ return isIdle
+ }
+
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
+ this.callback = callback
+ }
+
+ override fun onChanged(t: SyncState?) {
+ val isIdle = session.hasAlreadySynced()
+ if (isIdle) {
+ callback?.onTransitionToIdle()
+ session.getSyncStateLive().removeObserver(this)
+ }
+ }
+ }
+
+ runOnUiThread {
+ session.getSyncStateLive().observeForever(res)
+ }
+
+ return res
+}
+
+fun activityIdlingResource(activityClass: Class<*>): IdlingResource {
+ val res = object : IdlingResource, ActivityLifecycleCallback {
+ private var callback: IdlingResource.ResourceCallback? = null
+
+ var hasResumed = false
+ private var currentActivity : Activity? = null
+
+ val uniqTS = System.currentTimeMillis()
+ override fun getName() = "activityIdlingResource_${activityClass.name}_$uniqTS"
+
+ override fun isIdleNow(): Boolean {
+ val currentActivity = currentActivity ?: ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED).elementAtOrNull(0)
+
+ val isIdle = hasResumed || currentActivity?.javaClass?.let { activityClass.isAssignableFrom(it) } ?: false
+ println("*** [$name] isIdleNow activityIdlingResource $currentActivity isIdle:$isIdle")
+ return isIdle
+ }
+
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
+ println("*** [$name] registerIdleTransitionCallback $callback")
+ this.callback = callback
+ // if (hasResumed) callback?.onTransitionToIdle()
+ }
+
+ override fun onActivityLifecycleChanged(activity: Activity?, stage: Stage?) {
+ println("*** [$name] onActivityLifecycleChanged $activity $stage")
+ currentActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED).elementAtOrNull(0)
+ val isIdle = currentActivity?.javaClass?.let { activityClass.isAssignableFrom(it) } ?: false
+ println("*** [$name] onActivityLifecycleChanged $currentActivity isIdle:$isIdle")
+ if (isIdle) {
+ hasResumed = true
+ println("*** [$name] onActivityLifecycleChanged callback: $callback")
+ callback?.onTransitionToIdle()
+ ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(this)
+ }
+ }
+ }
+ ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(res)
+ return res
+}
+
+fun withIdlingResource(idlingResource: IdlingResource, block: (() -> Unit)) {
+ println("*** withIdlingResource register")
+ IdlingRegistry.getInstance().register(idlingResource)
+ block.invoke()
+ println("*** withIdlingResource unregister")
+ IdlingRegistry.getInstance().unregister(idlingResource)
+}
+
+fun allSecretsKnownIdling(session: Session): IdlingResource {
+ val res = object : IdlingResource, Observer> {
+ private var callback: IdlingResource.ResourceCallback? = null
+
+ var privateKeysInfo: PrivateKeysInfo? = session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()
+ override fun getName() = "AllSecretsKnownIdling_${session.myUserId}"
+
+ override fun isIdleNow(): Boolean {
+ println("*** [$name]/isIdleNow allSecretsKnownIdling ${privateKeysInfo?.allKnown()}")
+ return privateKeysInfo?.allKnown() == true
+ }
+
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
+ this.callback = callback
+ }
+
+ override fun onChanged(t: Optional?) {
+ println("*** [$name] allSecretsKnownIdling ${t?.getOrNull()}")
+ privateKeysInfo = t?.getOrNull()
+ if (t?.getOrNull()?.allKnown() == true) {
+ session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().removeObserver(this)
+ callback?.onTransitionToIdle()
+ }
+ }
+ }
+
+ runOnUiThread {
+ session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().observeForever(res)
+ }
+
+ return res
+}
diff --git a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt
new file mode 100644
index 0000000000..b88356db59
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
+import androidx.test.espresso.action.ViewActions.typeText
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isEnabled
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import im.vector.app.features.MainActivity
+import im.vector.app.features.home.HomeActivity
+import org.hamcrest.CoreMatchers.not
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class RegistrationTest {
+
+ @get:Rule
+ val activityRule = ActivityScenarioRule(MainActivity::class.java)
+
+ @Test
+ fun simpleRegister() {
+ val userId: String = "UiAutoTest_${System.currentTimeMillis()}"
+ val password: String = "password"
+ val homeServerUrl: String = "http://10.0.2.2:8080"
+
+ // Check splashscreen is there
+ onView(withId(R.id.loginSplashSubmit))
+ .check(matches(isDisplayed()))
+ .check(matches(withText(R.string.login_splash_submit)))
+
+ // Click on get started
+ onView(withId(R.id.loginSplashSubmit))
+ .perform(click())
+
+ // Check that home server options are shown
+ onView(withId(R.id.loginServerTitle))
+ .check(matches(isDisplayed()))
+ .check(matches(withText(R.string.login_server_title)))
+
+ // Chose custom server
+ onView(withId(R.id.loginServerChoiceOther))
+ .perform(click())
+
+ // Enter local synapse
+ onView((withId(R.id.loginServerUrlFormHomeServerUrl)))
+ .perform(typeText(homeServerUrl))
+
+ // Click on continue
+ onView(withId(R.id.loginServerUrlFormSubmit))
+ .check(matches(isEnabled()))
+ .perform(closeSoftKeyboard(), click())
+
+ // Click on the signup button
+ onView(withId(R.id.loginSignupSigninSubmit))
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ // Ensure password flow supported
+ onView(withId(R.id.loginField))
+ .check(matches(isDisplayed()))
+ onView(withId(R.id.passwordField))
+ .check(matches(isDisplayed()))
+
+ // Ensure user id
+ onView((withId(R.id.loginField)))
+ .perform(typeText(userId))
+
+ // Ensure login button not yet enabled
+ onView(withId(R.id.loginSubmit))
+ .check(matches(not(isEnabled())))
+
+ // Ensure password
+ onView((withId(R.id.passwordField)))
+ .perform(closeSoftKeyboard(), typeText(password))
+
+ // Submit
+ onView(withId(R.id.loginSubmit))
+ .check(matches(isEnabled()))
+ .perform(closeSoftKeyboard(), click())
+
+ withIdlingResource(activityIdlingResource(HomeActivity::class.java)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ }
+
+ val activity = EspressoHelper.getCurrentActivity()!!
+ val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession()
+
+ // Wait for initial sync and check room list is there
+ withIdlingResource(initialSyncIdlingResource(uiSession)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ }
+ }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt
new file mode 100644
index 0000000000..3ab8fe7dd9
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import android.app.Activity
+import android.app.Instrumentation.ActivityResult
+import android.content.Intent
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
+import androidx.test.espresso.action.ViewActions.pressBack
+import androidx.test.espresso.action.ViewActions.replaceText
+import androidx.test.espresso.action.ViewActions.typeText
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.intent.Intents
+import androidx.test.espresso.intent.Intents.intending
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
+import androidx.test.espresso.intent.matcher.IntentMatchers.isInternal
+import androidx.test.espresso.matcher.RootMatchers
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isRoot
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import im.vector.app.features.MainActivity
+import im.vector.app.features.crypto.recover.SetupMode
+import im.vector.app.features.home.HomeActivity
+import org.hamcrest.CoreMatchers.not
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.matrix.android.sdk.api.Matrix
+import org.matrix.android.sdk.api.session.Session
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class SecurityBootstrapTest : VerificationTestBase() {
+
+ var existingSession: Session? = null
+
+ @get:Rule
+ val activityRule = ActivityScenarioRule(MainActivity::class.java)
+
+ @Before
+ fun createSessionWithCrossSigning() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val matrix = Matrix.getInstance(context)
+ val userName = "foobar_${System.currentTimeMillis()}"
+ existingSession = createAccountAndSync(matrix, userName, password, true)
+ stubAllExternalIntents()
+ }
+
+ private fun stubAllExternalIntents() {
+ // By default Espresso Intents does not stub any Intents. Stubbing needs to be setup before
+ // every test run. In this case all external Intents will be blocked.
+ Intents.init()
+ intending(not(isInternal())).respondWith(ActivityResult(Activity.RESULT_OK, null))
+ }
+
+ @Test
+ fun testBasicBootstrap() {
+ val userId: String = existingSession!!.myUserId
+
+ doLogin(homeServerUrl, userId, password)
+
+ // Thread.sleep(6000)
+ withIdlingResource(activityIdlingResource(HomeActivity::class.java)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ .perform(closeSoftKeyboard())
+ }
+
+ val activity = EspressoHelper.getCurrentActivity()!!
+ val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession()
+
+ withIdlingResource(initialSyncIdlingResource(uiSession)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ }
+
+ activity.navigator.open4SSetup(activity, SetupMode.NORMAL)
+
+ Thread.sleep(1000)
+
+ onView(withId(R.id.bootstrapSetupSecureUseSecurityKey))
+ .check(matches(isDisplayed()))
+
+ onView(withId(R.id.bootstrapSetupSecureUseSecurityPassphrase))
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ onView(isRoot())
+ .perform(waitForView(withText(R.string.bootstrap_info_text_2)))
+
+ // test back
+ onView(isRoot()).perform(pressBack())
+
+ Thread.sleep(1000)
+
+ onView(withId(R.id.bootstrapSetupSecureUseSecurityKey))
+ .check(matches(isDisplayed()))
+
+ onView(withId(R.id.bootstrapSetupSecureUseSecurityPassphrase))
+ .check(matches(isDisplayed()))
+ .perform(click())
+
+ onView(isRoot())
+ .perform(waitForView(withText(R.string.bootstrap_info_text_2)))
+
+ onView(withId(R.id.ssss_passphrase_enter_edittext))
+ .perform(typeText("person woman man camera tv"))
+
+ onView(withId(R.id.bootstrapSubmit))
+ .perform(closeSoftKeyboard(), click())
+
+ // test bad pass
+ onView(withId(R.id.ssss_passphrase_enter_edittext))
+ .perform(typeText("person woman man cmera tv"))
+
+ onView(withId(R.id.bootstrapSubmit))
+ .perform(closeSoftKeyboard(), click())
+
+ onView(withText(R.string.passphrase_passphrase_does_not_match)).check(matches(isDisplayed()))
+
+ onView(withId(R.id.ssss_passphrase_enter_edittext))
+ .perform(replaceText("person woman man camera tv"))
+
+ onView(withId(R.id.bootstrapSubmit))
+ .perform(closeSoftKeyboard(), click())
+
+ onView(withId(R.id.bottomSheetScrollView))
+ .perform(waitForView(withText(R.string.bottom_sheet_save_your_recovery_key_content)))
+
+ intending(hasAction(Intent.ACTION_SEND)).respondWith(ActivityResult(Activity.RESULT_OK, null))
+
+ onView(withId(R.id.recoveryCopy))
+ .perform(click())
+
+ Thread.sleep(1000)
+
+ // Dismiss dialog
+ onView(withText(R.string.ok)).inRoot(RootMatchers.isDialog()).perform(click())
+
+ onView(withId(R.id.bottomSheetScrollView))
+ .perform(waitForView(withText(R.string.bottom_sheet_save_your_recovery_key_content)))
+
+ onView(withText(R.string._continue)).perform(click())
+
+ // Assert that all is configured
+ assert(uiSession.cryptoService().crossSigningService().isCrossSigningInitialized())
+ assert(uiSession.cryptoService().crossSigningService().canCrossSign())
+ assert(uiSession.cryptoService().crossSigningService().allPrivateKeysKnown())
+ assert(uiSession.cryptoService().keysBackupService().isEnabled)
+ assert(uiSession.cryptoService().keysBackupService().currentBackupVersion != null)
+ assert(uiSession.sharedSecretStorageService.isRecoverySetup())
+ assert(uiSession.sharedSecretStorageService.isMegolmKeyInBackup())
+ }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/SleepViewAction.java b/vector/src/androidTest/java/im/vector/app/SleepViewAction.java
new file mode 100644
index 0000000000..8623f24756
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/SleepViewAction.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app;
+
+import android.view.View;
+
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+
+import org.hamcrest.Matcher;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
+
+public class SleepViewAction {
+
+ public static ViewAction sleep(final long millis) {
+ return new ViewAction() {
+ @Override
+ public Matcher getConstraints() {
+ return isRoot();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Wait for at least " + millis + " millis";
+ }
+
+ @Override
+ public void perform(final UiController uiController, final View view) {
+ uiController.loopMainThreadUntilIdle();
+ uiController.loopMainThreadForAtLeast(millis);
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt b/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt
new file mode 100644
index 0000000000..2e254d48ef
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import androidx.annotation.CallSuper
+import junit.framework.TestCase.fail
+import org.matrix.android.sdk.api.MatrixCallback
+import timber.log.Timber
+import java.util.concurrent.CountDownLatch
+
+/**
+ * Simple implementation of MatrixCallback, which count down the CountDownLatch on each API callback
+ * @param onlySuccessful true to fail if an error occurs. This is the default behavior
+ * @param
+ */
+open class TestMatrixCallback(private val countDownLatch: CountDownLatch,
+ private val onlySuccessful: Boolean = true) : MatrixCallback {
+
+ @CallSuper
+ override fun onSuccess(data: T) {
+ countDownLatch.countDown()
+ }
+
+ @CallSuper
+ override fun onFailure(failure: Throwable) {
+ Timber.e(failure, "TestApiCallback")
+
+ if (onlySuccessful) {
+ fail("onFailure " + failure.localizedMessage)
+ }
+
+ countDownLatch.countDown()
+ }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt
new file mode 100644
index 0000000000..2a1b6d802f
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import android.net.Uri
+import androidx.lifecycle.Observer
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.ViewMatchers
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.hamcrest.CoreMatchers
+import org.junit.Assert
+import org.matrix.android.sdk.api.Matrix
+import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
+import org.matrix.android.sdk.api.auth.data.LoginFlowResult
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.sync.SyncState
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+abstract class VerificationTestBase {
+
+ val password = "password"
+ val homeServerUrl: String = "http://10.0.2.2:8080"
+
+ fun doLogin(homeServerUrl: String, userId: String, password: String) {
+ Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_splash_submit)))
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit))
+ .perform(ViewActions.click())
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginServerTitle))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_server_title)))
+
+ // Chose custom server
+ Espresso.onView(ViewMatchers.withId(R.id.loginServerChoiceOther))
+ .perform(ViewActions.click())
+
+ // Enter local synapse
+ Espresso.onView((ViewMatchers.withId(R.id.loginServerUrlFormHomeServerUrl)))
+ .perform(ViewActions.typeText(homeServerUrl))
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginServerUrlFormSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isEnabled()))
+ .perform(ViewActions.closeSoftKeyboard(), ViewActions.click())
+
+ // Click on the signin button
+ Espresso.onView(ViewMatchers.withId(R.id.loginSignupSigninSignIn))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .perform(ViewActions.click())
+
+ // Ensure password flow supported
+ Espresso.onView(ViewMatchers.withId(R.id.loginField))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ Espresso.onView(ViewMatchers.withId(R.id.passwordField))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+
+ Espresso.onView((ViewMatchers.withId(R.id.loginField)))
+ .perform(ViewActions.typeText(userId))
+ Espresso.onView(ViewMatchers.withId(R.id.loginSubmit))
+ .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isEnabled())))
+
+ Espresso.onView((ViewMatchers.withId(R.id.passwordField)))
+ .perform(ViewActions.closeSoftKeyboard(), ViewActions.typeText(password))
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isEnabled()))
+ .perform(ViewActions.closeSoftKeyboard(), ViewActions.click())
+ }
+
+ private fun createAccount(userId: String = "UiAutoTest", password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") {
+ Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_splash_submit)))
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit))
+ .perform(ViewActions.click())
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginServerTitle))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_server_title)))
+
+ // Chose custom server
+ Espresso.onView(ViewMatchers.withId(R.id.loginServerChoiceOther))
+ .perform(ViewActions.click())
+
+ // Enter local synapse
+ Espresso.onView((ViewMatchers.withId(R.id.loginServerUrlFormHomeServerUrl)))
+ .perform(ViewActions.typeText(homeServerUrl))
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginServerUrlFormSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isEnabled()))
+ .perform(ViewActions.closeSoftKeyboard(), ViewActions.click())
+
+ // Click on the signup button
+ Espresso.onView(ViewMatchers.withId(R.id.loginSignupSigninSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ .perform(ViewActions.click())
+
+ // Ensure password flow supported
+ Espresso.onView(ViewMatchers.withId(R.id.loginField))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ Espresso.onView(ViewMatchers.withId(R.id.passwordField))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+
+ Espresso.onView((ViewMatchers.withId(R.id.loginField)))
+ .perform(ViewActions.typeText(userId))
+ Espresso.onView(ViewMatchers.withId(R.id.loginSubmit))
+ .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isEnabled())))
+
+ Espresso.onView((ViewMatchers.withId(R.id.passwordField)))
+ .perform(ViewActions.typeText(password))
+
+ Espresso.onView(ViewMatchers.withId(R.id.loginSubmit))
+ .check(ViewAssertions.matches(ViewMatchers.isEnabled()))
+ .perform(ViewActions.closeSoftKeyboard(), ViewActions.click())
+
+ Espresso.onView(ViewMatchers.withId(R.id.homeDrawerFragmentContainer))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ }
+
+ fun createAccountAndSync(matrix: Matrix, userName: String,
+ password: String,
+ withInitialSync: Boolean): Session {
+ val hs = createHomeServerConfig()
+
+ doSync {
+ matrix.authenticationService()
+ .getLoginFlow(hs, it)
+ }
+
+ doSync {
+ matrix.authenticationService()
+ .getRegistrationWizard()
+ .createAccount(userName, password, null, it)
+ }
+
+ // Perform dummy step
+ val registrationResult = doSync {
+ matrix.authenticationService()
+ .getRegistrationWizard()
+ .dummy(it)
+ }
+
+ Assert.assertTrue(registrationResult is RegistrationResult.Success)
+ val session = (registrationResult as RegistrationResult.Success).session
+ if (withInitialSync) {
+ syncSession(session)
+ }
+
+ return session
+ }
+
+ fun createHomeServerConfig(): HomeServerConnectionConfig {
+ return HomeServerConnectionConfig.Builder()
+ .withHomeServerUri(Uri.parse(homeServerUrl))
+ .build()
+ }
+
+ // Transform a method with a MatrixCallback to a synchronous method
+ inline fun doSync(block: (MatrixCallback) -> Unit): T {
+ val lock = CountDownLatch(1)
+ var result: T? = null
+
+ val callback = object : TestMatrixCallback(lock) {
+ override fun onSuccess(data: T) {
+ result = data
+ super.onSuccess(data)
+ }
+ }
+
+ block.invoke(callback)
+
+ lock.await(20_000, TimeUnit.MILLISECONDS)
+
+ Assert.assertNotNull(result)
+ return result!!
+ }
+
+ fun syncSession(session: Session) {
+ val lock = CountDownLatch(1)
+
+ GlobalScope.launch(Dispatchers.Main) { session.open() }
+
+ session.startSync(true)
+
+ val syncLiveData = runBlocking(Dispatchers.Main) {
+ session.getSyncStateLive()
+ }
+ val syncObserver = object : Observer {
+ override fun onChanged(t: SyncState?) {
+ if (session.hasAlreadySynced()) {
+ lock.countDown()
+ syncLiveData.removeObserver(this)
+ }
+ }
+ }
+ GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
+
+ lock.await(20_000, TimeUnit.MILLISECONDS)
+ }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt
new file mode 100644
index 0000000000..d218b6ef7e
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.IdlingRegistry
+import androidx.test.espresso.IdlingResource
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem
+import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isRoot
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import im.vector.app.features.MainActivity
+import im.vector.app.features.home.HomeActivity
+import org.hamcrest.CoreMatchers.not
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.matrix.android.sdk.api.Matrix
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
+import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class VerifySessionInteractiveTest : VerificationTestBase() {
+
+ var existingSession: Session? = null
+
+ @get:Rule
+ val activityRule = ActivityScenarioRule(MainActivity::class.java)
+
+ @Before
+ fun createSessionWithCrossSigning() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val matrix = Matrix.getInstance(context)
+ val userName = "foobar_${System.currentTimeMillis()}"
+ existingSession = createAccountAndSync(matrix, userName, password, true)
+ doSync {
+ existingSession!!.cryptoService().crossSigningService()
+ .initializeCrossSigning(UserPasswordAuth(
+ user = existingSession!!.myUserId,
+ password = "password"
+ ), it)
+ }
+ }
+
+ @Test
+ fun checkVerifyPopup() {
+ val userId: String = existingSession!!.myUserId
+
+ doLogin(homeServerUrl, userId, password)
+
+ // Thread.sleep(6000)
+ withIdlingResource(activityIdlingResource(HomeActivity::class.java)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ .perform(closeSoftKeyboard())
+ }
+
+ val activity = EspressoHelper.getCurrentActivity()!!
+ val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession()
+
+ withIdlingResource(initialSyncIdlingResource(uiSession)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ }
+
+ // THIS IS THE ONLY WAY I FOUND TO CLICK ON ALERTERS... :(
+ // Cannot wait for view because of alerter animation? ...
+ onView(isRoot())
+ .perform(waitForView(withId(com.tapadoo.alerter.R.id.llAlertBackground)))
+// Thread.sleep(1000)
+// onView(withId(com.tapadoo.alerter.R.id.llAlertBackground))
+// .perform(click())
+ Thread.sleep(1000)
+ val popup = activity.findViewById(com.tapadoo.alerter.R.id.llAlertBackground)
+ activity.runOnUiThread {
+ popup.performClick()
+ }
+
+ onView(isRoot())
+ .perform(waitForView(withId(R.id.bottomSheetFragmentContainer)))
+// .check()
+// onView(withId(R.id.bottomSheetFragmentContainer))
+// .check(matches(isDisplayed()))
+
+// onView(isRoot()).perform(SleepViewAction.sleep(2000))
+
+ onView(withText(R.string.use_latest_app))
+ .check(matches(isDisplayed()))
+
+ // 4S is not setup so passphrase option should be hidden
+ onView(withId(R.id.bottomSheetFragmentContainer))
+ .check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
+
+ val request = existingSession!!.cryptoService().verificationService().requestKeyVerification(
+ listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
+ existingSession!!.myUserId,
+ listOf(uiSession.sessionParams.deviceId!!)
+ )
+
+ val transactionId = request.transactionId!!
+ val sasReadyIdle = verificationStateIdleResource(transactionId, VerificationTxState.ShortCodeReady, uiSession)
+ val otherSessionSasReadyIdle = verificationStateIdleResource(transactionId, VerificationTxState.ShortCodeReady, existingSession!!)
+
+ onView(isRoot()).perform(SleepViewAction.sleep(1000))
+
+ // Assert QR code option is there and available
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .check(matches(hasDescendant(withText(R.string.verification_scan_their_code))))
+
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .check(matches(hasDescendant(withId(R.id.itemVerificationQrCodeImage))))
+
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .perform(
+ actionOnItem(
+ hasDescendant(withText(R.string.verification_scan_emoji_title)),
+ click()
+ )
+ )
+
+ val firstSessionTr = existingSession!!.cryptoService().verificationService().getExistingTransaction(
+ existingSession!!.myUserId,
+ transactionId
+ ) as SasVerificationTransaction
+
+ IdlingRegistry.getInstance().register(sasReadyIdle)
+ IdlingRegistry.getInstance().register(otherSessionSasReadyIdle)
+ onView(isRoot()).perform(SleepViewAction.sleep(300))
+ // will only execute when Idle is ready
+ val expectedEmojis = firstSessionTr.getEmojiCodeRepresentation()
+ val targets = listOf(R.id.emoji0, R.id.emoji1, R.id.emoji2, R.id.emoji3, R.id.emoji4, R.id.emoji5, R.id.emoji6)
+ targets.forEachIndexed { index, res ->
+ onView(withId(res))
+ .check(
+ matches(hasDescendant(withText(expectedEmojis[index].nameResId)))
+ )
+ }
+
+ IdlingRegistry.getInstance().unregister(sasReadyIdle)
+ IdlingRegistry.getInstance().unregister(otherSessionSasReadyIdle)
+
+ val verificationSuccessIdle =
+ verificationStateIdleResource(transactionId, VerificationTxState.Verified, uiSession)
+
+ // CLICK ON THEY MATCH
+
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .perform(
+ actionOnItem(
+ hasDescendant(withText(R.string.verification_sas_match)),
+ click()
+ )
+ )
+
+ firstSessionTr.userHasVerifiedShortCode()
+
+ onView(isRoot()).perform(SleepViewAction.sleep(1000))
+
+ withIdlingResource(verificationSuccessIdle) {
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .check(
+ matches(hasDescendant(withText(R.string.verification_conclusion_ok_self_notice)))
+ )
+ }
+
+ // Wait a bit before done (to delay a bit sending of secrets to let other have time
+ // to mark as verified :/
+ Thread.sleep(5_000)
+ // Click on done
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .perform(
+ actionOnItem(
+ hasDescendant(withText(R.string.done)),
+ click()
+ )
+ )
+
+ // Wait until local secrets are known (gossip)
+ withIdlingResource(allSecretsKnownIdling(uiSession)) {
+ onView(withId(R.id.groupToolbarAvatarImageView))
+ .perform(click())
+ }
+ }
+
+ fun signout() {
+ onView((withId(R.id.groupToolbarAvatarImageView)))
+ .perform(click())
+
+ onView((withId(R.id.homeDrawerHeaderSettingsView)))
+ .perform(click())
+
+ onView(withText("General"))
+ .perform(click())
+ }
+
+ fun verificationStateIdleResource(transactionId: String, checkForState: VerificationTxState, session: Session): IdlingResource {
+ val idle = object : IdlingResource, VerificationService.Listener {
+ private var callback: IdlingResource.ResourceCallback? = null
+
+ private var currentState: VerificationTxState? = null
+
+ override fun getName() = "verificationSuccessIdle"
+
+ override fun isIdleNow(): Boolean {
+ return currentState == checkForState
+ }
+
+ override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
+ this.callback = callback
+ }
+
+ fun update(state: VerificationTxState) {
+ currentState = state
+ if (state == checkForState) {
+ session.cryptoService().verificationService().removeListener(this)
+ callback?.onTransitionToIdle()
+ }
+ }
+
+ /**
+ * Called when a transaction is created, either by the user or initiated by the other user.
+ */
+ override fun transactionCreated(tx: VerificationTransaction) {
+ if (tx.transactionId == transactionId) update(tx.state)
+ }
+
+ /**
+ * Called when a transaction is updated. You may be interested to track the state of the VerificationTransaction.
+ */
+ override fun transactionUpdated(tx: VerificationTransaction) {
+ if (tx.transactionId == transactionId) update(tx.state)
+ }
+ }
+
+ session.cryptoService().verificationService().addListener(idle)
+ return idle
+ }
+
+ object UITestVerificationUtils
+}
diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt
new file mode 100644
index 0000000000..f8c2a89ea8
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
+import androidx.test.espresso.action.ViewActions.typeText
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem
+import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isRoot
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.MainActivity
+import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
+import im.vector.app.features.crypto.recover.BootstrapCrossSigningTask
+import im.vector.app.features.crypto.recover.Params
+import im.vector.app.features.crypto.recover.SetupMode
+import im.vector.app.features.home.HomeActivity
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.matrix.android.sdk.api.Matrix
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class VerifySessionPassphraseTest : VerificationTestBase() {
+
+ var existingSession: Session? = null
+ val passphrase = "person woman camera tv"
+
+ @get:Rule
+ val activityRule = ActivityScenarioRule(MainActivity::class.java)
+
+ @Before
+ fun createSessionWithCrossSigningAnd4S() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val matrix = Matrix.getInstance(context)
+ val userName = "foobar_${System.currentTimeMillis()}"
+ existingSession = createAccountAndSync(matrix, userName, password, true)
+ doSync {
+ existingSession!!.cryptoService().crossSigningService()
+ .initializeCrossSigning(UserPasswordAuth(
+ user = existingSession!!.myUserId,
+ password = "password"
+ ), it)
+ }
+
+ val task = BootstrapCrossSigningTask(existingSession!!, StringProvider(context.resources))
+
+ runBlocking {
+ task.execute(Params(
+ userPasswordAuth = UserPasswordAuth(password = password),
+ passphrase = passphrase,
+ setupMode = SetupMode.NORMAL
+ ))
+ }
+ }
+
+ @Test
+ fun checkVerifyWithPassphrase() {
+ val userId: String = existingSession!!.myUserId
+
+ doLogin(homeServerUrl, userId, password)
+
+ // Thread.sleep(6000)
+ withIdlingResource(activityIdlingResource(HomeActivity::class.java)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ .perform(closeSoftKeyboard())
+ }
+
+ val activity = EspressoHelper.getCurrentActivity()!!
+ val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession()
+
+ withIdlingResource(initialSyncIdlingResource(uiSession)) {
+ onView(withId(R.id.roomListContainer))
+ .check(matches(isDisplayed()))
+ }
+
+ // THIS IS THE ONLY WAY I FOUND TO CLICK ON ALERTERS... :(
+ // Cannot wait for view because of alerter animation? ...
+ Thread.sleep(6000)
+ val popup = activity.findViewById(com.tapadoo.alerter.R.id.llAlertBackground)
+ activity.runOnUiThread {
+ popup.performClick()
+ }
+
+ onView(withId(R.id.bottomSheetFragmentContainer))
+ .check(matches(isDisplayed()))
+
+ onView(isRoot()).perform(SleepViewAction.sleep(2000))
+
+ onView(withText(R.string.use_latest_app))
+ .check(matches(isDisplayed()))
+
+ // 4S is not setup so passphrase option should be hidden
+ onView(withId(R.id.bottomSheetFragmentContainer))
+ .check(matches(hasDescendant(withText(R.string.verification_cannot_access_other_session))))
+
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .perform(
+ actionOnItem(
+ hasDescendant(withText(R.string.verification_cannot_access_other_session)),
+ click()
+ )
+ )
+
+ withIdlingResource(activityIdlingResource(SharedSecureStorageActivity::class.java)) {
+ onView(withId(R.id.ssss__root)).check(matches(isDisplayed()))
+ }
+
+ onView((withId(R.id.ssss_passphrase_enter_edittext)))
+ .perform(typeText(passphrase))
+
+ onView((withId(R.id.ssss_passphrase_submit)))
+ .perform(click())
+
+ System.out.println("*** passphrase 1")
+
+ withIdlingResource(activityIdlingResource(HomeActivity::class.java)) {
+ System.out.println("*** passphrase 1.1")
+ onView(withId(R.id.bottomSheetVerificationRecyclerView))
+ .check(
+ matches(hasDescendant(withText(R.string.verification_conclusion_ok_self_notice)))
+ )
+ }
+
+ System.out.println("*** passphrase 2")
+ // check that all secrets are known?
+ assert(uiSession.cryptoService().crossSigningService().canCrossSign())
+ assert(uiSession.cryptoService().crossSigningService().allPrivateKeysKnown())
+
+ Thread.sleep(10_000)
+ }
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index 5590e19c10..9cca462d1a 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -28,6 +28,7 @@ import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
@@ -196,33 +197,29 @@ class DebugMenuActivity : VectorBaseActivity() {
}
private fun doScanQRCode() {
- QrCodeScannerActivity.startForResult(this)
+ QrCodeScannerActivity.startForResult(this, qrStartForActivityResult)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (resultCode == Activity.RESULT_OK) {
- when (requestCode) {
- QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
- toast("QrCode: " + QrCodeScannerActivity.getResultText(data) + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(data))
+ private val qrStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ toast("QrCode: " + QrCodeScannerActivity.getResultText(activityResult.data)
+ + " is QRCode: " + QrCodeScannerActivity.getResultIsQrCode(activityResult.data))
- // Also update the current QR Code (reverse operation)
- // renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
- val result = QrCodeScannerActivity.getResultText(data)!!
+ // Also update the current QR Code (reverse operation)
+ // renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
+ val result = QrCodeScannerActivity.getResultText(activityResult.data)!!
- val qrCodeData = result.toQrCodeData()
- Timber.e("qrCodeData: $qrCodeData")
+ val qrCodeData = result.toQrCodeData()
+ Timber.e("qrCodeData: $qrCodeData")
- if (result.length != buffer.size) {
- Timber.e("Error, length are not the same")
- } else {
- // Convert to ByteArray
- val byteArrayResult = result.toByteArray(Charsets.ISO_8859_1)
- for (i in byteArrayResult.indices) {
- if (buffer[i] != byteArrayResult[i]) {
- Timber.e("Error for byte $i, expecting ${buffer[i]} and get ${byteArrayResult[i]}")
- }
- }
+ if (result.length != buffer.size) {
+ Timber.e("Error, length are not the same")
+ } else {
+ // Convert to ByteArray
+ val byteArrayResult = result.toByteArray(Charsets.ISO_8859_1)
+ for (i in byteArrayResult.indices) {
+ if (buffer[i] != byteArrayResult[i]) {
+ Timber.e("Error for byte $i, expecting ${buffer[i]} and get ${byteArrayResult[i]}")
}
}
}
diff --git a/vector/src/debug/res/xml/shortcuts.xml b/vector/src/debug/res/xml/shortcuts.xml
new file mode 100644
index 0000000000..100eac5691
--- /dev/null
+++ b/vector/src/debug/res/xml/shortcuts.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
index b7834ecf45..e46a07f712 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
@@ -15,6 +15,8 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences
@@ -28,7 +30,7 @@ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: Vecto
private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
- override fun perform() {
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
if (vectorPreferences.autoStartOnBoot()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.SUCCESS
@@ -38,7 +40,7 @@ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: Vecto
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true)
- manager?.retry()
+ manager?.retry(activityResultLauncher)
}
}
status = TestStatus.FAILED
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
index 3e053d7fec..abdd696724 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
@@ -15,7 +15,9 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
+import android.content.Intent
import android.net.ConnectivityManager
+import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.core.net.ConnectivityManagerCompat
@@ -28,7 +30,7 @@ class TestBackgroundRestrictions @Inject constructor(private val context: AppCom
private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
- override fun perform() {
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
context.getSystemService()!!.apply {
// Checks if the device is on a metered network
if (isActiveNetworkMetered) {
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt
index 510ade0a33..b1eeae6681 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt
@@ -15,12 +15,13 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
import im.vector.app.core.utils.requestDisablingBatteryOptimization
-import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import javax.inject.Inject
@@ -29,7 +30,7 @@ class TestBatteryOptimization @Inject constructor(
private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
- override fun perform() {
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
if (isIgnoringBatteryOptimizations(context)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success)
status = TestStatus.SUCCESS
@@ -38,7 +39,7 @@ class TestBatteryOptimization @Inject constructor(
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) {
override fun doFix() {
- requestDisablingBatteryOptimization(context, null, NotificationTroubleshootTestManager.REQ_CODE_FIX)
+ requestDisablingBatteryOptimization(context, activityResultLauncher)
}
}
status = TestStatus.FAILED
diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt
index 5f0ee396ee..7603e738d7 100755
--- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt
+++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt
@@ -1,5 +1,4 @@
/*
- * Copyright 2014 OpenMarket Ltd
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
index 65b8609446..6236aad65c 100644
--- a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
+++ b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
@@ -22,17 +22,21 @@ import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimizati
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
+import im.vector.app.features.settings.troubleshoot.TestNotification
import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
import javax.inject.Inject
-class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
- private val testAccountSettings: TestAccountSettings,
- private val testDeviceSettings: TestDeviceSettings,
- private val testPushRulesSettings: TestPushRulesSettings,
- private val testAutoStartBoot: TestAutoStartBoot,
- private val testBackgroundRestrictions: TestBackgroundRestrictions,
- private val testBatteryOptimization: TestBatteryOptimization) {
+class NotificationTroubleshootTestManagerFactory @Inject constructor(
+ private val testSystemSettings: TestSystemSettings,
+ private val testAccountSettings: TestAccountSettings,
+ private val testDeviceSettings: TestDeviceSettings,
+ private val testPushRulesSettings: TestPushRulesSettings,
+ private val testAutoStartBoot: TestAutoStartBoot,
+ private val testBackgroundRestrictions: TestBackgroundRestrictions,
+ private val testBatteryOptimization: TestBatteryOptimization,
+ private val testNotification: TestNotification
+) {
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment)
@@ -43,6 +47,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
mgr.addTest(testAutoStartBoot)
mgr.addTest(testBackgroundRestrictions)
mgr.addTest(testBatteryOptimization)
+ mgr.addTest(testNotification)
return mgr
}
}
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt
index 318867af91..32888dafd7 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt
@@ -15,12 +15,13 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.iid.FirebaseInstanceId
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startAddGoogleAccountIntent
-import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper
import timber.log.Timber
@@ -32,7 +33,7 @@ import javax.inject.Inject
class TestFirebaseToken @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
- override fun perform() {
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
status = TestStatus.RUNNING
try {
FirebaseInstanceId.getInstance().instanceId
@@ -48,7 +49,7 @@ class TestFirebaseToken @Inject constructor(private val context: AppCompatActivi
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
- startAddGoogleAccountIntent(context, NotificationTroubleshootTestManager.REQ_CODE_FIX)
+ startAddGoogleAccountIntent(context, activityResultLauncher)
}
}
} else {
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
index 133fe1cb05..92e713de81 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
@@ -15,6 +15,8 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
@@ -31,7 +33,7 @@ class TestPlayServices @Inject constructor(private val context: AppCompatActivit
private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
- override fun perform() {
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) {
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
new file mode 100644
index 0000000000..da93d54075
--- /dev/null
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.app.gplay.features.settings.troubleshoot
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.appcompat.app.AppCompatActivity
+import im.vector.app.R
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.pushers.PushersManager
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.settings.troubleshoot.TroubleshootTest
+import im.vector.app.push.fcm.FcmHelper
+import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure
+import org.matrix.android.sdk.api.util.Cancelable
+import javax.inject.Inject
+
+/**
+ * Test Push by asking the Push Gateway to send a Push back
+ */
+class TestPushFromPushGateway @Inject constructor(private val context: AppCompatActivity,
+ private val stringProvider: StringProvider,
+ private val errorFormatter: ErrorFormatter,
+ private val pushersManager: PushersManager)
+ : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
+
+ private var action: Cancelable? = null
+
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ val fcmToken = FcmHelper.getFcmToken(context) ?: run {
+ status = TestStatus.FAILED
+ return
+ }
+ action = pushersManager.testPush(fcmToken, object : MatrixCallback {
+ override fun onFailure(failure: Throwable) {
+ description = if (failure is PushGatewayFailure.PusherRejected) {
+ stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed)
+ } else {
+ errorFormatter.toHumanReadable(failure)
+ }
+ status = TestStatus.FAILED
+ }
+
+ override fun onSuccess(data: Unit) {
+ // Wait for the push to be received
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push)
+ status = TestStatus.RUNNING
+ }
+ })
+ }
+
+ override fun onPushReceived() {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_success)
+ status = TestStatus.SUCCESS
+ }
+
+ override fun cancel() {
+ action?.cancel()
+ }
+}
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
index 6cb9c38fc6..f400c17d46 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
@@ -15,6 +15,8 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
@@ -37,7 +39,7 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc
private val activeSessionHolder: ActiveSessionHolder)
: TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
- override fun perform() {
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
// Check if we have a registered pusher for this token
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
@@ -59,9 +61,9 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
- manager?.retry()
+ manager?.retry(activityResultLauncher)
} else if (workInfo.state == WorkInfo.State.FAILED) {
- manager?.retry()
+ manager?.retry(activityResultLauncher)
}
}
})
diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
index 8fdb65c8d0..cfd241d4f9 100755
--- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
@@ -19,10 +19,12 @@
package im.vector.app.gplay.push.fcm
+import android.content.Intent
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import im.vector.app.BuildConfig
@@ -34,6 +36,7 @@ import im.vector.app.features.badge.BadgeProxy
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotifiableMessageEvent
import im.vector.app.features.notifications.NotificationDrawerManager
+import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
@@ -60,11 +63,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
override fun onCreate() {
super.onCreate()
- notificationDrawerManager = vectorComponent().notificationDrawerManager()
- notifiableEventResolver = vectorComponent().notifiableEventResolver()
- pusherManager = vectorComponent().pusherManager()
- activeSessionHolder = vectorComponent().activeSessionHolder()
- vectorPreferences = vectorComponent().vectorPreferences()
+ with(vectorComponent()) {
+ notificationDrawerManager = notificationDrawerManager()
+ notifiableEventResolver = notifiableEventResolver()
+ pusherManager = pusherManager()
+ activeSessionHolder = activeSessionHolder()
+ vectorPreferences = vectorPreferences()
+ }
}
/**
@@ -73,6 +78,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* @param message the message
*/
override fun onMessageReceived(message: RemoteMessage) {
+ // Diagnostic Push
+ if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
+ val intent = Intent(NotificationUtils.PUSH_ACTION)
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
+ return
+ }
+
if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.i("Notification are disabled for this device")
return
diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
index d139cad9d3..913eab211d 100755
--- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
+++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
@@ -1,6 +1,4 @@
/*
- * Copyright 2014 OpenMarket Ltd
- * Copyright 2017 Vector Creations Ltd
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
index b2dad09483..e96c603e60 100644
--- a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
+++ b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
@@ -19,20 +19,26 @@ import androidx.fragment.app.Fragment
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
+import im.vector.app.features.settings.troubleshoot.TestNotification
import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
import im.vector.app.gplay.features.settings.troubleshoot.TestFirebaseToken
import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices
+import im.vector.app.gplay.features.settings.troubleshoot.TestPushFromPushGateway
import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration
import javax.inject.Inject
-class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings,
- private val testAccountSettings: TestAccountSettings,
- private val testDeviceSettings: TestDeviceSettings,
- private val testBingRulesSettings: TestPushRulesSettings,
- private val testPlayServices: TestPlayServices,
- private val testFirebaseToken: TestFirebaseToken,
- private val testTokenRegistration: TestTokenRegistration) {
+class NotificationTroubleshootTestManagerFactory @Inject constructor(
+ private val testSystemSettings: TestSystemSettings,
+ private val testAccountSettings: TestAccountSettings,
+ private val testDeviceSettings: TestDeviceSettings,
+ private val testBingRulesSettings: TestPushRulesSettings,
+ private val testPlayServices: TestPlayServices,
+ private val testFirebaseToken: TestFirebaseToken,
+ private val testTokenRegistration: TestTokenRegistration,
+ private val testPushFromPushGateway: TestPushFromPushGateway,
+ private val testNotification: TestNotification
+) {
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment)
@@ -43,6 +49,8 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
mgr.addTest(testPlayServices)
mgr.addTest(testFirebaseToken)
mgr.addTest(testTokenRegistration)
+ mgr.addTest(testPushFromPushGateway)
+ mgr.addTest(testNotification)
return mgr
}
}
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 2d00d94a01..1a55d22c13 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -62,6 +62,8 @@
@@ -73,6 +75,10 @@
+
+
@@ -91,11 +97,12 @@
-
+
+ android:theme="@style/AppTheme.Transparent"
+ tools:ignore="Instantiatable" />
-
@@ -171,6 +177,10 @@
+
+
@@ -220,6 +230,7 @@
+
@@ -232,9 +243,11 @@
+
+ android:exported="false"
+ tools:ignore="Instantiatable" />
+
+
Matrix SDK
- Copyright (c) 2016 OpenMarket Ltd
-
- Copyright (c) 2016-2017 Vector Creations Ltd
-
- Copyright (c) 2018-2019 New Vector Ltd
-
Copyright (c) 2019 The Matrix.org Foundation C.I.C
diff --git a/vector/src/main/java/im/vector/app/ActiveSessionDataSource.kt b/vector/src/main/java/im/vector/app/ActiveSessionDataSource.kt
index 12738e2b0b..f58c685ab9 100644
--- a/vector/src/main/java/im/vector/app/ActiveSessionDataSource.kt
+++ b/vector/src/main/java/im/vector/app/ActiveSessionDataSource.kt
@@ -18,8 +18,8 @@
package im.vector.app
import arrow.core.Option
-import org.matrix.android.sdk.api.session.Session
import im.vector.app.core.utils.BehaviorDataSource
+import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt
index 31bc822a22..1e92f7bc67 100644
--- a/vector/src/main/java/im/vector/app/AppStateHandler.kt
+++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt
@@ -20,9 +20,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import arrow.core.Option
-import org.matrix.android.sdk.api.session.group.model.GroupSummary
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
-import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import im.vector.app.features.grouplist.ALL_COMMUNITIES_GROUP_ID
import im.vector.app.features.grouplist.SelectedGroupDataSource
import im.vector.app.features.home.HomeRoomListDataSource
@@ -32,6 +29,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.addTo
+import org.matrix.android.sdk.api.session.group.model.GroupSummary
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.rx
import java.util.concurrent.TimeUnit
import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/EmojiCompatFontProvider.kt b/vector/src/main/java/im/vector/app/EmojiCompatFontProvider.kt
index 3aad81529c..689c12b6d3 100644
--- a/vector/src/main/java/im/vector/app/EmojiCompatFontProvider.kt
+++ b/vector/src/main/java/im/vector/app/EmojiCompatFontProvider.kt
@@ -23,7 +23,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class EmojiCompatFontProvider @Inject constructor(): FontsContractCompat.FontRequestCallback() {
+class EmojiCompatFontProvider @Inject constructor() : FontsContractCompat.FontRequestCallback() {
var typeface: Typeface? = null
set(value) {
diff --git a/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt b/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt
index dbb0848e74..c1ca3dd21d 100644
--- a/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt
+++ b/vector/src/main/java/im/vector/app/EmojiCompatWrapper.kt
@@ -24,7 +24,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class EmojiCompatWrapper @Inject constructor(private val context: Context) {
+class EmojiCompatWrapper @Inject constructor(private val context: Context) {
private var initialized = false
diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt
index 4b0ef66459..4f89763cda 100644
--- a/vector/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/app/VectorApplication.kt
@@ -17,7 +17,10 @@
package im.vector.app
import android.app.Application
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.content.res.Configuration
import android.os.Handler
import android.os.HandlerThread
@@ -92,6 +95,15 @@ class VectorApplication :
// font thread handler
private var fontThreadHandler: Handler? = null
+ private val powerKeyReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent) {
+ if (intent.action == Intent.ACTION_SCREEN_OFF
+ && vectorPreferences.useFlagPinCode()) {
+ pinLocker.screenIsOff()
+ }
+ }
+ }
+
override fun onCreate() {
enableStrictModeIfNeeded()
super.onCreate()
@@ -163,6 +175,12 @@ class VectorApplication :
ProcessLifecycleOwner.get().lifecycle.addObserver(pinLocker)
// This should be done as early as possible
// initKnownEmojiHashSet(appContext)
+
+ applicationContext.registerReceiver(powerKeyReceiver, IntentFilter().apply {
+ // Looks like i cannot receive OFF, if i don't have both ON and OFF
+ addAction(Intent.ACTION_SCREEN_OFF)
+ addAction(Intent.ACTION_SCREEN_ON)
+ })
}
private fun enableStrictModeIfNeeded() {
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index 75f61a7b01..014a244bf8 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -27,6 +27,7 @@ import im.vector.app.features.contactsbook.ContactsBookFragment
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
import im.vector.app.features.crypto.quads.SharedSecuredStorageKeyFragment
import im.vector.app.features.crypto.quads.SharedSecuredStoragePassphraseFragment
+import im.vector.app.features.crypto.quads.SharedSecuredStorageResetAllFragment
import im.vector.app.features.crypto.recover.BootstrapAccountPasswordFragment
import im.vector.app.features.crypto.recover.BootstrapConclusionFragment
import im.vector.app.features.crypto.recover.BootstrapConfirmPassphraseFragment
@@ -51,6 +52,7 @@ import im.vector.app.features.home.HomeDrawerFragment
import im.vector.app.features.home.LoadingFragment
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.home.room.detail.RoomDetailFragment
+import im.vector.app.features.home.room.detail.search.SearchFragment
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.login.LoginCaptchaFragment
import im.vector.app.features.login.LoginFragment
@@ -530,6 +532,11 @@ interface FragmentModule {
@FragmentKey(SharedSecuredStorageKeyFragment::class)
fun bindSharedSecuredStorageKeyFragment(fragment: SharedSecuredStorageKeyFragment): Fragment
+ @Binds
+ @IntoMap
+ @FragmentKey(SharedSecuredStorageResetAllFragment::class)
+ fun bindSharedSecuredStorageResetAllFragment(fragment: SharedSecuredStorageResetAllFragment): Fragment
+
@Binds
@IntoMap
@FragmentKey(SetIdentityServerFragment::class)
@@ -564,4 +571,9 @@ interface FragmentModule {
@IntoMap
@FragmentKey(RoomBannedMemberListFragment::class)
fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment
+
+ @Binds
+ @IntoMap
+ @FragmentKey(SearchFragment::class)
+ fun bindSearchFragment(fragment: SearchFragment): Fragment
}
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 d337ec7977..fde40f9195 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
@@ -38,6 +38,7 @@ import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.HomeModule
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
@@ -50,9 +51,7 @@ import im.vector.app.features.invite.VectorInviteView
import im.vector.app.features.link.LinkHandlerActivity
import im.vector.app.features.login.LoginActivity
import im.vector.app.features.media.BigImageViewerActivity
-import im.vector.app.features.media.ImageMediaViewerActivity
import im.vector.app.features.media.VectorAttachmentViewerActivity
-import im.vector.app.features.media.VideoMediaViewerActivity
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.permalink.PermalinkHandlerActivity
import im.vector.app.features.pin.PinLocker
@@ -124,10 +123,8 @@ interface ScreenComponent {
fun inject(activity: MainActivity)
fun inject(activity: RoomDirectoryActivity)
fun inject(activity: BugReportActivity)
- fun inject(activity: ImageMediaViewerActivity)
fun inject(activity: FilteredRoomsActivity)
fun inject(activity: CreateRoomActivity)
- fun inject(activity: VideoMediaViewerActivity)
fun inject(activity: CreateDirectRoomActivity)
fun inject(activity: IncomingShareActivity)
fun inject(activity: SoftLogoutActivity)
@@ -142,6 +139,7 @@ interface ScreenComponent {
fun inject(activity: VectorCallActivity)
fun inject(activity: VectorAttachmentViewerActivity)
fun inject(activity: VectorJitsiActivity)
+ fun inject(activity: SearchActivity)
/* ==========================================================================================
* BottomSheets
diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt
index 4ba3d6ba13..28f3a52efa 100644
--- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt
+++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt
@@ -36,6 +36,7 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan
import im.vector.app.features.grouplist.SelectedGroupDataSource
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.HomeRoomListDataSource
+import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.VectorHtmlCompressor
@@ -114,6 +115,8 @@ interface VectorComponent {
fun selectedGroupStore(): SelectedGroupDataSource
+ fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
+
fun activeSessionObservableStore(): ActiveSessionDataSource
fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler
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 753cfd646c..5226a69094 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
@@ -72,12 +72,12 @@ class UnrecognizedCertificateDialog @Inject constructor(
* @param callback callback to fire when the user makes a decision
*/
private fun internalShow(activity: Activity,
- unrecognizedFingerprint: Fingerprint,
- existing: Boolean,
- callback: Callback,
- userId: String?,
- homeServerUrl: String,
- homeServerConnectionConfigHasFingerprints: Boolean) {
+ unrecognizedFingerprint: Fingerprint,
+ existing: Boolean,
+ callback: Callback,
+ userId: String?,
+ homeServerUrl: String,
+ homeServerConnectionConfigHasFingerprints: Boolean) {
val dialogId = userId ?: homeServerUrl + unrecognizedFingerprint.displayableHexRepr
if (openDialogIds.contains(dialogId)) {
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt
new file mode 100644
index 0000000000..3dceec48ef
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.epoxy
+
+import android.animation.ObjectAnimator
+import android.text.TextUtils
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.doOnPreDraw
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.extensions.copyOnLongClick
+
+@EpoxyModelClass(layout = R.layout.item_expandable_textview)
+abstract class ExpandableTextItem : VectorEpoxyModel() {
+
+ @EpoxyAttribute
+ lateinit var content: String
+
+ @EpoxyAttribute
+ var maxLines: Int = 3
+
+ private var isExpanded = false
+ private var expandedLines = 0
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.content.text = content
+ holder.content.copyOnLongClick()
+
+ holder.content.doOnPreDraw {
+ if (holder.content.lineCount > maxLines) {
+ expandedLines = holder.content.lineCount
+ holder.content.maxLines = maxLines
+
+ holder.view.setOnClickListener {
+ if (isExpanded) {
+ collapse(holder)
+ } else {
+ expand(holder)
+ }
+ }
+ holder.arrow.isVisible = true
+ } else {
+ holder.arrow.isVisible = false
+ }
+ }
+ }
+
+ private fun expand(holder: Holder) {
+ ObjectAnimator
+ .ofInt(holder.content, "maxLines", expandedLines)
+ .setDuration(200)
+ .start()
+
+ holder.content.ellipsize = null
+ holder.arrow.setImageResource(R.drawable.ic_expand_less)
+ holder.arrow.contentDescription = holder.view.context.getString(R.string.merged_events_collapse)
+ isExpanded = true
+ }
+
+ private fun collapse(holder: Holder) {
+ ObjectAnimator
+ .ofInt(holder.content, "maxLines", maxLines)
+ .setDuration(200)
+ .start()
+
+ holder.content.ellipsize = TextUtils.TruncateAt.END
+ holder.arrow.setImageResource(R.drawable.ic_expand_more)
+ holder.arrow.contentDescription = holder.view.context.getString(R.string.merged_events_expand)
+ isExpanded = false
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val content by bind(R.id.expandableContent)
+ val arrow by bind(R.id.expandableArrow)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyHolder.kt b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyHolder.kt
index 8a75a6bddf..f33b4dccbe 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyHolder.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/VectorEpoxyHolder.kt
@@ -36,7 +36,7 @@ abstract class VectorEpoxyHolder : EpoxyHolder() {
protected fun bind(id: Int): ReadOnlyProperty =
Lazy { holder: VectorEpoxyHolder, prop ->
holder.view.findViewById(id) as V?
- ?: throw IllegalStateException("View ID $id for '${prop.name}' not found.")
+ ?: throw IllegalStateException("View ID $id for '${prop.name}' not found.")
}
/**
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt
index 4beefeb737..e28bec6874 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt
@@ -42,18 +42,25 @@ abstract class BottomSheetActionItem : VectorEpoxyModel Unit)? = null
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt
index 0c5919e5f3..ca8ef89c68 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt
@@ -33,10 +33,13 @@ abstract class BottomSheetQuickReactionsItem : VectorEpoxyModel
+
@EpoxyAttribute
lateinit var selecteds: List
+
@EpoxyAttribute
var listener: Listener? = null
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt
index 119950f1b8..8f5ccba158 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt
@@ -34,8 +34,10 @@ abstract class BottomSheetSendStateItem : VectorEpoxyModel : VectorEpoxy
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var editable: Boolean = true
+
@EpoxyAttribute
var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var clickListener: View.OnClickListener? = null
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt
index c5d5ee52a8..3bef38d4cb 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt
@@ -38,6 +38,7 @@ abstract class ProfileActionItem : VectorEpoxyModel()
@EpoxyAttribute
lateinit var title: String
+
@EpoxyAttribute
var subtitle: String? = null
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileSectionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileSectionItem.kt
index 6fa1ed23be..b38342c057 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileSectionItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileSectionItem.kt
@@ -24,7 +24,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_profile_section)
-abstract class ProfileSectionItem: VectorEpoxyModel() {
+abstract class ProfileSectionItem : VectorEpoxyModel() {
@EpoxyAttribute
lateinit var title: String
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 43395b97f7..6065c74541 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
@@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection
import java.net.SocketTimeoutException
-import java.net.UnknownHostException
import javax.inject.Inject
import javax.net.ssl.SSLException
import javax.net.ssl.SSLPeerUnverifiedException
@@ -45,60 +44,57 @@ class DefaultErrorFormatter @Inject constructor(
when (throwable.ioException) {
is SocketTimeoutException ->
stringProvider.getString(R.string.error_network_timeout)
- is UnknownHostException ->
- // Invalid homeserver?
- // TODO Check network state, airplane mode, etc.
- stringProvider.getString(R.string.login_error_unknown_host)
is SSLPeerUnverifiedException ->
stringProvider.getString(R.string.login_error_ssl_peer_unverified)
is SSLException ->
stringProvider.getString(R.string.login_error_ssl_other)
else ->
+ // TODO Check network state, airplane mode, etc.
stringProvider.getString(R.string.error_no_network)
}
}
is Failure.ServerError -> {
when {
- throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
+ throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
// Special case for terms and conditions
stringProvider.getString(R.string.error_terms_not_accepted)
}
- throwable.isInvalidPassword() -> {
+ throwable.isInvalidPassword() -> {
stringProvider.getString(R.string.auth_invalid_login_param)
}
- throwable.error.code == MatrixError.M_USER_IN_USE -> {
+ throwable.error.code == MatrixError.M_USER_IN_USE -> {
stringProvider.getString(R.string.login_signup_error_user_in_use)
}
- throwable.error.code == MatrixError.M_BAD_JSON -> {
+ throwable.error.code == MatrixError.M_BAD_JSON -> {
stringProvider.getString(R.string.login_error_bad_json)
}
- throwable.error.code == MatrixError.M_NOT_JSON -> {
+ throwable.error.code == MatrixError.M_NOT_JSON -> {
stringProvider.getString(R.string.login_error_not_json)
}
- throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
+ throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
stringProvider.getString(R.string.login_error_threepid_denied)
}
- throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {
+ throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {
limitExceededError(throwable.error)
}
- throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
+ throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
stringProvider.getString(R.string.login_reset_password_error_not_found)
}
- throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
+ throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
stringProvider.getString(R.string.auth_invalid_login_deactivated_account)
}
throwable.error.code == MatrixError.M_THREEPID_IN_USE
- && throwable.error.message == "Email is already in use" -> {
+ && throwable.error.message == "Email is already in use" -> {
stringProvider.getString(R.string.account_email_already_used_error)
}
throwable.error.code == MatrixError.M_THREEPID_IN_USE
&& throwable.error.message == "MSISDN is already in use" -> {
stringProvider.getString(R.string.account_phone_number_already_used_error)
}
- throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> {
+ throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> {
stringProvider.getString(R.string.error_threepid_auth_failed)
}
- else -> {
+ else -> {
throwable.error.message.takeIf { it.isNotEmpty() }
?: throwable.error.code.takeIf { it.isNotEmpty() }
}
diff --git a/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt
index ce0b2d0703..129491fe15 100644
--- a/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt
+++ b/vector/src/main/java/im/vector/app/core/error/ResourceLimitErrorFormatter.kt
@@ -20,8 +20,8 @@ import android.content.Context
import androidx.annotation.StringRes
import androidx.core.text.HtmlCompat
import im.vector.app.R
-import org.matrix.android.sdk.api.failure.MatrixError
import me.gujun.android.span.span
+import org.matrix.android.sdk.api.failure.MatrixError
class ResourceLimitErrorFormatter(private val context: Context) {
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Activity.kt b/vector/src/main/java/im/vector/app/core/extensions/Activity.kt
index cc67f633eb..53c2b7fc6c 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Activity.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Activity.kt
@@ -17,11 +17,20 @@
package im.vector.app.core.extensions
import android.app.Activity
+import android.content.Intent
import android.os.Parcelable
+import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import im.vector.app.core.platform.VectorBaseActivity
+fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher {
+ return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
+}
+
fun VectorBaseActivity.addFragment(
frameId: Int,
fragment: Fragment,
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt b/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt
index fbcd6900c1..2740d5393a 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt
@@ -17,7 +17,11 @@
package im.vector.app.core.extensions
import android.app.Activity
+import android.content.Intent
import android.os.Parcelable
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
@@ -26,6 +30,10 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+fun Fragment.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher {
+ return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
+}
+
fun VectorBaseFragment.addFragment(
frameId: Int,
fragment: Fragment,
@@ -160,26 +168,24 @@ fun Fragment.getAllChildFragments(): List {
// Define a missing constant
const val POP_BACK_STACK_EXCLUSIVE = 0
-fun Fragment.queryExportKeys(userId: String, requestCode: Int) {
+fun Fragment.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher) {
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
activity = requireActivity(),
- fragment = this,
+ activityResultLauncher = activityResultLauncher,
defaultFileName = "element-megolm-export-$userId-$timestamp.txt",
- chooserHint = getString(R.string.keys_backup_setup_step1_manual_export),
- requestCode = requestCode
+ chooserHint = getString(R.string.keys_backup_setup_step1_manual_export)
)
}
-fun Activity.queryExportKeys(userId: String, requestCode: Int) {
+fun Activity.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher) {
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
activity = this,
- fragment = null,
+ activityResultLauncher = activityResultLauncher,
defaultFileName = "element-megolm-export-$userId-$timestamp.txt",
- chooserHint = getString(R.string.keys_backup_setup_step1_manual_export),
- requestCode = requestCode
+ chooserHint = getString(R.string.keys_backup_setup_step1_manual_export)
)
}
diff --git a/vector/src/main/java/im/vector/app/core/files/FileSaver.kt b/vector/src/main/java/im/vector/app/core/files/FileSaver.kt
index c4acbb5e65..406fa76520 100644
--- a/vector/src/main/java/im/vector/app/core/files/FileSaver.kt
+++ b/vector/src/main/java/im/vector/app/core/files/FileSaver.kt
@@ -59,7 +59,7 @@ fun addEntryToDownloadManager(context: Context,
file: File,
mimeType: String,
title: String = file.name,
- description: String = file.name) : Uri? {
+ description: String = file.name): Uri? {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
@@ -71,16 +71,16 @@ fun addEntryToDownloadManager(context: Context,
return context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
?.let { uri ->
Timber.v("## addEntryToDownloadManager(): $uri")
- val source = file.inputStream().source().buffer()
- context.contentResolver.openOutputStream(uri)?.sink()?.buffer()?.let { sink ->
- source.use { input ->
- sink.use { output ->
- output.writeAll(input)
+ val source = file.inputStream().source().buffer()
+ context.contentResolver.openOutputStream(uri)?.sink()?.buffer()?.let { sink ->
+ source.use { input ->
+ sink.use { output ->
+ output.writeAll(input)
+ }
+ }
}
- }
- }
uri
- } ?: run {
+ } ?: run {
Timber.v("## addEntryToDownloadManager(): context.contentResolver.insert failed")
null
diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
index 0eba78dbf8..71bd3ccc05 100644
--- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
+++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
@@ -33,8 +33,6 @@ import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.InputStream
-import java.lang.Exception
-import java.lang.IllegalArgumentException
class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessionHolder)
: ModelLoaderFactory {
@@ -118,7 +116,7 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde
url = data.url,
fileName = data.filename,
elementToDecrypt = data.elementToDecrypt,
- callback = object: MatrixCallback {
+ callback = object : MatrixCallback {
override fun onSuccess(data: File) {
callback.onDataReady(data.inputStream())
}
diff --git a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt
index 24e03b4f40..0d18f808fb 100644
--- a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt
+++ b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentAnalyser.kt
@@ -109,7 +109,7 @@ fun analyseIntent(intent: Intent): List {
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
val mimeType = mimeTypes?.getOrElse(i) { mimeTypes[0] }
- // uris list is not a valid mimetype
+ // uris list is not a valid mimetype
.takeUnless { it == ClipDescription.MIMETYPE_TEXT_URILIST }
externalIntentDataList.add(ExternalIntentData.IntentDataClipData(item, mimeType))
diff --git a/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt b/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt
index d7dea789d3..bc1ed3609b 100644
--- a/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt
+++ b/vector/src/main/java/im/vector/app/core/linkify/VectorLinkify.kt
@@ -121,7 +121,7 @@ object VectorLinkify {
remove = if (a.important) i + 1 else i
} else {
when {
- b.end <= a.end ->
+ b.end <= a.end ->
// b is inside a -> b should be removed
remove = i + 1
a.end - a.start > b.end - b.start ->
diff --git a/vector/src/main/java/im/vector/app/core/platform/CheckableFrameLayout.kt b/vector/src/main/java/im/vector/app/core/platform/CheckableFrameLayout.kt
index a755903468..1111a9651c 100644
--- a/vector/src/main/java/im/vector/app/core/platform/CheckableFrameLayout.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/CheckableFrameLayout.kt
@@ -26,11 +26,11 @@ class CheckableFrameLayout : FrameLayout, Checkable {
private var mChecked = false
- constructor(context: Context) : super(context) {}
+ constructor(context: Context) : super(context)
- constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun isChecked(): Boolean {
return mChecked
diff --git a/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt b/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt
index 12f173d0ac..cfb3e3b656 100644
--- a/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt
@@ -25,8 +25,8 @@ import butterknife.BindView
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.hideKeyboard
-import org.matrix.android.sdk.api.session.Session
import kotlinx.android.synthetic.main.activity.*
+import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
/**
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 81f73556a5..79021902c4 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -18,7 +18,6 @@ package im.vector.app.core.platform
import android.app.Activity
import android.content.Context
-import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.Parcelable
@@ -60,6 +59,7 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.observeNotNull
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.utils.toast
@@ -68,7 +68,6 @@ import im.vector.app.features.MainActivityArgs
import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.consent.ConsentNotGivenHelper
import im.vector.app.features.navigation.Navigator
-import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.PinMode
import im.vector.app.features.pin.UnlockedActivity
@@ -178,7 +177,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
override fun onCreate(savedInstanceState: Bundle?) {
- Timber.i("onCreate Activity ${this.javaClass.simpleName}")
+ Timber.i("onCreate Activity ${javaClass.simpleName}")
val vectorComponent = getVectorComponent()
screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this)
val timeForInjection = measureTimeMillis {
@@ -206,7 +205,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
})
pinLocker.getLiveState().observeNotNull(this) {
if (this@VectorBaseActivity !is UnlockedActivity && it == PinLocker.State.LOCKED) {
- navigator.openPinCode(this, PinMode.AUTH)
+ navigator.openPinCode(this, pinStartForActivityResult, PinMode.AUTH)
}
}
sessionListener = vectorComponent.sessionListener()
@@ -306,29 +305,27 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
override fun onDestroy() {
super.onDestroy()
- Timber.i("onDestroy Activity ${this.javaClass.simpleName}")
+ Timber.i("onDestroy Activity ${javaClass.simpleName}")
unBinder?.unbind()
unBinder = null
uiDisposables.dispose()
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == PinActivity.PIN_REQUEST_CODE) {
- when (resultCode) {
- Activity.RESULT_OK -> {
- Timber.v("Pin ok, unlock app")
- pinLocker.unlock()
+ private val pinStartForActivityResult = registerStartForActivityResult { activityResult ->
+ when (activityResult.resultCode) {
+ Activity.RESULT_OK -> {
+ Timber.v("Pin ok, unlock app")
+ pinLocker.unlock()
- // Cancel any new started PinActivity, after a screen rotation for instance
- finishActivity(PinActivity.PIN_REQUEST_CODE)
- }
- else -> {
- if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
- // Remove the task, to be sure that PIN code will be requested when resumed
- finishAndRemoveTask()
- }
+ // Cancel any new started PinActivity, after a screen rotation for instance
+ // FIXME I cannot use this anymore :/
+ // finishActivity(PinActivity.PIN_REQUEST_CODE)
+ }
+ else -> {
+ if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
+ // Remove the task, to be sure that PIN code will be requested when resumed
+ finishAndRemoveTask()
}
}
}
@@ -336,7 +333,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
override fun onResume() {
super.onResume()
- Timber.i("onResume Activity ${this.javaClass.simpleName}")
+ Timber.i("onResume Activity ${javaClass.simpleName}")
configurationViewModel.onActivityResumed()
@@ -376,7 +373,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
override fun onPause() {
super.onPause()
- Timber.i("onPause Activity ${this.javaClass.simpleName}")
+ Timber.i("onPause Activity ${javaClass.simpleName}")
rageShake.stop()
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 9ed5c5c455..e674c277aa 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
@@ -17,6 +17,7 @@ package im.vector.app.core.platform
import android.app.Dialog
import android.content.Context
+import android.content.DialogInterface
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
@@ -86,6 +87,24 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
open val showExpanded = false
+ interface ResultListener {
+ fun onBottomSheetResult(resultCode: Int, data: Any?)
+
+ companion object {
+ const val RESULT_OK = 1
+ const val RESULT_CANCEL = 0
+ }
+ }
+
+ var resultListener: ResultListener? = null
+ var bottomSheetResult: Int = ResultListener.RESULT_CANCEL
+ var bottomSheetResultData: Any? = null
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ resultListener?.onBottomSheetResult(bottomSheetResult, bottomSheetResultData)
+ }
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(getLayoutResId(), container, false)
unBinder = ButterKnife.bind(this, view)
@@ -122,7 +141,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
override fun onResume() {
super.onResume()
- Timber.i("onResume BottomSheet ${this.javaClass.simpleName}")
+ Timber.i("onResume BottomSheet ${javaClass.simpleName}")
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
index 4ce4d210b0..179e21a6d8 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
@@ -58,7 +58,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
// Butterknife unbinder
private var mUnBinder: Unbinder? = null
- val vectorBaseActivity: VectorBaseActivity by lazy {
+ protected val vectorBaseActivity: VectorBaseActivity by lazy {
activity as VectorBaseActivity
}
@@ -112,7 +112,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
}
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- Timber.i("onCreateView Fragment ${this.javaClass.simpleName}")
+ Timber.i("onCreateView Fragment ${javaClass.simpleName}")
return inflater.inflate(getLayoutResId(), container, false)
}
@@ -122,7 +122,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper
override fun onResume() {
super.onResume()
- Timber.i("onResume Fragment ${this.javaClass.simpleName}")
+ Timber.i("onResume Fragment ${javaClass.simpleName}")
}
@CallSuper
@@ -142,7 +142,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper
override fun onDestroyView() {
super.onDestroyView()
- Timber.i("onDestroyView Fragment ${this.javaClass.simpleName}")
+ Timber.i("onDestroyView Fragment ${javaClass.simpleName}")
mUnBinder?.unbind()
mUnBinder = null
uiDisposables.clear()
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt
index 6429c9dfe5..002dfcf068 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt
@@ -42,9 +42,11 @@ abstract class VectorViewModel Single.toAsync(stateReducer: S.(Async) -> S): Single> {
setState { stateReducer(Loading()) }
- return this.map { Success(it) as Async }
+ return map { Success(it) as Async }
.onErrorReturn { Fail(it) }
.doOnSuccess { setState { stateReducer(it) } }
}
@@ -53,9 +55,11 @@ abstract class VectorViewModel Observable.toAsync(stateReducer: S.(Async) -> S): Observable> {
setState { stateReducer(Loading()) }
- return this.map { Success(it) as Async }
+ return map { Success(it) as Async }
.onErrorReturn { Fail(it) }
.doOnNext { setState { stateReducer(it) } }
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
index f0d27182e7..5fe30141d9 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
@@ -22,6 +22,7 @@ import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.util.Cancelable
import java.util.UUID
import javax.inject.Inject
import kotlin.math.abs
@@ -34,6 +35,17 @@ class PushersManager @Inject constructor(
private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider
) {
+ fun testPush(pushKey: String, callback: MatrixCallback): Cancelable {
+ val currentSession = activeSessionHolder.getActiveSession()
+
+ return currentSession.testPush(
+ stringProvider.getString(R.string.pusher_http_url),
+ stringProvider.getString(R.string.pusher_app_id),
+ pushKey,
+ TEST_EVENT_ID,
+ callback
+ )
+ }
fun registerPusherWithFcmKey(pushKey: String): UUID {
val currentSession = activeSessionHolder.getActiveSession()
@@ -56,4 +68,8 @@ class PushersManager @Inject constructor(
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback)
}
+
+ companion object {
+ const val TEST_EVENT_ID = "\$THIS_IS_A_FAKE_EVENT_ID"
+ }
}
diff --git a/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt b/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt
index 2f6b9aa6d2..c184b04bd9 100644
--- a/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt
+++ b/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt
@@ -29,6 +29,7 @@ class DrawableProvider @Inject constructor(private val context: Context) {
fun getDrawable(@DrawableRes colorRes: Int): Drawable? {
return ContextCompat.getDrawable(context, colorRes)
}
+
fun getDrawable(@DrawableRes colorRes: Int, @ColorInt color: Int): Drawable? {
return ContextCompat.getDrawable(context, colorRes)?.let {
ThemeUtils.tintDrawableWithColor(it, color)
diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericLoaderItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericLoaderItem.kt
index 634e58e943..b458f10680 100644
--- a/vector/src/main/java/im/vector/app/core/ui/list/GenericLoaderItem.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericLoaderItem.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package im.vector.app.core.ui.list
import com.airbnb.epoxy.EpoxyModelClass
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt
index 0259898ee3..d418822b7f 100644
--- a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt
@@ -24,6 +24,7 @@ import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes
import androidx.core.view.isGone
import androidx.core.view.isInvisible
@@ -107,6 +108,12 @@ class BottomSheetActionButton @JvmOverloads constructor(
leftIconImageView.imageTintList = value?.let { ColorStateList.valueOf(value) }
}
+ var titleTextColor: Int? = null
+ set(value) {
+ field = value
+ value?.let { actionTextView.setTextColor(it) }
+ }
+
init {
inflate(context, R.layout.item_verification_action, this)
ButterKnife.bind(this)
@@ -120,6 +127,7 @@ class BottomSheetActionButton @JvmOverloads constructor(
rightIcon = getDrawable(R.styleable.BottomSheetActionButton_rightIcon)
tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor))
+ titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ContextCompat.getColor(context, R.color.riotx_accent))
}
}
}
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 828e0ab687..4620d7a1fb 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
@@ -28,11 +28,11 @@ import im.vector.app.R
import im.vector.app.core.error.ResourceLimitErrorFormatter
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.themes.ThemeUtils
-import org.matrix.android.sdk.api.failure.MatrixError
-import org.matrix.android.sdk.api.session.events.model.Event
import kotlinx.android.synthetic.main.view_notification_area.view.*
import me.gujun.android.span.span
import me.saket.bettermovementmethod.BetterLinkMovementMethod
+import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.session.events.model.Event
import timber.log.Timber
/**
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt
index 65577d18f3..64ee71edf8 100644
--- a/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt
@@ -56,15 +56,19 @@ class PasswordStrengthBar @JvmOverloads constructor(
@BindColor(R.color.password_strength_bar_undefined)
@JvmField
var colorBackground: Int = 0
+
@BindColor(R.color.password_strength_bar_weak)
@JvmField
var colorWeak: Int = 0
+
@BindColor(R.color.password_strength_bar_low)
@JvmField
var colorLow: Int = 0
+
@BindColor(R.color.password_strength_bar_ok)
@JvmField
var colorOk: Int = 0
+
@BindColor(R.color.password_strength_bar_strong)
@JvmField
var colorStrong: Int = 0
diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
index d28f6749a6..ff1055fa44 100644
--- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
@@ -31,22 +31,22 @@ import android.provider.Browser
import android.provider.MediaStore
import android.webkit.MimeTypeMap
import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsSession
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
-import androidx.fragment.app.Fragment
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils
-import org.matrix.android.sdk.api.extensions.tryOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okio.buffer
import okio.sink
import okio.source
+import org.matrix.android.sdk.api.extensions.tryOrNull
import timber.log.Timber
import java.io.File
import java.io.FileInputStream
@@ -130,7 +130,7 @@ fun openSoundRecorder(activity: Activity, requestCode: Int) {
* Open file selection activity
*/
fun openFileSelection(activity: Activity,
- fragment: Fragment?,
+ activityResultLauncher: ActivityResultLauncher?,
allowMultipleSelection: Boolean,
requestCode: Int) {
val fileIntent = Intent(Intent.ACTION_GET_CONTENT)
@@ -140,8 +140,8 @@ fun openFileSelection(activity: Activity,
fileIntent.type = "*/*"
try {
- fragment
- ?.startActivityForResult(fileIntent, requestCode)
+ activityResultLauncher
+ ?.launch(fileIntent)
?: run {
activity.startActivityForResult(fileIntent, requestCode)
}
@@ -300,11 +300,24 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) {
sendIntent.type = mediaMimeType
sendIntent.putExtra(Intent.EXTRA_STREAM, mediaUri)
- try {
- context.startActivity(sendIntent)
- } catch (activityNotFoundException: ActivityNotFoundException) {
- context.toast(R.string.error_no_external_application_found)
- }
+ sendShareIntent(context, sendIntent)
+ }
+}
+
+fun shareText(context: Context, text: String) {
+ val sendIntent = Intent()
+ sendIntent.action = Intent.ACTION_SEND
+ sendIntent.type = "text/plain"
+ sendIntent.putExtra(Intent.EXTRA_TEXT, text)
+
+ sendShareIntent(context, sendIntent)
+}
+
+private fun sendShareIntent(context: Context, intent: Intent) {
+ try {
+ context.startActivity(Intent.createChooser(intent, context.getString(R.string.share)))
+ } catch (activityNotFoundException: ActivityNotFoundException) {
+ context.toast(R.string.error_no_external_application_found)
}
}
@@ -440,10 +453,9 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID
*/
fun selectTxtFileToWrite(
activity: Activity,
- fragment: Fragment?,
+ activityResultLauncher: ActivityResultLauncher,
defaultFileName: String,
- chooserHint: String,
- requestCode: Int
+ chooserHint: String
) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
@@ -452,11 +464,7 @@ fun selectTxtFileToWrite(
try {
val chooserIntent = Intent.createChooser(intent, chooserHint)
- if (fragment != null) {
- fragment.startActivityForResult(chooserIntent, requestCode)
- } else {
- activity.startActivityForResult(chooserIntent, requestCode)
- }
+ activityResultLauncher.launch(chooserIntent)
} catch (activityNotFoundException: ActivityNotFoundException) {
activity.toast(R.string.error_no_external_application_found)
}
diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
index aff26ae494..44fc6afa4e 100644
--- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
@@ -22,6 +22,8 @@ import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
@@ -94,6 +96,12 @@ fun logPermissionStatuses(context: Context) {
}
}
+fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher> {
+ return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
+ allGranted.invoke(result.keys.all { result[it] == true })
+ }
+}
+
/**
* See [.checkPermissions]
*
@@ -112,14 +120,14 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
* See [.checkPermissions]
*
* @param permissionsToBeGrantedBitMap
- * @param fragment
+ * @param activityResultLauncher from the calling fragment that is requesting the permissions
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- fragment: Fragment,
- requestCode: Int,
+ activity: Activity,
+ activityResultLauncher: ActivityResultLauncher>,
@StringRes rationaleMessage: Int = 0): Boolean {
- return checkPermissions(permissionsToBeGrantedBitMap, fragment.activity, fragment, requestCode, rationaleMessage)
+ return checkPermissions(permissionsToBeGrantedBitMap, activity, activityResultLauncher, 0, rationaleMessage)
}
/**
@@ -137,22 +145,19 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
*
* @param permissionsToBeGrantedBitMap the permissions bit map to be granted
* @param activity the calling Activity that is requesting the permissions (or fragment parent)
- * @param fragment the calling fragment that is requesting the permissions
+ * @param activityResultLauncher from the calling fragment that is requesting the permissions
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity?,
- fragment: Fragment?,
+ activity: Activity,
+ activityResultLauncher: ActivityResultLauncher>?,
requestCode: Int,
@StringRes rationaleMessage: Int
): Boolean {
var isPermissionGranted = false
// sanity check
- if (null == activity) {
- Timber.w("## checkPermissions(): invalid input data")
- isPermissionGranted = false
- } else if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
+ if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
isPermissionGranted = true
} else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap
@@ -222,7 +227,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
.setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
.setPositiveButton(R.string.ok) { _, _ ->
if (permissionsListToBeGranted.isNotEmpty()) {
- fragment?.requestPermissions(permissionsListToBeGranted.toTypedArray(), requestCode)
+ activityResultLauncher
+ ?.launch(permissionsListToBeGranted.toTypedArray())
?: run {
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
}
@@ -262,7 +268,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
.show()
*/
} else {
- fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
+ activityResultLauncher
+ ?.launch(permissionsArrayToBeGranted)
?: run {
ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
}
@@ -307,43 +314,6 @@ private fun updatePermissionsToBeGranted(activity: Activity,
return isRequestPermissionRequested
}
-/**
- * Helper method to process [.PERMISSIONS_FOR_AUDIO_IP_CALL]
- * on onRequestPermissionsResult() methods.
- *
- * @param context App context
- * @param grantResults permissions granted results
- * @return true if audio IP call is permitted, false otherwise
- */
-fun onPermissionResultAudioIpCall(context: Context, grantResults: IntArray): Boolean {
- val arePermissionsGranted = allGranted(grantResults)
-
- if (!arePermissionsGranted) {
- Toast.makeText(context, R.string.permissions_action_not_performed_missing_permissions, Toast.LENGTH_SHORT).show()
- }
-
- return arePermissionsGranted
-}
-
-/**
- * Helper method to process [.PERMISSIONS_FOR_VIDEO_IP_CALL]
- * on onRequestPermissionsResult() methods.
- * For video IP calls, record audio and camera permissions are both mandatory.
- *
- * @param context App context
- * @param grantResults permissions granted results
- * @return true if video IP call is permitted, false otherwise
- */
-fun onPermissionResultVideoIpCall(context: Context, grantResults: IntArray): Boolean {
- val arePermissionsGranted = allGranted(grantResults)
-
- if (!arePermissionsGranted) {
- Toast.makeText(context, R.string.permissions_action_not_performed_missing_permissions, Toast.LENGTH_SHORT).show()
- }
-
- return arePermissionsGranted
-}
-
/**
* Return true if all permissions are granted, false if not or if permission request has been cancelled
*/
diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
index 0e722da34a..d228adab12 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
@@ -28,8 +28,8 @@ import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.StringRes
-import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import im.vector.app.R
@@ -55,6 +55,10 @@ fun isAirplaneModeOn(context: Context): Boolean {
return Settings.Global.getInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
}
+fun isAnimationDisabled(context: Context): Boolean {
+ return Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f
+}
+
/**
* display the system dialog for granting this permission. If previously granted, the
* system will not show it (so you should call this method).
@@ -63,15 +67,11 @@ fun isAirplaneModeOn(context: Context): Boolean {
* will return false and the notification privacy will fallback to "LOW_DETAIL".
*/
@TargetApi(Build.VERSION_CODES.M)
-fun requestDisablingBatteryOptimization(activity: Activity, fragment: Fragment?, requestCode: Int) {
+fun requestDisablingBatteryOptimization(activity: Activity, activityResultLauncher: ActivityResultLauncher) {
val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + activity.packageName)
- if (fragment != null) {
- fragment.startActivityForResult(intent, requestCode)
- } else {
- activity.startActivityForResult(intent, requestCode)
- }
+ activityResultLauncher.launch(intent)
}
// ==============================================================================================================
@@ -96,17 +96,17 @@ fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = t
* Shows notification settings for the current app.
* In android O will directly opens the notification settings, in lower version it will show the App settings
*/
-fun startNotificationSettingsIntent(activity: AppCompatActivity, requestCode: Int) {
+fun startNotificationSettingsIntent(context: Context, activityResultLauncher: ActivityResultLauncher) {
val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName)
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
} else {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
- intent.putExtra("app_package", activity.packageName)
- intent.putExtra("app_uid", activity.applicationInfo?.uid)
+ intent.putExtra("app_package", context.packageName)
+ intent.putExtra("app_uid", context.applicationInfo?.uid)
}
- activity.startActivityForResult(intent, requestCode)
+ activityResultLauncher.launch(intent)
}
/**
@@ -122,42 +122,47 @@ fun startNotificationChannelSettingsIntent(fragment: Fragment, channelID: String
fragment.startActivity(intent)
}
-fun startAddGoogleAccountIntent(context: AppCompatActivity, requestCode: Int) {
+fun startAddGoogleAccountIntent(context: Context, activityResultLauncher: ActivityResultLauncher) {
try {
val intent = Intent(Settings.ACTION_ADD_ACCOUNT)
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google"))
- context.startActivityForResult(intent, requestCode)
+ activityResultLauncher.launch(intent)
} catch (activityNotFoundException: ActivityNotFoundException) {
context.toast(R.string.error_no_external_application_found)
}
}
-fun startSharePlainTextIntent(fragment: Fragment, chooserTitle: String?, text: String, subject: String? = null, requestCode: Int? = null) {
+fun startSharePlainTextIntent(fragment: Fragment,
+ activityResultLauncher: ActivityResultLauncher?,
+ chooserTitle: String?,
+ text: String,
+ subject: String? = null) {
val share = Intent(Intent.ACTION_SEND)
share.type = "text/plain"
share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
// Add data to the intent, the receiving app will decide what to do with it.
share.putExtra(Intent.EXTRA_SUBJECT, subject)
share.putExtra(Intent.EXTRA_TEXT, text)
+ val intent = Intent.createChooser(share, chooserTitle)
try {
- if (requestCode != null) {
- fragment.startActivityForResult(Intent.createChooser(share, chooserTitle), requestCode)
+ if (activityResultLauncher != null) {
+ activityResultLauncher.launch(intent)
} else {
- fragment.startActivity(Intent.createChooser(share, chooserTitle))
+ fragment.startActivity(intent)
}
} catch (activityNotFoundException: ActivityNotFoundException) {
fragment.activity?.toast(R.string.error_no_external_application_found)
}
}
-fun startImportTextFromFileIntent(fragment: Fragment, requestCode: Int) {
+fun startImportTextFromFileIntent(context: Context, activityResultLauncher: ActivityResultLauncher) {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "text/plain"
}
- if (intent.resolveActivity(fragment.requireActivity().packageManager) != null) {
- fragment.startActivityForResult(intent, requestCode)
+ if (intent.resolveActivity(context.packageManager) != null) {
+ activityResultLauncher.launch(intent)
} else {
- fragment.activity?.toast(R.string.error_no_external_application_found)
+ context.toast(R.string.error_no_external_application_found)
}
}
diff --git a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt
index 32b995c004..63f80341eb 100644
--- a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt
@@ -23,6 +23,7 @@ const val THREE_MINUTES = 3 * 60_000L
/**
* Store an object T for a specific period of time
+ * @param delay delay to keep the data, in millis
*/
open class TemporaryStore(private val delay: Long = THREE_MINUTES) {
@@ -30,14 +31,16 @@ open class TemporaryStore(private val delay: Long = THREE_MINUTES) {
var data: T? = null
set(value) {
- field = value
timer?.cancel()
- timer = Timer().also {
- it.schedule(object : TimerTask() {
- override fun run() {
- field = null
- }
- }, delay)
+ field = value
+ if (value != null) {
+ timer = Timer().also {
+ it.schedule(object : TimerTask() {
+ override fun run() {
+ field = null
+ }
+ }, delay)
+ }
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
index ce81ba12f1..b5552e4d62 100644
--- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
@@ -22,8 +22,6 @@ import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AlertDialog
import com.bumptech.glide.Glide
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.failure.GlobalError
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
@@ -38,6 +36,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.UnlockedActivity
+import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity
@@ -47,6 +46,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber
import javax.inject.Inject
@@ -89,6 +90,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity {
@Inject lateinit var shortcutsHandler: ShortcutsHandler
@Inject lateinit var pinCodeStore: PinCodeStore
@Inject lateinit var pinLocker: PinLocker
+ @Inject lateinit var popupAlertManager: PopupAlertManager
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
@@ -115,6 +117,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity {
// Also clear the dynamic shortcuts
shortcutsHandler.clearShortcuts()
+
+ // Also clear the alerts
+ popupAlertManager.cancelAll()
}
private fun parseArgs(): MainActivityArgs {
diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt
index 1d7c67b046..d4efb22eb8 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt
@@ -15,12 +15,11 @@
*/
package im.vector.app.features.attachments
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import androidx.fragment.app.Fragment
+import androidx.activity.result.ActivityResultLauncher
import im.vector.app.core.platform.Restorable
import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.BuildConfig
@@ -48,6 +47,7 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
// Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
private var captureUri: Uri? = null
+
// The pending type is set if we have to handle permission request. It must be restored if the activity gets killed.
var pendingType: AttachmentTypeSelectorView.Type? = null
@@ -72,99 +72,93 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
/**
* Starts the process for handling file picking
*/
- fun selectFile(fragment: Fragment) {
- MultiPicker.get(MultiPicker.FILE).startWith(fragment)
+ fun selectFile(activityResultLauncher: ActivityResultLauncher) {
+ MultiPicker.get(MultiPicker.FILE).startWith(activityResultLauncher)
}
/**
* Starts the process for handling image picking
*/
- fun selectGallery(fragment: Fragment) {
- MultiPicker.get(MultiPicker.IMAGE).startWith(fragment)
+ fun selectGallery(activityResultLauncher: ActivityResultLauncher) {
+ MultiPicker.get(MultiPicker.IMAGE).startWith(activityResultLauncher)
}
/**
* Starts the process for handling audio picking
*/
- fun selectAudio(fragment: Fragment) {
- MultiPicker.get(MultiPicker.AUDIO).startWith(fragment)
+ fun selectAudio(activityResultLauncher: ActivityResultLauncher) {
+ MultiPicker.get(MultiPicker.AUDIO).startWith(activityResultLauncher)
}
/**
* Starts the process for handling capture image picking
*/
- fun openCamera(fragment: Fragment) {
- captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment)
+ fun openCamera(context: Context, activityResultLauncher: ActivityResultLauncher) {
+ captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(context, activityResultLauncher)
}
/**
* Starts the process for handling contact picking
*/
- fun selectContact(fragment: Fragment) {
- MultiPicker.get(MultiPicker.CONTACT).startWith(fragment)
+ fun selectContact(activityResultLauncher: ActivityResultLauncher) {
+ MultiPicker.get(MultiPicker.CONTACT).startWith(activityResultLauncher)
}
/**
- * This methods aims to handle on activity result data.
- *
- * @return true if it can handle the data, false otherwise
+ * This methods aims to handle the result data.
*/
- fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
- if (resultCode == Activity.RESULT_OK) {
- when (requestCode) {
- MultiPicker.REQUEST_CODE_PICK_FILE -> {
- callback.onContentAttachmentsReady(
- MultiPicker.get(MultiPicker.FILE)
- .getSelectedFiles(context, requestCode, resultCode, data)
- .map { it.toContentAttachmentData() }
- )
+ fun onFileResult(data: Intent?) {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.FILE)
+ .getSelectedFiles(context, data)
+ .map { it.toContentAttachmentData() }
+ )
+ }
+
+ fun onAudioResult(data: Intent?) {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.AUDIO)
+ .getSelectedFiles(context, data)
+ .map { it.toContentAttachmentData() }
+ )
+ }
+
+ fun onContactResult(data: Intent?) {
+ MultiPicker.get(MultiPicker.CONTACT)
+ .getSelectedFiles(context, data)
+ .firstOrNull()
+ ?.toContactAttachment()
+ ?.let {
+ callback.onContactAttachmentReady(it)
}
- MultiPicker.REQUEST_CODE_PICK_AUDIO -> {
- callback.onContentAttachmentsReady(
- MultiPicker.get(MultiPicker.AUDIO)
- .getSelectedFiles(context, requestCode, resultCode, data)
- .map { it.toContentAttachmentData() }
- )
- }
- MultiPicker.REQUEST_CODE_PICK_CONTACT -> {
- MultiPicker.get(MultiPicker.CONTACT)
- .getSelectedFiles(context, requestCode, resultCode, data)
- .firstOrNull()
- ?.toContactAttachment()
- ?.let {
- callback.onContactAttachmentReady(it)
- }
- }
- MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
- callback.onContentAttachmentsReady(
- MultiPicker.get(MultiPicker.IMAGE)
- .getSelectedFiles(context, requestCode, resultCode, data)
- .map { it.toContentAttachmentData() }
- )
- }
- MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
- captureUri?.let { captureUri ->
- MultiPicker.get(MultiPicker.CAMERA)
- .getTakenPhoto(context, requestCode, resultCode, captureUri)
- ?.let {
- callback.onContentAttachmentsReady(
- listOf(it).map { it.toContentAttachmentData() }
- )
- }
+ }
+
+ fun onImageResult(data: Intent?) {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.IMAGE)
+ .getSelectedFiles(context, data)
+ .map { it.toContentAttachmentData() }
+ )
+ }
+
+ fun onPhotoResult() {
+ captureUri?.let { captureUri ->
+ MultiPicker.get(MultiPicker.CAMERA)
+ .getTakenPhoto(context, captureUri)
+ ?.let {
+ callback.onContentAttachmentsReady(
+ listOf(it).map { it.toContentAttachmentData() }
+ )
}
- }
- MultiPicker.REQUEST_CODE_PICK_VIDEO -> {
- callback.onContentAttachmentsReady(
- MultiPicker.get(MultiPicker.VIDEO)
- .getSelectedFiles(context, requestCode, resultCode, data)
- .map { it.toContentAttachmentData() }
- )
- }
- else -> return false
- }
- return true
}
- return false
+ }
+
+ fun onVideoResult(data: Intent?) {
+ callback.onContentAttachmentsReady(
+ MultiPicker.get(MultiPicker.VIDEO)
+ .getSelectedFiles(context, data)
+ .map { it.toContentAttachmentData() }
+ )
}
/**
diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentPreviewItems.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentPreviewItems.kt
index 066f69b555..f715d0cb3f 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentPreviewItems.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentPreviewItems.kt
@@ -55,8 +55,10 @@ abstract class AttachmentPreviewItem : VectorE
abstract class AttachmentMiniaturePreviewItem : AttachmentPreviewItem() {
@EpoxyAttribute override lateinit var attachment: ContentAttachmentData
+
@EpoxyAttribute
var clickListener: View.OnClickListener? = null
+
@EpoxyAttribute
var checked: Boolean = false
diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewAction.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewAction.kt
index 16f6fe2b97..4364300c3a 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewAction.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewAction.kt
@@ -22,6 +22,6 @@ import im.vector.app.core.platform.VectorViewModelAction
sealed class AttachmentsPreviewAction : VectorViewModelAction {
object RemoveCurrentAttachment : AttachmentsPreviewAction()
- data class SetCurrentAttachment(val index: Int): AttachmentsPreviewAction()
- data class UpdatePathOfCurrentAttachment(val newUri: Uri): AttachmentsPreviewAction()
+ data class SetCurrentAttachment(val index: Int) : AttachmentsPreviewAction()
+ data class UpdatePathOfCurrentAttachment(val newUri: Uri) : AttachmentsPreviewAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt
index bfa44a7a70..8e830d00a4 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt
@@ -30,8 +30,6 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
class AttachmentsPreviewActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object {
- const val REQUEST_CODE = 55
-
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
private const val ATTACHMENTS_PREVIEW_RESULT = "ATTACHMENTS_PREVIEW_RESULT"
private const val KEEP_ORIGINAL_IMAGES_SIZE = "KEEP_ORIGINAL_IMAGES_SIZE"
diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt
index 387cfb2261..b040101c84 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt
@@ -81,6 +81,10 @@ class AttachmentsPreviewFragment @Inject constructor(
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ // TODO handle this one (Ucrop lib)
+ @Suppress("DEPRECATION")
+ super.onActivityResult(requestCode, resultCode, data)
+
if (resultCode == RESULT_OK) {
if (requestCode == UCrop.REQUEST_CROP && data != null) {
Timber.v("Crop success")
@@ -98,7 +102,7 @@ class AttachmentsPreviewFragment @Inject constructor(
handleRemoveAction()
true
}
- R.id.attachmentsPreviewEditAction -> {
+ R.id.attachmentsPreviewEditAction -> {
handleEditAction()
true
}
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/RecyclerViewPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/RecyclerViewPresenter.kt
index e7ece3bb9e..c4631300f1 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/RecyclerViewPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/RecyclerViewPresenter.kt
@@ -51,6 +51,7 @@ abstract class RecyclerViewPresenter(context: Context?) : AutocompletePresent
}
override fun onViewShown() {}
+
@CallSuper
override fun onViewHidden() {
observer?.also {
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandItem.kt
index 257a2abd6c..0f24b866bf 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandItem.kt
@@ -29,10 +29,13 @@ abstract class AutocompleteCommandItem : VectorEpoxyModel, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
start()
} else {
diff --git a/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt
index 6d3f8b5885..c70b52b09b 100644
--- a/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt
+++ b/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt
@@ -183,7 +183,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
fun addIfNeeded(renderer: SurfaceViewRenderer?, list: MutableList>) {
if (renderer == null) return
- val exists = list.firstOrNull() {
+ val exists = list.firstOrNull {
it.get() == renderer
} != null
if (!exists) {
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
index 5a4c25a8ad..1ab6fb6363 100644
--- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
@@ -150,6 +150,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
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 bdfa7779fb..2f8531929a 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
@@ -54,5 +54,5 @@ sealed class ParsedCommand {
class SendSpoiler(val message: String) : ParsedCommand()
class SendShrug(val message: CharSequence) : ParsedCommand()
class SendPoll(val question: String, val options: List) : ParsedCommand()
- object DiscardSession: ParsedCommand()
+ object DiscardSession : ParsedCommand()
}
diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
index dd449de989..c4cf9eab39 100644
--- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
@@ -33,9 +33,9 @@ import im.vector.app.features.userdirectory.UserDirectoryAction
import im.vector.app.features.userdirectory.UserDirectorySharedAction
import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
import im.vector.app.features.userdirectory.UserDirectoryViewModel
+import kotlinx.android.synthetic.main.fragment_contacts_book.*
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.user.model.User
-import kotlinx.android.synthetic.main.fragment_contacts_book.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
index d9b136691e..167660d11e 100644
--- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
@@ -33,18 +33,18 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.invite.InviteUsersToRoomActivity
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.FoundThreePid
import org.matrix.android.sdk.api.session.identity.ThreePid
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import timber.log.Timber
private typealias PhoneBookSearch = String
class ContactsBookViewModel @AssistedInject constructor(@Assisted
- initialState: ContactsBookViewState,
+ initialState: ContactsBookViewState,
private val contactsDataSource: ContactsDataSource,
private val session: Session)
: VectorViewModel(initialState) {
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 27ca636142..a1bb12a84b 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -111,9 +111,10 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
+ doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
}
}
}
@@ -139,7 +140,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
private fun renderCreationFailure(error: Throwable) {
hideWaitingView()
when (error) {
- is CreateRoomFailure.CreatedWithTimeout -> {
+ is CreateRoomFailure.CreatedWithTimeout -> {
finish()
}
is CreateRoomFailure.CreatedWithFederationFailure -> {
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 56114cdb4b..c42e10686f 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
@@ -70,7 +70,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
.apply {
invitees.forEach {
when (it) {
- is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
+ is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
}.exhaustive
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt
index 953d415140..27a8cc0a97 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt
@@ -21,5 +21,5 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
data class CreateDirectRoomViewState(
- val createAndInviteState: Async = Uninitialized
+ val createAndInviteState: Async = Uninitialized
) : MvRxState
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
index d35d031286..282f7b1a71 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
@@ -18,14 +18,14 @@ package im.vector.app.features.crypto.keys
import android.content.Context
import android.net.Uri
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.internal.extensions.foldToCallback
-import org.matrix.android.sdk.internal.util.awaitCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.internal.extensions.foldToCallback
+import org.matrix.android.sdk.internal.util.awaitCallback
class KeysExporter(private val session: Session) {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt
index d2227841b5..8932bb9489 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt
@@ -20,15 +20,15 @@ import android.content.Context
import android.net.Uri
import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.resources.openResource
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import timber.log.Timber
class KeysImporter(private val session: Session) {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
index 40953cb5f6..80ae46262d 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
@@ -23,6 +23,7 @@ import androidx.lifecycle.Observer
import im.vector.app.R
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.observeEvent
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.ui.views.KeysBackupBanner
@@ -32,8 +33,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
class KeysBackupRestoreActivity : SimpleFragmentActivity() {
companion object {
-
- private const val REQUEST_4S_SECRET = 100
const val SECRET_ALIAS = SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
fun intent(context: Context): Intent {
@@ -130,22 +129,19 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME),
resultKeyStoreAlias = SECRET_ALIAS
).let {
- startActivityForResult(it, REQUEST_4S_SECRET)
+ secretStartForActivityResult.launch(it)
}
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == REQUEST_4S_SECRET) {
- val extraResult = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
- if (resultCode == Activity.RESULT_OK && extraResult != null) {
- viewModel.handleGotSecretFromSSSS(
- extraResult,
- SECRET_ALIAS
- )
- } else {
- finish()
- }
+ private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
+ val extraResult = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
+ if (activityResult.resultCode == Activity.RESULT_OK && extraResult != null) {
+ viewModel.handleGotSecretFromSSSS(
+ extraResult,
+ SECRET_ALIAS
+ )
+ } else {
+ finish()
}
- super.onActivityResult(requestCode, resultCode, data)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt
index cc10256fc4..580a3443e7 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt
@@ -16,9 +16,9 @@
package im.vector.app.features.crypto.keysbackup.restore
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
import android.text.Editable
+import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.lifecycle.Observer
@@ -27,19 +27,15 @@ import butterknife.OnClick
import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent
-import timber.log.Timber
+import org.matrix.android.sdk.api.extensions.tryOrNull
import javax.inject.Inject
class KeysBackupRestoreFromKeyFragment @Inject constructor()
: VectorBaseFragment() {
- companion object {
-
- private const val REQUEST_TEXT_FILE_GET = 1
- }
-
override fun getLayoutResId() = R.layout.fragment_keys_backup_restore_from_key
private lateinit var viewModel: KeysBackupRestoreFromKeyViewModel
@@ -47,11 +43,12 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
@BindView(R.id.keys_backup_key_enter_til)
lateinit var mKeyInputLayout: TextInputLayout
+
@BindView(R.id.keys_restore_key_enter_edittext)
lateinit var mKeyTextEdit: EditText
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromKeyViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
mKeyTextEdit.setText(viewModel.recoveryCode.value)
@@ -88,29 +85,23 @@ class KeysBackupRestoreFromKeyFragment @Inject constructor()
@OnClick(R.id.keys_backup_import)
fun onImport() {
- startImportTextFromFileIntent(this, REQUEST_TEXT_FILE_GET)
+ startImportTextFromFileIntent(requireContext(), textFileStartForActivityResult)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == REQUEST_TEXT_FILE_GET && resultCode == Activity.RESULT_OK) {
- val dataURI = data?.data
- if (dataURI != null) {
- try {
- activity
- ?.contentResolver
- ?.openInputStream(dataURI)
- ?.bufferedReader()
- ?.use { it.readText() }
- ?.let {
- mKeyTextEdit.setText(it)
- mKeyTextEdit.setSelection(it.length)
- }
- } catch (e: Exception) {
- Timber.e(e, "Failed to read recovery kay from text")
- }
+ private val textFileStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val dataURI = activityResult.data?.data ?: return@registerStartForActivityResult
+ tryOrNull(message = "Failed to read recovery kay from text") {
+ activity
+ ?.contentResolver
+ ?.openInputStream(dataURI)
+ ?.bufferedReader()
+ ?.use { it.readText() }
+ ?.let {
+ mKeyTextEdit.setText(it)
+ mKeyTextEdit.setSelection(it.length)
+ }
}
- return
}
- super.onActivityResult(requestCode, resultCode, data)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt
index 7941d95add..79aa728da1 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseFragment.kt
@@ -59,8 +59,8 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
}
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
viewModel = fragmentViewModelProvider.get(KeysBackupRestoreFromPassphraseViewModel::class.java)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
index 7d2d74db2f..d2cf871701 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
@@ -23,6 +23,8 @@ import im.vector.app.R
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.LiveEvent
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.Session
@@ -34,8 +36,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt
index fa571b86c1..b4969d28b7 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSuccessFragment.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.crypto.keysbackup.restore
import android.os.Bundle
+import android.view.View
import android.widget.TextView
import androidx.core.view.isVisible
import butterknife.BindView
@@ -31,13 +32,14 @@ class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragmen
@BindView(R.id.keys_backup_restore_success)
lateinit var mSuccessText: TextView
+
@BindView(R.id.keys_backup_restore_success_info)
lateinit var mSuccessDetailsText: TextView
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
sharedViewModel = activityViewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
if (compareValues(sharedViewModel.importKeyResult?.totalNumberOfKeys, 0) > 0) {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
index 1bba2c3a39..ab8e725959 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
@@ -26,6 +26,7 @@ import im.vector.app.R
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.utils.toast
@@ -93,7 +94,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
.show()
}
KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT -> {
- queryExportKeys(session.myUserId, REQUEST_CODE_SAVE_MEGOLM_EXPORT)
+ queryExportKeys(session.myUserId, saveStartForActivityResult)
}
}
}
@@ -125,10 +126,10 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
})
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == REQUEST_CODE_SAVE_MEGOLM_EXPORT) {
- val uri = data?.data
- if (resultCode == Activity.RESULT_OK && uri != null) {
+ private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val uri = activityResult.data?.data
+ if (uri != null) {
ExportKeysDialog().show(this, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
showWaitingView()
@@ -163,7 +164,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
hideWaitingView()
}
}
- super.onActivityResult(requestCode, resultCode, data)
}
override fun onBackPressed() {
@@ -198,7 +198,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
const val KEYS_VERSION = "KEYS_VERSION"
const val MANUAL_EXPORT = "MANUAL_EXPORT"
const val EXTRA_SHOW_MANUAL_EXPORT = "SHOW_MANUAL_EXPORT"
- const val REQUEST_CODE_SAVE_MEGOLM_EXPORT = 101
fun intent(context: Context, showManualExport: Boolean): Intent {
val intent = Intent(context, KeysBackupSetupActivity::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep1Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep1Fragment.kt
index 0a82edd150..f855b86b6c 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep1Fragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep1Fragment.kt
@@ -40,8 +40,8 @@ class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment()
@BindView(R.id.keys_backup_setup_step1_manualExport)
lateinit var manualExportButton: Button
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
index 0520098866..af279c9140 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.crypto.keysbackup.setup
import android.os.Bundle
+import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
@@ -77,8 +78,8 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
private lateinit var viewModel: KeysBackupSetupSharedViewModel
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
index 41b7cbddfe..78471c6848 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
@@ -16,7 +16,6 @@
package im.vector.app.features.crypto.keysbackup.setup
import android.app.Activity
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
@@ -31,6 +30,7 @@ import butterknife.BindView
import butterknife.OnClick
import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.app.R
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.LiveEvent
import im.vector.app.core.utils.copyToClipboard
@@ -48,10 +48,6 @@ import javax.inject.Inject
class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() {
- companion object {
- private const val SAVE_RECOVERY_KEY_REQUEST_CODE = 2754
- }
-
override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step3
@BindView(R.id.keys_backup_setup_step3_button)
@@ -65,8 +61,8 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
private lateinit var viewModel: KeysBackupSetupSharedViewModel
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
viewModel = activityViewModelProvider.get(KeysBackupSetupSharedViewModel::class.java)
viewModel.shouldPromptOnBack = false
@@ -138,19 +134,20 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
selectTxtFileToWrite(
activity = requireActivity(),
- fragment = this,
+ activityResultLauncher = saveRecoveryActivityResultLauncher,
defaultFileName = "recovery-key-$userId-$timestamp.txt",
- chooserHint = getString(R.string.save_recovery_key_chooser_hint),
- requestCode = SAVE_RECOVERY_KEY_REQUEST_CODE
+ chooserHint = getString(R.string.save_recovery_key_chooser_hint)
)
dialog.dismiss()
}
dialog.findViewById(R.id.keys_backup_setup_share)?.setOnClickListener {
- startSharePlainTextIntent(this,
- context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
- recoveryKey,
- context?.getString(R.string.recovery_key))
+ startSharePlainTextIntent(
+ fragment = this,
+ activityResultLauncher = null,
+ chooserTitle = context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
+ text = recoveryKey,
+ subject = context?.getString(R.string.recovery_key))
viewModel.copyHasBeenMade = true
dialog.dismiss()
}
@@ -202,15 +199,11 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
}
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- when (requestCode) {
- SAVE_RECOVERY_KEY_REQUEST_CODE -> {
- val uri = data?.data
- if (resultCode == Activity.RESULT_OK && uri != null) {
- viewModel.recoveryKey.value?.let {
- exportRecoveryKeyToFile(uri, it)
- }
- }
+ private val saveRecoveryActivityResultLauncher = registerStartForActivityResult { activityRessult ->
+ val uri = activityRessult.data?.data ?: return@registerStartForActivityResult
+ if (activityRessult.resultCode == Activity.RESULT_OK) {
+ viewModel.recoveryKey.value?.let {
+ exportRecoveryKeyToFile(uri, it)
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt
index 4ed0e037d4..8a02b8deac 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt
@@ -1,7 +1,4 @@
/*
- * Copyright 2016 OpenMarket Ltd
- * Copyright 2017 Vector Creations Ltd
- * Copyright 2018 New Vector Ltd
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -126,7 +123,7 @@ class KeyRequestHandler @Inject constructor(
// can we get more info on this device?
session?.cryptoService()?.getMyDevicesInfo()?.firstOrNull { it.deviceId == deviceId }?.let {
postAlert(context, userId, deviceId, true, deviceInfo, it)
- } ?: kotlin.run {
+ } ?: run {
postAlert(context, userId, deviceId, true, deviceInfo)
}
} else {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt
index b47b7dc3a9..30a7ab3cc0 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt
@@ -28,6 +28,8 @@ sealed class SharedSecureStorageAction : VectorViewModelAction {
object Cancel : SharedSecureStorageAction()
data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction()
data class SubmitKey(val recoveryKey: String) : SharedSecureStorageAction()
+ object ForgotResetAll : SharedSecureStorageAction()
+ object DoResetAll : SharedSecureStorageAction()
}
sealed class SharedSecureStorageViewEvent : VectorViewEvents {
@@ -40,4 +42,5 @@ sealed class SharedSecureStorageViewEvent : VectorViewEvents {
object ShowModalLoading : SharedSecureStorageViewEvent()
object HideModalLoading : SharedSecureStorageViewEvent()
data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent()
+ object ShowResetBottomSheet : SharedSecureStorageViewEvent()
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
index bca7a63470..2abad9dd38 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
@@ -24,6 +24,8 @@ import android.os.Parcelable
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentOnAttachListener
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import im.vector.app.R
@@ -31,12 +33,17 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.platform.SimpleFragmentActivity
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.features.crypto.recover.SetupMode
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity.*
import javax.inject.Inject
import kotlin.reflect.KClass
-class SharedSecureStorageActivity : SimpleFragmentActivity() {
+class SharedSecureStorageActivity :
+ SimpleFragmentActivity(),
+ VectorBaseBottomSheetDialogFragment.ResultListener,
+ FragmentOnAttachListener {
@Parcelize
data class Args(
@@ -56,6 +63,8 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ supportFragmentManager.addFragmentOnAttachListener(this)
+
toolbar.visibility = View.GONE
viewModel.observeViewEvents { observeViewEvents(it) }
@@ -63,24 +72,33 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
viewModel.subscribe(this) { renderState(it) }
}
+ override fun onDestroy() {
+ super.onDestroy()
+ supportFragmentManager.removeFragmentOnAttachListener(this)
+ }
+
override fun onBackPressed() {
viewModel.handle(SharedSecureStorageAction.Back)
}
private fun renderState(state: SharedSecureStorageViewState) {
if (!state.ready) return
- val fragment = if (state.hasPassphrase) {
- if (state.useKey) SharedSecuredStorageKeyFragment::class else SharedSecuredStoragePassphraseFragment::class
- } else SharedSecuredStorageKeyFragment::class
+ val fragment =
+ when (state.step) {
+ SharedSecureStorageViewState.Step.EnterPassphrase -> SharedSecuredStoragePassphraseFragment::class
+ SharedSecureStorageViewState.Step.EnterKey -> SharedSecuredStorageKeyFragment::class
+ SharedSecureStorageViewState.Step.ResetAll -> SharedSecuredStorageResetAllFragment::class
+ }
+
showFragment(fragment, Bundle())
}
private fun observeViewEvents(it: SharedSecureStorageViewEvent?) {
when (it) {
- is SharedSecureStorageViewEvent.Dismiss -> {
+ is SharedSecureStorageViewEvent.Dismiss -> {
finish()
}
- is SharedSecureStorageViewEvent.Error -> {
+ is SharedSecureStorageViewEvent.Error -> {
AlertDialog.Builder(this)
.setTitle(getString(R.string.dialog_title_error))
.setMessage(it.message)
@@ -92,21 +110,30 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
}
.show()
}
- is SharedSecureStorageViewEvent.ShowModalLoading -> {
+ is SharedSecureStorageViewEvent.ShowModalLoading -> {
showWaitingView()
}
- is SharedSecureStorageViewEvent.HideModalLoading -> {
+ is SharedSecureStorageViewEvent.HideModalLoading -> {
hideWaitingView()
}
- is SharedSecureStorageViewEvent.UpdateLoadingState -> {
+ is SharedSecureStorageViewEvent.UpdateLoadingState -> {
updateWaitingView(it.waitingData)
}
- is SharedSecureStorageViewEvent.FinishSuccess -> {
+ is SharedSecureStorageViewEvent.FinishSuccess -> {
val dataResult = Intent()
dataResult.putExtra(EXTRA_DATA_RESULT, it.cypherResult)
setResult(Activity.RESULT_OK, dataResult)
finish()
}
+ is SharedSecureStorageViewEvent.ShowResetBottomSheet -> {
+ navigator.open4SSetup(this, SetupMode.HARD_RESET)
+ }
+ }
+ }
+
+ override fun onAttachFragment(fragmentManager: FragmentManager, fragment: Fragment) {
+ if (fragment is VectorBaseBottomSheetDialogFragment) {
+ fragment.resultListener = this
}
}
@@ -124,6 +151,7 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
companion object {
const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT"
+ const val EXTRA_DATA_RESET = "EXTRA_DATA_RESET"
const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"
fun newIntent(context: Context,
@@ -140,4 +168,12 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() {
}
}
}
+
+ override fun onBottomSheetResult(resultCode: Int, data: Any?) {
+ if (resultCode == VectorBaseBottomSheetDialogFragment.ResultListener.RESULT_OK) {
+ // the 4S has been reset
+ setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_DATA_RESET, true) })
+ finish()
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
index 5bc87dfce7..951ff4ede6 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
@@ -33,6 +33,9 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
@@ -40,19 +43,26 @@ import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import java.io.ByteArrayOutputStream
data class SharedSecureStorageViewState(
val ready: Boolean = false,
val hasPassphrase: Boolean = true,
- val useKey: Boolean = false,
val passphraseVisible: Boolean = false,
- val checkingSSSSAction: Async = Uninitialized
-) : MvRxState
+ val checkingSSSSAction: Async = Uninitialized,
+ val step: Step = Step.EnterPassphrase,
+ val activeDeviceCount: Int = 0,
+ val showResetAllAction: Boolean = false,
+ val userId: String = ""
+) : MvRxState {
+ enum class Step {
+ EnterPassphrase,
+ EnterKey,
+ ResetAll
+ }
+}
class SharedSecureStorageViewModel @AssistedInject constructor(
@Assisted initialState: SharedSecureStorageViewState,
@@ -67,6 +77,10 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
}
init {
+
+ setState {
+ copy(userId = session.myUserId)
+ }
val isValid = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(args.requestedSecrets, args.keyId) is IntegrityResult.Success
if (!isValid) {
_viewEvents.post(
@@ -86,20 +100,30 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
if (info.content.passphrase != null) {
setState {
copy(
- ready = true,
hasPassphrase = true,
- useKey = false
+ ready = true,
+ step = SharedSecureStorageViewState.Step.EnterPassphrase
)
}
} else {
setState {
copy(
+ hasPassphrase = false,
ready = true,
- hasPassphrase = false
+ step = SharedSecureStorageViewState.Step.EnterKey
)
}
}
}
+
+ session.rx()
+ .liveUserCryptoDevices(session.myUserId)
+ .distinctUntilChanged()
+ .execute {
+ copy(
+ activeDeviceCount = it.invoke()?.size ?: 0
+ )
+ }
}
override fun handle(action: SharedSecureStorageAction) = withState {
@@ -110,27 +134,52 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
SharedSecureStorageAction.UseKey -> handleUseKey()
is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action)
SharedSecureStorageAction.Back -> handleBack()
+ SharedSecureStorageAction.ForgotResetAll -> handleResetAll()
+ SharedSecureStorageAction.DoResetAll -> handleDoResetAll()
}.exhaustive
}
+ private fun handleDoResetAll() {
+ _viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet)
+ }
+
+ private fun handleResetAll() {
+ setState {
+ copy(
+ step = SharedSecureStorageViewState.Step.ResetAll
+ )
+ }
+ }
+
private fun handleUseKey() {
setState {
copy(
- useKey = true
+ step = SharedSecureStorageViewState.Step.EnterKey
)
}
}
private fun handleBack() = withState { state ->
if (state.checkingSSSSAction is Loading) return@withState // ignore
- if (state.hasPassphrase && state.useKey) {
- setState {
- copy(
- useKey = false
- )
+ when (state.step) {
+ SharedSecureStorageViewState.Step.EnterKey -> {
+ setState {
+ copy(
+ step = SharedSecureStorageViewState.Step.EnterPassphrase
+ )
+ }
+ }
+ SharedSecureStorageViewState.Step.ResetAll -> {
+ setState {
+ copy(
+ step = if (state.hasPassphrase) SharedSecureStorageViewState.Step.EnterPassphrase
+ else SharedSecureStorageViewState.Step.EnterKey
+ )
+ }
+ }
+ else -> {
+ _viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
}
- } else {
- _viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
}
}
@@ -158,6 +207,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
val keySpec = RawBytesKeySpec.fromRecoveryKey(recoveryKey) ?: return@launch Unit.also {
_viewEvents.post(SharedSecureStorageViewEvent.KeyInlineError(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
+ setState { copy(checkingSSSSAction = Fail(IllegalArgumentException(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))) }
}
withContext(Dispatchers.IO) {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
index ee47a4d8e9..9fb3f637e1 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
@@ -17,7 +17,6 @@
package im.vector.app.features.crypto.quads
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
@@ -25,11 +24,12 @@ import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent
-import org.matrix.android.sdk.api.extensions.tryOrNull
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
+import org.matrix.android.sdk.api.extensions.tryOrNull
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -61,7 +61,11 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
}
.disposeOnDestroyView()
- ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) }
+ ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
+
+ ssss_key_reset.clickableView.debouncedClicks {
+ sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
+ }
sharedViewModel.observeViewEvents {
when (it) {
@@ -81,9 +85,9 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
sharedViewModel.handle(SharedSecureStorageAction.SubmitKey(text))
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
- data?.data?.let { dataURI ->
+ private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ activityResult.data?.data?.let { dataURI ->
tryOrNull {
activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader()
@@ -93,12 +97,6 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
}
}
}
- return
}
- super.onActivityResult(requestCode, resultCode, data)
- }
-
- companion object {
- private const val IMPORT_FILE_REQ = 0
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
index 09e67948d0..97047fbc65 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
@@ -74,6 +74,10 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
}
.disposeOnDestroyView()
+ ssss_passphrase_reset.clickableView.debouncedClicks {
+ sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
+ }
+
sharedViewModel.observeViewEvents {
when (it) {
is SharedSecureStorageViewEvent.InlineError -> {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt
new file mode 100644
index 0000000000..d7db779230
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.crypto.quads
+
+import android.os.Bundle
+import android.view.View
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
+import kotlinx.android.synthetic.main.fragment_ssss_reset_all.*
+import javax.inject.Inject
+
+class SharedSecuredStorageResetAllFragment @Inject constructor() : VectorBaseFragment() {
+
+ override fun getLayoutResId() = R.layout.fragment_ssss_reset_all
+
+ val sharedViewModel: SharedSecureStorageViewModel by activityViewModel()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ ssss_reset_button_reset.debouncedClicks {
+ sharedViewModel.handle(SharedSecureStorageAction.DoResetAll)
+ }
+
+ ssss_reset_button_cancel.debouncedClicks {
+ sharedViewModel.handle(SharedSecureStorageAction.Back)
+ }
+
+ ssss_reset_other_devices.debouncedClicks {
+ withState(sharedViewModel) {
+ DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST")
+ }
+ }
+
+ sharedViewModel.subscribe(this) { state ->
+ ssss_reset_other_devices.setTextOrHide(
+ state.activeDeviceCount
+ .takeIf { it > 0 }
+ ?.let { resources.getQuantityString(R.plurals.secure_backup_reset_devices_you_can_verify, it, it) }
+ )
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
index 1b9beabe9c..e6260b6e7e 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
@@ -45,8 +45,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
@Parcelize
data class Args(
- val initCrossSigningOnly: Boolean,
- val forceReset4S: Boolean
+ val setUpMode: SetupMode = SetupMode.NORMAL
) : Parcelable
override val showExpanded = true
@@ -66,7 +65,10 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
super.onViewCreated(view, savedInstanceState)
viewModel.observeViewEvents { event ->
when (event) {
- is BootstrapViewEvents.Dismiss -> dismiss()
+ is BootstrapViewEvents.Dismiss -> {
+ bottomSheetResult = if (event.success) ResultListener.RESULT_OK else ResultListener.RESULT_CANCEL
+ dismiss()
+ }
is BootstrapViewEvents.ModalError -> {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)
@@ -90,6 +92,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
.setMessage(R.string.bootstrap_cancel_text)
.setPositiveButton(R.string._continue, null)
.setNegativeButton(R.string.skip) { _, _ ->
+ bottomSheetResult = ResultListener.RESULT_CANCEL
dismiss()
}
.show()
@@ -181,16 +184,15 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
const val EXTRA_ARGS = "EXTRA_ARGS"
- fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean, forceReset4S: Boolean) {
- BootstrapBottomSheet().apply {
+ fun show(fragmentManager: FragmentManager, mode: SetupMode): BootstrapBottomSheet {
+ return BootstrapBottomSheet().apply {
isCancelable = false
arguments = Bundle().apply {
- this.putParcelable(EXTRA_ARGS, Args(
- initCrossSigningOnly,
- forceReset4S
- ))
+ this.putParcelable(EXTRA_ARGS, Args(setUpMode = mode))
}
- }.show(fragmentManager, "BootstrapBottomSheet")
+ }.also {
+ it.show(fragmentManager, "BootstrapBottomSheet")
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt
index 5da788583e..b7c689f41f 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt
@@ -69,10 +69,10 @@ interface BootstrapProgressListener {
data class Params(
val userPasswordAuth: UserPasswordAuth? = null,
- val initOnlyCrossSigning: Boolean = false,
val progressListener: BootstrapProgressListener? = null,
val passphrase: String?,
- val keySpec: SsssKeySpec? = null
+ val keySpec: SsssKeySpec? = null,
+ val setupMode: SetupMode
)
// TODO Rename to CreateServerRecovery
@@ -84,9 +84,13 @@ class BootstrapCrossSigningTask @Inject constructor(
override suspend fun execute(params: Params): BootstrapResult {
val crossSigningService = session.cryptoService().crossSigningService()
- Timber.d("## BootstrapCrossSigningTask: initXSOnly:${params.initOnlyCrossSigning} Starting...")
+ Timber.d("## BootstrapCrossSigningTask: mode:${params.setupMode} Starting...")
// Ensure cross-signing is initialized. Due to migration it is maybe not always correctly initialized
- if (!crossSigningService.isCrossSigningInitialized()) {
+
+ val shouldSetCrossSigning = !crossSigningService.isCrossSigningInitialized()
+ || (params.setupMode == SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET && !crossSigningService.allPrivateKeysKnown())
+ || (params.setupMode == SetupMode.HARD_RESET)
+ if (shouldSetCrossSigning) {
Timber.d("## BootstrapCrossSigningTask: Cross signing not enabled, so initialize")
params.progressListener?.onProgress(
WaitingViewData(
@@ -99,7 +103,7 @@ class BootstrapCrossSigningTask @Inject constructor(
awaitCallback {
crossSigningService.initializeCrossSigning(params.userPasswordAuth, it)
}
- if (params.initOnlyCrossSigning) {
+ if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
return BootstrapResult.SuccessCrossSigningOnly
}
} catch (failure: Throwable) {
@@ -107,7 +111,7 @@ class BootstrapCrossSigningTask @Inject constructor(
}
} else {
Timber.d("## BootstrapCrossSigningTask: Cross signing already setup, go to 4S setup")
- if (params.initOnlyCrossSigning) {
+ if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) {
// not sure how this can happen??
return handleInitializeXSigningError(IllegalArgumentException("Cross signing already setup"))
}
@@ -135,7 +139,7 @@ class BootstrapCrossSigningTask @Inject constructor(
null,
it
)
- } ?: kotlin.run {
+ } ?: run {
ssssService.generateKey(
UUID.randomUUID().toString(),
params.keySpec,
@@ -236,7 +240,13 @@ class BootstrapCrossSigningTask @Inject constructor(
val serverVersion = awaitCallback {
session.cryptoService().keysBackupService().getCurrentVersion(it)
}
- if (serverVersion == null) {
+
+ val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
+ val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version
+ val shouldCreateKeyBackup = serverVersion == null
+ || (params.setupMode == SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET && !isMegolmBackupSecretKnown)
+ || (params.setupMode == SetupMode.HARD_RESET)
+ if (shouldCreateKeyBackup) {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup")
val creationInfo = awaitCallback {
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
@@ -260,16 +270,15 @@ class BootstrapCrossSigningTask @Inject constructor(
} else {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found")
// ensure we store existing backup secret if we have it!
- val knownSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
- if (knownSecret != null && knownSecret.version == serverVersion.version) {
+ if (isMegolmBackupSecretKnown) {
// check it matches
val isValid = awaitCallback {
- session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownSecret.recoveryKey, it)
+ session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey, it)
}
if (isValid) {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
awaitCallback {
- extractCurveKeyFromRecoveryKey(knownSecret.recoveryKey)?.toBase64NoPadding()?.let { secret ->
+ extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret ->
ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME,
secret,
@@ -286,7 +295,7 @@ class BootstrapCrossSigningTask @Inject constructor(
Timber.e("## BootstrapCrossSigningTask: Failed to init keybackup")
}
- Timber.d("## BootstrapCrossSigningTask: initXSOnly:${params.initOnlyCrossSigning} Finished")
+ Timber.d("## BootstrapCrossSigningTask: mode:${params.setupMode} Finished")
return BootstrapResult.Success(keyInfo)
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
index a89e08988c..0e3ba4c526 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
@@ -17,7 +17,6 @@
package im.vector.app.features.crypto.recover
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
import android.text.InputType.TYPE_CLASS_TEXT
import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
@@ -32,16 +31,17 @@ import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.startImportTextFromFileIntent
-import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
import kotlinx.android.synthetic.main.fragment_bootstrap_migrate_backup.*
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -82,7 +82,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
bootstrapMigrateContinueButton.debouncedClicks { submit() }
bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
- bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) }
+ bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
}
private fun submit() = withState(sharedViewModel) { state ->
@@ -147,9 +147,9 @@ class BootstrapMigrateBackupFragment @Inject constructor(
}
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
- data?.data?.let { dataURI ->
+ private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ activityResult.data?.data?.let { dataURI ->
tryOrNull {
activity?.contentResolver?.openInputStream(dataURI)
?.bufferedReader()
@@ -159,12 +159,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
}
}
}
- return
}
- super.onActivityResult(requestCode, resultCode, data)
- }
-
- companion object {
- private const val IMPORT_FILE_REQ = 0
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
index 750dadbc9f..e426394d77 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
@@ -16,7 +16,7 @@
package im.vector.app.features.crypto.recover
-import android.app.Activity.RESULT_OK
+import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
@@ -25,6 +25,7 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.startSharePlainTextIntent
@@ -65,43 +66,46 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
try {
sharedViewModel.handle(BootstrapActions.SaveReqQueryStarted)
- startActivityForResult(Intent.createChooser(intent, getString(R.string.keys_backup_setup_step3_please_make_copy)), REQUEST_CODE_SAVE)
+ saveStartForActivityResult.launch(Intent.createChooser(intent, getString(R.string.keys_backup_setup_step3_please_make_copy)))
} catch (activityNotFoundException: ActivityNotFoundException) {
requireActivity().toast(R.string.error_no_external_application_found)
sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == REQUEST_CODE_SAVE) {
- val uri = data?.data
- if (resultCode == RESULT_OK && uri != null) {
- GlobalScope.launch(Dispatchers.IO) {
- try {
- sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
- } catch (failure: Throwable) {
- sharedViewModel.handle(BootstrapActions.SaveReqFailed)
- }
+ private val saveStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val uri = activityResult.data?.data ?: return@registerStartForActivityResult
+ GlobalScope.launch(Dispatchers.IO) {
+ try {
+ sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
+ } catch (failure: Throwable) {
+ sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
- } else {
- // result code seems to be always cancelled here.. so act as if it was saved
- sharedViewModel.handle(BootstrapActions.SaveReqFailed)
}
- return
- } else if (requestCode == REQUEST_CODE_COPY) {
+ } else {
+ // result code seems to be always cancelled here.. so act as if it was saved
+ sharedViewModel.handle(BootstrapActions.SaveReqFailed)
+ }
+ }
+
+ private val copyStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
sharedViewModel.handle(BootstrapActions.RecoveryKeySaved)
}
- super.onActivityResult(requestCode, resultCode, data)
}
private fun shareRecoveryKey() = withState(sharedViewModel) { state ->
val recoveryKey = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
?: return@withState
- startSharePlainTextIntent(this,
+ startSharePlainTextIntent(
+ this,
+ copyStartForActivityResult,
context?.getString(R.string.keys_backup_setup_step3_share_intent_chooser_title),
recoveryKey,
- context?.getString(R.string.recovery_key), REQUEST_CODE_COPY)
+ context?.getString(R.string.recovery_key)
+ )
}
override fun invalidate() = withState(sharedViewModel) { state ->
@@ -111,9 +115,4 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
recoveryContinue.isVisible = step.isSaved
bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
}
-
- companion object {
- const val REQUEST_CODE_SAVE = 123
- const val REQUEST_CODE_COPY = 124
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt
index 32b4771286..72b767e12f 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt
@@ -34,6 +34,8 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.login.ReAuthHelper
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
@@ -41,8 +43,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import java.io.OutputStream
class BootstrapSharedViewModel @AssistedInject constructor(
@@ -69,46 +69,52 @@ class BootstrapSharedViewModel @AssistedInject constructor(
init {
- if (args.forceReset4S) {
- setState {
- copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true))
- }
- } else if (args.initCrossSigningOnly) {
- // Go straight to account password
- setState {
- copy(step = BootstrapStep.AccountPassword(false))
- }
- } else {
- // need to check if user have an existing keybackup
- setState {
- copy(step = BootstrapStep.CheckingMigration)
- }
-
- // We need to check if there is an existing backup
- viewModelScope.launch(Dispatchers.IO) {
- val version = awaitCallback {
- session.cryptoService().keysBackupService().getCurrentVersion(it)
+ when (args.setUpMode) {
+ SetupMode.PASSPHRASE_RESET,
+ SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET,
+ SetupMode.HARD_RESET -> {
+ setState {
+ copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true))
}
- if (version == null) {
- // we just resume plain bootstrap
- doesKeyBackupExist = false
- setState {
- copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist))
+ }
+ SetupMode.CROSS_SIGNING_ONLY -> {
+ // Go straight to account password
+ setState {
+ copy(step = BootstrapStep.AccountPassword(false))
+ }
+ }
+ SetupMode.NORMAL -> {
+ // need to check if user have an existing keybackup
+ setState {
+ copy(step = BootstrapStep.CheckingMigration)
+ }
+
+ // We need to check if there is an existing backup
+ viewModelScope.launch(Dispatchers.IO) {
+ val version = awaitCallback {
+ session.cryptoService().keysBackupService().getCurrentVersion(it)
}
- } else {
- // we need to get existing backup passphrase/key and convert to SSSS
- val keyVersion = awaitCallback {
- session.cryptoService().keysBackupService().getVersion(version.version ?: "", it)
- }
- if (keyVersion == null) {
- // strange case... just finish?
- _viewEvents.post(BootstrapViewEvents.Dismiss)
- } else {
- doesKeyBackupExist = true
- isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
+ if (version == null) {
+ // we just resume plain bootstrap
+ doesKeyBackupExist = false
setState {
copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist))
}
+ } else {
+ // we need to get existing backup passphrase/key and convert to SSSS
+ val keyVersion = awaitCallback {
+ session.cryptoService().keysBackupService().getVersion(version.version ?: "", it)
+ }
+ if (keyVersion == null) {
+ // strange case... just finish?
+ _viewEvents.post(BootstrapViewEvents.Dismiss(false))
+ } else {
+ doesKeyBackupExist = true
+ isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
+ setState {
+ copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist))
+ }
+ }
}
}
}
@@ -234,7 +240,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
}
}
BootstrapActions.Completed -> {
- _viewEvents.post(BootstrapViewEvents.Dismiss)
+ _viewEvents.post(BootstrapViewEvents.Dismiss(true))
}
BootstrapActions.GoToCompleted -> {
setState {
@@ -295,7 +301,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
// =======================================
private fun saveRecoveryKeyToUri(os: OutputStream) = withState { state ->
viewModelScope.launch(Dispatchers.IO) {
- kotlin.runCatching {
+ runCatching {
os.use {
os.write((state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey() ?: "").toByteArray())
}
@@ -395,16 +401,16 @@ class BootstrapSharedViewModel @AssistedInject constructor(
bootstrapTask.invoke(this,
Params(
userPasswordAuth = userPasswordAuth,
- initOnlyCrossSigning = args.initCrossSigningOnly,
progressListener = progressListener,
passphrase = state.passphrase,
- keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } }
+ keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } },
+ setupMode = args.setUpMode
)
) { bootstrapResult ->
when (bootstrapResult) {
- is BootstrapResult.SuccessCrossSigningOnly -> {
+ is BootstrapResult.SuccessCrossSigningOnly -> {
// TPD
- _viewEvents.post(BootstrapViewEvents.Dismiss)
+ _viewEvents.post(BootstrapViewEvents.Dismiss(true))
}
is BootstrapResult.Success -> {
setState {
@@ -428,7 +434,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
}
is BootstrapResult.UnsupportedAuthFlow -> {
_viewEvents.post(BootstrapViewEvents.ModalError(stringProvider.getString(R.string.auth_flow_not_supported)))
- _viewEvents.post(BootstrapViewEvents.Dismiss)
+ _viewEvents.post(BootstrapViewEvents.Dismiss(false))
}
is BootstrapResult.InvalidPasswordError -> {
// it's a bad password
@@ -522,7 +528,13 @@ class BootstrapSharedViewModel @AssistedInject constructor(
}
BootstrapStep.CheckingMigration -> Unit
is BootstrapStep.FirstForm -> {
- _viewEvents.post(BootstrapViewEvents.SkipBootstrap())
+ _viewEvents.post(
+ when (args.setUpMode) {
+ SetupMode.CROSS_SIGNING_ONLY,
+ SetupMode.NORMAL -> BootstrapViewEvents.SkipBootstrap()
+ else -> BootstrapViewEvents.Dismiss(success = false)
+ }
+ )
}
is BootstrapStep.GetBackupSecretForMigration -> {
setState {
@@ -558,7 +570,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
val args: BootstrapBottomSheet.Args = fragment.arguments?.getParcelable(BootstrapBottomSheet.EXTRA_ARGS)
- ?: BootstrapBottomSheet.Args(initCrossSigningOnly = true, forceReset4S = false)
+ ?: BootstrapBottomSheet.Args(SetupMode.CROSS_SIGNING_ONLY)
return fragment.bootstrapViewModelFactory.create(state, args)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt
index 58bc64a9ad..10a092ccbb 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt
@@ -19,8 +19,8 @@ package im.vector.app.features.crypto.recover
import im.vector.app.core.platform.VectorViewEvents
sealed class BootstrapViewEvents : VectorViewEvents {
- object Dismiss : BootstrapViewEvents()
+ data class Dismiss(val success: Boolean) : BootstrapViewEvents()
data class ModalError(val error: String) : BootstrapViewEvents()
- object RecoveryKeySaved: BootstrapViewEvents()
- data class SkipBootstrap(val genKeyOption: Boolean = true): BootstrapViewEvents()
+ object RecoveryKeySaved : BootstrapViewEvents()
+ data class SkipBootstrap(val genKeyOption: Boolean = true) : BootstrapViewEvents()
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt
new file mode 100644
index 0000000000..0879490e79
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.crypto.recover
+
+enum class SetupMode {
+
+ /**
+ * Only setup cross signing, no 4S or megolm backup
+ */
+ CROSS_SIGNING_ONLY,
+
+ /**
+ * Normal setup mode.
+ */
+ NORMAL,
+
+ /**
+ * Only reset the 4S passphrase/key, but do not touch
+ * to existing cross-signing or megolm backup
+ * It take the local known secrets and put them in 4S
+ */
+ PASSPHRASE_RESET,
+
+ /**
+ * Resets the passphrase/key, and all missing secrets
+ * are re-created. Meaning that if cross signing is setup and the secrets
+ * keys are not known, cross signing will be reset (if secret is known we just keep same cross signing)
+ * Same apply to megolm
+ */
+ PASSPHRASE_AND_NEEDED_SECRETS_RESET,
+
+ /**
+ * Resets the passphrase/key, cross signing and megolm backup
+ */
+ HARD_RESET
+}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt
index 1c6ea413cb..a32a9de97f 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt
@@ -31,4 +31,5 @@ sealed class VerificationAction : VectorViewModelAction {
object SkipVerification : VerificationAction()
object VerifyFromPassphrase : VerificationAction()
data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction()
+ object SecuredStorageHasBeenReset : VerificationAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
index f979539f2e..35ea96de6f 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
@@ -17,7 +17,6 @@ package im.vector.app.features.crypto.verification
import android.app.Activity
import android.app.Dialog
-import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.KeyEvent
@@ -35,6 +34,7 @@ import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
@@ -48,6 +48,7 @@ import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrS
import im.vector.app.features.crypto.verification.request.VerificationRequestFragment
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.settings.VectorSettingsActivity
+import kotlinx.android.parcel.Parcelize
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@@ -55,7 +56,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
-import kotlinx.android.parcel.Parcelize
import timber.log.Timber
import javax.inject.Inject
import kotlin.reflect.KClass
@@ -76,6 +76,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
@Inject
lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory
+
@Inject
lateinit var avatarRenderer: AvatarRenderer
@@ -107,12 +108,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
when (it) {
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
- startActivityForResult(SharedSecureStorageActivity.newIntent(
+ secretStartForActivityResult.launch(SharedSecureStorageActivity.newIntent(
requireContext(),
null, // use default key
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
- ), SECRET_REQUEST_CODE)
+ ))
}
is VerificationBottomSheetViewEvents.ModalError -> {
AlertDialog.Builder(requireContext())
@@ -144,17 +145,20 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) {
- data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)?.let {
- viewModel.handle(VerificationAction.GotResultFromSsss(it, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS))
+ private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val result = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
+ val reset = activityResult.data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false
+ if (result != null) {
+ viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS))
+ } else if (reset) {
+ // all have been reset, so we are verified?
+ viewModel.handle(VerificationAction.SecuredStorageHasBeenReset)
}
}
- super.onActivityResult(requestCode, resultCode, data)
}
override fun invalidate() = withState(viewModel) { state ->
-
state.otherUserMxItem?.let { matrixItem ->
if (state.isMe) {
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
@@ -182,6 +186,17 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
}
+ if (state.quadSHasBeenReset) {
+ showFragment(VerificationConclusionFragment::class, Bundle().apply {
+ putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
+ isSuccessFull = true,
+ isMe = true,
+ cancelReason = null
+ ))
+ })
+ return@withState
+ }
+
if (state.userThinkItsNotHim) {
otherUserNameText.text = getString(R.string.dialog_title_warning)
showFragment(VerificationNotMeFragment::class, Bundle())
@@ -330,9 +345,6 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
companion object {
-
- const val SECRET_REQUEST_CODE = 101
-
fun withArgs(roomId: String?, otherUserId: String, transactionId: String? = null): VerificationBottomSheet {
return VerificationBottomSheet().apply {
arguments = Bundle().apply {
@@ -356,6 +368,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
}
}
+
fun forSelfVerification(session: Session, outgoingRequest: String): VerificationBottomSheet {
return VerificationBottomSheet().apply {
arguments = Bundle().apply {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
index a4ce9bd38d..2720c20fb0 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
@@ -31,6 +31,8 @@ import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
@@ -56,7 +58,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.launch
import timber.log.Timber
data class VerificationBottomSheetViewState(
@@ -74,7 +75,9 @@ data class VerificationBottomSheetViewState(
val currentDeviceCanCrossSign: Boolean = false,
val userWantsToCancel: Boolean = false,
val userThinkItsNotHim: Boolean = false,
- val quadSContainsSecrets: Boolean = true
+ val quadSContainsSecrets: Boolean = true,
+ val quadSHasBeenReset: Boolean = false,
+ val hasAnyOtherSession: Boolean = false
) : MvRxState
class VerificationBottomSheetViewModel @AssistedInject constructor(
@@ -117,6 +120,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
session.cryptoService().verificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction
}
+ val hasAnyOtherSession = session.cryptoService()
+ .getCryptoDeviceInfo(session.myUserId)
+ .any {
+ it.deviceId != session.sessionParams.deviceId
+ }
+
setState {
copy(
otherUserMxItem = userItem?.toMatrixItem(),
@@ -128,7 +137,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
roomId = args.roomId,
isMe = args.otherUserId == session.myUserId,
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
- quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup()
+ quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
+ hasAnyOtherSession = hasAnyOtherSession
)
}
@@ -349,6 +359,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
is VerificationAction.GotResultFromSsss -> {
handleSecretBackFromSSSS(action)
}
+ VerificationAction.SecuredStorageHasBeenReset -> {
+ if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) {
+ setState {
+ copy(quadSHasBeenReset = true)
+ }
+ }
+ Unit
+ }
}.exhaustive
}
@@ -393,7 +411,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
private fun tentativeRestoreBackup(res: Map?) {
- viewModelScope.launch {
+ viewModelScope.launch(Dispatchers.IO) {
try {
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
Timber.v("## Keybackup secret not restored from SSSS")
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
index fc715301f2..72cd063bbd 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
@@ -16,7 +16,6 @@
package im.vector.app.features.crypto.verification.choose
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.fragmentViewModel
@@ -25,11 +24,11 @@ import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel
import im.vector.app.features.qrcode.QrCodeScannerActivity
@@ -75,16 +74,14 @@ class VerificationChooseMethodFragment @Inject constructor(
state.pendingRequest.invoke()?.transactionId ?: ""))
}
- override fun openCamera() {
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ if (allGranted) {
doOpenQRCodeScanner()
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
-
- if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
+ override fun openCamera() {
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
doOpenQRCodeScanner()
}
}
@@ -94,24 +91,18 @@ class VerificationChooseMethodFragment @Inject constructor(
}
private fun doOpenQRCodeScanner() {
- QrCodeScannerActivity.startForResult(this)
+ QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
+ private val scanActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val scannedQrCode = QrCodeScannerActivity.getResultText(activityResult.data)
+ val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(activityResult.data)
- if (resultCode == Activity.RESULT_OK) {
- when (requestCode) {
- QrCodeScannerActivity.QR_CODE_SCANNER_REQUEST_CODE -> {
- val scannedQrCode = QrCodeScannerActivity.getResultText(data)
- val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(data)
-
- if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
- onRemoteQrCodeScanned(scannedQrCode)
- } else {
- Timber.w("It was not a QR code, or empty result")
- }
- }
+ if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
+ onRemoteQrCodeScanned(scannedQrCode)
+ } else {
+ Timber.w("It was not a QR code, or empty result")
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt
index 3c7c6d5745..d12b2c088f 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt
@@ -46,7 +46,7 @@ class VerificationConclusionController @Inject constructor(
val state = viewState ?: return
when (state.conclusionState) {
- ConclusionState.SUCCESS -> {
+ ConclusionState.SUCCESS -> {
bottomSheetVerificationNoticeItem {
id("notice")
notice(stringProvider.getString(
@@ -61,7 +61,7 @@ class VerificationConclusionController @Inject constructor(
bottomDone()
}
- ConclusionState.WARNING -> {
+ ConclusionState.WARNING -> {
bottomSheetVerificationNoticeItem {
id("notice")
notice(stringProvider.getString(R.string.verification_conclusion_not_secure))
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt
index e87be702db..d738efe3ba 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt
@@ -38,12 +38,16 @@ abstract class BottomSheetVerificationActionItem : VectorEpoxyModel(R.id.item_emoji_tv).isVisible = false
view.findViewById(R.id.item_emoji_image).isVisible = true
view.findViewById(R.id.item_emoji_image).setImageDrawable(ContextCompat.getDrawable(view.context, it))
- } ?: kotlin.run {
+ } ?: run {
view.findViewById(R.id.item_emoji_tv).isVisible = true
view.findViewById(R.id.item_emoji_image).isVisible = false
view.findViewById(R.id.item_emoji_tv).text = rep.emoji
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
index 3f4c3120e7..c7740e2ac5 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
@@ -52,25 +52,32 @@ class VerificationRequestController @Inject constructor(
val matrixItem = viewState?.otherUserMxItem ?: return
if (state.selfVerificationMode) {
- bottomSheetVerificationNoticeItem {
- id("notice")
- notice(stringProvider.getString(R.string.verification_open_other_to_verify))
- }
+ if (state.hasAnyOtherSession) {
+ bottomSheetVerificationNoticeItem {
+ id("notice")
+ notice(stringProvider.getString(R.string.verification_open_other_to_verify))
+ }
- bottomSheetSelfWaitItem {
- id("waiting")
- }
+ bottomSheetSelfWaitItem {
+ id("waiting")
+ }
- dividerItem {
- id("sep")
+ dividerItem {
+ id("sep")
+ }
}
if (state.quadSContainsSecrets) {
+ val subtitle = if (state.hasAnyOtherSession) {
+ stringProvider.getString(R.string.verification_use_passphrase)
+ } else {
+ null
+ }
bottomSheetVerificationActionItem {
id("passphrase")
title(stringProvider.getString(R.string.verification_cannot_access_other_session))
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
- subTitle(stringProvider.getString(R.string.verification_use_passphrase))
+ subTitle(subtitle)
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
listener { listener?.onClickRecoverFromPassphrase() }
diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
index 21f65ec9ef..bfbc00b15a 100644
--- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
@@ -16,7 +16,6 @@
package im.vector.app.features.discovery
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
@@ -27,16 +26,16 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.settings.VectorSettingsActivity
-import im.vector.app.features.terms.ReviewTermsActivity
+import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.terms.TermsService
-import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import javax.inject.Inject
class DiscoverySettingsFragment @Inject constructor(
@@ -92,22 +91,19 @@ class DiscoverySettingsFragment @Inject constructor(
viewModel.handle(DiscoverySettingsAction.Refresh)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
- if (Activity.RESULT_OK == resultCode) {
- viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
- } else {
- // add some error?
- }
+ private val termsActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
+ } else {
+ // add some error?
}
-
- super.onActivityResult(requestCode, resultCode, data)
}
override fun openIdentityServerTerms() = withState(viewModel) { state ->
if (state.termsNotSigned) {
navigator.openTerms(
- this,
+ requireContext(),
+ termsActivityResultLauncher,
TermsService.ServiceType.IdentityService,
state.identityServer()?.ensureProtocol() ?: "",
null)
diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
index 7ec6082111..0bfcdd9984 100644
--- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
@@ -28,13 +28,13 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.launch
import org.matrix.android.sdk.rx.rx
class DiscoverySettingsViewModel @AssistedInject constructor(
diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt
index 863270b762..8fb4fc4156 100644
--- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt
@@ -16,7 +16,6 @@
package im.vector.app.features.discovery.change
import android.app.Activity
-import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
@@ -28,15 +27,15 @@ import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.features.discovery.DiscoverySharedViewModel
-import im.vector.app.features.terms.ReviewTermsActivity
-import org.matrix.android.sdk.api.session.terms.TermsService
import kotlinx.android.synthetic.main.fragment_set_identity_server.*
+import org.matrix.android.sdk.api.session.terms.TermsService
import javax.inject.Inject
class SetIdentityServerFragment @Inject constructor(
@@ -121,7 +120,8 @@ class SetIdentityServerFragment @Inject constructor(
is SetIdentityServerViewEvents.TermsAccepted -> processIdentityServerChange()
is SetIdentityServerViewEvents.ShowTerms -> {
navigator.openTerms(
- this,
+ requireContext(),
+ termsActivityResultLauncher,
TermsService.ServiceType.IdentityService,
it.identityServerUrl,
null)
@@ -150,15 +150,12 @@ class SetIdentityServerFragment @Inject constructor(
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.identity_server)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == ReviewTermsActivity.TERMS_REQUEST_CODE) {
- if (Activity.RESULT_OK == resultCode) {
- processIdentityServerChange()
- } else {
- // add some error?
- }
+ private val termsActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ processIdentityServerChange()
+ } else {
+ // add some error?
}
- super.onActivityResult(requestCode, resultCode, data)
}
private fun processIdentityServerChange() {
diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt
index e7faf79f9d..9331f67812 100644
--- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt
@@ -27,13 +27,13 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureProtocol
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.terms.GetTermsResponse
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.internal.util.awaitCallback
-import kotlinx.coroutines.launch
import java.net.UnknownHostException
class SetIdentityServerViewModel @AssistedInject constructor(
diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt
index 434ab7f1ec..d4ba4f3150 100644
--- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt
@@ -31,8 +31,8 @@ import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.home.HomeActivitySharedAction
import im.vector.app.features.home.HomeSharedActionViewModel
-import org.matrix.android.sdk.api.session.group.model.GroupSummary
import kotlinx.android.synthetic.main.fragment_group_list.*
+import org.matrix.android.sdk.api.session.group.model.GroupSummary
import javax.inject.Inject
class GroupListFragment @Inject constructor(
diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt
index 51831e8fb9..588d939635 100644
--- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt
@@ -26,14 +26,14 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
+import io.reactivex.Observable
+import io.reactivex.functions.BiFunction
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.Membership
-import io.reactivex.Observable
-import io.reactivex.functions.BiFunction
import org.matrix.android.sdk.rx.rx
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index ae3a9f9680..c074d66f7c 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -139,6 +139,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
+ is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
}.exhaustive
}
homeActivityViewModel.subscribe(this) { renderState(it) }
@@ -183,6 +184,17 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
}
}
+ private fun handleCrossSigningInvalidated(event: HomeActivityViewEvents.OnCrossSignedInvalidated) {
+ // We need to ask
+ promptSecurityEvent(
+ event.userItem,
+ R.string.crosssigning_verify_this_session,
+ R.string.confirm_your_identity
+ ) {
+ it.navigator.waitSessionVerification(it)
+ }
+ }
+
private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) {
// We need to ask
promptSecurityEvent(
@@ -310,6 +322,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
navigator.openRoomsFiltering(this)
return true
}
+ R.id.menu_home_setting -> {
+ navigator.openSettings(this)
+ return true
+ }
}
return super.onOptionsItemSelected(item)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
index be62997e0c..2a29e13572 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
@@ -22,5 +22,6 @@ import org.matrix.android.sdk.api.util.MatrixItem
sealed class HomeActivityViewEvents : VectorViewEvents {
data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents()
data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents()
+ data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents()
object PromptToEnableSessionPush : HomeActivityViewEvents()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index 46ae65ec02..48a71db35c 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -27,6 +27,9 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.settings.VectorPreferences
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.pushrules.RuleIds
@@ -38,9 +41,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
import org.matrix.android.sdk.rx.asObservable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import org.matrix.android.sdk.rx.rx
import timber.log.Timber
class HomeActivityViewModel @AssistedInject constructor(
@@ -67,11 +68,39 @@ class HomeActivityViewModel @AssistedInject constructor(
}
private var checkBootstrap = false
+ private var onceTrusted = false
init {
observeInitialSync()
mayBeInitializeCrossSigning()
checkSessionPushIsOn()
+ observeCrossSigningReset()
+ }
+
+ private fun observeCrossSigningReset() {
+ val safeActiveSession = activeSessionHolder.getSafeActiveSession()
+ val crossSigningService = safeActiveSession
+ ?.cryptoService()
+ ?.crossSigningService()
+ onceTrusted = crossSigningService
+ ?.allPrivateKeysKnown() ?: false
+
+ safeActiveSession
+ ?.rx()
+ ?.liveCrossSigningInfo(safeActiveSession.myUserId)
+ ?.subscribe {
+ val isVerified = it.getOrNull()?.isTrusted() ?: false
+ if (!isVerified && onceTrusted) {
+ // cross signing keys have been reset
+ // Tigger a popup to re-verify
+ _viewEvents.post(
+ HomeActivityViewEvents.OnCrossSignedInvalidated(
+ safeActiveSession.getUser(safeActiveSession.myUserId)?.toMatrixItem()
+ )
+ )
+ }
+ onceTrusted = isVerified
+ }?.disposeOnClear()
}
private fun observeInitialSync() {
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
index c381f998c7..88c310fde8 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
@@ -27,9 +27,9 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.grouplist.SelectedGroupDataSource
import im.vector.app.features.ui.UiStateRepository
+import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.Membership
-import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.rx.rx
/**
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
index 3344989b0a..12689cd983 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
@@ -18,18 +18,23 @@ package im.vector.app.features.home
import android.os.Bundle
import android.view.View
+import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.observeK
import im.vector.app.core.extensions.replaceChildFragment
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.grouplist.GroupListFragment
+import im.vector.app.features.settings.VectorPreferences
+import im.vector.app.features.settings.VectorSettingsActivity
+import im.vector.app.features.workers.signout.SignOutUiWorker
+import kotlinx.android.synthetic.main.fragment_home_drawer.*
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.toMatrixItem
-import kotlinx.android.synthetic.main.fragment_home_drawer.*
import javax.inject.Inject
class HomeDrawerFragment @Inject constructor(
private val session: Session,
+ private val vectorPreferences: VectorPreferences,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment() {
@@ -53,12 +58,24 @@ class HomeDrawerFragment @Inject constructor(
homeDrawerUserIdView.text = user.userId
}
}
+ // Profile
+ homeDrawerHeader.debouncedClicks {
+ sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
+ navigator.openSettings(requireActivity(), directAccess = VectorSettingsActivity.EXTRA_DIRECT_ACCESS_GENERAL)
+ }
+ // Settings
homeDrawerHeaderSettingsView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openSettings(requireActivity())
}
+ // Sign out
+ homeDrawerHeaderSignoutView.debouncedClicks {
+ sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
+ SignOutUiWorker(requireActivity()).perform()
+ }
// Debug menu
+ homeDrawerHeaderDebugView.isVisible = vectorPreferences.developerMode()
homeDrawerHeaderDebugView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openDebug(requireActivity())
diff --git a/vector/src/main/java/im/vector/app/features/home/LoadingFragment.kt b/vector/src/main/java/im/vector/app/features/home/LoadingFragment.kt
index b3c80b3642..e56887b85f 100644
--- a/vector/src/main/java/im/vector/app/features/home/LoadingFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/LoadingFragment.kt
@@ -24,7 +24,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_loading.*
import javax.inject.Inject
-class LoadingFragment @Inject constructor(): VectorBaseFragment() {
+class LoadingFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_loading
diff --git a/vector/src/main/java/im/vector/app/features/home/RoomListDisplayMode.kt b/vector/src/main/java/im/vector/app/features/home/RoomListDisplayMode.kt
index 91f08a4d21..ad99d8e1c4 100644
--- a/vector/src/main/java/im/vector/app/features/home/RoomListDisplayMode.kt
+++ b/vector/src/main/java/im/vector/app/features/home/RoomListDisplayMode.kt
@@ -20,9 +20,9 @@ import androidx.annotation.StringRes
import im.vector.app.R
enum class RoomListDisplayMode(@StringRes val titleRes: Int) {
- ALL(R.string.bottom_action_all),
- NOTIFICATIONS(R.string.bottom_action_notification),
- PEOPLE(R.string.bottom_action_people_x),
- ROOMS(R.string.bottom_action_rooms),
- FILTERED(/* Not used */ 0)
- }
+ ALL(R.string.bottom_action_all),
+ NOTIFICATIONS(R.string.bottom_action_notification),
+ PEOPLE(R.string.bottom_action_people_x),
+ ROOMS(R.string.bottom_action_rooms),
+ FILTERED(/* Not used */ 0)
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt
new file mode 100644
index 0000000000..db396cf990
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.os.Build
+import androidx.annotation.WorkerThread
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import im.vector.app.BuildConfig
+import im.vector.app.core.glide.GlideApp
+import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.home.room.detail.RoomDetailActivity
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.util.toMatrixItem
+import javax.inject.Inject
+
+private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+private const val adaptiveIconSizeDp = 108
+private const val adaptiveIconOuterSidesDp = 18
+private const val directShareCategory = BuildConfig.APPLICATION_ID + ".SHORTCUT_SHARE"
+
+class ShortcutCreator @Inject constructor(
+ private val context: Context,
+ private val avatarRenderer: AvatarRenderer,
+ private val dimensionConverter: DimensionConverter
+) {
+ private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
+ private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
+ private val iconSize by lazy {
+ if (useAdaptiveIcon) {
+ adaptiveIconSize - adaptiveIconOuterSides
+ } else {
+ dimensionConverter.dpToPx(72)
+ }
+ }
+
+ fun canCreateShortcut(): Boolean {
+ return ShortcutManagerCompat.isRequestPinShortcutSupported(context)
+ }
+
+ @WorkerThread
+ fun create(roomSummary: RoomSummary): ShortcutInfoCompat {
+ val intent = RoomDetailActivity.shortcutIntent(context, roomSummary.roomId)
+ val bitmap = try {
+ avatarRenderer.shortcutDrawable(GlideApp.with(context), roomSummary.toMatrixItem(), iconSize)
+ } catch (failure: Throwable) {
+ null
+ }
+ return ShortcutInfoCompat.Builder(context, roomSummary.roomId)
+ .setShortLabel(roomSummary.displayName)
+ .setIcon(bitmap?.toProfileImageIcon())
+ .setIntent(intent)
+
+ // Make it show up in the direct share menu
+ .setCategories(setOf(directShareCategory))
+
+ .build()
+ }
+
+ private fun Bitmap.toProfileImageIcon(): IconCompat {
+ return if (useAdaptiveIcon) {
+ IconCompat.createWithAdaptiveBitmap(this)
+ } else {
+ IconCompat.createWithBitmap(this)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt
index 1a476913f3..3684a8b3f8 100644
--- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt
@@ -18,40 +18,19 @@ package im.vector.app.features.home
import android.content.Context
import android.content.pm.ShortcutManager
-import android.graphics.Bitmap
import android.os.Build
import androidx.core.content.getSystemService
-import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
-import androidx.core.graphics.drawable.IconCompat
-import im.vector.app.core.glide.GlideApp
-import im.vector.app.core.utils.DimensionConverter
-import im.vector.app.features.home.room.detail.RoomDetailActivity
-import org.matrix.android.sdk.api.util.toMatrixItem
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
-private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-private const val adaptiveIconSizeDp = 108
-private const val adaptiveIconOuterSidesDp = 18
-
class ShortcutsHandler @Inject constructor(
private val context: Context,
private val homeRoomListStore: HomeRoomListDataSource,
- private val avatarRenderer: AvatarRenderer,
- private val dimensionConverter: DimensionConverter
+ private val shortcutCreator: ShortcutCreator
) {
- private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
- private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
- private val iconSize by lazy {
- if (useAdaptiveIcon) {
- adaptiveIconSize - adaptiveIconOuterSides
- } else {
- dimensionConverter.dpToPx(72)
- }
- }
fun observeRoomsAndBuildShortcuts(): Disposable {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
@@ -67,19 +46,7 @@ class ShortcutsHandler @Inject constructor(
val shortcuts = rooms
.filter { room -> room.isFavorite }
.take(n = 4) // Android only allows us to create 4 shortcuts
- .map { room ->
- val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
- val bitmap = try {
- avatarRenderer.shortcutDrawable(GlideApp.with(context), room.toMatrixItem(), iconSize)
- } catch (failure: Throwable) {
- null
- }
- ShortcutInfoCompat.Builder(context, room.roomId)
- .setShortLabel(room.displayName)
- .setIcon(bitmap?.toProfileImageIcon())
- .setIntent(intent)
- .build()
- }
+ .map { shortcutCreator.create(it) }
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
@@ -104,14 +71,4 @@ class ShortcutsHandler @Inject constructor(
}
}
}
-
- // PRIVATE API *********************************************************************************
-
- private fun Bitmap.toProfileImageIcon(): IconCompat {
- return if (useAdaptiveIcon) {
- IconCompat.createWithAdaptiveBitmap(this)
- } else {
- IconCompat.createWithBitmap(this)
- }
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
index eeb1639045..3bdcfc4018 100644
--- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
@@ -30,6 +30,8 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.settings.VectorPreferences
+import io.reactivex.Observable
+import io.reactivex.functions.Function3
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
@@ -39,8 +41,6 @@ import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
-import io.reactivex.Observable
-import io.reactivex.functions.Function3
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import java.util.concurrent.TimeUnit
diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt
index 4ddc4bf972..8a32157097 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt
@@ -24,11 +24,11 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
+import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
-import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.rx.rx
class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState,
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 4bdf2e7e57..88eb1b5109 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
@@ -51,7 +51,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction()
data class EnterQuoteMode(val eventId: String, val text: String) : RoomDetailAction()
data class EnterReplyMode(val eventId: String, val text: String) : RoomDetailAction()
- data class ExitSpecialMode(val text: String) : RoomDetailAction()
+ data class EnterRegularMode(val text: String, val fromSharing: Boolean) : RoomDetailAction()
data class ResendMessage(val eventId: String) : RoomDetailAction()
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
@@ -81,11 +81,13 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
object SelectStickerAttachment : RoomDetailAction()
- object OpenIntegrationManager: RoomDetailAction()
- object ManageIntegrations: RoomDetailAction()
- data class AddJitsiWidget(val withVideo: Boolean): RoomDetailAction()
- data class RemoveWidget(val widgetId: String): RoomDetailAction()
+ object OpenIntegrationManager : RoomDetailAction()
+ object ManageIntegrations : RoomDetailAction()
+ data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction()
+ data class RemoveWidget(val widgetId: String) : RoomDetailAction()
data class EnsureNativeWidgetAllowed(val widget: Widget,
val userJustAccepted: Boolean,
val grantedEvents: RoomDetailViewEvents) : RoomDetailAction()
+
+ data class JumpToReadReceipt(val userId: String) : RoomDetailAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index b0e43e3396..af806c729c 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
@@ -17,7 +17,7 @@
package im.vector.app.features.home.room.detail
import android.annotation.SuppressLint
-import android.app.Activity.RESULT_OK
+import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Typeface
@@ -53,6 +53,8 @@ import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
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
@@ -71,10 +73,12 @@ import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.hideKeyboard
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.trackItemsVisibilityChange
import im.vector.app.core.glide.GlideApp
+import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.intent.getMimeTypeFromUri
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
@@ -88,21 +92,18 @@ import im.vector.app.core.utils.KeyboardStateUtils
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_INCOMING_URI
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_PICK_ATTACHMENT
import im.vector.app.core.utils.TextUtils
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.isValidUrl
-import im.vector.app.core.utils.onPermissionResultAudioIpCall
-import im.vector.app.core.utils.onPermissionResultVideoIpCall
import im.vector.app.core.utils.openUrlInExternalBrowser
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.saveMedia
import im.vector.app.core.utils.shareMedia
+import im.vector.app.core.utils.shareText
import im.vector.app.core.utils.toast
import im.vector.app.features.attachments.AttachmentTypeSelectorView
import im.vector.app.features.attachments.AttachmentsHelper
@@ -136,7 +137,6 @@ 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.widget.RoomWidgetsBottomSheet
-import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.invite.VectorInviteView
@@ -205,8 +205,6 @@ data class RoomDetailArgs(
val sharedData: SharedData? = null
) : Parcelable
-private const val REACTION_SELECT_REQUEST_CODE = 0
-
class RoomDetailFragment @Inject constructor(
private val session: Session,
private val avatarRenderer: AvatarRenderer,
@@ -220,7 +218,9 @@ class RoomDetailFragment @Inject constructor(
private val colorProvider: ColorProvider,
private val notificationUtils: NotificationUtils,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
- private val matrixItemColorProvider: MatrixItemColorProvider
+ private val matrixItemColorProvider: MatrixItemColorProvider,
+ private val imageContentRenderer: ImageContentRenderer,
+ private val roomDetailPendingActionStore: RoomDetailPendingActionStore
) :
VectorBaseFragment(),
TimelineEventController.Callback,
@@ -232,11 +232,6 @@ class RoomDetailFragment @Inject constructor(
ActiveCallView.Callback {
companion object {
-
- private const val AUDIO_CALL_PERMISSION_REQUEST_CODE = 1
- private const val VIDEO_CALL_PERMISSION_REQUEST_CODE = 2
- private const val SAVE_ATTACHEMENT_REQUEST_CODE = 3
-
/**
* Sanitize the display name.
*
@@ -369,6 +364,10 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
}.exhaustive
}
+
+ if (savedInstanceState == null) {
+ handleShareData()
+ }
}
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
@@ -399,9 +398,14 @@ class RoomDetailFragment @Inject constructor(
}
}
+ private val integrationManagerActivityResultLauncher = registerStartForActivityResult {
+ // Noop
+ }
+
private fun openIntegrationManager(screen: String? = null) {
navigator.openIntegrationManager(
- fragment = this,
+ context = requireContext(),
+ activityResultLauncher = integrationManagerActivityResultLauncher,
roomId = roomDetailArgs.roomId,
integId = null,
screen = screen
@@ -438,7 +442,7 @@ class RoomDetailFragment @Inject constructor(
}
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) {
- navigator.openStickerPicker(this, roomDetailArgs.roomId, event.widget)
+ navigator.openStickerPicker(requireContext(), stickerActivityResultLauncher, roomDetailArgs.roomId, event.widget)
}
private fun startOpenFileIntent(action: RoomDetailViewEvents.OpenFile) {
@@ -478,21 +482,17 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoom(vectorBaseActivity, action.roomId)
}
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
- if (savedInstanceState == null) {
- when (val sharedData = roomDetailArgs.sharedData) {
- is SharedData.Text -> {
- // Save a draft to set the shared text to the composer
- roomDetailViewModel.handle(RoomDetailAction.SaveDraft(sharedData.text))
- }
- is SharedData.Attachments -> {
- // open share edition
- onContentAttachmentsReady(sharedData.attachmentData)
- }
- null -> Timber.v("No share data to process")
- }.exhaustive
- }
+ private fun handleShareData() {
+ when (val sharedData = roomDetailArgs.sharedData) {
+ is SharedData.Text -> {
+ roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
+ }
+ is SharedData.Attachments -> {
+ // open share edition
+ onContentAttachmentsReady(sharedData.attachmentData)
+ }
+ null -> Timber.v("No share data to process")
+ }.exhaustive
}
override fun onDestroyView() {
@@ -616,8 +616,8 @@ class RoomDetailFragment @Inject constructor(
withState(roomDetailViewModel) { state ->
// Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
- 1 -> false
- 2 -> state.isAllowedToStartWebRTCCall
+ 1 -> false
+ 2 -> state.isAllowedToStartWebRTCCall
else -> state.isAllowedToManageWidgets
}
setOf(R.id.voice_call, R.id.video_call).forEach {
@@ -654,6 +654,14 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
true
}
+ R.id.invite -> {
+ navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
+ true
+ }
+ R.id.timeline_setting -> {
+ navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
+ true
+ }
R.id.resend_all -> {
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
true
@@ -679,10 +687,22 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
true
}
+ R.id.search -> {
+ handleSearchAction()
+ true
+ }
else -> super.onOptionsItemSelected(item)
}
}
+ private fun handleSearchAction() {
+ if (session.getRoom(roomDetailArgs.roomId)?.isEncrypted() == false) {
+ navigator.openSearch(requireContext(), roomDetailArgs.roomId)
+ } else {
+ showDialogWithMessage(getString(R.string.search_is_not_supported_in_e2e_room))
+ }
+ }
+
private fun handleCallRequest(item: MenuItem) = withState(roomDetailViewModel) { state ->
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
val isVideoCall = item.itemId == R.id.video_call
@@ -710,7 +730,13 @@ class RoomDetailFragment @Inject constructor(
// safeStartCall(it, isVideoCall)
// }
} else if (!state.isAllowedToStartWebRTCCall) {
- showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call))
+ showDialogWithMessage(getString(
+ if (state.isDm()) {
+ R.string.no_permissions_to_start_webrtc_call_in_direct_room
+ } else {
+ R.string.no_permissions_to_start_webrtc_call
+ })
+ )
} else {
safeStartCall(isVideoCall)
}
@@ -720,7 +746,13 @@ class RoomDetailFragment @Inject constructor(
// can you add widgets??
if (!state.isAllowedToManageWidgets) {
// You do not have permission to start a conference call in this room
- showDialogWithMessage(getString(R.string.no_permissions_to_start_conf_call))
+ showDialogWithMessage(getString(
+ if (state.isDm()) {
+ R.string.no_permissions_to_start_conf_call_in_direct_room
+ } else {
+ R.string.no_permissions_to_start_conf_call
+ }
+ ))
} else {
if (state.activeRoomWidgets()?.filter { it.type == WidgetType.Jitsi }?.any() == true) {
// A conference is already in progress!
@@ -766,19 +798,33 @@ class RoomDetailFragment @Inject constructor(
}
}
+ private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ if (allGranted) {
+ (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.handle(it)
+ }
+ } else {
+ context?.toast(R.string.permissions_action_not_performed_missing_permissions)
+ cleanUpAfterPermissionNotGranted()
+ }
+ }
+
private fun safeStartCall2(isVideoCall: Boolean) {
val startCallAction = RoomDetailAction.StartCall(isVideoCall)
roomDetailViewModel.pendingAction = startCallAction
if (isVideoCall) {
if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL,
- this, VIDEO_CALL_PERMISSION_REQUEST_CODE,
+ requireActivity(),
+ startCallActivityResultLauncher,
R.string.permissions_rationale_msg_camera_and_audio)) {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(startCallAction)
}
} else {
if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL,
- this, AUDIO_CALL_PERMISSION_REQUEST_CODE,
+ requireActivity(),
+ startCallActivityResultLauncher,
R.string.permissions_rationale_msg_record_audio)) {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(startCallAction)
@@ -844,6 +890,17 @@ class RoomDetailFragment @Inject constructor(
override fun onResume() {
super.onResume()
notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId)
+ roomDetailPendingActionStore.data?.let { handlePendingAction(it) }
+ roomDetailPendingActionStore.data = null
+ }
+
+ private fun handlePendingAction(roomDetailPendingAction: RoomDetailPendingAction) {
+ when (roomDetailPendingAction) {
+ is RoomDetailPendingAction.JumpToReadReceipt ->
+ roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
+ is RoomDetailPendingAction.MentionUser ->
+ insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
+ }.exhaustive
}
override fun onPause() {
@@ -854,27 +911,63 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(composerLayout.composerEditText.text.toString()))
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data)
- if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
- when (requestCode) {
- AttachmentsPreviewActivity.REQUEST_CODE -> {
- val sendData = AttachmentsPreviewActivity.getOutput(data)
- val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
- roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
- }
- REACTION_SELECT_REQUEST_CODE -> {
- val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
- roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
- }
- WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE -> {
- val content = WidgetActivity.getOutput(data).toModel() ?: return
- roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
- }
+ private val attachmentFileActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ attachmentsHelper.onImageResult(it.data)
+ }
+ }
+
+ private val attachmentAudioActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ attachmentsHelper.onAudioResult(it.data)
+ }
+ }
+
+ private val attachmentContactActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ attachmentsHelper.onContactResult(it.data)
+ }
+ }
+
+ private val attachmentImageActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ attachmentsHelper.onImageResult(it.data)
+ }
+ }
+
+ private val attachmentPhotoActivityResultLauncher = registerStartForActivityResult {
+ if (it.resultCode == Activity.RESULT_OK) {
+ attachmentsHelper.onPhotoResult()
+ }
+ }
+
+ private val contentAttachmentActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ val data = activityResult.data ?: return@registerStartForActivityResult
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val sendData = AttachmentsPreviewActivity.getOutput(data)
+ val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
+ roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
+ }
+ }
+
+ private val emojiActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val eventId = EmojiReactionPickerActivity.getOutputEventId(activityResult.data)
+ val reaction = EmojiReactionPickerActivity.getOutputReaction(activityResult.data)
+ if (eventId != null && reaction != null) {
+ roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
}
}
- // TODO why don't we call super here?
- // super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ private val stickerActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ val data = activityResult.data ?: return@registerStartForActivityResult
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ WidgetActivity.getOutput(data).toModel()
+ ?.let { content ->
+ roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
+ }
+ }
}
// PRIVATE METHODS *****************************************************************************
@@ -931,6 +1024,16 @@ class RoomDetailFragment @Inject constructor(
val touchHelper = ItemTouchHelper(swipeCallback)
touchHelper.attachToRecyclerView(recyclerView)
}
+ recyclerView.addGlidePreloader(
+ epoxyController = timelineEventController,
+ requestManager = GlideApp.with(this),
+ preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ ->
+ imageContentRenderer.createGlideRequest(
+ epoxyModel.mediaData,
+ ImageContentRenderer.Mode.THUMBNAIL,
+ requestManager as GlideRequests
+ )
+ })
}
private fun updateJumpToReadMarkerViewVisibility() {
@@ -959,6 +1062,18 @@ class RoomDetailFragment @Inject constructor(
}
}
+ private val writingFileActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ if (allGranted) {
+ val pendingUri = roomDetailViewModel.pendingUri
+ if (pendingUri != null) {
+ roomDetailViewModel.pendingUri = null
+ sendUri(pendingUri)
+ }
+ } else {
+ cleanUpAfterPermissionNotGranted()
+ }
+ }
+
private fun setupComposer() {
autoCompleter.setup(composerLayout.composerEditText)
@@ -986,12 +1101,12 @@ class RoomDetailFragment @Inject constructor(
}
override fun onCloseRelatedMessage() {
- roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString()))
+ roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false))
}
override fun onRichContentSelected(contentUri: Uri): Boolean {
// We need WRITE_EXTERNAL permission
- return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this@RoomDetailFragment, PERMISSION_REQUEST_CODE_INCOMING_URI)) {
+ return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), writingFileActivityResultLauncher)) {
sendUri(contentUri)
} else {
roomDetailViewModel.pendingUri = contentUri
@@ -1119,12 +1234,8 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) {
- is RoomDetailViewEvents.MessageSent -> {
- updateComposerText("")
- }
is RoomDetailViewEvents.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
- updateComposerText("")
}
is RoomDetailViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
@@ -1385,52 +1496,11 @@ class RoomDetailFragment @Inject constructor(
// // }
// }
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- if (allGranted(grantResults)) {
- when (requestCode) {
- SAVE_ATTACHEMENT_REQUEST_CODE -> {
- sharedActionViewModel.pendingAction?.let {
- handleActions(it)
- sharedActionViewModel.pendingAction = null
- }
- }
- PERMISSION_REQUEST_CODE_INCOMING_URI -> {
- val pendingUri = roomDetailViewModel.pendingUri
- if (pendingUri != null) {
- roomDetailViewModel.pendingUri = null
- sendUri(pendingUri)
- }
- }
- PERMISSION_REQUEST_CODE_PICK_ATTACHMENT -> {
- val pendingType = attachmentsHelper.pendingType
- if (pendingType != null) {
- attachmentsHelper.pendingType = null
- launchAttachmentProcess(pendingType)
- }
- }
- AUDIO_CALL_PERMISSION_REQUEST_CODE -> {
- if (onPermissionResultAudioIpCall(requireContext(), grantResults)) {
- (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
- roomDetailViewModel.pendingAction = null
- roomDetailViewModel.handle(it)
- }
- }
- }
- VIDEO_CALL_PERMISSION_REQUEST_CODE -> {
- if (onPermissionResultVideoIpCall(requireContext(), grantResults)) {
- (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
- roomDetailViewModel.pendingAction = null
- roomDetailViewModel.handle(it)
- }
- }
- }
- }
- } else {
- // Reset all pending data
- roomDetailViewModel.pendingAction = null
- roomDetailViewModel.pendingUri = null
- attachmentsHelper.pendingType = null
- }
+ private fun cleanUpAfterPermissionNotGranted() {
+ // Reset all pending data
+ roomDetailViewModel.pendingAction = null
+ roomDetailViewModel.pendingUri = null
+ attachmentsHelper.pendingType = null
}
// override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) {
@@ -1528,26 +1598,41 @@ class RoomDetailFragment @Inject constructor(
}
private fun onShareActionClicked(action: EventSharedAction.Share) {
- session.fileService().downloadFile(
- downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
- id = action.eventId,
- fileName = action.messageContent.body,
- mimeType = action.messageContent.mimeType,
- url = action.messageContent.getFileUrl(),
- elementToDecrypt = action.messageContent.encryptedFileInfo?.toElementToDecrypt(),
- callback = object : MatrixCallback {
- override fun onSuccess(data: File) {
- if (isAdded) {
- shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri()))
+ if (action.messageContent is MessageTextContent) {
+ shareText(requireContext(), action.messageContent.body)
+ } else if (action.messageContent is MessageWithAttachmentContent) {
+ session.fileService().downloadFile(
+ downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
+ id = action.eventId,
+ fileName = action.messageContent.body,
+ mimeType = action.messageContent.mimeType,
+ url = action.messageContent.getFileUrl(),
+ elementToDecrypt = action.messageContent.encryptedFileInfo?.toElementToDecrypt(),
+ callback = object : MatrixCallback {
+ override fun onSuccess(data: File) {
+ if (isAdded) {
+ shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri()))
+ }
}
}
- }
- )
+ )
+ }
+ }
+
+ private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ if (allGranted) {
+ sharedActionViewModel.pendingAction?.let {
+ handleActions(it)
+ sharedActionViewModel.pendingAction = null
+ }
+ } else {
+ cleanUpAfterPermissionNotGranted()
+ }
}
private fun onSaveActionClicked(action: EventSharedAction.Save) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
- && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, SAVE_ATTACHEMENT_REQUEST_CODE)) {
+ && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), saveActionActivityResultLauncher)) {
sharedActionViewModel.pendingAction = action
return
}
@@ -1580,7 +1665,7 @@ class RoomDetailFragment @Inject constructor(
openRoomMemberProfile(action.userId)
}
is EventSharedAction.AddReaction -> {
- startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE)
+ emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
}
is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData)
@@ -1787,8 +1872,20 @@ class RoomDetailFragment @Inject constructor(
// AttachmentTypeSelectorView.Callback
+ private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ if (allGranted) {
+ val pendingType = attachmentsHelper.pendingType
+ if (pendingType != null) {
+ attachmentsHelper.pendingType = null
+ launchAttachmentProcess(pendingType)
+ }
+ } else {
+ cleanUpAfterPermissionNotGranted()
+ }
+ }
+
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
- if (checkPermissions(type.permissionsBit, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)) {
+ if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
launchAttachmentProcess(type)
} else {
attachmentsHelper.pendingType = type
@@ -1797,11 +1894,11 @@ class RoomDetailFragment @Inject constructor(
private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) {
when (type) {
- AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(this)
- AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(this)
- AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this)
- AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this)
- AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this)
+ AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher)
+ AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
+ AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher)
+ AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
+ AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
}.exhaustive
}
@@ -1820,7 +1917,7 @@ class RoomDetailFragment @Inject constructor(
}
if (grouped.previewables.isNotEmpty()) {
val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables))
- startActivityForResult(intent, AttachmentsPreviewActivity.REQUEST_CODE)
+ contentAttachmentActivityResultLauncher.launch(intent)
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/WidgetRequestCodes.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
similarity index 71%
rename from vector/src/main/java/im/vector/app/features/home/room/detail/widget/WidgetRequestCodes.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
index 5f0e6866db..394d46ef8d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/WidgetRequestCodes.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package im.vector.app.features.home.room.detail.widget
+package im.vector.app.features.home.room.detail
-object WidgetRequestCodes {
- const val STICKER_PICKER_REQUEST_CODE = 16000
- const val INTEGRATION_MANAGER_REQUEST_CODE = 16001
+sealed class RoomDetailPendingAction {
+ data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction()
+ data class MentionUser(val userId: String) : RoomDetailPendingAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt
new file mode 100644
index 0000000000..9ffbb83a47
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingActionStore.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail
+
+import im.vector.app.core.utils.TemporaryStore
+import javax.inject.Inject
+import javax.inject.Singleton
+
+// Store to keep a pending action from sub screen of a room detail
+@Singleton
+class RoomDetailPendingActionStore @Inject constructor() : TemporaryStore(10_000)
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 29ed43f17d..ee2d193473 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,8 +41,8 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
- object ShowWaitingView: RoomDetailViewEvents()
- object HideWaitingView: RoomDetailViewEvents()
+ object ShowWaitingView : RoomDetailViewEvents()
+ object HideWaitingView : RoomDetailViewEvents()
data class FileTooBigError(
val filename: String,
@@ -64,14 +64,14 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
abstract class SendMessageResult : RoomDetailViewEvents()
- object DisplayPromptForIntegrationManager: RoomDetailViewEvents()
+ object DisplayPromptForIntegrationManager : RoomDetailViewEvents()
- object DisplayEnableIntegrationsWarning: RoomDetailViewEvents()
+ object DisplayEnableIntegrationsWarning : RoomDetailViewEvents()
- data class OpenStickerPicker(val widget: Widget): RoomDetailViewEvents()
+ data class OpenStickerPicker(val widget: Widget) : RoomDetailViewEvents()
- object OpenIntegrationManager: RoomDetailViewEvents()
- object OpenActiveWidgetBottomSheet: RoomDetailViewEvents()
+ object OpenIntegrationManager : RoomDetailViewEvents()
+ object OpenActiveWidgetBottomSheet : RoomDetailViewEvents()
data class RequestNativeWidgetPermission(val widget: Widget,
val domain: String,
val grantedEvents: RoomDetailViewEvents) : RoomDetailViewEvents()
@@ -83,6 +83,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult()
object SlashCommandResultOk : SendMessageResult()
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
+
// TODO Remove
object SlashCommandNotImplemented : SendMessageResult()
}
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 c459090245..f9a8669744 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
@@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor(
getUnreadState()
observeSyncState()
observeEventDisplayedActions()
- observeDrafts()
+ getDraftIfAny()
observeUnreadState()
observeMyRoomMember()
observeActiveRoomWidgets()
@@ -180,11 +180,13 @@ class RoomDetailViewModel @AssistedInject constructor(
PowerLevelsObservableFactory(room).createObservable()
.subscribe {
val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
+ val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId)
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
setState {
copy(
canSendMessage = canSendMessage,
+ canInvite = canInvite,
isAllowedToManageWidgets = isAllowedToManageWidgets,
isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall
)
@@ -240,7 +242,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
- is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action)
+ is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
@@ -272,9 +274,15 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action)
+ is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
}.exhaustive
}
+ private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
+ room.getUserReadReceipt(action.userId)
+ ?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
+ }
+
private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
room.sendEvent(EventType.STICKER, action.stickerContent.toContent())
}
@@ -449,47 +457,52 @@ class RoomDetailViewModel @AssistedInject constructor(
/**
* Convert a send mode to a draft and save the draft
*/
- private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) {
- withState {
- when (it.sendMode) {
- is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
- is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
- is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
- is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
- }.exhaustive
+ private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
+ when {
+ it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
+ setState { copy(sendMode = it.sendMode.copy(action.draft)) }
+ room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
+ }
+ it.sendMode is SendMode.REPLY -> {
+ setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
+ room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+ }
+ it.sendMode is SendMode.QUOTE -> {
+ setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
+ room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+ }
+ it.sendMode is SendMode.EDIT -> {
+ setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
+ room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
+ }
}
}
- private fun observeDrafts() {
- room.rx().liveDrafts()
- .subscribe {
- Timber.d("Draft update --> SetState")
- setState {
- val draft = it.lastOrNull() ?: UserDraft.REGULAR("")
- copy(
- // Create a sendMode from a draft and retrieve the TimelineEvent
- sendMode = when (draft) {
- is UserDraft.REGULAR -> SendMode.REGULAR(draft.text)
- is UserDraft.QUOTE -> {
- room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
- SendMode.QUOTE(timelineEvent, draft.text)
- }
- }
- is UserDraft.REPLY -> {
- room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
- SendMode.REPLY(timelineEvent, draft.text)
- }
- }
- is UserDraft.EDIT -> {
- room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent ->
- SendMode.EDIT(timelineEvent, draft.text)
- }
- }
- } ?: SendMode.REGULAR("")
- )
- }
- }
- .disposeOnClear()
+ private fun getDraftIfAny() {
+ val currentDraft = room.getDraft() ?: return
+ setState {
+ copy(
+ // Create a sendMode from a draft and retrieve the TimelineEvent
+ sendMode = when (currentDraft) {
+ is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false)
+ is UserDraft.QUOTE -> {
+ room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
+ SendMode.QUOTE(timelineEvent, currentDraft.text)
+ }
+ }
+ is UserDraft.REPLY -> {
+ room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
+ SendMode.REPLY(timelineEvent, currentDraft.text)
+ }
+ }
+ is UserDraft.EDIT -> {
+ room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
+ SendMode.EDIT(timelineEvent, currentDraft.text)
+ }
+ }
+ } ?: SendMode.REGULAR("", fromSharing = false)
+ )
+ }
}
private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) {
@@ -533,6 +546,8 @@ class RoomDetailViewModel @AssistedInject constructor(
// For now always disable when not in developer mode, worker cancellation is not working properly
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
+ R.id.timeline_setting -> true
+ R.id.invite -> state.canInvite
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.open_matrix_apps -> session.integrationManagerService().isIntegrationEnabled()
R.id.voice_call,
@@ -540,6 +555,7 @@ class RoomDetailViewModel @AssistedInject constructor(
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
R.id.show_room_info -> true
R.id.show_participants -> true
+ R.id.search -> true
else -> false
}
}
@@ -741,8 +757,15 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
- private fun popDraft() {
- room.deleteDraft(NoOpMatrixCallback())
+ private fun popDraft() = withState {
+ if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) {
+ // If we were sharing, we want to get back our last value from draft
+ getDraftIfAny()
+ } else {
+ // Otherwise we clear the composer and remove the draft from db
+ setState { copy(sendMode = SendMode.REGULAR("", false)) }
+ room.deleteDraft(NoOpMatrixCallback())
+ }
}
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
@@ -916,74 +939,25 @@ class RoomDetailViewModel @AssistedInject constructor(
}
private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
- saveCurrentDraft(action.text)
-
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
- setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) }
- timelineEvent.root.eventId?.let {
- room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback())
- }
+ setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
}
}
private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
- saveCurrentDraft(action.text)
-
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) }
- withState { state ->
- // Save a new draft and keep the previously entered text, if it was not an edit
- timelineEvent.root.eventId?.let {
- if (state.sendMode is SendMode.EDIT) {
- room.saveDraft(UserDraft.QUOTE(it, ""), NoOpMatrixCallback())
- } else {
- room.saveDraft(UserDraft.QUOTE(it, action.text), NoOpMatrixCallback())
- }
- }
- }
}
}
private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
- saveCurrentDraft(action.text)
-
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) }
- withState { state ->
- // Save a new draft and keep the previously entered text, if it was not an edit
- timelineEvent.root.eventId?.let {
- if (state.sendMode is SendMode.EDIT) {
- room.saveDraft(UserDraft.REPLY(it, ""), NoOpMatrixCallback())
- } else {
- room.saveDraft(UserDraft.REPLY(it, action.text), NoOpMatrixCallback())
- }
- }
- }
}
}
- private fun saveCurrentDraft(draft: String) {
- // Save the draft with the current text if any
- withState {
- if (draft.isNotBlank()) {
- when (it.sendMode) {
- is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(draft), NoOpMatrixCallback())
- is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
- is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
- is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback())
- }
- }
- }
- }
-
- private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState {
- if (it.sendMode is SendMode.EDIT) {
- room.deleteDraft(NoOpMatrixCallback())
- } else {
- // Save a new draft and keep the previously entered text
- room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback())
- }
- setState { copy(sendMode = SendMode.REGULAR(action.text)) }
+ private fun handleEnterRegularMode(action: RoomDetailAction.EnterRegularMode) = setState {
+ copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing))
}
private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
@@ -1108,7 +1082,7 @@ class RoomDetailViewModel @AssistedInject constructor(
.buffer(1, TimeUnit.SECONDS)
.filter { it.isNotEmpty() }
.subscribeBy(onNext = { actions ->
- val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event ?: return@subscribeBy
+ val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy
val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
if (trackUnreadMessages.get()) {
if (globalMostRecentDisplayedEvent == null) {
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 16a7379b6a..b31c972d1a 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
@@ -37,7 +37,13 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
*/
sealed class SendMode(open val text: String) {
- data class REGULAR(override val text: String) : SendMode(text)
+ data class REGULAR(
+ override val text: String,
+ val fromSharing: Boolean,
+ // This is necessary for forcing refresh on selectSubscribe
+ private val ts: Long = System.currentTimeMillis()
+ ) : SendMode(text)
+
data class QUOTE(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
data class EDIT(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
@@ -58,7 +64,7 @@ data class RoomDetailViewState(
val asyncRoomSummary: Async = Uninitialized,
val activeRoomWidgets: Async> = Uninitialized,
val typingMessage: String? = null,
- val sendMode: SendMode = SendMode.REGULAR(""),
+ val sendMode: SendMode = SendMode.REGULAR("", false),
val tombstoneEvent: Event? = null,
val tombstoneEventHandling: Async = Uninitialized,
val syncState: SyncState = SyncState.Idle,
@@ -67,6 +73,7 @@ data class RoomDetailViewState(
val canShowJumpToReadMarker: Boolean = true,
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
val canSendMessage: Boolean = true,
+ val canInvite: Boolean = true,
val isAllowedToManageWidgets: Boolean = false,
val isAllowedToStartWebRTCCall: Boolean = true
) : MvRxState {
@@ -77,4 +84,6 @@ data class RoomDetailViewState(
// Also highlight the target event, if any
highlightedEventId = args.eventId
)
+
+ fun isDm() = asyncRoomSummary()?.isDirect == true
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt
index ef13865374..41f386c606 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt
@@ -25,12 +25,15 @@ import android.view.MotionEvent
import android.view.View
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyTouchHelperCallback
import com.airbnb.epoxy.EpoxyViewHolder
+import im.vector.app.R
+import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber
import kotlin.math.abs
import kotlin.math.min
@@ -52,7 +55,16 @@ class RoomMessageTouchHelperCallback(private val context: Context,
private var replyButtonProgress: Float = 0F
private var lastReplyButtonAnimationTime: Long = 0
- private var imageDrawable: Drawable = ContextCompat.getDrawable(context, actionIcon)!!
+ private val imageDrawable: Drawable = DrawableCompat.wrap(
+ ContextCompat.getDrawable(context, actionIcon)!!
+ )
+
+ init {
+ DrawableCompat.setTint(
+ imageDrawable,
+ ThemeUtils.getColor(context, R.attr.riotx_text_primary)
+ )
+ }
private val triggerDistance = convertToPx(100)
private val minShowDistance = convertToPx(20)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt
index caa8841690..f4b14571c0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt
@@ -28,14 +28,16 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
-import androidx.transition.AutoTransition
+import androidx.transition.ChangeBounds
+import androidx.transition.Fade
import androidx.transition.Transition
import androidx.transition.TransitionManager
+import androidx.transition.TransitionSet
import butterknife.BindView
import butterknife.ButterKnife
import im.vector.app.R
-import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
+import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
/**
* Encapsulate the timeline composer UX.
@@ -54,18 +56,25 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
@BindView(R.id.composer_related_message_sender)
lateinit var composerRelatedMessageTitle: TextView
+
@BindView(R.id.composer_related_message_preview)
lateinit var composerRelatedMessageContent: TextView
+
@BindView(R.id.composer_related_message_avatar_view)
lateinit var composerRelatedMessageAvatar: ImageView
+
@BindView(R.id.composer_related_message_action_image)
lateinit var composerRelatedMessageActionIcon: ImageView
+
@BindView(R.id.composer_related_message_close)
lateinit var composerRelatedMessageCloseButton: ImageButton
+
@BindView(R.id.composerEditText)
lateinit var composerEditText: ComposerEditText
+
@BindView(R.id.composer_avatar_view)
lateinit var composerAvatarImageView: ImageView
+
@BindView(R.id.composer_shield)
lateinit var composerShieldImageView: ImageView
@@ -106,29 +115,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
return
}
currentConstraintSetId = R.layout.constraint_set_composer_layout_compact
- if (animate) {
- val transition = AutoTransition()
- transition.duration = animationDuration
- transition.addListener(object : Transition.TransitionListener {
- override fun onTransitionEnd(transition: Transition) {
- transitionComplete?.invoke()
- }
-
- override fun onTransitionResume(transition: Transition) {}
-
- override fun onTransitionPause(transition: Transition) {}
-
- override fun onTransitionCancel(transition: Transition) {}
-
- override fun onTransitionStart(transition: Transition) {}
- }
- )
- TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
- }
- ConstraintSet().also {
- it.clone(context, currentConstraintSetId)
- it.applyTo(this)
- }
+ applyNewConstraintSet(animate, transitionComplete)
}
fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) {
@@ -137,10 +124,28 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
return
}
currentConstraintSetId = R.layout.constraint_set_composer_layout_expanded
+ applyNewConstraintSet(animate, transitionComplete)
+ }
+
+ private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) {
if (animate) {
- val transition = AutoTransition()
- transition.duration = animationDuration
- transition.addListener(object : Transition.TransitionListener {
+ configureAndBeginTransition(transitionComplete)
+ }
+ ConstraintSet().also {
+ it.clone(context, currentConstraintSetId)
+ // in case shield is hidden, we will have glitch without this
+ it.getConstraint(R.id.composer_shield).propertySet.visibility = composerShieldImageView.visibility
+ it.applyTo(this)
+ }
+ }
+
+ private fun configureAndBeginTransition(transitionComplete: (() -> Unit)? = null) {
+ val transition = TransitionSet().apply {
+ ordering = TransitionSet.ORDERING_SEQUENTIAL
+ addTransition(ChangeBounds())
+ addTransition(Fade(Fade.IN))
+ duration = animationDuration
+ addListener(object : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {
transitionComplete?.invoke()
}
@@ -152,14 +157,9 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
override fun onTransitionCancel(transition: Transition) {}
override fun onTransitionStart(transition: Transition) {}
- }
- )
- TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
- }
- ConstraintSet().also {
- it.clone(context, currentConstraintSetId)
- it.applyTo(this)
+ })
}
+ TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition)
}
fun setRoomEncrypted(isEncrypted: Boolean, roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt
index 7bed9f8e64..7f9e40f218 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt
@@ -18,8 +18,10 @@ package im.vector.app.features.home.room.detail.composer.rainbow
import im.vector.app.core.utils.splitEmoji
import javax.inject.Inject
-import kotlin.math.abs
+import kotlin.math.cos
+import kotlin.math.pow
import kotlin.math.roundToInt
+import kotlin.math.sin
/**
* Inspired from React-Sdk
@@ -29,7 +31,7 @@ class RainbowGenerator @Inject constructor() {
fun generate(text: String): String {
val split = text.splitEmoji()
- val frequency = 360f / split.size
+ val frequency = 2 * Math.PI / split.size
return split
.mapIndexed { idx, letter ->
@@ -37,53 +39,55 @@ class RainbowGenerator @Inject constructor() {
if (letter == " ") {
"$letter"
} else {
- val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor()
+ val (a, b) = generateAB(idx * frequency, 1f)
+ val dashColor = labToRGB(75, a, b).toDashColor()
"$letter"
}
}
.joinToString(separator = "")
}
- private fun hueToRGB(h: Float, s: Float, l: Float): RgbColor {
- val c = s * (1 - abs(2 * l - 1))
- val x = c * (1 - abs((h / 60) % 2 - 1))
- val m = l - c / 2
+ private fun generateAB(hue: Double, chroma: Float): Pair {
+ val a = chroma * 127 * cos(hue)
+ val b = chroma * 127 * sin(hue)
- var r = 0f
- var g = 0f
- var b = 0f
+ return Pair(a, b)
+ }
- when {
- h < 60f -> {
- r = c
- g = x
- }
- h < 120f -> {
- r = x
- g = c
- }
- h < 180f -> {
- g = c
- b = x
- }
- h < 240f -> {
- g = x
- b = c
- }
- h < 300f -> {
- r = x
- b = c
- }
- else -> {
- r = c
- b = x
- }
+ private fun labToRGB(l: Int, a: Double, b: Double): RgbColor {
+ // Convert CIELAB to CIEXYZ (D65)
+ var y = (l + 16) / 116.0
+ val x = adjustXYZ(y + a / 500) * 0.9505
+ val z = adjustXYZ(y - b / 200) * 1.0890
+
+ y = adjustXYZ(y)
+
+ // Linear transformation from CIEXYZ to RGB
+ val red = 3.24096994 * x - 1.53738318 * y - 0.49861076 * z
+ val green = -0.96924364 * x + 1.8759675 * y + 0.04155506 * z
+ val blue = 0.05563008 * x - 0.20397696 * y + 1.05697151 * z
+
+ return RgbColor(adjustRGB(red), adjustRGB(green), adjustRGB(blue))
+ }
+
+ private fun adjustXYZ(value: Double): Double {
+ if (value > 0.2069) {
+ return value.pow(3)
}
+ return 0.1284 * value - 0.01771
+ }
- return RgbColor(
- ((r + m) * 255).roundToInt(),
- ((g + m) * 255).roundToInt(),
- ((b + m) * 255).roundToInt()
- )
+ private fun gammaCorrection(value: Double): Double {
+ // Non-linear transformation to sRGB
+ if (value <= 0.0031308) {
+ return 12.92 * value
+ }
+ return 1.055 * value.pow(1 / 2.4) - 0.055
+ }
+
+ private fun adjustRGB(value: Double): Int {
+ return (gammaCorrection(value)
+ .coerceIn(0.0, 1.0) * 255)
+ .roundToInt()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt
index 7593118c9f..5fefc9aba8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt
@@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.readreceipts
import android.os.Bundle
import android.os.Parcelable
+import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
@@ -59,8 +60,8 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment(), Di
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.seen_by)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchAction.kt
new file mode 100644
index 0000000000..36d22f1914
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchAction.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class SearchAction : VectorViewModelAction {
+ data class SearchWith(val searchTerm: String) : SearchAction()
+ object LoadMore : SearchAction()
+ object Retry : SearchAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt
new file mode 100644
index 0000000000..f85dccbb27
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.widget.SearchView
+import com.airbnb.mvrx.MvRx
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.VectorBaseActivity
+import kotlinx.android.synthetic.main.activity_search.*
+
+class SearchActivity : VectorBaseActivity() {
+
+ private val searchFragment: SearchFragment?
+ get() {
+ return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? SearchFragment
+ }
+
+ override fun getLayoutRes() = R.layout.activity_search
+
+ override fun injectWith(injector: ScreenComponent) {
+ super.injectWith(injector)
+ injector.inject(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ configureToolbar(searchToolbar)
+ }
+
+ override fun initUiAndData() {
+ if (isFirstCreation()) {
+ val fragmentArgs: SearchArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
+ addFragment(R.id.searchFragmentContainer, SearchFragment::class.java, fragmentArgs, FRAGMENT_TAG)
+ }
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String): Boolean {
+ searchFragment?.search(query)
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String): Boolean {
+ return true
+ }
+ })
+ // Open the keyboard immediately
+ searchView.requestFocus()
+ }
+
+ companion object {
+ private const val FRAGMENT_TAG = "SearchFragment"
+
+ fun newIntent(context: Context, args: SearchArgs): Intent {
+ return Intent(context, SearchActivity::class.java).apply {
+ // If we do that we will have the same room two times on the stack. Let's allow infinite stack for the moment.
+ // flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
+ putExtra(MvRx.KEY_ARG, args)
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
new file mode 100644
index 0000000000..10dc9254d8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.args
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.extensions.cleanup
+import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.extensions.hideKeyboard
+import im.vector.app.core.extensions.trackItemsVisibilityChange
+import im.vector.app.core.platform.StateView
+import im.vector.app.core.platform.VectorBaseFragment
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.fragment_search.*
+import org.matrix.android.sdk.api.session.events.model.Event
+import javax.inject.Inject
+
+@Parcelize
+data class SearchArgs(
+ val roomId: String
+) : Parcelable
+
+class SearchFragment @Inject constructor(
+ val viewModelFactory: SearchViewModel.Factory,
+ private val controller: SearchResultController
+) : VectorBaseFragment(), StateView.EventCallback, SearchResultController.Listener {
+
+ private val fragmentArgs: SearchArgs by args()
+ private val searchViewModel: SearchViewModel by fragmentViewModel()
+
+ private var pendingScrollToPosition: Int? = null
+
+ override fun getLayoutResId() = R.layout.fragment_search
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ stateView.contentView = searchResultRecycler
+ stateView.eventCallback = this
+
+ configureRecyclerView()
+ }
+
+ private fun configureRecyclerView() {
+ searchResultRecycler.trackItemsVisibilityChange()
+ searchResultRecycler.configureWith(controller, showDivider = false)
+ (searchResultRecycler.layoutManager as? LinearLayoutManager)?.stackFromEnd = true
+ controller.listener = this
+
+ controller.addModelBuildListener {
+ pendingScrollToPosition?.let {
+ searchResultRecycler.smoothScrollToPosition(it)
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ searchResultRecycler?.cleanup()
+ controller.listener = null
+ }
+
+ override fun invalidate() = withState(searchViewModel) { state ->
+ if (state.searchResult.isNullOrEmpty()) {
+ when (state.asyncSearchRequest) {
+ is Loading -> {
+ stateView.state = StateView.State.Loading
+ }
+ is Fail -> {
+ stateView.state = StateView.State.Error(errorFormatter.toHumanReadable(state.asyncSearchRequest.error))
+ }
+ is Success -> {
+ stateView.state = StateView.State.Empty(
+ title = getString(R.string.search_no_results),
+ image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results))
+ }
+ }
+ } else {
+ pendingScrollToPosition = (state.lastBatchSize - 1).coerceAtLeast(0)
+
+ stateView.state = StateView.State.Content
+ controller.setData(state)
+ }
+ }
+
+ fun search(query: String) {
+ view?.hideKeyboard()
+ searchViewModel.handle(SearchAction.SearchWith(query))
+ }
+
+ override fun onRetryClicked() {
+ searchViewModel.handle(SearchAction.Retry)
+ }
+
+ override fun onItemClicked(event: Event) {
+ event.roomId?.let {
+ navigator.openRoom(requireContext(), it, event.eventId)
+ }
+ }
+
+ override fun loadMore() {
+ searchViewModel.handle(SearchAction.LoadMore)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
new file mode 100644
index 0000000000..c917c4557d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import com.airbnb.epoxy.TypedEpoxyController
+import com.airbnb.epoxy.VisibilityState
+import im.vector.app.core.date.DateFormatKind
+import im.vector.app.core.date.VectorDateFormatter
+import im.vector.app.core.epoxy.loadingItem
+import im.vector.app.core.ui.list.genericItemHeader
+import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.search.EventAndSender
+import org.matrix.android.sdk.api.util.toMatrixItem
+import java.util.Calendar
+import javax.inject.Inject
+
+class SearchResultController @Inject constructor(
+ private val session: Session,
+ private val avatarRenderer: AvatarRenderer,
+ private val dateFormatter: VectorDateFormatter
+) : TypedEpoxyController() {
+
+ var listener: Listener? = null
+
+ private var idx = 0
+
+ interface Listener {
+ fun onItemClicked(event: Event)
+ fun loadMore()
+ }
+
+ init {
+ setData(null)
+ }
+
+ override fun buildModels(data: SearchViewState?) {
+ data ?: return
+
+ if (data.hasMoreResult) {
+ loadingItem {
+ // Always use a different id, because we can be notified several times of visibility state changed
+ id("loadMore${idx++}")
+ onVisibilityStateChanged { _, _, visibilityState ->
+ if (visibilityState == VisibilityState.VISIBLE) {
+ listener?.loadMore()
+ }
+ }
+ }
+ }
+
+ buildSearchResultItems(data.searchResult)
+ }
+
+ private fun buildSearchResultItems(events: List) {
+ var lastDate: Calendar? = null
+
+ events.forEach { eventAndSender ->
+ val eventDate = Calendar.getInstance().apply {
+ timeInMillis = eventAndSender.event.originServerTs ?: System.currentTimeMillis()
+ }
+ if (lastDate?.get(Calendar.DAY_OF_YEAR) != eventDate.get(Calendar.DAY_OF_YEAR)) {
+ genericItemHeader {
+ id(eventDate.hashCode())
+ text(dateFormatter.format(eventDate.timeInMillis, DateFormatKind.EDIT_HISTORY_HEADER))
+ }
+ }
+ lastDate = eventDate
+
+ searchResultItem {
+ id(eventAndSender.event.eventId)
+ avatarRenderer(avatarRenderer)
+ dateFormatter(dateFormatter)
+ event(eventAndSender.event)
+ sender(eventAndSender.sender
+ ?: eventAndSender.event.senderId?.let { session.getUser(it) }?.toMatrixItem())
+ listener { listener?.onItemClicked(eventAndSender.event) }
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt
new file mode 100644
index 0000000000..10407c64e0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import android.widget.ImageView
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.date.DateFormatKind
+import im.vector.app.core.date.VectorDateFormatter
+import im.vector.app.core.epoxy.ClickListener
+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.core.extensions.setTextOrHide
+import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.util.MatrixItem
+
+@EpoxyModelClass(layout = R.layout.item_search_result)
+abstract class SearchResultItem : VectorEpoxyModel() {
+
+ @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
+ @EpoxyAttribute var dateFormatter: VectorDateFormatter? = null
+ @EpoxyAttribute lateinit var event: Event
+ @EpoxyAttribute var sender: MatrixItem? = null
+ @EpoxyAttribute var listener: ClickListener? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+
+ holder.view.onClick(listener)
+ sender?.let { avatarRenderer.render(it, holder.avatarImageView) }
+ holder.memberNameView.setTextOrHide(sender?.getBestName())
+ holder.timeView.text = dateFormatter?.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
+ // TODO Improve that (use formattedBody, etc.)
+ holder.contentView.text = event.content?.get("body") as? String
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val avatarImageView by bind(R.id.messageAvatarImageView)
+ val memberNameView by bind(R.id.messageMemberNameView)
+ val timeView by bind(R.id.messageTimeView)
+ val contentView by bind(R.id.messageContentView)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewEvents.kt
new file mode 100644
index 0000000000..6f07cb765c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewEvents.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class SearchViewEvents : VectorViewEvents {
+ data class Failure(val throwable: Throwable) : SearchViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
new file mode 100644
index 0000000000..f61bcbd029
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.platform.VectorViewModel
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.search.SearchResult
+import org.matrix.android.sdk.api.util.Cancelable
+import org.matrix.android.sdk.internal.util.awaitCallback
+
+class SearchViewModel @AssistedInject constructor(
+ @Assisted private val initialState: SearchViewState,
+ session: Session
+) : VectorViewModel(initialState) {
+
+ private var room: Room? = session.getRoom(initialState.roomId)
+
+ private var currentTask: Cancelable? = null
+
+ private var nextBatch: String? = null
+
+ @AssistedInject.Factory
+ interface Factory {
+ fun create(initialState: SearchViewState): SearchViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ @JvmStatic
+ override fun create(viewModelContext: ViewModelContext, state: SearchViewState): SearchViewModel? {
+ val fragment: SearchFragment = (viewModelContext as FragmentViewModelContext).fragment()
+ return fragment.viewModelFactory.create(state)
+ }
+ }
+
+ override fun handle(action: SearchAction) {
+ when (action) {
+ is SearchAction.SearchWith -> handleSearchWith(action)
+ is SearchAction.LoadMore -> handleLoadMore()
+ is SearchAction.Retry -> handleRetry()
+ }.exhaustive
+ }
+
+ private fun handleSearchWith(action: SearchAction.SearchWith) {
+ if (action.searchTerm.isNotEmpty()) {
+ setState {
+ copy(
+ searchResult = emptyList(),
+ hasMoreResult = false,
+ lastBatchSize = 0,
+ searchTerm = action.searchTerm
+ )
+ }
+ startSearching(false)
+ }
+ }
+
+ private fun handleLoadMore() {
+ startSearching(true)
+ }
+
+ private fun handleRetry() {
+ startSearching(false)
+ }
+
+ private fun startSearching(isNextBatch: Boolean) = withState { state ->
+ if (state.searchTerm == null) return@withState
+
+ // There is no batch to retrieve
+ if (isNextBatch && nextBatch == null) return@withState
+
+ // Show full screen loading just for the clean search
+ if (!isNextBatch) {
+ setState {
+ copy(
+ asyncSearchRequest = Loading()
+ )
+ }
+ }
+
+ currentTask?.cancel()
+
+ viewModelScope.launch {
+ try {
+ val result = awaitCallback {
+ currentTask = room?.search(
+ searchTerm = state.searchTerm,
+ nextBatch = nextBatch,
+ orderByRecent = true,
+ beforeLimit = 0,
+ afterLimit = 0,
+ includeProfile = true,
+ limit = 20,
+ callback = it
+ )
+ }
+ onSearchResultSuccess(result)
+ } catch (failure: Throwable) {
+ if (failure is Failure.Cancelled) return@launch
+
+ _viewEvents.post(SearchViewEvents.Failure(failure))
+ setState {
+ copy(
+ asyncSearchRequest = Fail(failure)
+ )
+ }
+ }
+ }
+ }
+
+ private fun onSearchResultSuccess(searchResult: SearchResult) = withState { state ->
+ val accumulatedResult = searchResult.results.orEmpty().plus(state.searchResult)
+
+ // Note: We do not care about the highlights for the moment, but it will be the same algorithm
+
+ nextBatch = searchResult.nextBatch
+
+ setState {
+ copy(
+ searchResult = accumulatedResult,
+ hasMoreResult = !nextBatch.isNullOrEmpty(),
+ lastBatchSize = searchResult.results.orEmpty().size,
+ asyncSearchRequest = Success(Unit)
+ )
+ }
+ }
+
+ override fun onCleared() {
+ currentTask?.cancel()
+ super.onCleared()
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt
new file mode 100644
index 0000000000..9f700b6e31
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.search
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+import org.matrix.android.sdk.api.session.search.EventAndSender
+
+data class SearchViewState(
+ // Accumulated search result
+ val searchResult: List = emptyList(),
+ val hasMoreResult: Boolean = false,
+ // Last batch size, will help RecyclerView to position itself
+ val lastBatchSize: Int = 0,
+ val searchTerm: String? = null,
+ val roomId: String = "",
+ // Current pagination request
+ val asyncSearchRequest: Async = Uninitialized
+) : MvRxState {
+
+ constructor(args: SearchArgs) : this(roomId = args.roomId)
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/sticker/StickerPickerActionHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/sticker/StickerPickerActionHandler.kt
index 9cdc9eeabc..d24b41ffb0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/sticker/StickerPickerActionHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/sticker/StickerPickerActionHandler.kt
@@ -17,10 +17,10 @@
package im.vector.app.features.home.room.detail.sticker
import im.vector.app.features.home.room.detail.RoomDetailViewEvents
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import javax.inject.Inject
class StickerPickerActionHandler @Inject constructor(private val session: Session) {
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 be59128c26..bddc7fa126 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
@@ -50,18 +50,28 @@ import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_
import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer
+import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import javax.inject.Inject
+private const val DEFAULT_PREFETCH_THRESHOLD = 30
+
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
+ private val vectorPreferences: VectorPreferences,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
private val timelineItemFactory: TimelineItemFactory,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val mergedHeaderItemFactory: MergedHeaderItemFactory,
+ private val session: Session,
@TimelineEventControllerHandler
private val backgroundHandler: Handler
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
@@ -113,9 +123,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
private val modelCache = arrayListOf()
private var currentSnapshot: List = emptyList()
private var inSubmitList: Boolean = false
+ private var hasReachedInvite: Boolean = false
+ private var hasUTD: Boolean = false
private var unreadState: UnreadState = UnreadState.Unknown
private var positionOfReadMarker: Int? = null
private var eventIdToHighlight: String? = null
+ private var previousModelsSize = 0
var callback: Callback? = null
var timeline: Timeline? = null
@@ -191,6 +204,29 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
models.add(position, readMarker)
}
}
+ val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false
+ if (shouldAddBackwardPrefetch) {
+ val indexOfPrefetchBackward = (previousModelsSize - 1)
+ .coerceAtMost(models.size - DEFAULT_PREFETCH_THRESHOLD)
+ .coerceAtLeast(0)
+
+ val loadingItem = LoadingItem_()
+ .id("prefetch_backward_loading${System.currentTimeMillis()}")
+ .showLoader(false)
+ .setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS)
+
+ models.add(indexOfPrefetchBackward, loadingItem)
+ }
+ val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false
+ if (shouldAddForwardPrefetch) {
+ val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(models.size - 1)
+ val loadingItem = LoadingItem_()
+ .id("prefetch_forward_loading${System.currentTimeMillis()}")
+ .showLoader(false)
+ .setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
+ models.add(indexOfPrefetchForward, loadingItem)
+ }
+ previousModelsSize = models.size
}
fun update(viewState: RoomDetailViewState) {
@@ -241,7 +277,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val timelineModels = getModels()
add(timelineModels)
-
+ if (hasReachedInvite && hasUTD) {
+ return
+ }
// Avoid displaying two loaders if there is no elements between them
val showBackwardsLoader = !showingForwardLoader || timelineModels.isNotEmpty()
// We can hide the loader but still add the item to controller so it can trigger backwards pagination
@@ -301,6 +339,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
}
private fun buildCacheItemsIfNeeded() = synchronized(modelCache) {
+ hasUTD = false
+ hasReachedInvite = false
+
if (modelCache.isEmpty()) {
return
}
@@ -316,13 +357,21 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
private fun buildCacheItem(currentPosition: Int, items: List): CacheItemData {
val event = items[currentPosition]
val nextEvent = items.nextOrNull(currentPosition)
- val date = event.root.localDateTime()
- val nextDate = nextEvent?.root?.localDateTime()
- val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
+ if (hasReachedInvite && hasUTD) {
+ return CacheItemData(event.localId, event.root.eventId, null, null, null)
+ }
+ updateUTDStates(event, nextEvent)
val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, callback).also {
it.id(event.localId)
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
}
+ val addDaySeparator = if (hasReachedInvite && hasUTD) {
+ true
+ } else {
+ val date = event.root.localDateTime()
+ val nextDate = nextEvent?.root?.localDateTime()
+ date.toLocalDate() != nextDate?.toLocalDate()
+ }
val mergedHeaderModel = mergedHeaderItemFactory.create(event,
nextEvent = nextEvent,
items = items,
@@ -346,6 +395,27 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
}
}
+ private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) {
+ if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) {
+ return
+ }
+ if (event.root.type == EventType.STATE_ROOM_MEMBER
+ && event.root.stateKey == session.myUserId) {
+ val content = event.root.content.toModel()
+ if (content?.membership == Membership.INVITE) {
+ hasReachedInvite = true
+ } else if (content?.membership == Membership.JOIN) {
+ val prevContent = event.root.resolvedPrevContent().toModel()
+ if (prevContent?.membership?.isActive() == false) {
+ hasReachedInvite = true
+ }
+ }
+ }
+ if (nextEvent?.root?.getClearType() == EventType.ENCRYPTED) {
+ hasUTD = true
+ }
+ }
+
/**
* Return true if added
*/
@@ -355,9 +425,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
return shouldAdd
}
- /**
- * Return true if added
- */
private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt
index 7693d97c35..c21d552409 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt
@@ -21,6 +21,7 @@ import androidx.annotation.StringRes
import im.vector.app.R
import im.vector.app.core.platform.VectorSharedAction
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
sealed class EventSharedAction(@StringRes val titleRes: Int,
@@ -47,7 +48,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
data class Reply(val eventId: String) :
EventSharedAction(R.string.reply, R.drawable.ic_reply)
- data class Share(val eventId: String, val messageContent: MessageWithAttachmentContent) :
+ data class Share(val eventId: String, val messageContent: MessageContent) :
EventSharedAction(R.string.share, R.drawable.ic_share)
data class Save(val eventId: String, val messageContent: MessageWithAttachmentContent) :
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
index 9858d2a03c..f337f0ba5f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.action
import android.os.Bundle
+import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.fragmentViewModel
@@ -51,8 +52,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false, disableItemAnimation = true)
messageActionsEpoxyController.listener = this
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index a49b74c243..0d1e2261cd 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -190,7 +190,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
EventType.CALL_CANDIDATES,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> {
- noticeEventFormatter.format(timelineEvent)
+ noticeEventFormatter.format(timelineEvent, room?.roomSummary())
}
else -> null
} ?: ""
@@ -275,8 +275,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
add(EventSharedAction.ViewEditHistory(informationData))
}
- if (canShare(msgType) && messageContent is MessageWithAttachmentContent) {
- add(EventSharedAction.Share(timelineEvent.eventId, messageContent))
+ if (canShare(msgType)) {
+ add(EventSharedAction.Share(timelineEvent.eventId, messageContent!!))
}
if (canSave(msgType) && messageContent is MessageWithAttachmentContent) {
@@ -409,6 +409,10 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun canShare(msgType: String?): Boolean {
return when (msgType) {
+ MessageType.MSGTYPE_TEXT,
+ MessageType.MSGTYPE_NOTICE,
+ MessageType.MSGTYPE_EMOTE,
+ MessageType.MSGTYPE_LOCATION,
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_VIDEO,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt
index cff9a8c5e2..b6023333b1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageSharedActionViewModel.kt
@@ -22,5 +22,5 @@ import javax.inject.Inject
* Activity shared view model to handle message actions
*/
class MessageSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() {
- var pendingAction : EventSharedAction? = null
+ var pendingAction: EventSharedAction? = null
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt
index 93fdd253d8..080ccaea7c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.edithistory
import android.os.Bundle
+import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
@@ -55,8 +56,8 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(
epoxyController,
showDivider = true,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
index 42d5596300..f77e39c245 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
@@ -28,13 +28,13 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttrib
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
import im.vector.app.features.settings.VectorPreferences
+import me.gujun.android.span.image
+import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
-import me.gujun.android.span.image
-import me.gujun.android.span.span
import javax.inject.Inject
// This class handles timeline events who haven't been successfully decrypted
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
index 5374b4792a..1eb09f2e7a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
@@ -25,6 +25,8 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@@ -36,11 +38,15 @@ class EncryptionItemFactory @Inject constructor(
private val messageColorProvider: MessageColorProvider,
private val stringProvider: StringProvider,
private val informationDataFactory: MessageInformationDataFactory,
- private val avatarSizeProvider: AvatarSizeProvider) {
+ private val avatarSizeProvider: AvatarSizeProvider,
+ private val session: Session) {
fun create(event: TimelineEvent,
highlight: Boolean,
callback: TimelineEventController.Callback?): StatusTileTimelineItem? {
+ if (!event.root.isStateEvent()) {
+ return null
+ }
val algorithm = event.root.getClearContent().toModel()?.algorithm
val informationData = informationDataFactory.create(event, null)
val attributes = messageItemAttributesFactory.create(null, informationData, callback)
@@ -51,7 +57,13 @@ class EncryptionItemFactory @Inject constructor(
val shield: StatusTileTimelineItem.ShieldUIState
if (isSafeAlgorithm) {
title = stringProvider.getString(R.string.encryption_enabled)
- description = stringProvider.getString(R.string.encryption_enabled_tile_description)
+ description = stringProvider.getString(
+ if (session.getRoomSummary(event.root.roomId ?: "")?.isDirect.orFalse()) {
+ R.string.direct_room_encryption_enabled_tile_description
+ } else {
+ R.string.encryption_enabled_tile_description
+ }
+ )
shield = StatusTileTimelineItem.ShieldUIState.BLACK
} else {
title = stringProvider.getString(R.string.encryption_not_enabled)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 0ba3b4d47e..e7a911ceb1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -22,6 +22,7 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.app.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener
+import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged
import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration
import im.vector.app.features.home.room.detail.timeline.helper.prevSameTypeEvents
@@ -30,22 +31,19 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEve
import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_
-import im.vector.app.features.home.room.detail.timeline.item.MergedUTDItem
-import im.vector.app.features.home.room.detail.timeline.item.MergedUTDItem_
-import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
-import timber.log.Timber
import javax.inject.Inject
class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val avatarRenderer: AvatarRenderer,
private val avatarSizeProvider: AvatarSizeProvider,
- private val vectorPreferences: VectorPreferences) {
+ private val roomSummaryHolder: RoomSummaryHolder) {
private val collapsedEventIds = linkedSetOf()
private val mergeItemCollapseStates = HashMap()
@@ -63,10 +61,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
callback: TimelineEventController.Callback?,
requestModelBuild: () -> Unit)
: BasedMergedItem<*>? {
- return if (shouldMergedAsCannotDecryptGroup(event, nextEvent)) {
- Timber.v("## MERGE: Candidate for merge, top event ${event.eventId}")
- buildUTDMergedSummary(currentPosition, items, event, eventIdToHighlight, /*requestModelBuild,*/ callback)
- } else if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE
+ return if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE
&& event.isRoomConfiguration(nextEvent.root.getClearContent()?.toModel()?.creator)) {
// It's the first item before room.create
// Collapse all room configuration events
@@ -78,6 +73,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
}
}
+ private fun isDirectRoom() = roomSummaryHolder.roomSummary?.isDirect.orFalse()
+
private fun buildMembershipEventsMergedSummary(currentPosition: Int,
items: List,
event: TimelineEvent,
@@ -100,7 +97,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
avatarUrl = mergedEvent.senderInfo.avatarUrl,
memberName = mergedEvent.senderInfo.disambiguatedDisplayName,
localId = mergedEvent.localId,
- eventId = mergedEvent.root.eventId ?: ""
+ eventId = mergedEvent.root.eventId ?: "",
+ isDirectRoom = isDirectRoom()
)
mergedData.add(data)
}
@@ -138,82 +136,6 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
}
}
- // Event should be UTD
- // Next event should not
- private fun shouldMergedAsCannotDecryptGroup(event: TimelineEvent, nextEvent: TimelineEvent?): Boolean {
- if (!vectorPreferences.mergeUTDinTimeline()) return false
- // if event is not UTD return false
- if (!isEventUTD(event)) return false
- // At this point event cannot be decrypted
- // Let's check if older event is not UTD
- return nextEvent == null || !isEventUTD(event)
- }
-
- private fun isEventUTD(event: TimelineEvent): Boolean {
- return event.root.getClearType() == EventType.ENCRYPTED && !event.root.isRedacted()
- }
-
- private fun buildUTDMergedSummary(currentPosition: Int,
- items: List,
- event: TimelineEvent,
- eventIdToHighlight: String?,
- // requestModelBuild: () -> Unit,
- callback: TimelineEventController.Callback?): MergedUTDItem_? {
- Timber.v("## MERGE: buildUTDMergedSummary from position $currentPosition")
- var prevEvent = items.prevOrNull(currentPosition)
- var tmpPos = currentPosition - 1
- val mergedEvents = ArrayList().also { it.add(event) }
-
- while (prevEvent != null && isEventUTD(prevEvent)) {
- mergedEvents.add(prevEvent)
- tmpPos--
- prevEvent = if (tmpPos >= 0) items[tmpPos] else null
- }
-
- Timber.v("## MERGE: buildUTDMergedSummary merge group size ${mergedEvents.size}")
- if (mergedEvents.size < 3) return null
-
- var highlighted = false
- val mergedData = ArrayList(mergedEvents.size)
- mergedEvents.reversed()
- .forEach { mergedEvent ->
- if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) {
- highlighted = true
- }
- val senderAvatar = mergedEvent.senderInfo.avatarUrl
- val senderName = mergedEvent.senderInfo.disambiguatedDisplayName
- val data = BasedMergedItem.Data(
- userId = mergedEvent.root.senderId ?: "",
- avatarUrl = senderAvatar,
- memberName = senderName,
- localId = mergedEvent.localId,
- eventId = mergedEvent.root.eventId ?: ""
- )
- mergedData.add(data)
- }
- val mergedEventIds = mergedEvents.map { it.localId }
-
- collapsedEventIds.addAll(mergedEventIds)
-
- val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
-
- val attributes = MergedUTDItem.Attributes(
- isCollapsed = true,
- mergeData = mergedData,
- avatarRenderer = avatarRenderer,
- onCollapsedStateChanged = {}
- )
- return MergedUTDItem_()
- .id(mergeId)
- .big(mergedEventIds.size > 5)
- .leftGuideline(avatarSizeProvider.leftGuideline)
- .highlighted(highlighted)
- .attributes(attributes)
- .also {
- it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
- }
- }
-
private fun buildRoomCreationMergedSummary(currentPosition: Int,
items: List,
event: TimelineEvent,
@@ -226,7 +148,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
var hasEncryption = false
var encryptionAlgorithm: String? = null
while (prevEvent != null && prevEvent.isRoomConfiguration(null)) {
- if (prevEvent.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION) {
+ if (prevEvent.root.isStateEvent() && prevEvent.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION) {
hasEncryption = true
encryptionAlgorithm = prevEvent.root.getClearContent()?.toModel()?.algorithm
}
@@ -247,7 +169,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
avatarUrl = mergedEvent.senderInfo.avatarUrl,
memberName = mergedEvent.senderInfo.disambiguatedDisplayName,
localId = mergedEvent.localId,
- eventId = mergedEvent.root.eventId ?: ""
+ eventId = mergedEvent.root.eventId ?: "",
+ isDirectRoom = isDirectRoom()
)
mergedData.add(data)
}
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 97bc693f26..dd7a87cce6 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
@@ -38,6 +38,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadSt
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
+import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
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.MessageBlockCodeItem
@@ -62,6 +63,8 @@ import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.VectorHtmlCompressor
import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer
+import me.gujun.android.span.span
+import org.commonmark.node.Document
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -85,8 +88,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
-import me.gujun.android.span.span
-import org.commonmark.node.Document
import javax.inject.Inject
class MessageItemFactory @Inject constructor(
@@ -101,6 +102,7 @@ class MessageItemFactory @Inject constructor(
private val messageItemAttributesFactory: MessageItemAttributesFactory,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
+ private val roomSummaryHolder: RoomSummaryHolder,
private val defaultItemFactory: DefaultItemFactory,
private val noticeItemFactory: NoticeItemFactory,
private val avatarSizeProvider: AvatarSizeProvider,
@@ -130,7 +132,7 @@ class MessageItemFactory @Inject constructor(
|| event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE
) {
// This is an edit event, we should display it when debugging as a notice event
- return noticeItemFactory.create(event, highlight, callback)
+ return noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
}
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback)
@@ -146,7 +148,7 @@ class MessageItemFactory @Inject constructor(
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes)
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes)
- is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback)
+ is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt
index cd8c682f39..ec065543f5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt
@@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvide
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.app.features.home.room.detail.timeline.item.NoticeItem
import im.vector.app.features.home.room.detail.timeline.item.NoticeItem_
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import javax.inject.Inject
@@ -34,8 +35,9 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
fun create(event: TimelineEvent,
highlight: Boolean,
+ roomSummary: RoomSummary?,
callback: TimelineEventController.Callback?): NoticeItem? {
- val formattedText = eventFormatter.format(event) ?: return null
+ val formattedText = eventFormatter.format(event, roomSummary) ?: return null
val informationData = informationDataFactory.create(event, null)
val attributes = NoticeItem.Attributes(
avatarRenderer = avatarRenderer,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt
index 31adbdb8a6..25b5fd718b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt
@@ -21,6 +21,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
import im.vector.app.features.home.room.detail.timeline.item.RoomCreateItem_
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.Session
@@ -32,6 +33,7 @@ import javax.inject.Inject
class RoomCreateItemFactory @Inject constructor(private val stringProvider: StringProvider,
private val userPreferencesProvider: UserPreferencesProvider,
private val session: Session,
+ private val roomSummaryHolder: RoomSummaryHolder,
private val noticeItemFactory: NoticeItemFactory) {
fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? {
@@ -52,7 +54,7 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri
private fun defaultRendering(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? {
return if (userPreferencesProvider.shouldShowHiddenEvents()) {
- noticeItemFactory.create(event, false, callback)
+ noticeItemFactory.create(event, false, roomSummaryHolder.roomSummary, callback)
} else {
null
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index 1f99efd08e..1a4db3bdfc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -20,6 +20,7 @@ import im.vector.app.core.epoxy.EmptyItem_
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import timber.log.Timber
@@ -31,6 +32,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
private val defaultItemFactory: DefaultItemFactory,
private val encryptionItemFactory: EncryptionItemFactory,
private val roomCreateItemFactory: RoomCreateItemFactory,
+ private val roomSummaryHolder: RoomSummaryHolder,
private val verificationConclusionItemFactory: VerificationItemFactory,
private val userPreferencesProvider: UserPreferencesProvider) {
@@ -43,7 +45,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
val computedModel = try {
when (event.root.getClearType()) {
EventType.STICKER,
- EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, callback)
+ EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, callback)
// State and call
EventType.STATE_ROOM_TOMBSTONE,
EventType.STATE_ROOM_NAME,
@@ -63,14 +65,12 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.CALL_ANSWER,
EventType.STATE_ROOM_POWER_LEVELS,
EventType.REACTION,
- EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback)
- EventType.STATE_ROOM_ENCRYPTION -> {
- encryptionItemFactory.create(event, highlight, callback)
- }
+ EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
+ EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback)
// State room create
- EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
+ EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
// Crypto
- EventType.ENCRYPTED -> {
+ EventType.ENCRYPTED -> {
if (event.root.isRedacted()) {
// Redacted event, let the MessageItemFactory handle it
messageItemFactory.create(event, nextEvent, highlight, callback)
@@ -83,22 +83,22 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_MAC,
- EventType.CALL_CANDIDATES -> {
+ EventType.CALL_CANDIDATES -> {
// TODO These are not filtered out by timeline when encrypted
// For now manually ignore
if (userPreferencesProvider.shouldShowHiddenEvents()) {
- noticeItemFactory.create(event, highlight, callback)
+ noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
} else {
null
}
}
EventType.KEY_VERIFICATION_CANCEL,
- EventType.KEY_VERIFICATION_DONE -> {
+ EventType.KEY_VERIFICATION_DONE -> {
verificationConclusionItemFactory.create(event, highlight, callback)
}
// Unhandled event types
- else -> {
+ else -> {
// Should only happen when shouldShowHiddenEvents() settings is ON
Timber.v("Type ${event.root.getClearType()} not handled")
defaultItemFactory.create(event, highlight, callback)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
index 0b623d78f1..59daf5a0a0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
@@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
+import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_
import org.matrix.android.sdk.api.session.Session
@@ -50,6 +51,7 @@ class VerificationItemFactory @Inject constructor(
private val avatarSizeProvider: AvatarSizeProvider,
private val noticeItemFactory: NoticeItemFactory,
private val userPreferencesProvider: UserPreferencesProvider,
+ private val roomSummaryHolder: RoomSummaryHolder,
private val stringProvider: StringProvider,
private val session: Session
) {
@@ -151,7 +153,7 @@ class VerificationItemFactory @Inject constructor(
highlight: Boolean,
callback: TimelineEventController.Callback?
): VectorEpoxyModel<*>? {
- if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback)
+ if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
return null
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
index cb9f583a2a..f4632b0e10 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
@@ -23,6 +23,7 @@ import im.vector.app.core.resources.StringProvider
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS
@@ -40,7 +41,7 @@ class DisplayableEventFormatter @Inject constructor(
private val noticeEventFormatter: NoticeEventFormatter
) {
- fun format(timelineEvent: TimelineEvent, appendAuthor: Boolean): CharSequence {
+ fun format(timelineEvent: TimelineEvent, appendAuthor: Boolean, roomSummary: RoomSummary?): CharSequence {
if (timelineEvent.root.isRedacted()) {
return noticeEventFormatter.formatRedactedEvent(timelineEvent.root)
}
@@ -53,16 +54,16 @@ class DisplayableEventFormatter @Inject constructor(
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
when (timelineEvent.root.getClearType()) {
- EventType.STICKER -> {
+ EventType.STICKER -> {
return simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
}
- EventType.REACTION -> {
+ EventType.REACTION -> {
timelineEvent.root.getClearContent().toModel()?.relatesTo?.let {
val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
return simpleFormat(senderName, emojiSpanned, appendAuthor)
}
}
- EventType.MESSAGE -> {
+ EventType.MESSAGE -> {
timelineEvent.getLastMessageContent()?.let { messageContent ->
when (messageContent.msgType) {
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
@@ -125,12 +126,12 @@ class DisplayableEventFormatter @Inject constructor(
EventType.KEY_VERIFICATION_MAC,
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_READY,
- EventType.CALL_CANDIDATES -> {
+ EventType.CALL_CANDIDATES -> {
return span { }
}
- else -> {
+ else -> {
return span {
- text = noticeEventFormatter.format(timelineEvent) ?: ""
+ text = noticeEventFormatter.format(timelineEvent, roomSummary) ?: ""
textStyle = "italic"
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
index 9a4729abee..8055ef9a99 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
@@ -56,23 +57,26 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId
- fun format(timelineEvent: TimelineEvent): CharSequence? {
+ private fun RoomSummary?.isDm() = this?.isDirect.orFalse()
+
+ fun format(timelineEvent: TimelineEvent, rs: RoomSummary?): CharSequence? {
return when (val type = timelineEvent.root.getClearType()) {
- EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
- EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root)
+ EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
+ EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root, rs)
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
- EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
- EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
+ EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
+ EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
- EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
- EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
+ EventType.STATE_ROOM_HISTORY_VISIBILITY ->
+ formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
+ EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.STATE_ROOM_WIDGET,
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
- EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
+ EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
EventType.CALL_INVITE,
EventType.CALL_CANDIDATES,
@@ -151,19 +155,19 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
- fun format(event: Event, senderName: String?): CharSequence? {
+ fun format(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
return when (val type = event.getClearType()) {
- EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName)
+ EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName, rs)
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName)
- EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName)
- EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName)
- EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName)
+ EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName, rs)
+ EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName, rs)
+ EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName, rs)
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
- EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName)
+ EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, rs)
else -> {
Timber.v("Type $type not handled by this formatter")
null
@@ -175,14 +179,14 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
return "{ \"type\": ${event.getClearType()} }"
}
- private fun formatRoomCreateEvent(event: Event): CharSequence? {
+ private fun formatRoomCreateEvent(event: Event, rs: RoomSummary?): CharSequence? {
return event.getClearContent().toModel()
?.takeIf { it.creator.isNullOrBlank().not() }
?.let {
if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_room_created_by_you)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_created_by_you else R.string.notice_room_created_by_you)
} else {
- sp.getString(R.string.notice_room_created, it.creator)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_created else R.string.notice_room_created, it.creator)
}
}
}
@@ -204,11 +208,11 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
- private fun formatRoomTombstoneEvent(event: Event, senderName: String?): CharSequence? {
+ private fun formatRoomTombstoneEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
return if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_room_update_by_you)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_update_by_you else R.string.notice_room_update_by_you)
} else {
- sp.getString(R.string.notice_room_update, senderName)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_update else R.string.notice_room_update, senderName)
}
}
@@ -246,18 +250,20 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
- private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
+ private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null
val formattedVisibility = roomHistoryVisibilityFormatter.format(historyVisibility)
return if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_made_future_room_visibility_by_you, formattedVisibility)
+ sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you,
+ formattedVisibility)
} else {
- sp.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility)
+ sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility,
+ senderName, formattedVisibility)
}
}
- private fun formatRoomThirdPartyInvite(event: Event, senderName: String?): CharSequence? {
+ private fun formatRoomThirdPartyInvite(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
val content = event.getClearContent().toModel()
val prevContent = event.resolvedPrevContent()?.toModel()
@@ -265,17 +271,26 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
prevContent != null -> {
// Revoke case
if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_room_third_party_revoked_invite_by_you, prevContent.displayName)
+ sp.getString(
+ if (rs.isDm()) {
+ R.string.notice_direct_room_third_party_revoked_invite_by_you
+ } else {
+ R.string.notice_room_third_party_revoked_invite_by_you
+ },
+ prevContent.displayName)
} else {
- sp.getString(R.string.notice_room_third_party_revoked_invite, senderName, prevContent.displayName)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_third_party_revoked_invite else R.string.notice_room_third_party_revoked_invite,
+ senderName, prevContent.displayName)
}
}
content != null -> {
// Invitation case
if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_room_third_party_invite_by_you, content.displayName)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_third_party_invite_by_you else R.string.notice_room_third_party_invite_by_you,
+ content.displayName)
} else {
- sp.getString(R.string.notice_room_third_party_invite, senderName, content.displayName)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_third_party_invite else R.string.notice_room_third_party_invite,
+ senderName, content.displayName)
}
}
else -> null
@@ -323,13 +338,13 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
- private fun formatRoomMemberEvent(event: Event, senderName: String?): String? {
+ private fun formatRoomMemberEvent(event: Event, senderName: String?, rs: RoomSummary?): String? {
val eventContent: RoomMemberContent? = event.getClearContent().toModel()
val prevEventContent: RoomMemberContent? = event.resolvedPrevContent().toModel()
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
|| eventContent?.membership == Membership.LEAVE
return if (isMembershipEvent) {
- buildMembershipNotice(event, senderName, eventContent, prevEventContent)
+ buildMembershipNotice(event, senderName, eventContent, prevEventContent, rs)
} else {
buildProfileNotice(event, senderName, eventContent, prevEventContent)
}
@@ -387,26 +402,35 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
- private fun formatRoomGuestAccessEvent(event: Event, senderName: String?): String? {
+ private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, rs: RoomSummary?): String? {
val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel()
return when (eventContent?.guestAccess) {
GuestAccess.CanJoin ->
if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_room_guest_access_can_join_by_you)
+ sp.getString(
+ if (rs.isDm()) R.string.notice_direct_room_guest_access_can_join_by_you else R.string.notice_room_guest_access_can_join_by_you
+ )
} else {
- sp.getString(R.string.notice_room_guest_access_can_join, senderName)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_guest_access_can_join else R.string.notice_room_guest_access_can_join,
+ senderName)
}
GuestAccess.Forbidden ->
if (event.isSentByCurrentUser()) {
- sp.getString(R.string.notice_room_guest_access_forbidden_by_you)
+ sp.getString(
+ if (rs.isDm()) R.string.notice_direct_room_guest_access_forbidden_by_you else R.string.notice_room_guest_access_forbidden_by_you
+ )
} else {
- sp.getString(R.string.notice_room_guest_access_forbidden, senderName)
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_guest_access_forbidden else R.string.notice_room_guest_access_forbidden,
+ senderName)
}
else -> null
}
}
private fun formatRoomEncryptionEvent(event: Event, senderName: String?): CharSequence? {
+ if (!event.isStateEvent()) {
+ return null
+ }
val content = event.content.toModel() ?: return null
return when (content.algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM ->
@@ -476,7 +500,11 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
return displayText.toString()
}
- private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String? {
+ private fun buildMembershipNotice(event: Event,
+ senderName: String?,
+ eventContent: RoomMemberContent?,
+ prevEventContent: RoomMemberContent?,
+ rs: RoomSummary?): String? {
val senderDisplayName = senderName ?: event.senderId ?: ""
val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: ""
return when (eventContent?.membership) {
@@ -524,14 +552,21 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
Membership.JOIN ->
- if (event.isSentByCurrentUser()) {
- eventContent.safeReason?.let { reason ->
- sp.getString(R.string.notice_room_join_with_reason_by_you, reason)
- } ?: sp.getString(R.string.notice_room_join_by_you)
- } else {
- eventContent.safeReason?.let { reason ->
- sp.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason)
- } ?: sp.getString(R.string.notice_room_join, senderDisplayName)
+ eventContent.safeReason?.let { reason ->
+ if (event.isSentByCurrentUser()) {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_join_with_reason_by_you else R.string.notice_room_join_with_reason_by_you,
+ reason)
+ } else {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_join_with_reason else R.string.notice_room_join_with_reason,
+ senderDisplayName, reason)
+ }
+ } ?: run {
+ if (event.isSentByCurrentUser()) {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_join_by_you else R.string.notice_room_join_by_you)
+ } else {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_join else R.string.notice_room_join,
+ senderDisplayName)
+ }
}
Membership.LEAVE ->
// 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked
@@ -548,14 +583,27 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
} ?: sp.getString(R.string.notice_room_reject, senderDisplayName)
}
else ->
- if (event.isSentByCurrentUser()) {
- eventContent.safeReason?.let { reason ->
- sp.getString(R.string.notice_room_leave_with_reason_by_you, reason)
- } ?: sp.getString(R.string.notice_room_leave_by_you)
- } else {
- eventContent.safeReason?.let { reason ->
- sp.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason)
- } ?: sp.getString(R.string.notice_room_leave, senderDisplayName)
+ eventContent.safeReason?.let { reason ->
+ if (event.isSentByCurrentUser()) {
+ sp.getString(
+ if (rs.isDm()) {
+ R.string.notice_direct_room_leave_with_reason_by_you
+ } else {
+ R.string.notice_room_leave_with_reason_by_you
+ },
+ reason
+ )
+ } else {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_leave_with_reason else R.string.notice_room_leave_with_reason,
+ senderDisplayName, reason)
+ }
+ } ?: run {
+ if (event.isSentByCurrentUser()) {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_leave_by_you else R.string.notice_room_leave_by_you)
+ } else {
+ sp.getString(if (rs.isDm()) R.string.notice_direct_room_leave else R.string.notice_room_leave,
+ senderDisplayName)
+ }
}
}
} else {
@@ -618,14 +666,15 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
}
}
- private fun formatJoinRulesEvent(event: Event, senderName: String?): CharSequence? {
+ private fun formatJoinRulesEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
val content = event.getClearContent().toModel() ?: return null
return when (content.joinRules) {
RoomJoinRules.INVITE ->
if (event.isSentByCurrentUser()) {
- sp.getString(R.string.room_join_rules_invite_by_you)
+ sp.getString(if (rs.isDm()) R.string.direct_room_join_rules_invite_by_you else R.string.room_join_rules_invite_by_you)
} else {
- sp.getString(R.string.room_join_rules_invite, senderName)
+ sp.getString(if (rs.isDm()) R.string.direct_room_join_rules_invite else R.string.room_join_rules_invite,
+ senderName)
}
RoomJoinRules.PUBLIC ->
if (event.isSentByCurrentUser()) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
index b1b1109580..14b8c12fee 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
@@ -55,7 +55,7 @@ fun TimelineEvent.canBeMerged(): Boolean {
}
fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
- return when (root.getClearType()) {
+ return root.isStateEvent() && when (root.getClearType()) {
EventType.STATE_ROOM_GUEST_ACCESS,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
EventType.STATE_ROOM_JOIN_RULES,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 631cd9ff66..29aca2c4d5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -136,8 +136,10 @@ abstract class AbsBaseMessageItem : BaseEventItem
val messageColorProvider: MessageColorProvider
val itemLongClickListener: View.OnLongClickListener?
val itemClickListener: View.OnClickListener?
+
// val memberClickListener: View.OnClickListener?
val reactionPillCallback: TimelineEventController.ReactionPillCallback?
+
// val avatarCallback: TimelineEventController.AvatarCallback?
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback?
// val emojiTypeFace: Typeface?
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
index 3f921fdd15..280a148dae 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
@@ -43,6 +43,7 @@ abstract class BaseEventItem : VectorEpoxyModel
// To use for instance when opening a permalink with an eventId
@EpoxyAttribute
var highlighted: Boolean = false
+
@EpoxyAttribute
open var leftGuideline: Int = 0
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt
index d9dcc98ed1..1f8ad3df1b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt
@@ -62,7 +62,8 @@ abstract class BasedMergedItem : BaseEventItem()
val eventId: String,
val userId: String,
val memberName: String,
- val avatarUrl: String?
+ val avatarUrl: String?,
+ val isDirectRoom: Boolean
)
fun Data.toMatrixItem() = MatrixItem.UserItem(userId, memberName, avatarUrl)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
index dad5f99eeb..1896a812fc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
@@ -48,9 +48,17 @@ abstract class MergedRoomCreationItem : BasedMergedItem() {
-
- @EpoxyAttribute
- override lateinit var attributes: Attributes
-
- @EpoxyAttribute
- var big: Boolean? = false
-
- override fun getViewType() = STUB_ID
-
- override fun bind(holder: Holder) {
- super.bind(holder)
-
- holder.mergedTile.updateLayoutParams {
- this.marginEnd = leftGuideline
- if (big == true) {
- this.height = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- 800f,
- holder.view.context.resources.displayMetrics
- ).toInt()
- } else {
- this.height = LinearLayout.LayoutParams.WRAP_CONTENT
- }
- }
-
-// if (attributes.isCollapsed) {
-// // Take the oldest data
-// val data = distinctMergeData.lastOrNull()
-//
-// val summary = holder.expandView.resources.getString(R.string.room_created_summary_item,
-// data?.memberName ?: data?.userId ?: "")
-// holder.summaryView.text = summary
-// holder.summaryView.visibility = View.VISIBLE
-// holder.avatarView.visibility = View.VISIBLE
-// if (data != null) {
-// holder.avatarView.visibility = View.VISIBLE
-// attributes.avatarRenderer.render(data.toMatrixItem(), holder.avatarView)
-// } else {
-// holder.avatarView.visibility = View.GONE
-// }
-//
-// if (attributes.hasEncryptionEvent) {
-// holder.encryptionTile.isVisible = true
-// holder.encryptionTile.updateLayoutParams {
-// this.marginEnd = leftGuideline
-// }
-// if (attributes.isEncryptionAlgorithmSecure) {
-// holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled)
-// holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_enabled_tile_description)
-// holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER
-// holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
-// ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black),
-// null, null, null
-// )
-// } else {
-// holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled)
-// holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description)
-// holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds(
-// ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning),
-// null, null, null
-// )
-// }
-// } else {
-// holder.encryptionTile.isVisible = false
-// }
-// } else {
-// holder.avatarView.visibility = View.INVISIBLE
-// holder.summaryView.visibility = View.GONE
-// holder.encryptionTile.isGone = true
-// }
- // No read receipt for this item
- holder.readReceiptsView.isVisible = false
- }
-
- class Holder : BasedMergedItem.Holder(STUB_ID) {
- // val summaryView by bind(R.id.itemNoticeTextView)
-// val avatarView by bind(R.id.itemNoticeAvatarView)
- val mergedTile by bind(R.id.mergedUTDTile)
-//
-// val e2eTitleTextView by bind(R.id.itemVerificationDoneTitleTextView)
-// val e2eTitleDescriptionView by bind(R.id.itemVerificationDoneDetailTextView)
- }
-
- companion object {
- private const val STUB_ID = R.id.messageContentMergedUTDStub
- }
-
- data class Attributes(
- override val isCollapsed: Boolean,
- override val mergeData: List,
- override val avatarRenderer: AvatarRenderer,
- override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
- override val onCollapsedStateChanged: (Boolean) -> Unit
- ) : BasedMergedItem.Attributes
-}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
index 1309d4d084..45c00e1843 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageBlockCodeItem.kt
@@ -29,6 +29,7 @@ abstract class MessageBlockCodeItem : AbsMessageItem() {
}
} else {
holder.resultWrapper.isVisible = true
- val maxCount = votes?.maxBy { it.value }?.value ?: 0
+ val maxCount = votes?.maxByOrNull { it.value }?.value ?: 0
optionsContent?.options?.forEachIndexed { index, item ->
if (index < resultLines.size) {
val optionCount = votes?.get(index) ?: 0
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index ffe15e2745..5cd45547b1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -32,10 +32,13 @@ abstract class MessageTextItem : AbsMessageItem() {
@EpoxyAttribute
var searchForPills: Boolean = false
+
@EpoxyAttribute
var message: CharSequence? = null
+
@EpoxyAttribute
var useBigFont: Boolean = false
+
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var movementMethod: MovementMethod? = null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt
index 9fc5275a81..2d60556fa3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt
@@ -32,10 +32,13 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder Unit)? = null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt
index a5555e4ebb..2e0d07aa67 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt
@@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.timeline.reactions
import android.os.Bundle
+import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
@@ -55,8 +56,8 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment(), ViewReac
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(epoxyController, hasFixedSize = false, showDivider = true)
bottomSheetTitle.text = context?.getString(R.string.reactions)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt
index 691b6ec4ee..33a6f627a1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetItem.kt
@@ -36,6 +36,7 @@ abstract class RoomWidgetItem : EpoxyModelWithHolder() {
@EpoxyAttribute lateinit var widget: Widget
@EpoxyAttribute var widgetClicked: ClickListener? = null
+
@DrawableRes
@EpoxyAttribute var iconRes: Int? = null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBannerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBannerView.kt
index 19897a9300..7fe8b87fe0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBannerView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBannerView.kt
@@ -21,8 +21,8 @@ import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import im.vector.app.R
-import org.matrix.android.sdk.api.session.widgets.model.Widget
import kotlinx.android.synthetic.main.view_room_widgets_banner.view.*
+import org.matrix.android.sdk.api.session.widgets.model.Widget
class RoomWidgetsBannerView @JvmOverloads constructor(
context: Context,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
index e87f5cdaa9..923f9d8e2e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
@@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.widget
import android.os.Bundle
+import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.parentFragmentViewModel
@@ -31,8 +32,8 @@ import im.vector.app.features.home.room.detail.RoomDetailAction
import im.vector.app.features.home.room.detail.RoomDetailViewModel
import im.vector.app.features.home.room.detail.RoomDetailViewState
import im.vector.app.features.navigation.Navigator
-import org.matrix.android.sdk.api.session.widgets.model.Widget
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
+import org.matrix.android.sdk.api.session.widgets.model.Widget
import javax.inject.Inject
/**
@@ -55,8 +56,8 @@ class RoomWidgetsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidget
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(epoxyController, hasFixedSize = false)
bottomSheetTitle.text = getString(R.string.active_widgets_title)
bottomSheetTitle.textSize = 20f
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt
index f6b2a2e298..b5b3feb599 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt
@@ -17,9 +17,9 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
+import io.reactivex.functions.Predicate
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
-import io.reactivex.functions.Predicate
class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) : Predicate {
@@ -32,8 +32,8 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) :
RoomListDisplayMode.NOTIFICATIONS ->
roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty()
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive()
- RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive()
- RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
+ RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive()
+ RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
}
}
}
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 47f036e143..57102b8bf1 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
@@ -47,12 +47,12 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.FabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.fragment_room_list.*
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
-import kotlinx.android.parcel.Parcelize
-import kotlinx.android.synthetic.main.fragment_room_list.*
import javax.inject.Inject
@Parcelize
@@ -155,8 +155,8 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.ALL,
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
- RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
- else -> Unit // No button in this mode
+ RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
+ else -> Unit // No button in this mode
}
createChatRoomButton.debouncedClicks {
@@ -182,8 +182,8 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.ALL,
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide()
- RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
- else -> Unit
+ RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
+ else -> Unit
}
}
}
@@ -226,8 +226,8 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.ALL,
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.show()
- RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
- else -> Unit
+ RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
+ else -> Unit
}
}
}
@@ -326,19 +326,19 @@ class RoomListFragment @Inject constructor(
getString(R.string.room_list_catchup_empty_body))
}
}
- RoomListDisplayMode.PEOPLE ->
+ RoomListDisplayMode.PEOPLE ->
StateView.State.Empty(
getString(R.string.room_list_people_empty_title),
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_chat),
getString(R.string.room_list_people_empty_body)
)
- RoomListDisplayMode.ROOMS ->
+ RoomListDisplayMode.ROOMS ->
StateView.State.Empty(
getString(R.string.room_list_rooms_empty_title),
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_group),
getString(R.string.room_list_rooms_empty_body)
)
- else ->
+ else ->
// Always display the content in this mode, because if the footer
StateView.State.Content
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt
index 4c4a9755d2..274cf7c869 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt
@@ -16,8 +16,8 @@
package im.vector.app.features.home.room.list
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
import io.reactivex.functions.Predicate
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
class RoomListNameFilter @Inject constructor() : Predicate {
@@ -25,7 +25,7 @@ class RoomListNameFilter @Inject constructor() : Predicate {
var filter: String = ""
override fun test(roomSummary: RoomSummary): Boolean {
- if (filter.isBlank()) {
+ if (filter.isEmpty()) {
// No filter
return true
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
index 4e11d91a4b..cbe4509ed0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
@@ -24,6 +24,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.utils.DataSource
import im.vector.app.features.home.RoomListDisplayMode
+import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.extensions.orFalse
@@ -31,7 +32,6 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
-import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
index 304a592e7d..461374a85a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
@@ -43,8 +43,10 @@ abstract class RoomSummaryItem : VectorEpoxyModel() {
@EpoxyAttribute lateinit var typingMessage: CharSequence
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
+
// Used only for diff calculation
@EpoxyAttribute lateinit var lastEvent: String
+
// We use DoNotHash here as Spans are not implementing equals/hashcode
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var lastFormattedEvent: CharSequence
@EpoxyAttribute lateinit var lastEventTime: CharSequence
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
index a2715e86b4..98da91effa 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
@@ -88,7 +88,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
var latestEventTime: CharSequence = ""
val latestEvent = roomSummary.scLatestPreviewableEvent(scSdkPreferences)
if (latestEvent != null) {
- latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect.not())
+ latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect.not(), roomSummary)
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
}
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt
index c33d964d22..ccd38125f9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt
@@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list.actions
import android.os.Bundle
import android.os.Parcelable
+import android.view.View
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.fragmentViewModel
@@ -67,8 +68,8 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
roomListActionsEpoxyController.listener = this
diff --git a/vector/src/main/java/im/vector/app/features/html/FontTagHandler.kt b/vector/src/main/java/im/vector/app/features/html/FontTagHandler.kt
index 72e337b48f..6a925997b7 100644
--- a/vector/src/main/java/im/vector/app/features/html/FontTagHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/html/FontTagHandler.kt
@@ -16,8 +16,8 @@
package im.vector.app.features.html
import android.graphics.Color
-import android.text.style.ForegroundColorSpan
import android.text.style.BackgroundColorSpan
+import android.text.style.ForegroundColorSpan
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.RenderProps
import io.noties.markwon.html.HtmlTag
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
index b4f0aabea5..087b7c2f55 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
@@ -112,9 +112,10 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
+ doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewEvents.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewEvents.kt
index 786fab58c3..87b10f909f 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewEvents.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.invite
import im.vector.app.core.platform.VectorViewEvents
-sealed class InviteUsersToRoomViewEvents : VectorViewEvents {
+sealed class InviteUsersToRoomViewEvents : VectorViewEvents {
object Loading : InviteUsersToRoomViewEvents()
data class Failure(val throwable: Throwable) : InviteUsersToRoomViewEvents()
data class Success(val successMessage: String) : InviteUsersToRoomViewEvents()
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt
index 23325a9c41..21a998d8e2 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt
@@ -25,9 +25,9 @@ import im.vector.app.R
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.userdirectory.PendingInvitee
+import io.reactivex.Observable
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.rx.rx
-import io.reactivex.Observable
class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
initialState: InviteUsersToRoomViewState,
diff --git a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt
index 2de587e52b..881c446eb2 100644
--- a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt
@@ -25,10 +25,10 @@ import im.vector.app.R
import im.vector.app.core.di.HasScreenInjector
import im.vector.app.core.platform.ButtonStateView
import im.vector.app.features.home.AvatarRenderer
+import kotlinx.android.synthetic.main.vector_invite_view.view.*
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.toMatrixItem
-import kotlinx.android.synthetic.main.vector_invite_view.view.*
import javax.inject.Inject
class VectorInviteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
index 9067984852..eb5aa86b3b 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt
@@ -27,7 +27,7 @@ sealed class LoginAction : VectorViewModelAction {
data class UpdateSignMode(val signMode: SignMode) : LoginAction()
data class LoginWithToken(val loginToken: String) : LoginAction()
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
- data class InitWith(val loginConfig: LoginConfig) : LoginAction()
+ data class InitWith(val loginConfig: LoginConfig?) : LoginAction()
data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
object ResetPasswordMailConfirmed : LoginAction()
@@ -39,6 +39,7 @@ sealed class LoginAction : VectorViewModelAction {
data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction()
object SendAgainThreePid : RegisterAction()
+
// TODO Confirm Email (from link in the email, open in the phone, intercepted by RiotX)
data class ValidateThreePid(val code: String) : RegisterAction()
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index 2a692c2d53..01e835b4e3 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -91,19 +91,19 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
addFirstFragment()
}
- // Get config extra
- val loginConfig = intent.getParcelableExtra(EXTRA_CONFIG)
- if (loginConfig != null && isFirstCreation()) {
- // TODO Check this
- loginViewModel.handle(LoginAction.InitWith(loginConfig))
- }
-
loginViewModel
.subscribe(this) {
updateWithState(it)
}
loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
+
+ // Get config extra
+ val loginConfig = intent.getParcelableExtra(EXTRA_CONFIG)
+ if (isFirstCreation()) {
+ // TODO Check this
+ loginViewModel.handle(LoginAction.InitWith(loginConfig))
+ }
}
protected open fun addFirstFragment() {
@@ -146,8 +146,10 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
LoginServerSelectionFragment::class.java,
option = { ft ->
findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
- findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
- findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // Disable transition of text
+ // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // No transition here now actually
+ // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO Disabled because it provokes a flickering
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
})
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt
index f84c4b7022..6dd17a7d58 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt
@@ -33,9 +33,9 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import im.vector.app.R
import im.vector.app.core.utils.AssetReader
-import org.matrix.android.sdk.internal.di.MoshiProvider
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_login_captcha.*
+import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
import java.net.URLDecoder
import java.util.Formatter
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt
index 1b5502eb34..453575b91b 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt
@@ -32,11 +32,11 @@ import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.setTextOrHide
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.fragment_login_generic_text_input_form.*
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.is401
-import kotlinx.android.parcel.Parcelize
-import kotlinx.android.synthetic.main.fragment_login_generic_text_input_form.*
import javax.inject.Inject
enum class TextInputFormFragmentMode {
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt
index c12f10e191..577edf754b 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt
@@ -21,8 +21,8 @@ import butterknife.OnClick
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Success
import im.vector.app.R
-import org.matrix.android.sdk.api.failure.is401
import kotlinx.android.synthetic.main.fragment_login_reset_password_mail_confirmation.*
+import org.matrix.android.sdk.api.failure.is401
import javax.inject.Inject
/**
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
index 4b9f528254..af959fecd4 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt
@@ -28,6 +28,8 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.openUrlInChromeCustomTab
import kotlinx.android.synthetic.main.fragment_login_server_url_form.*
+import org.matrix.android.sdk.api.failure.Failure
+import java.net.UnknownHostException
import javax.inject.Inject
/**
@@ -115,7 +117,13 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
}
override fun onError(throwable: Throwable) {
- loginServerUrlFormHomeServerUrlTil.error = errorFormatter.toHumanReadable(throwable)
+ loginServerUrlFormHomeServerUrlTil.error = if (throwable is Failure.NetworkConnection
+ && throwable.ioException is UnknownHostException) {
+ // Invalid homeserver?
+ getString(R.string.login_error_homeserver_not_found)
+ } else {
+ errorFormatter.toHumanReadable(throwable)
+ }
}
override fun updateWithState(state: LoginViewState) {
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 f986227961..81d6a78123 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
@@ -417,6 +417,18 @@ class LoginViewModel @AssistedInject constructor(
private fun handleInitWith(action: LoginAction.InitWith) {
loginConfig = action.loginConfig
+
+ // If there is a pending email validation continue on this step
+ try {
+ if (registrationWizard?.isRegistrationStarted == true) {
+ currentThreePid?.let {
+ handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendEmailSuccess(it)))
+ }
+ }
+ } catch (e: Throwable) {
+ // NOOP. API is designed to use wizards in a login/registration flow,
+ // but we need to check the state anyway.
+ }
}
private fun handleResetPassword(action: LoginAction.ResetPassword) {
@@ -672,6 +684,7 @@ class LoginViewModel @AssistedInject constructor(
private fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session)
+ authenticationService.reset()
session.configureAndStart(applicationContext)
setState {
copy(
@@ -740,7 +753,7 @@ class LoginViewModel @AssistedInject constructor(
override fun onSuccess(data: LoginFlowResult) {
when (data) {
- is LoginFlowResult.Success -> {
+ is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWaitForEmailFragment.kt
index 7202ffc950..6f86f1c9d6 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginWaitForEmailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginWaitForEmailFragment.kt
@@ -21,9 +21,9 @@ import android.os.Parcelable
import android.view.View
import com.airbnb.mvrx.args
import im.vector.app.R
-import org.matrix.android.sdk.api.failure.is401
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_login_wait_for_email.*
+import org.matrix.android.sdk.api.failure.is401
import javax.inject.Inject
@Parcelize
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
index 6d943fd99f..78a24abfd3 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
@@ -35,11 +35,11 @@ import im.vector.app.core.extensions.appendParamToUrl
import im.vector.app.core.utils.AssetReader
import im.vector.app.features.signout.soft.SoftLogoutAction
import im.vector.app.features.signout.soft.SoftLogoutViewModel
+import kotlinx.android.synthetic.main.fragment_login_web.*
import org.matrix.android.sdk.api.auth.LOGIN_FALLBACK_PATH
import org.matrix.android.sdk.api.auth.REGISTER_FALLBACK_PATH
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.internal.di.MoshiProvider
-import kotlinx.android.synthetic.main.fragment_login_web.*
import timber.log.Timber
import java.net.URLDecoder
import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/features/login/SignMode.kt b/vector/src/main/java/im/vector/app/features/login/SignMode.kt
index 9933b92f27..1853a3e10f 100644
--- a/vector/src/main/java/im/vector/app/features/login/SignMode.kt
+++ b/vector/src/main/java/im/vector/app/features/login/SignMode.kt
@@ -18,10 +18,13 @@ package im.vector.app.features.login
enum class SignMode {
Unknown,
+
// Account creation
SignUp,
+
// Login
SignIn,
+
// Login directly with matrix Id
SignInWithMatrixId
}
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 529713af37..624167dd91 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
@@ -29,9 +29,9 @@ import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.features.login.AbstractLoginFragment
import im.vector.app.features.login.LoginAction
import im.vector.app.features.login.LoginViewState
-import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_login_terms.*
+import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms
import javax.inject.Inject
@Parcelize
diff --git a/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt
index c619b4aa92..81d6f1f996 100644
--- a/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt
@@ -30,6 +30,7 @@ import com.yalantis.ucrop.UCrop
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
@@ -112,10 +113,10 @@ class BigImageViewerActivity : VectorBaseActivity() {
private fun onAvatarTypeSelected(isCamera: Boolean) {
if (isCamera) {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
- avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this)
+ avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this, takePhotoActivityResultLauncher)
}
} else {
- MultiPicker.get(MultiPicker.IMAGE).single().startWith(this)
+ MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
}
}
@@ -127,33 +128,43 @@ class BigImageViewerActivity : VectorBaseActivity() {
.start(this)
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (resultCode == Activity.RESULT_OK) {
- when (requestCode) {
- MultiPicker.REQUEST_CODE_TAKE_PHOTO -> {
- avatarCameraUri?.let { uri ->
- MultiPicker.get(MultiPicker.CAMERA)
- .getTakenPhoto(this, requestCode, resultCode, uri)
- ?.let {
- onRoomAvatarSelected(it)
- }
- }
- }
- MultiPicker.REQUEST_CODE_PICK_IMAGE -> {
- MultiPicker
- .get(MultiPicker.IMAGE)
- .getSelectedFiles(this, requestCode, resultCode, data)
- .firstOrNull()?.let {
- onRoomAvatarSelected(it)
- }
- }
- UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
+ private val takePhotoActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ avatarCameraUri?.let { uri ->
+ MultiPicker.get(MultiPicker.CAMERA)
+ .getTakenPhoto(this, uri)
+ ?.let {
+ onRoomAvatarSelected(it)
+ }
}
}
+ }
+
+ private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ MultiPicker
+ .get(MultiPicker.IMAGE)
+ .getSelectedFiles(this, activityResult.data)
+ .firstOrNull()?.let {
+ onRoomAvatarSelected(it)
+ }
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ // TODO handle this one (Ucrop lib)
+ @Suppress("DEPRECATION")
super.onActivityResult(requestCode, resultCode, data)
+
+ if (resultCode == Activity.RESULT_OK) {
+ when (requestCode) {
+ UCrop.REQUEST_CROP -> data?.let { onAvatarCropped(UCrop.getOutput(it)) }
+ }
+ }
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
when (requestCode) {
PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -> onAvatarTypeSelected(true)
@@ -174,7 +185,6 @@ class BigImageViewerActivity : VectorBaseActivity() {
private const val EXTRA_TITLE = "EXTRA_TITLE"
private const val EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"
private const val EXTRA_CAN_EDIT_IMAGE = "EXTRA_CAN_EDIT_IMAGE"
- const val REQUEST_CODE = 1000
fun newIntent(context: Context, title: String?, imageUrl: String, canEditImage: Boolean = false): Intent {
return Intent(context, BigImageViewerActivity::class.java).apply {
diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
index 0b3b35e93e..4223cda229 100644
--- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt
@@ -33,13 +33,14 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequest
+import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.ui.model.Size
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.isLocalFile
import kotlinx.android.parcel.Parcelize
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
-import org.matrix.android.sdk.api.extensions.tryOrNull
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@@ -51,6 +52,7 @@ interface AttachmentData : Parcelable {
val mimeType: String?
val url: String?
val elementToDecrypt: ElementToDecrypt?
+
// If true will load non mxc url, be careful to set it only for attachments sent by you
val allowNonMxcUrls: Boolean
}
@@ -206,12 +208,14 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
.into(imageView)
}
- private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest {
+ fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest {
+ return createGlideRequest(data, mode, GlideApp.with(imageView), size)
+ }
+
+ fun createGlideRequest(data: Data, mode: Mode, glideRequests: GlideRequests, size: Size = processSize(data, mode)): GlideRequest {
return if (data.elementToDecrypt != null) {
// Encrypted image
- GlideApp
- .with(imageView)
- .load(data)
+ glideRequests.load(data)
} else {
// Clear image
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
@@ -223,15 +227,12 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
// Fallback to base url
?: data.url.takeIf { it?.startsWith("content://") == true }
- GlideApp
- .with(imageView)
+ glideRequests
.load(resolvedUrl)
.apply {
if (mode == Mode.THUMBNAIL) {
error(
- GlideApp
- .with(imageView)
- .load(resolveUrl(data))
+ glideRequests.load(resolveUrl(data))
)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/media/ImageMediaViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/ImageMediaViewerActivity.kt
deleted file mode 100644
index fa7f397b8f..0000000000
--- a/vector/src/main/java/im/vector/app/features/media/ImageMediaViewerActivity.kt
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.media
-
-import android.content.Context
-import android.content.Intent
-import android.graphics.drawable.Drawable
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.appcompat.widget.Toolbar
-import androidx.core.net.toUri
-import androidx.core.transition.addListener
-import androidx.core.view.ViewCompat
-import androidx.core.view.isInvisible
-import androidx.core.view.isVisible
-import androidx.transition.Transition
-import com.bumptech.glide.load.DataSource
-import com.bumptech.glide.load.engine.GlideException
-import com.bumptech.glide.request.RequestListener
-import com.bumptech.glide.request.target.Target
-import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
-import com.github.piasy.biv.view.GlideImageViewFactory
-import im.vector.app.R
-import im.vector.app.core.di.ScreenComponent
-import im.vector.app.core.glide.GlideApp
-import im.vector.app.core.intent.getMimeTypeFromUri
-import im.vector.app.core.platform.VectorBaseActivity
-import im.vector.app.core.utils.shareMedia
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.file.FileService
-import kotlinx.android.synthetic.main.activity_image_media_viewer.*
-import timber.log.Timber
-import java.io.File
-import javax.inject.Inject
-
-class ImageMediaViewerActivity : VectorBaseActivity() {
-
- @Inject lateinit var session: Session
- @Inject lateinit var imageContentRenderer: ImageContentRenderer
-
- private lateinit var mediaData: ImageContentRenderer.Data
-
- override fun injectWith(injector: ScreenComponent) {
- injector.inject(this)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(im.vector.app.R.layout.activity_image_media_viewer)
-
- if (intent.hasExtra(EXTRA_MEDIA_DATA)) {
- mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA)!!
- } else {
- finish()
- }
-
- intent.extras?.getString(EXTRA_SHARED_TRANSITION_NAME)?.let {
- ViewCompat.setTransitionName(imageTransitionView, it)
- }
-
- if (mediaData.url.isNullOrEmpty()) {
- supportFinishAfterTransition()
- return
- }
-
- configureToolbar(imageMediaViewerToolbar, mediaData)
-
- if (isFirstCreation() && addTransitionListener()) {
- // Encrypted image
- imageTransitionView.isVisible = true
- imageMediaViewerImageView.isVisible = false
- encryptedImageView.isVisible = false
- // Postpone transaction a bit until thumbnail is loaded
- supportPostponeEnterTransition()
-
- // We are not passing the exact same image that in the
- imageContentRenderer.renderFitTarget(mediaData, ImageContentRenderer.Mode.THUMBNAIL, imageTransitionView) {
- // Proceed with transaction
- scheduleStartPostponedTransition(imageTransitionView)
- }
- } else {
- imageTransitionView.isVisible = false
-
- if (mediaData.elementToDecrypt != null) {
- // Encrypted image
- imageMediaViewerImageView.isVisible = false
- encryptedImageView.isVisible = true
-
- GlideApp
- .with(this)
- .load(mediaData)
- .dontAnimate()
- .into(encryptedImageView)
- } else {
- // Clear image
- imageMediaViewerImageView.isVisible = true
- encryptedImageView.isVisible = false
-
- imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
- imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
- imageContentRenderer.render(mediaData, imageMediaViewerImageView)
- }
- }
- }
-
- override fun getMenuRes() = R.menu.vector_media_viewer
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.mediaViewerShareAction -> {
- onShareActionClicked()
- return true
- }
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun onShareActionClicked() {
- session.fileService().downloadFile(
- downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
- id = mediaData.eventId,
- fileName = mediaData.filename,
- mimeType = mediaData.mimeType,
- url = mediaData.url,
- elementToDecrypt = mediaData.elementToDecrypt,
- callback = object : MatrixCallback {
- override fun onSuccess(data: File) {
- shareMedia(this@ImageMediaViewerActivity, data, getMimeTypeFromUri(this@ImageMediaViewerActivity, data.toUri()))
- }
- }
- )
- }
-
- private fun configureToolbar(toolbar: Toolbar, mediaData: ImageContentRenderer.Data) {
- setSupportActionBar(toolbar)
- supportActionBar?.apply {
- title = mediaData.filename
- setHomeButtonEnabled(true)
- setDisplayHomeAsUpEnabled(true)
- }
- }
-
- override fun onBackPressed() {
- // show again for exit animation
- imageTransitionView.isVisible = true
- super.onBackPressed()
- }
-
- private fun scheduleStartPostponedTransition(sharedElement: View) {
- sharedElement.viewTreeObserver.addOnPreDrawListener(
- object : ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- sharedElement.viewTreeObserver.removeOnPreDrawListener(this)
- supportStartPostponedEnterTransition()
- return true
- }
- })
- }
-
- /**
- * Try and add a [Transition.TransitionListener] to the entering shared element
- * [Transition]. We do this so that we can load the full-size image after the transition
- * has completed.
- *
- * @return true if we were successful in adding a listener to the enter transition
- */
- private fun addTransitionListener(): Boolean {
- val transition = window.sharedElementEnterTransition
-
- if (transition != null) {
- // There is an entering shared element transition so add a listener to it
- transition.addListener(
- onEnd = {
- if (mediaData.elementToDecrypt != null) {
- // Encrypted image
- GlideApp
- .with(this)
- .load(mediaData)
- .dontAnimate()
- .listener(object : RequestListener {
- override fun onLoadFailed(e: GlideException?,
- model: Any?,
- target: Target?,
- isFirstResource: Boolean): Boolean {
- // TODO ?
- Timber.e("TRANSITION onLoadFailed")
- imageMediaViewerImageView.isVisible = false
- encryptedImageView.isVisible = true
- return false
- }
-
- override fun onResourceReady(resource: Drawable?,
- model: Any?,
- target: Target?,
- dataSource: DataSource?,
- isFirstResource: Boolean): Boolean {
- Timber.e("TRANSITION onResourceReady")
- imageTransitionView.isInvisible = true
- imageMediaViewerImageView.isVisible = false
- encryptedImageView.isVisible = true
- return false
- }
- })
- .into(encryptedImageView)
- } else {
- imageTransitionView.isInvisible = true
- // Clear image
- imageMediaViewerImageView.isVisible = true
- encryptedImageView.isVisible = false
-
- imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
- imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
- imageContentRenderer.render(mediaData, imageMediaViewerImageView)
- }
- },
- onCancel = {
- // Something to do?
- }
- )
- return true
- }
-
- // If we reach here then we have not added a listener
- return false
- }
-
- companion object {
-
- private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA"
- private const val EXTRA_SHARED_TRANSITION_NAME = "EXTRA_SHARED_TRANSITION_NAME"
-
- fun newIntent(context: Context, mediaData: ImageContentRenderer.Data, shareTransitionName: String?): Intent {
- return Intent(context, ImageMediaViewerActivity::class.java).apply {
- putExtra(EXTRA_MEDIA_DATA, mediaData)
- putExtra(EXTRA_SHARED_TRANSITION_NAME, shareTransitionName)
- }
- }
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt
index 74f4c7148f..9302be502d 100644
--- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt
@@ -74,7 +74,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Timber.i("onCreate Activity ${this.javaClass.simpleName}")
+ Timber.i("onCreate Activity ${javaClass.simpleName}")
val vectorComponent = getVectorComponent()
screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this)
val timeForInjection = measureTimeMillis {
@@ -154,6 +154,16 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen
window.navigationBarColor = ContextCompat.getColor(this, R.color.black_alpha)
}
+ override fun onResume() {
+ super.onResume()
+ Timber.i("onResume Activity ${javaClass.simpleName}")
+ }
+
+ override fun onPause() {
+ super.onPause()
+ Timber.i("onPause Activity ${javaClass.simpleName}")
+ }
+
private fun getOtherThemes() = ActivityOtherThemes.VectorAttachmentsPreview
override fun shouldAnimateDismiss(): Boolean {
diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt
index 4eb14592e0..f8cd09ce2f 100644
--- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt
@@ -25,10 +25,10 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.utils.isLocalFile
+import kotlinx.android.parcel.Parcelize
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
-import kotlinx.android.parcel.Parcelize
import timber.log.Timber
import java.io.File
import java.net.URLEncoder
diff --git a/vector/src/main/java/im/vector/app/features/media/VideoMediaViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VideoMediaViewerActivity.kt
deleted file mode 100644
index 5bdda9b0be..0000000000
--- a/vector/src/main/java/im/vector/app/features/media/VideoMediaViewerActivity.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.media
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.appcompat.widget.Toolbar
-import androidx.core.net.toUri
-import im.vector.app.R
-import im.vector.app.core.di.ScreenComponent
-import im.vector.app.core.intent.getMimeTypeFromUri
-import im.vector.app.core.platform.VectorBaseActivity
-import im.vector.app.core.utils.shareMedia
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.file.FileService
-import kotlinx.android.synthetic.main.activity_video_media_viewer.*
-import java.io.File
-import javax.inject.Inject
-
-class VideoMediaViewerActivity : VectorBaseActivity() {
-
- @Inject lateinit var session: Session
- @Inject lateinit var imageContentRenderer: ImageContentRenderer
- @Inject lateinit var videoContentRenderer: VideoContentRenderer
-
- private lateinit var mediaData: VideoContentRenderer.Data
-
- override fun injectWith(injector: ScreenComponent) {
- injector.inject(this)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(im.vector.app.R.layout.activity_video_media_viewer)
-
- if (intent.hasExtra(EXTRA_MEDIA_DATA)) {
- mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA)!!
-
- configureToolbar(videoMediaViewerToolbar, mediaData)
- imageContentRenderer.render(mediaData.thumbnailMediaData, ImageContentRenderer.Mode.FULL_SIZE, videoMediaViewerThumbnailView)
- videoContentRenderer.render(mediaData,
- videoMediaViewerThumbnailView,
- videoMediaViewerLoading,
- videoMediaViewerVideoView,
- videoMediaViewerErrorView)
- } else {
- finish()
- }
- }
-
- override fun getMenuRes() = R.menu.vector_media_viewer
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.mediaViewerShareAction -> {
- onShareActionClicked()
- return true
- }
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun onShareActionClicked() {
- session.fileService().downloadFile(
- downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
- id = mediaData.eventId,
- fileName = mediaData.filename,
- mimeType = mediaData.mimeType,
- url = mediaData.url,
- elementToDecrypt = mediaData.elementToDecrypt,
- callback = object : MatrixCallback {
- override fun onSuccess(data: File) {
- shareMedia(this@VideoMediaViewerActivity, data, getMimeTypeFromUri(this@VideoMediaViewerActivity, data.toUri()))
- }
- }
- )
- }
-
- private fun configureToolbar(toolbar: Toolbar, mediaData: VideoContentRenderer.Data) {
- setSupportActionBar(toolbar)
- supportActionBar?.apply {
- title = mediaData.filename
- setHomeButtonEnabled(true)
- setDisplayHomeAsUpEnabled(true)
- }
- }
-
- companion object {
-
- private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA"
-
- fun newIntent(context: Context, mediaData: VideoContentRenderer.Data): Intent {
- return Intent(context, VideoMediaViewerActivity::class.java).apply {
- putExtra(EXTRA_MEDIA_DATA, mediaData)
- }
- }
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 5de8796c06..106d804cd3 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -21,11 +21,11 @@ import android.content.Context
import android.content.Intent
import android.view.View
import android.view.Window
+import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.TaskStackBuilder
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
-import androidx.fragment.app.Fragment
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError
@@ -37,12 +37,14 @@ import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
+import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
-import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes
+import im.vector.app.features.home.room.detail.search.SearchActivity
+import im.vector.app.features.home.room.detail.search.SearchArgs
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.invite.InviteUsersToRoomActivity
import im.vector.app.features.media.AttachmentData
@@ -153,7 +155,10 @@ class DefaultNavigator @Inject constructor(
override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) {
if (context is VectorBaseActivity) {
- BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly, false)
+ BootstrapBottomSheet.show(
+ context.supportFragmentManager,
+ if (initCrossSigningOnly) SetupMode.CROSS_SIGNING_ONLY else SetupMode.NORMAL
+ )
}
}
@@ -226,13 +231,19 @@ class DefaultNavigator @Inject constructor(
// if cross signing is enabled we should propose full 4S
sessionHolder.getSafeActiveSession()?.let { session ->
if (session.cryptoService().crossSigningService().canCrossSign() && context is VectorBaseActivity) {
- BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
+ BootstrapBottomSheet.show(context.supportFragmentManager, SetupMode.NORMAL)
} else {
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
}
}
}
+ override fun open4SSetup(context: Context, setupMode: SetupMode) {
+ if (context is VectorBaseActivity) {
+ BootstrapBottomSheet.show(context.supportFragmentManager, setupMode)
+ }
+ }
+
override fun openKeysBackupManager(context: Context) {
context.startActivity(KeysBackupManageActivity.intent(context))
}
@@ -254,21 +265,32 @@ class DefaultNavigator @Inject constructor(
}
}
- override fun openTerms(fragment: Fragment, serviceType: TermsService.ServiceType, baseUrl: String, token: String?, requestCode: Int) {
- val intent = ReviewTermsActivity.intent(fragment.requireContext(), serviceType, baseUrl, token)
- fragment.startActivityForResult(intent, requestCode)
+ override fun openTerms(context: Context,
+ activityResultLauncher: ActivityResultLauncher,
+ serviceType: TermsService.ServiceType,
+ baseUrl: String,
+ token: String?) {
+ val intent = ReviewTermsActivity.intent(context, serviceType, baseUrl, token)
+ activityResultLauncher.launch(intent)
}
- override fun openStickerPicker(fragment: Fragment, roomId: String, widget: Widget, requestCode: Int) {
+ override fun openStickerPicker(context: Context,
+ activityResultLauncher: ActivityResultLauncher,
+ roomId: String,
+ widget: Widget) {
val widgetArgs = widgetArgsBuilder.buildStickerPickerArgs(roomId, widget)
- val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
- fragment.startActivityForResult(intent, WidgetRequestCodes.STICKER_PICKER_REQUEST_CODE)
+ val intent = WidgetActivity.newIntent(context, widgetArgs)
+ activityResultLauncher.launch(intent)
}
- override fun openIntegrationManager(fragment: Fragment, roomId: String, integId: String?, screen: String?) {
+ override fun openIntegrationManager(context: Context,
+ activityResultLauncher: ActivityResultLauncher,
+ roomId: String,
+ integId: String?,
+ screen: String?) {
val widgetArgs = widgetArgsBuilder.buildIntegrationManagerArgs(roomId, integId, screen)
- val intent = WidgetActivity.newIntent(fragment.requireContext(), widgetArgs)
- fragment.startActivityForResult(intent, WidgetRequestCodes.INTEGRATION_MANAGER_REQUEST_CODE)
+ val intent = WidgetActivity.newIntent(context, widgetArgs)
+ activityResultLauncher.launch(intent)
}
override fun openRoomWidget(context: Context, roomId: String, widget: Widget, options: Map