diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index b063c93530..b28dbbde69 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -21,6 +21,8 @@ body: - [ ] While Weblate is locked, and after the PR from Weblate has been merged, handle all the TODOs in the main `strings.xml` file - [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect. + - [ ] Ensure all [the required PRs](https://github.com/vector-im/element-android/pulls?q=is%3Aopen+is%3Apr+label%3AZ-NextRelease) have been merged + ### Do the release - [ ] Make sure `develop` and `main` are up to date (git pull) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index a1d754b4de..8a892b9b15 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,8 +11,10 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@11.1.1 + uses: danger/danger-js@11.1.2 with: args: "--dangerfile tools/danger/dangerfile.js" env: DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} + # Fallback for forks + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 6e5e2e4d67..70669596bb 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -66,11 +66,13 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.1.1 + uses: danger/danger-js@11.1.2 with: args: "--dangerfile tools/danger/dangerfile-lint.js" env: DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} + # Fallback for forks + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Gradle dependency analysis using https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin dependency-analysis: diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 90f03779a6..f478d2bd7b 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -98,7 +98,8 @@ jobs: # Skip in forks if: > github.repository == 'vector-im/element-android' && - (contains(github.event.issue.labels.*.name, 'Team: Delight')) + (contains(github.event.issue.labels.*.name, 'Team: Delight') || + contains(github.event.issue.labels.*.name, 'Z-AppLayout')) steps: - uses: octokit/graphql-action@v2.x with: diff --git a/CHANGES.md b/CHANGES.md index 829b1a0caa..4615ec8ff0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,51 @@ +Changes in Element v1.4.34 (2022-08-23) +======================================= + +Features ✨ +---------- + - [Notification] - Handle creation of notification for live location and poll start ([#6746](https://github.com/vector-im/element-android/issues/6746)) + +Bugfixes 🐛 +---------- + - Fixes onboarding requiring matrix.org to be accessible on the first step, the server can now be manually changed ([#6718](https://github.com/vector-im/element-android/issues/6718)) + - Fixing sign in/up for homeservers that rely on the SSO fallback url ([#6827](https://github.com/vector-im/element-android/issues/6827)) + - Fixes uncaught exceptions in the SyncWorker to cause the worker to become stuck in the failure state ([#6836](https://github.com/vector-im/element-android/issues/6836)) + - Fixes onboarding captcha crashing when no WebView is available by showing an error with information instead ([#6855](https://github.com/vector-im/element-android/issues/6855)) + - Removes ability to continue registration after the app has been destroyed, fixes the next steps crashing due to missing information from the previous steps ([#6860](https://github.com/vector-im/element-android/issues/6860)) + - Fixes crash when exiting the login or registration entry screens whilst they're loading ([#6861](https://github.com/vector-im/element-android/issues/6861)) + - Fixes server selection being unable to trust certificates ([#6864](https://github.com/vector-im/element-android/issues/6864)) + - Ensure SyncThread is started when the app is launched after a Push has been received. ([#6884](https://github.com/vector-im/element-android/issues/6884)) + - Fixes missing firebase notifications after logging in when UnifiedPush distributor is installed ([#6891](https://github.com/vector-im/element-android/issues/6891)) + +In development 🚧 +---------------- + - Create DM room only on first message - Trigger the flow when the "Direct Message" action is selected from the room member details screen ([#5525](https://github.com/vector-im/element-android/issues/5525)) + - added filter tabs for new App layout's Home screen ([#6505](https://github.com/vector-im/element-android/issues/6505)) + - [App Layout] added dialog to configure app layout ([#6506](https://github.com/vector-im/element-android/issues/6506)) + - Adds space list bottom sheet for new app layout ([#6749](https://github.com/vector-im/element-android/issues/6749)) + - [App Layout] Dialpad moved from bottom navigation tab to a separate activity accessed via home screen context menu ([#6787](https://github.com/vector-im/element-android/issues/6787)) + - Makes toolbar switch title based on space in New App Layout ([#6795](https://github.com/vector-im/element-android/issues/6795)) + - [Devices management] Add a feature flag and empty screen for future new layout ([#6798](https://github.com/vector-im/element-android/issues/6798)) + - Adds new chat bottom sheet as the click action of the main FAB in the new app layout ([#6801](https://github.com/vector-im/element-android/issues/6801)) + - [Devices management] Other sessions section in new layout ([#6806](https://github.com/vector-im/element-android/issues/6806)) + - [New Layout] Adds space settings accessible through clicking the toolbar ([#6859](https://github.com/vector-im/element-android/issues/6859)) + - Adds New App Layout FABs (hidden behind feature flag) ([#6693](https://github.com/vector-im/element-android/issues/6693)) + +SDK API changes ⚠️ +------------------ + - Rename `DebugService.logDbUsageInfo` (resp. `Session.logDbUsageInfo`) to `DebugService.getDbUsageInfo` (resp. `Session.getDbUsageInfo`) and return a String instead of logging. The caller may want to log the String. ([#6884](https://github.com/vector-im/element-android/issues/6884)) + +Other changes +------------- + - Removes the Login2 proof of concept - replaced by the FTUE changes ([#5974](https://github.com/vector-im/element-android/issues/5974)) + - Enable auto-capitalization for Room creation Title field ([#6645](https://github.com/vector-im/element-android/issues/6645)) + - Decouples the variant logic from the vector module ([#6783](https://github.com/vector-im/element-android/issues/6783)) + - Add a developer setting to enable LeakCanary at runtime ([#6786](https://github.com/vector-im/element-android/issues/6786)) + - [Create Room] Reduce some boilerplate with room state event contents ([#6799](https://github.com/vector-im/element-android/issues/6799)) + - [Call] Memory leak after a call ([#6808](https://github.com/vector-im/element-android/issues/6808)) + - Fix some string template ([#6843](https://github.com/vector-im/element-android/issues/6843)) + + Changes in Element v1.4.32 (2022-08-10) ======================================= diff --git a/build.gradle b/build.gradle index afe51cc734..aa8a3e2c4c 100644 --- a/build.gradle +++ b/build.gradle @@ -24,12 +24,12 @@ buildscript { classpath libs.gradle.gradlePlugin classpath libs.gradle.kotlinPlugin classpath libs.gradle.hiltPlugin - classpath 'com.google.firebase:firebase-appdistribution-gradle:3.0.2' + classpath 'com.google.firebase:firebase-appdistribution-gradle:3.0.3' classpath 'com.google.gms:google-services:4.3.13' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.1.1" - classpath 'org.owasp:dependency-check-gradle:7.1.1' + classpath 'org.owasp:dependency-check-gradle:7.1.2' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" // NOTE: Do not place your application dependencies here; they belong @@ -44,7 +44,7 @@ plugins { id "io.gitlab.arturbosch.detekt" version "1.21.0" // Dependency Analysis - id 'com.autonomousapps.dependency-analysis' version "1.11.2" + id 'com.autonomousapps.dependency-analysis' version "1.12.0" } // https://github.com/jeremylong/DependencyCheck @@ -151,6 +151,8 @@ allprojects { "experimental:comment-wrapping", // - A KDoc comment after any other element on the same line must be separated by a new line "experimental:kdoc-wrapping", + // Ignore error "Redundant curly braces", since we use it to fix false positives, for instance in "elementLogs.${i}.txt" + "string-template", ] } diff --git a/changelog.d/5525.wip b/changelog.d/5525.wip new file mode 100644 index 0000000000..0d54c06b6a --- /dev/null +++ b/changelog.d/5525.wip @@ -0,0 +1 @@ +Create DM room only on first message - Create the DM and navigate to the new room after sending an event diff --git a/changelog.d/6505.wip b/changelog.d/6505.wip deleted file mode 100644 index 1109c5fff1..0000000000 --- a/changelog.d/6505.wip +++ /dev/null @@ -1 +0,0 @@ -added filter tabs for new App layout's Home screen diff --git a/changelog.d/6565.wip b/changelog.d/6565.wip new file mode 100644 index 0000000000..0e89c63e75 --- /dev/null +++ b/changelog.d/6565.wip @@ -0,0 +1 @@ +[App Layout] Bottom navigation tabs are removed for new home screen diff --git a/changelog.d/6645.misc b/changelog.d/6645.misc deleted file mode 100644 index b24655879d..0000000000 --- a/changelog.d/6645.misc +++ /dev/null @@ -1 +0,0 @@ -Enable auto-capitalization for Room creation Title field diff --git a/changelog.d/6693.feature b/changelog.d/6693.feature deleted file mode 100644 index 5e905766a9..0000000000 --- a/changelog.d/6693.feature +++ /dev/null @@ -1 +0,0 @@ -Adds New App Layout FABs (hidden behind feature flag) diff --git a/changelog.d/6746.feature b/changelog.d/6746.feature deleted file mode 100644 index 7869e7f57a..0000000000 --- a/changelog.d/6746.feature +++ /dev/null @@ -1 +0,0 @@ -[Notification] - Handle creation of notification for live location and poll start diff --git a/changelog.d/6750.wip b/changelog.d/6750.wip new file mode 100644 index 0000000000..2e18110c97 --- /dev/null +++ b/changelog.d/6750.wip @@ -0,0 +1 @@ +[App Layout] fixed space switching dialog measured with wrong height sometimes diff --git a/changelog.d/6783.misc b/changelog.d/6783.misc deleted file mode 100644 index d1095c1203..0000000000 --- a/changelog.d/6783.misc +++ /dev/null @@ -1 +0,0 @@ -Decouples the variant logic from the vector module diff --git a/changelog.d/6786.misc b/changelog.d/6786.misc deleted file mode 100644 index a916336ae4..0000000000 --- a/changelog.d/6786.misc +++ /dev/null @@ -1 +0,0 @@ -Add a developer setting to enable LeakCanary at runtime diff --git a/changelog.d/6798.wip b/changelog.d/6798.wip deleted file mode 100644 index a16270666b..0000000000 --- a/changelog.d/6798.wip +++ /dev/null @@ -1 +0,0 @@ -[Devices management] Add a feature flag and empty screen for future new layout diff --git a/changelog.d/6799.misc b/changelog.d/6799.misc deleted file mode 100644 index b756c2c07b..0000000000 --- a/changelog.d/6799.misc +++ /dev/null @@ -1 +0,0 @@ -[Create Room] Reduce some boilerplate with room state event contents diff --git a/changelog.d/6806.wip b/changelog.d/6806.wip deleted file mode 100644 index 9b00139c62..0000000000 --- a/changelog.d/6806.wip +++ /dev/null @@ -1 +0,0 @@ -[Devices management] Other sessions section in new layout diff --git a/changelog.d/6808.misc b/changelog.d/6808.misc deleted file mode 100644 index 06eeff862b..0000000000 --- a/changelog.d/6808.misc +++ /dev/null @@ -1 +0,0 @@ -[Call] Memory leak after a call diff --git a/changelog.d/6889.wip b/changelog.d/6889.wip new file mode 100644 index 0000000000..067973aad9 --- /dev/null +++ b/changelog.d/6889.wip @@ -0,0 +1 @@ +[App Layout] new room invites screen diff --git a/changelog.d/6894.misc b/changelog.d/6894.misc new file mode 100644 index 0000000000..abb1a69a71 --- /dev/null +++ b/changelog.d/6894.misc @@ -0,0 +1 @@ +Remove FragmentModule and the Fragment factory. No need to Inject the constructor on your Fragment, just add @AndroidEntryPoint annotation and @Inject class members. diff --git a/changelog.d/6926.misc b/changelog.d/6926.misc new file mode 100644 index 0000000000..dc1330d9fc --- /dev/null +++ b/changelog.d/6926.misc @@ -0,0 +1 @@ +Focus input field when editing homeserver address to speed up login and registration. \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index 93a62a548e..7a9ed3f931 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -22,7 +22,7 @@ def markwon = "4.6.2" def moshi = "1.13.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.156.0" +def flipper = "0.161.0" def epoxy = "4.6.2" def mavericks = "2.7.0" def glide = "4.13.2" @@ -30,7 +30,7 @@ def bigImageViewer = "1.8.1" def jjwt = "0.11.5" def vanniktechEmoji = "0.15.0" -def fragment = "1.5.1" +def fragment = "1.5.2" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 @@ -104,6 +104,7 @@ ext.libs = [ 'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", 'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi", + 'moshiAdapters' : "com.squareup.moshi:moshi-adapters:$moshi", 'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit", 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" ], diff --git a/docs/danger.md b/docs/danger.md index acf14018e6..afa3555469 100644 --- a/docs/danger.md +++ b/docs/danger.md @@ -85,6 +85,8 @@ To let Danger check all the PRs, including PRs form forks, a GitHub account have - password: Stored on Passbolt - GitHub token: A token with limited access has been created and added to the repository https://github.com/vector-im/element-android as secret DANGER_GITHUB_API_TOKEN. This token is not saved anywhere else. In case of problem, just delete it and create a new one, then update the secret. +PRs from forks do not always have access to the secret `secrets.DANGER_GITHUB_API_TOKEN`, so `secrets.GITHUB_TOKEN` is also provided to the job environment. If `secrets.DANGER_GITHUB_API_TOKEN` is available, it will be used, so user `ElementBot` will comment the PR. Else `secrets.GITHUB_TOKEN` will be used, and bot `github-actions` will comment the PR. + ## Useful links - https://danger.systems/ diff --git a/docs/hilt_migration.md b/docs/hilt_migration.md index 50021e9792..0556cf85dc 100644 --- a/docs/hilt_migration.md +++ b/docs/hilt_migration.md @@ -7,8 +7,8 @@ Hilt is built on top of Dagger 2 and simplify usage by removing needs to create When you create a new feature, you should have the following: Annotate your Activity with @AndroidEntryPoint +Annotate your Fragment with @AndroidEntryPoint If you have a BottomSheetFragment => Annotate it with @AndroidEntryPoint -Otherwise => Add your Fragment to the FragmentModule Add your ViewModel.Factory to the MavericksViewModelModule Makes sure your ViewModel as the following code: diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104300.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104300.txt new file mode 100644 index 0000000000..e74d892209 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje vylepšené přihlašování a registraci. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104310.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104310.txt new file mode 100644 index 0000000000..e74d892209 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje vylepšené přihlašování a registraci. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104340.txt b/fastlane/metadata/android/en-US/changelogs/40104340.txt new file mode 100644 index 0000000000..61db61727a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Main changes in this version: Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104300.txt b/fastlane/metadata/android/et/changelogs/40104300.txt new file mode 100644 index 0000000000..e01c9b4329 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: senisest parem liitumise ja sisselogimise töövoog. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104310.txt b/fastlane/metadata/android/et/changelogs/40104310.txt new file mode 100644 index 0000000000..e01c9b4329 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: senisest parem liitumise ja sisselogimise töövoog. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104300.txt b/fastlane/metadata/android/fa/changelogs/40104300.txt new file mode 100644 index 0000000000..7a0e87b263 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104300.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: به کار انداختن ورود بهبود یافته و سفرهای ورود. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104310.txt b/fastlane/metadata/android/fa/changelogs/40104310.txt new file mode 100644 index 0000000000..7a0e87b263 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104310.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: به کار انداختن ورود بهبود یافته و سفرهای ورود. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104300.txt b/fastlane/metadata/android/fr-FR/changelogs/40104300.txt new file mode 100644 index 0000000000..328e66aaa0 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Activation de l’authentification et du parcours d’inscription améliorés. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104310.txt b/fastlane/metadata/android/fr-FR/changelogs/40104310.txt new file mode 100644 index 0000000000..328e66aaa0 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Activation de l’authentification et du parcours d’inscription améliorés. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/gl/changelogs/40104160.txt b/fastlane/metadata/android/gl-ES/changelogs/40104160.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104160.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104160.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104180.txt b/fastlane/metadata/android/gl-ES/changelogs/40104180.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104180.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104180.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104190.txt b/fastlane/metadata/android/gl-ES/changelogs/40104190.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104190.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104190.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104200.txt b/fastlane/metadata/android/gl-ES/changelogs/40104200.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104200.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104200.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104220.txt b/fastlane/metadata/android/gl-ES/changelogs/40104220.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104220.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104220.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104230.txt b/fastlane/metadata/android/gl-ES/changelogs/40104230.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104230.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104230.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104240.txt b/fastlane/metadata/android/gl-ES/changelogs/40104240.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104240.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104240.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104250.txt b/fastlane/metadata/android/gl-ES/changelogs/40104250.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104250.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104250.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104260.txt b/fastlane/metadata/android/gl-ES/changelogs/40104260.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104260.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104260.txt diff --git a/fastlane/metadata/android/gl/changelogs/40104270.txt b/fastlane/metadata/android/gl-ES/changelogs/40104270.txt similarity index 100% rename from fastlane/metadata/android/gl/changelogs/40104270.txt rename to fastlane/metadata/android/gl-ES/changelogs/40104270.txt diff --git a/fastlane/metadata/android/gl/full_description.txt b/fastlane/metadata/android/gl-ES/full_description.txt similarity index 100% rename from fastlane/metadata/android/gl/full_description.txt rename to fastlane/metadata/android/gl-ES/full_description.txt diff --git a/fastlane/metadata/android/gl/short_description.txt b/fastlane/metadata/android/gl-ES/short_description.txt similarity index 100% rename from fastlane/metadata/android/gl/short_description.txt rename to fastlane/metadata/android/gl-ES/short_description.txt diff --git a/fastlane/metadata/android/gl/title.txt b/fastlane/metadata/android/gl-ES/title.txt similarity index 100% rename from fastlane/metadata/android/gl/title.txt rename to fastlane/metadata/android/gl-ES/title.txt diff --git a/fastlane/metadata/android/id/changelogs/40104300.txt b/fastlane/metadata/android/id/changelogs/40104300.txt new file mode 100644 index 0000000000..3d8d13e23d --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Mengaktifkan perjalanan masuk dan keluar yang diperbaiki. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104310.txt b/fastlane/metadata/android/id/changelogs/40104310.txt new file mode 100644 index 0000000000..3d8d13e23d --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Mengaktifkan perjalanan masuk dan keluar yang diperbaiki. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt index d3bed0bf6b..20d805c582 100644 --- a/fastlane/metadata/android/id/full_description.txt +++ b/fastlane/metadata/android/id/full_description.txt @@ -1,42 +1,42 @@ -Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung-ke-ujung untuk memberikan konferensi video, pembagian file, dan panggilan suara yang aman. +Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung-ke-ujung untuk menyediakan konferensi video, pembagian berkas, dan panggilan suara yang aman. -Fitur Element termasuk -- Alat komunikasi online yang canggih +Fitur Element termasuk: +- Alat komunikasi daring yang canggih - Pesan-pesan yang dienkripsi sepenuhnya untuk memungkinkan komunikasi perusahaan yang lebih aman, bahkan untuk pekerja jarak jauh -- Obrolan terdesentralisasi berdasarkan kerangka Matrix yang sumber terbuka -- Pembagian file aman dengan data terenkripsi saat mengelola proyek +- Obrolan terdesentralisasi berdasarkan kerangka kerja Matrix yang sumber terbuka +- Pembagian berkas aman dengan data terenkripsi saat mengelola proyek - Obrolan video dengan VoIP dan pembagian layar -- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya +- Integrasi yang mudah dengan alat kolaborasi daring favorit Anda, alat pengelola proyek, layanan VoIP dan aplikasi perpesanan tim lainnya -Element benar-benar berbeda dari aplikasi perpesanan dan aplikasi kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. +Element benar-benar berbeda dari aplikasi perpesanan dan aplikasi kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi yang terdesentralisasi. Perpesanan dengan privasi dan enkripsi -Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung-ke-ujung dan verifikasi perangkat menggunakan penandatanganan silang. +Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data, dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung-ke-ujung, dan verifikasi perangkat menggunakan penandatanganan silang. -Element memberikan Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan siapa saja secara aman di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan aplikasi-aplikasi seperti Slack. +Element memberikan Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan siapa saja secara aman di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan aplikasi seperti Slack. -Element dapat dihost sendiri -Untuk memungkinkan lebih banyak kendali atas data dan pesan-pesan sensitif Anda, Element dapat dihost sendiri atau Anda dapat memilih host berbasis Matrix, standar untuk komunikasi terdesentralisasi sumber terbuka. Element memberi Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi. +Element dapat di-host sendiri +Untuk memungkinkan lebih banyak kendali atas data dan pesan-pesan sensitif Anda, Element dapat dilayani sendiri atau Anda dapat memilih layanan berbasis Matrix, standar untuk komunikasi terdesentralisasi sumber terbuka. Element memberikan Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi. Miliki data Anda Anda memutuskan di mana untuk menyimpan data dan pesan-pesan Anda, tanpa risiko penambangan data atau akses dari pihak ketiga. Element menempatkan Anda dalam kendali dengan cara yang berbeda: -1. Dapatkan akun gratis pada server publik matrix.org yang dihost oleh pengembang Matrix, atau memilih dari ribuan server publik yang dihost oleh sukarelawan -2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri +1. Dapatkan akun gratis pada server publik matrix.org yang dilayani oleh pengembang Matrix, atau memilih dari ribuan server publik yang dilayani oleh sukarelawan +2. Layani akun Anda sendiri dengan menjalankan server pada infrastruktur IT Anda sendiri 3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element Perpesanan dan kolaborasi terbuka -Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda. +Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain, atau bahkan menggunakan aplikasi perpesanan yang berbeda. Sangat aman -Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam obrolan dapat mendekripsi pesan), dan verifikasi perangkat menggunakan penandatanganan silang. +Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang di dalam obrolan dapat mendekripsikan pesan), dan verifikasi perangkat menggunakan penandatanganan silang. Komunikasi dan integrasi lengkap -Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi bot dan widget. Buat ruangan dan komunitas, tetap terhubung dan selesaikan hal-hal penting. +Perpesanan, panggilan suara dan video, pembagian berkas, pembagian layar dan banyak integrasi bot dan widget. Buat ruangan dan komunitas, tetap terhubung, dan selesaikan hal-hal penting. Ambil di mana Anda tinggalkan -Tetap terhubung di mana Anda berada, dengan riwayat pesan yang disinkronkan di semua perangkat Anda dan web di https://app.element.io +Tetap terhubung di mana Anda berada, dengan riwayat pesan yang disinkronkan pada semua perangkat Anda dan pada web di https://app.element.io Sumber terbuka -Element Android adalah proyek sumber terbuka, dihost oleh GitHub. Silakan laporkan masalah yang Anda temukan, atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android +Element Android adalah proyek sumber terbuka, dilayani oleh GitHub. Silakan laporkan masalah yang Anda temukan, atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android diff --git a/fastlane/metadata/android/it-IT/changelogs/40104300.txt b/fastlane/metadata/android/it-IT/changelogs/40104300.txt new file mode 100644 index 0000000000..40d9618137 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: introduce i percorsi migliorati di accesso e registrazione. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104310.txt b/fastlane/metadata/android/it-IT/changelogs/40104310.txt new file mode 100644 index 0000000000..40d9618137 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: introduce i percorsi migliorati di accesso e registrazione. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104300.txt b/fastlane/metadata/android/pt-BR/changelogs/40104300.txt new file mode 100644 index 0000000000..5f1aaf4b3d --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Habilita as jornadas melhoradas de sign in e sign up. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104310.txt b/fastlane/metadata/android/pt-BR/changelogs/40104310.txt new file mode 100644 index 0000000000..5f1aaf4b3d --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Habilita as jornadas melhoradas de sign in e sign up. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104300.txt b/fastlane/metadata/android/sk/changelogs/40104300.txt new file mode 100644 index 0000000000..dd0f554532 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Umožňuje vylepšené postupy prihlasovania a registrácie. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104310.txt b/fastlane/metadata/android/sk/changelogs/40104310.txt new file mode 100644 index 0000000000..dd0f554532 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Umožňuje vylepšené postupy prihlasovania a registrácie. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104300.txt b/fastlane/metadata/android/uk/changelogs/40104300.txt new file mode 100644 index 0000000000..727508a0cc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Поліпшені вхід і реєстрація. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104310.txt b/fastlane/metadata/android/uk/changelogs/40104310.txt new file mode 100644 index 0000000000..727508a0cc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Поліпшені вхід і реєстрація. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104300.txt b/fastlane/metadata/android/zh-TW/changelogs/40104300.txt new file mode 100644 index 0000000000..3055389b2b --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104300.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:啟用改善的登入與註冊流程。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104310.txt b/fastlane/metadata/android/zh-TW/changelogs/40104310.txt new file mode 100644 index 0000000000..3055389b2b --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104310.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:啟用改善的登入與註冊流程。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e6b526585b..faa798c9dc 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -17,7 +17,7 @@ buildscript { } } dependencies { - classpath "io.realm:realm-gradle-plugin:10.11.0" + classpath "io.realm:realm-gradle-plugin:10.11.1" } } @@ -60,7 +60,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.34\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.36\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -163,6 +163,7 @@ dependencies { implementation 'com.squareup.okhttp3:logging-interceptor' implementation libs.squareup.moshi + implementation libs.squareup.moshiAdapters kapt libs.squareup.moshiKotlin api "com.atlassian.commonmark:commonmark:0.13.0" @@ -199,7 +200,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54' testImplementation libs.tests.junit // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt index d0cee08831..7f5e4f2ee7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt @@ -28,7 +28,7 @@ interface DebugService { fun getAllRealmConfigurations(): List /** - * Prints out info on DB size to logcat. + * Get info on DB size. */ - fun logDbUsageInfo() + fun getDbUsageInfo(): String } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 68b931b33c..5b41ddaaec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -89,10 +89,14 @@ fun Throwable.isInvalidUIAAuth() = this is Failure.ServerError && fun Throwable.isHomeserverUnavailable() = this is Failure.NetworkConnection && this.ioException is UnknownHostException +fun Throwable.isHomeserverConnectionError() = this is Failure.NetworkConnection + fun Throwable.isMissingEmailVerification() = this is Failure.ServerError && error.code == MatrixError.M_UNAUTHORIZED && error.message == "Unable to get validated threepid" +fun Throwable.isUnrecognisedCertificate() = this is Failure.UnrecognizedCertificateFailure + /** * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 63c1c25130..13993149f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -323,9 +323,9 @@ interface Session { fun getUiaSsoFallbackUrl(authenticationSessionId: String): String /** - * Debug API, will print out info on DB size to logcat. + * Debug API, will return info about the DB. */ - fun logDbUsageInfo() + fun getDbUsageInfo(): String /** * Debug API, return the list of all RealmConfiguration used by this session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 8fdbba21c5..84c25776e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -70,6 +70,9 @@ object EventType { const val STATE_ROOM_ENCRYPTION = "m.room.encryption" const val STATE_ROOM_SERVER_ACL = "m.room.server_acl" + // This type is for local purposes, it should never be processed by the server + const val LOCAL_STATE_ROOM_THIRD_PARTY_INVITE = "local.room.third_party_invite" + // Call Events const val CALL_INVITE = "m.call.invite" const val CALL_CANDIDATES = "m.call.candidates" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt index 6bcf576824..24748f88e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt @@ -18,10 +18,14 @@ package org.matrix.android.sdk.api.session.identity import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier sealed class ThreePid(open val value: String) { + @JsonClass(generateAdapter = true) data class Email(val email: String) : ThreePid(email) + + @JsonClass(generateAdapter = true) data class Msisdn(val msisdn: String) : ThreePid(msisdn) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index b7b0cc890b..d6eb7b30d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -17,13 +17,16 @@ package org.matrix.android.sdk.api.session.room.model.create import android.net.Uri +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.internal.di.MoshiProvider +@JsonClass(generateAdapter = true) open class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. @@ -61,12 +64,12 @@ open class CreateRoomParams { * A list of user IDs to invite to the room. * This will tell the server to invite everyone in the list to the newly created room. */ - val invitedUserIds = mutableListOf() + var invitedUserIds = mutableListOf() /** * A list of objects representing third party IDs to invite into the room. */ - val invite3pids = mutableListOf() + var invite3pids = mutableListOf() /** * Initial Guest Access. @@ -99,14 +102,14 @@ open class CreateRoomParams { * The server will clobber the following keys: creator. * Future versions of the specification may allow the server to clobber other keys. */ - val creationContent = mutableMapOf() + var creationContent = mutableMapOf() /** * A list of state events to set in the new room. This allows the user to override the default state events * set in the new room. The expected format of the state events are an object with type, state_key and content keys set. * Takes precedence over events set by preset, but gets overridden by name and topic keys. */ - val initialStates = mutableListOf() + var initialStates = mutableListOf() /** * Set to true to disable federation of this room. @@ -151,7 +154,7 @@ open class CreateRoomParams { * Supported value: MXCRYPTO_ALGORITHM_MEGOLM. */ var algorithm: String? = null - private set + internal set var historyVisibility: RoomHistoryVisibility? = null @@ -161,10 +164,18 @@ open class CreateRoomParams { var roomVersion: String? = null - var featurePreset: RoomFeaturePreset? = null + @Transient var featurePreset: RoomFeaturePreset? = null companion object { - private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" - private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + internal const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" + internal const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + + fun fromJson(json: String?): CreateRoomParams? { + return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).fromJson(it) } + } } } + +internal fun CreateRoomParams.toJSONString(): String { + return MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).toJson(this) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt index fcfdc3e333..d89c72c513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.api.session.room.model.create +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content +@JsonClass(generateAdapter = true) data class CreateRoomStateEvent( /** * Required. The type of event to send. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt index 71f7ab8494..6640b8a9af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/SyncService.kt @@ -53,6 +53,11 @@ interface SyncService { */ fun getSyncState(): SyncState + /** + * This method returns true if the sync thread is alive, i.e. started. + */ + fun isSyncThreadAlive(): Boolean + /** * This method allows to listen the sync state. * @return a [LiveData] of [SyncState]. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index bb14b417dd..405757e3b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -16,10 +16,12 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.task.Task @@ -37,12 +39,17 @@ internal class DefaultSendEventTask @Inject constructor( private val localEchoRepository: LocalEchoRepository, private val encryptEventTask: EncryptEventTask, private val loadRoomMembersTask: LoadRoomMembersTask, + private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask, private val roomAPI: RoomAPI, private val globalErrorReceiver: GlobalErrorReceiver ) : SendEventTask { override suspend fun execute(params: SendEventTask.Params): String { try { + if (params.event.isLocalRoomEvent) { + return createRoomAndSendEvent(params) + } + // Make sure to load all members in the room before sending the event. params.event.roomId ?.takeIf { params.encrypt } @@ -78,6 +85,12 @@ internal class DefaultSendEventTask @Inject constructor( } } + private suspend fun createRoomAndSendEvent(params: SendEventTask.Params): String { + val roomId = createRoomFromLocalRoomTask.execute(CreateRoomFromLocalRoomTask.Params(params.event.roomId.orEmpty())) + Timber.d("State event: convert local room (${params.event.roomId}) to existing room ($roomId) before sending the event.") + return execute(params.copy(event = params.event.copy(roomId = roomId))) + } + @Throws private suspend fun handleEncryption(params: SendEventTask.Params): Event { if (params.encrypt && !params.event.isEncrypted()) { @@ -91,4 +104,7 @@ internal class DefaultSendEventTask @Inject constructor( } return params.event } + + private val Event.isLocalRoomEvent + get() = RoomLocalEcho.isLocalEchoId(roomId.orEmpty()) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt index 861a7a3a77..23a75d2bb3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt @@ -76,12 +76,12 @@ internal class VerificationTransportToDevice( .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { - Timber.v("## verification [$tx.transactionId] send toDevice request success") + Timber.v("## verification [${tx?.transactionId}] send toDevice request success") callback.invoke(localId, validKeyReq) } override fun onFailure(failure: Throwable) { - Timber.e("## verification [$tx.transactionId] failed to send toDevice request") + Timber.e("## verification [${tx?.transactionId}] failed to send toDevice request") } } } @@ -103,12 +103,12 @@ internal class VerificationTransportToDevice( .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_READY, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { - Timber.v("## verification [$tx.transactionId] send toDevice request success") + Timber.v("## verification [${tx?.transactionId}] send toDevice request success") callback?.invoke() } override fun onFailure(failure: Throwable) { - Timber.e("## verification [$tx.transactionId] failed to send toDevice request") + Timber.e("## verification [${tx?.transactionId}] failed to send toDevice request") } } } @@ -136,7 +136,7 @@ internal class VerificationTransportToDevice( .configureWith(SendToDeviceTask.Params(type, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { - Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.") + Timber.v("## SAS verification [${tx.transactionId}] toDevice type '$type' success.") if (onDone != null) { onDone() } else { @@ -149,7 +149,7 @@ internal class VerificationTransportToDevice( } override fun onFailure(failure: Throwable) { - Timber.e("## SAS verification [$tx.transactionId] failed to send toDevice in state : $tx.state") + Timber.e("## SAS verification [${tx.transactionId}] failed to send toDevice in state : ${tx.state}") tx.cancel(onErrorReason) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmCompactOnLaunch.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmCompactOnLaunch.kt new file mode 100644 index 0000000000..1efb2541a7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmCompactOnLaunch.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import io.realm.DefaultCompactOnLaunchCallback + +class RealmCompactOnLaunch : DefaultCompactOnLaunchCallback() { + /** + * Forces all RealmCompactOnLaunch instances to be equal. + * Avoids Realm throwing when multiple instances of this class are used. + */ + override fun equals(other: Any?) = other is RealmCompactOnLaunch + override fun hashCode() = 0x1000 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt index f2f88e216b..020b42b3b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt @@ -38,7 +38,7 @@ internal abstract class RealmLiveEntityObserver(protected val r LiveEntityObserver, RealmChangeListener> { private companion object { - val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND") + val BACKGROUND_HANDLER = createBackgroundHandler("Matrix-LIVE_ENTITY_BACKGROUND") } protected val observerScope = CoroutineScope(SupervisorJob() + BACKGROUND_HANDLER.asCoroutineDispatcher()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index b733aa6fc0..0b11863864 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -60,7 +61,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 35L, + schemaVersion = 36L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -105,5 +106,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 33) MigrateSessionTo033(realm).perform() if (oldVersion < 34) MigrateSessionTo034(realm).perform() if (oldVersion < 35) MigrateSessionTo035(realm).perform() + if (oldVersion < 36) MigrateSessionTo036(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 949dd5daa1..16a55c22ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -64,7 +64,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( } val realmConfiguration = RealmConfiguration.Builder() - .compactOnLaunch() + .compactOnLaunch(RealmCompactOnLaunch()) .directory(directory) .name(REALM_NAME) .apply { @@ -93,7 +93,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( return } - listOf(REALM_NAME, "$REALM_NAME.lock", "$REALM_NAME.note", "$REALM_NAME.management").forEach { file -> + listOf(REALM_NAME, "${REALM_NAME}.lock", "${REALM_NAME}.note", "${REALM_NAME}.management").forEach { file -> try { File(directory, file).deleteRecursively() } catch (e: Exception) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt new file mode 100644 index 0000000000..efcb181ecb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo036(realm: DynamicRealm) : RealmMigrator(realm, 36) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.create("LocalRoomSummaryEntity") + .addField(LocalRoomSummaryEntityFields.ROOM_ID, String::class.java) + .addPrimaryKey(LocalRoomSummaryEntityFields.ROOM_ID) + .setRequired(LocalRoomSummaryEntityFields.ROOM_ID, true) + .addField(LocalRoomSummaryEntityFields.CREATE_ROOM_PARAMS_STR, String::class.java) + .addRealmObjectField(LocalRoomSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt new file mode 100644 index 0000000000..fd8331e986 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.toJSONString + +internal open class LocalRoomSummaryEntity( + @PrimaryKey var roomId: String = "", + var roomSummaryEntity: RoomSummaryEntity? = null, + private var createRoomParamsStr: String? = null +) : RealmObject() { + + var createRoomParams: CreateRoomParams? + get() { + return CreateRoomParams.fromJson(createRoomParamsStr) + } + set(value) { + createRoomParamsStr = value?.toJSONString() + } + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index d131589dd1..b222bcb710 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit ReadReceiptEntity::class, RoomEntity::class, RoomSummaryEntity::class, + LocalRoomSummaryEntity::class, RoomTagEntity::class, SyncEntity::class, PendingThreePidEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt new file mode 100644 index 0000000000..527350bedc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields + +internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { + val query = realm.where() + if (roomId != null) { + query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId) + } + return query +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt index dc20549eb3..2e9c3303d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/tools/RealmDebugTools.kt @@ -19,16 +19,15 @@ package org.matrix.android.sdk.internal.database.tools import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.BuildConfig -import timber.log.Timber internal class RealmDebugTools( private val realmConfiguration: RealmConfiguration ) { /** - * Log info about the DB. + * Get info about the DB. */ - fun logInfo(baseName: String) { - buildString { + fun getInfo(baseName: String): String { + return buildString { append("\n$baseName Realm located at : ${realmConfiguration.realmDirectory}/${realmConfiguration.realmFileName}") if (BuildConfig.LOG_PRIVATE_DATA) { @@ -54,7 +53,6 @@ internal class RealmDebugTools( separator() } } - .let { Timber.i(it) } } private fun StringBuilder.separator() = append("\n==============================================") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt index 3f2e6fafc8..46479c3db6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt @@ -36,9 +36,9 @@ internal class DefaultDebugService @Inject constructor( realmConfigurationGlobal } - override fun logDbUsageInfo() { - RealmDebugTools(realmConfigurationAuth).logInfo("Auth") - RealmDebugTools(realmConfigurationGlobal).logInfo("Global") - sessionManager.getLastSession()?.logDbUsageInfo() + override fun getDbUsageInfo() = buildString { + append(RealmDebugTools(realmConfigurationAuth).getInfo("Auth")) + append(RealmDebugTools(realmConfigurationGlobal).getInfo("Global")) + append(sessionManager.getLastSession()?.getDbUsageInfo()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index 49713a1d7f..f2f8a5dc04 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -40,7 +40,7 @@ internal object MatrixModule { io = Dispatchers.IO, computation = Dispatchers.Default, main = Dispatchers.Main, - crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(), + crypto = createBackgroundHandler("Matrix-Crypto_Thread").asCoroutineDispatcher(), dmVerif = Executors.newSingleThreadExecutor().asCoroutineDispatcher() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt index 8f007f227c..0a737d5e64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.di import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageDefaultContent @@ -60,6 +62,12 @@ internal object MoshiProvider { .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE) ) .add(SerializeNulls.JSON_ADAPTER_FACTORY) + .add( + PolymorphicJsonAdapterFactory.of(ThreePid::class.java, "type") + .withSubtype(ThreePid.Email::class.java, "email") + .withSubtype(ThreePid.Msisdn::class.java, "msisdn") + .withDefaultValue(null) + ) .build() fun providesMoshi(): Moshi { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 57db187bdc..679c5085ef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -263,11 +263,11 @@ internal class DefaultSession @Inject constructor( } } - override fun logDbUsageInfo() { - RealmDebugTools(realmConfiguration).logInfo("Session") - RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto") - RealmDebugTools(realmConfigurationIdentity).logInfo("Identity") - RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") + override fun getDbUsageInfo() = buildString { + append(RealmDebugTools(realmConfiguration).getInfo("Session")) + append(RealmDebugTools(realmConfigurationCrypto).getInfo("Crypto")) + append(RealmDebugTools(realmConfigurationIdentity).getInfo("Identity")) + append(RealmDebugTools(realmConfigurationContentScanner).getInfo("ContentScanner")) } override fun getRealmConfigurations(): List { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index d01324a35f..1475b67276 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -43,9 +43,13 @@ import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAli import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomStateEventsTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask import org.matrix.android.sdk.internal.session.room.delete.DefaultDeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask @@ -213,6 +217,12 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + @Binds + abstract fun bindCreateLocalRoomStateEventsTask(task: DefaultCreateLocalRoomStateEventsTask): CreateLocalRoomStateEventsTask + + @Binds + abstract fun bindCreateRoomFromLocalRoomTask(task: DefaultCreateRoomFromLocalRoomTask): CreateRoomFromLocalRoomTask + @Binds abstract fun bindDeleteLocalRoomTask(task: DefaultDeleteLocalRoomTask): DeleteLocalRoomTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt new file mode 100644 index 0000000000..a9ff4970fe --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -0,0 +1,299 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import org.matrix.android.sdk.api.MatrixPatterns.getServerName +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.Content +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.LocalEcho +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +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.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.banOrDefault +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.inviteOrDefault +import org.matrix.android.sdk.api.session.room.model.kickOrDefault +import org.matrix.android.sdk.api.session.room.model.redactOrDefault +import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask.Params +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock +import javax.inject.Inject + +/** + * Generate a list of local state events from the given [CreateRoomBody]. + * The states events are generated according to the given configuration and following the matrix specification. + * This list reflects as much as possible a list of state events related to a real room configured and got from the server. + * + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ +internal interface CreateLocalRoomStateEventsTask : Task> { + data class Params(val createRoomBody: CreateRoomBody) +} + +internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( + @UserId private val myUserId: String, + private val userService: UserService, + private val clock: Clock, +) : CreateLocalRoomStateEventsTask { + + private lateinit var createRoomBody: CreateRoomBody + + override suspend fun execute(params: Params): List { + createRoomBody = params.createRoomBody + + // Build the list of the state events following the priorities from the matrix specification + // Changing the order of the events might break the correct display of the room on the client side + return buildList { + createRoomCreateEvent() + createRoomMemberEvents(listOf(myUserId)) + createRoomPowerLevelsEvent() + createRoomAliasEvent() + createRoomPresetEvents() + createRoomInitialStateEvents() + createRoomNameAndTopicStateEvents() + createRoomMemberEvents(createRoomBody.invitedUserIds.orEmpty()) + createRoomThreePidEvents() + createRoomDefaultEvents() + } + } + + /** + * Generate the create state event related to this room. + */ + private fun MutableList.createRoomCreateEvent() { + val roomCreateEvent = createLocalStateEvent( + type = EventType.STATE_ROOM_CREATE, + content = RoomCreateContent( + creator = myUserId, + roomVersion = createRoomBody.roomVersion, + type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String + + ).toContent(), + ) + add(roomCreateEvent) + } + + /** + * Generate the create state event related to the power levels using the given overridden values or the default values according to the specification. + * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels + */ + private fun MutableList.createRoomPowerLevelsEvent() { + val powerLevelsContent = createLocalStateEvent( + type = EventType.STATE_ROOM_POWER_LEVELS, + content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { + it.copy( + ban = it.banOrDefault(), + eventsDefault = it.eventsDefaultOrDefault(), + invite = it.inviteOrDefault(), + kick = it.kickOrDefault(), + redact = it.redactOrDefault(), + stateDefault = it.stateDefaultOrDefault(), + usersDefault = it.usersDefaultOrDefault(), + ) + }.toContent(), + ) + add(powerLevelsContent) + } + + /** + * Generate the local room member state events related to the given user ids, if any. + */ + private suspend fun MutableList.createRoomMemberEvents(userIds: List) { + val memberEvents = userIds + .mapNotNull { tryOrNull { userService.resolveUser(it) } } + .map { user -> + createLocalStateEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + isDirect = createRoomBody.isDirect.takeUnless { user.userId == myUserId }.orFalse(), + membership = if (user.userId == myUserId) Membership.JOIN else Membership.INVITE, + displayName = user.displayName, + avatarUrl = user.avatarUrl + ).toContent(), + stateKey = user.userId + ) + } + addAll(memberEvents) + } + + /** + * Generate the local state events related to the given third party invites, if any. + */ + private fun MutableList.createRoomThreePidEvents() { + createRoomBody.invite3pids.orEmpty().forEach { body -> + val localThirdPartyInviteEvent = createLocalStateEvent( + type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + content = LocalRoomThirdPartyInviteContent( + isDirect = createRoomBody.isDirect.orFalse(), + membership = Membership.INVITE, + displayName = body.address, + thirdPartyInvite = body.toThreePid() + ).toContent(), + ) + val thirdPartyInviteEvent = createLocalStateEvent( + type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, + content = RoomThirdPartyInviteContent( + displayName = body.address, + keyValidityUrl = null, + publicKey = null, + publicKeys = null + ).toContent(), + ) + add(localThirdPartyInviteEvent) + add(thirdPartyInviteEvent) + } + } + + /** + * Generate the local state event related to the given alias, if any. + */ + fun MutableList.createRoomAliasEvent() { + if (createRoomBody.roomAliasName != null) { + val canonicalAliasContent = createLocalStateEvent( + type = EventType.STATE_ROOM_CANONICAL_ALIAS, + content = RoomCanonicalAliasContent( + canonicalAlias = "${createRoomBody.roomAliasName}:${myUserId.getServerName()}" + ).toContent(), + ) + add(canonicalAliasContent) + } + } + + /** + * Generate the local state events related to the given [CreateRoomPreset]. + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ + private fun MutableList.createRoomPresetEvents() { + val preset = createRoomBody.preset ?: return + + var joinRules: RoomJoinRules? = null + var historyVisibility: RoomHistoryVisibility? = null + var guestAccess: GuestAccess? = null + when (preset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT, + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> { + joinRules = RoomJoinRules.INVITE + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.CanJoin + } + CreateRoomPreset.PRESET_PUBLIC_CHAT -> { + joinRules = RoomJoinRules.PUBLIC + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.Forbidden + } + } + + add(createLocalStateEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent())) + add(createLocalStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent())) + add(createLocalStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent())) + } + + /** + * Generate the local state events related to the given initial states, if any. + * The given initial state events override the potential existing ones of the same type. + */ + private fun MutableList.createRoomInitialStateEvents() { + val initialStates = createRoomBody.initialStates ?: return + + val initialStateEvents = initialStates.map { createLocalStateEvent(it.type, it.content, it.stateKey) } + // Erase existing events of the same type + removeAll { event -> event.type in initialStateEvents.map { it.type } } + // Add the initial state events to the list + addAll(initialStateEvents) + } + + /** + * Generate the local events related to the given room name and topic, if any. + */ + private fun MutableList.createRoomNameAndTopicStateEvents() { + if (createRoomBody.name != null) { + add(createLocalStateEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent())) + } + if (createRoomBody.topic != null) { + add(createLocalStateEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent())) + } + } + + /** + * Generate the local events which have not been set and are in that case provided by the server with default values. + * Default events: + * - m.room.history_visibility (https://spec.matrix.org/latest/client-server-api/#server-behaviour-5) + * - m.room.guest_access (https://spec.matrix.org/latest/client-server-api/#mroomguest_access) + */ + private fun MutableList.createRoomDefaultEvents() { + // HistoryVisibility + if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { + add( + createLocalStateEvent( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + content = RoomHistoryVisibilityContent(RoomHistoryVisibility.SHARED.value).toContent(), + ) + ) + } + // GuestAccess + if (none { it.type == EventType.STATE_ROOM_GUEST_ACCESS }) { + add( + createLocalStateEvent( + type = EventType.STATE_ROOM_GUEST_ACCESS, + content = RoomGuestAccessContent(GuestAccess.Forbidden.value).toContent(), + ) + ) + } + } + + /** + * Generate a local state event from the given parameters. + * + * @param type the event type, see [EventType] + * @param content the content of the event + * @param stateKey the stateKey, if any + * + * @return a local state event + */ + private fun createLocalStateEvent(type: String?, content: Content?, stateKey: String? = ""): Event { + return Event( + type = type, + senderId = myUserId, + stateKey = stateKey, + content = content, + originServerTs = clock.epochMillis(), + eventId = LocalEcho.createLocalEchoId() + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index d57491a4c8..03c2b2a47e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -21,26 +21,15 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.events.model.Content -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.LocalEcho -import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure -import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary -import org.matrix.android.sdk.api.session.user.UserService -import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -48,6 +37,7 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -56,7 +46,6 @@ import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater @@ -70,22 +59,22 @@ import javax.inject.Inject internal interface CreateLocalRoomTask : Task internal class DefaultCreateLocalRoomTask @Inject constructor( - @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomSummaryUpdater: RoomSummaryUpdater, @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, - private val userService: UserService, + private val cryptoService: DefaultCryptoService, private val clock: Clock, + private val createLocalRoomStateEventsTask: CreateLocalRoomStateEventsTask, ) : CreateLocalRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomBody = createRoomBodyBuilder.build(params.withDefault()) + val createRoomBody = createRoomBodyBuilder.build(params) val roomId = RoomLocalEcho.createLocalEchoId() monarchy.awaitTransaction { realm -> createLocalRoomEntity(realm, roomId, createRoomBody) - createLocalRoomSummaryEntity(realm, roomId, createRoomBody) + createLocalRoomSummaryEntity(realm, roomId, params, createRoomBody) } // Wait for room to be created in DB @@ -114,14 +103,29 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } } - private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { - val otherUserId = createRoomBody.getDirectUserId() - if (otherUserId != null) { - RoomSummaryEntity.getOrCreate(realm, roomId).apply { + private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomParams: CreateRoomParams, createRoomBody: CreateRoomBody) { + // Create the room summary entity + val roomSummaryEntity = realm.createObject(roomId).apply { + val otherUserId = createRoomBody.getDirectUserId() + if (otherUserId != null) { isDirect = true directUserId = otherUserId } } + + // Update the createRoomParams from the potential feature preset before saving + createRoomParams.featurePreset?.let { featurePreset -> + featurePreset.updateRoomParams(createRoomParams) + createRoomParams.initialStates.addAll(featurePreset.setupInitialStates().orEmpty()) + } + + // Create a LocalRoomSummaryEntity decorated by the related RoomSummaryEntity and the updated CreateRoomParams + realm.createObject(roomId).also { + it.roomSummaryEntity = roomSummaryEntity + it.createRoomParams = createRoomParams + } + + // Update the RoomSummaryEntity by simulating a fake sync response roomSummaryUpdater.update( realm = realm, roomId = roomId, @@ -150,7 +154,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( isLastForward = true } - val eventList = createLocalRoomEvents(createRoomBody) + val eventList = createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(createRoomBody)) val roomMemberContentsByUser = HashMap() for (event in eventList) { @@ -169,6 +173,9 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent() roomMemberEventHandler.handle(realm, roomId, event, false) } + + // Give info to crypto module + cryptoService.onStateEvent(roomId, event) } roomMemberContentsByUser.getOrPut(event.senderId) { @@ -187,81 +194,4 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( return chunkEntity } - - /** - * Build the list of the events related to the room creation params. - * - * @param createRoomBody the room creation params - * - * @return the list of events - */ - private suspend fun createLocalRoomEvents(createRoomBody: CreateRoomBody): List { - val myUser = userService.getUser(userId) ?: User(userId) - val invitedUsers = createRoomBody.invitedUserIds.orEmpty() - .mapNotNull { tryOrNull { userService.resolveUser(it) } } - - val createRoomEvent = createLocalEvent( - type = EventType.STATE_ROOM_CREATE, - content = RoomCreateContent( - creator = userId - ).toContent() - ) - val myRoomMemberEvent = createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - membership = Membership.JOIN, - displayName = myUser.displayName, - avatarUrl = myUser.avatarUrl - ).toContent(), - stateKey = userId - ) - val roomMemberEvents = invitedUsers.map { - createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - isDirect = createRoomBody.isDirect.orFalse(), - membership = Membership.INVITE, - displayName = it.displayName, - avatarUrl = it.avatarUrl - ).toContent(), - stateKey = it.userId - ) - } - - return buildList { - add(createRoomEvent) - add(myRoomMemberEvent) - addAll(createRoomBody.initialStates.orEmpty().map { createLocalEvent(it.type, it.content, it.stateKey) }) - addAll(roomMemberEvents) - } - } - - /** - * Generate a local event from the given parameters. - * - * @param type the event type, see [EventType] - * @param content the content of the Event - * @param stateKey the stateKey, if any - * - * @return a fake event - */ - private fun createLocalEvent(type: String?, content: Content?, stateKey: String? = ""): Event { - return Event( - type = type, - senderId = userId, - stateKey = stateKey, - content = content, - originServerTs = clock.epochMillis(), - eventId = LocalEcho.createLocalEchoId() - ) - } - - /** - * Setup default values to the CreateRoomParams as the room is created locally (the default values will not be defined by the server). - */ - private fun CreateRoomParams.withDefault() = this.apply { - if (visibility == null) visibility = RoomDirectoryVisibility.PRIVATE - if (historyVisibility == null) historyVisibility = RoomHistoryVisibility.SHARED - if (guestAccess == null) guestAccess = GuestAccess.Forbidden - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index b326c3618c..17e1aba6f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody /** @@ -119,7 +120,13 @@ internal data class CreateRoomBody( */ @Json(name = "room_version") val roomVersion: String? -) +) { + companion object { + fun fromJson(json: String?): CreateRoomBody? { + return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomBody::class.java).fromJson(it) } + } + } +} /** * Tells if the created room can be a direct chat one. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt new file mode 100644 index 0000000000..02538a5cc3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import com.zhuinden.monarchy.Monarchy +import io.realm.kotlin.where +import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock +import java.util.UUID +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Create a room on the server from a local room. + * The configuration of the local room will be use to configure the new room. + * The potential local room members will also be invited to this new room. + * + * A local tombstone event will be created to indicate that the local room has been replacing by the new one. + */ +internal interface CreateRoomFromLocalRoomTask : Task { + data class Params(val localRoomId: String) +} + +internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( + @UserId private val userId: String, + @SessionDatabase private val monarchy: Monarchy, + private val createRoomTask: CreateRoomTask, + private val stateEventDataSource: StateEventDataSource, + private val clock: Clock, +) : CreateRoomFromLocalRoomTask { + + private val realmConfiguration + get() = monarchy.realmConfiguration + + override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { + val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + ?.content.toModel() + ?.replacementRoomId + + if (replacementRoomId != null) { + return replacementRoomId + } + + var createRoomParams: CreateRoomParams? = null + var isEncrypted = false + monarchy.doWithRealm { realm -> + realm.where() + .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId) + .findFirst() + ?.let { + createRoomParams = it.createRoomParams + isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse() + } + } + val roomId = createRoomTask.execute(createRoomParams!!) + + try { + // Wait for all the room events before triggering the replacement room + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0) + } + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + EventEntity.whereRoomId(realm, roomId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY) + } + if (isEncrypted) { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + EventEntity.whereRoomId(realm, roomId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION) + } + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(roomId) + } + + createTombstoneEvent(params, roomId) + return roomId + } + + /** + * Create a Tombstone event to indicate that the local room has been replaced by a new one. + */ + private suspend fun createTombstoneEvent(params: CreateRoomFromLocalRoomTask.Params, roomId: String) { + val now = clock.epochMillis() + val event = Event( + type = EventType.STATE_ROOM_TOMBSTONE, + senderId = userId, + originServerTs = now, + stateKey = "", + eventId = UUID.randomUUID().toString(), + content = RoomTombstoneContent( + replacementRoomId = roomId + ).toContent() + ) + monarchy.awaitTransaction { realm -> + val eventEntity = event.toEntity(params.localRoomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) + if (event.stateKey != null && event.type != null && event.eventId != null) { + CurrentStateEventEntity.getOrCreate(realm, params.localRoomId, event.stateKey, event.type).apply { + eventId = event.eventId + root = eventEntity + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index d76640573f..e558d34ff9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -54,8 +54,7 @@ internal class DefaultCreateRoomTask @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration, + @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val globalErrorReceiver: GlobalErrorReceiver, private val clock: Clock, @@ -71,7 +70,6 @@ internal class DefaultCreateRoomTask @Inject constructor( } val createRoomBody = createRoomBodyBuilder.build(params) - val createRoomResponse = try { executeRequest(globalErrorReceiver) { roomAPI.createRoom(createRoomBody) @@ -90,6 +88,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } throw throwable } + val roomId = createRoomResponse.roomId // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt new file mode 100644 index 0000000000..617ed35326 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.model.Membership + +/** + * Class representing the EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE state event content + * This class is only used to store the third party invite data of a local room. + */ +@JsonClass(generateAdapter = true) +internal data class LocalRoomThirdPartyInviteContent( + @Json(name = "membership") val membership: Membership, + @Json(name = "displayname") val displayName: String? = null, + @Json(name = "is_direct") val isDirect: Boolean = false, + @Json(name = "third_party_invite") val thirdPartyInvite: ThreePid? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt index 936c94e520..49951d2da0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -70,6 +71,9 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( RoomEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - RoomEntity - delete ${it.size} entries") } ?.deleteAllFromRealm() + LocalRoomSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - LocalRoomSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() } } else { Timber.i("## DeleteLocalRoomTask - Failed to remove room with id $roomId: not a local room") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt index 3141c052c3..d7b78faea8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.membership.threepid import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.internal.auth.data.ThreePidMedium @JsonClass(generateAdapter = true) internal data class ThreePidInviteBody( @@ -43,3 +45,9 @@ internal data class ThreePidInviteBody( @Json(name = "address") val address: String ) + +internal fun ThreePidInviteBody.toThreePid() = when (medium) { + ThreePidMedium.EMAIL -> ThreePid.Email(address) + ThreePidMedium.MSISDN -> ThreePid.Msisdn(address) + else -> null +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt index 2afca6e554..801ff0ec79 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt @@ -53,7 +53,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo @Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon @Inject lateinit var localEchoRepository: LocalEchoRepository - override fun doOnError(params: Params): Result { + override fun doOnError(params: Params, failureMessage: String): Result { params.localEchoIds.forEach { localEchoIds -> localEchoRepository.updateSendState( eventId = localEchoIds.eventId, @@ -63,7 +63,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo ) } - return super.doOnError(params) + return super.doOnError(params, failureMessage) } override fun injectWith(injector: SessionComponent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index 51107c9655..55363a7251 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -55,7 +55,7 @@ internal class EventSenderProcessorThread @Inject constructor( private val queuedTaskFactory: QueuedTaskFactory, private val taskExecutor: TaskExecutor, private val memento: QueueMemento -) : Thread("SENDER_THREAD_SID_${sessionParams.credentials.sessionId()}"), EventSenderProcessor { +) : Thread("Matrix-SENDER_THREAD_SID_${sessionParams.credentials.sessionId()}"), EventSenderProcessor { private fun markAsManaged(task: QueuedTask) { memento.track(task) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 59c9de2932..ecc452edb3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -16,10 +16,12 @@ package org.matrix.android.sdk.internal.session.room.state +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.task.Task import timber.log.Timber import javax.inject.Inject @@ -35,28 +37,40 @@ internal interface SendStateTask : Task { internal class DefaultSendStateTask @Inject constructor( private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask, ) : SendStateTask { override suspend fun execute(params: SendStateTask.Params): String { return executeRequest(globalErrorReceiver) { - val response = if (params.stateKey.isEmpty()) { - roomAPI.sendStateEvent( - roomId = params.roomId, - stateEventType = params.eventType, - params = params.body - ) + if (RoomLocalEcho.isLocalEchoId(params.roomId)) { + // Room is local, so create a real one and send the event to this new room + createRoomAndSendEvent(params) } else { - roomAPI.sendStateEvent( - roomId = params.roomId, - stateEventType = params.eventType, - stateKey = params.stateKey, - params = params.body - ) - } - response.eventId.also { - Timber.d("State event: $it just sent in room ${params.roomId}") + val response = if (params.stateKey.isEmpty()) { + roomAPI.sendStateEvent( + roomId = params.roomId, + stateEventType = params.eventType, + params = params.body + ) + } else { + roomAPI.sendStateEvent( + roomId = params.roomId, + stateEventType = params.eventType, + stateKey = params.stateKey, + params = params.body + ) + } + response.eventId.also { + Timber.d("State event: $it just sent in room ${params.roomId}") + } } } } + + private suspend fun createRoomAndSendEvent(params: SendStateTask.Params): String { + val roomId = createRoomFromLocalRoomTask.execute(CreateRoomFromLocalRoomTask.Params(params.roomId)) + Timber.d("State event: convert local room (${params.roomId}) to existing room ($roomId) before sending the event.") + return execute(params.copy(roomId = roomId)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 4eaac67e5a..c380ccf14f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -76,7 +76,7 @@ internal class DefaultTimeline( ) : Timeline { companion object { - val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread") + val BACKGROUND_HANDLER = createBackgroundHandler("Matrix-DefaultTimeline_Thread") } override val timelineID = UUID.randomUUID().toString() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt index 691dd7b20d..76c3c38abf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/DefaultSyncService.kt @@ -73,6 +73,8 @@ internal class DefaultSyncService @Inject constructor( override fun getSyncState() = getSyncThread().currentState() + override fun isSyncThreadAlive() = getSyncThread().isAlive + override fun getSyncRequestStateFlow() = syncRequestStateTracker.syncRequestState override fun hasAlreadySynced(): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 24a60a80da..b47b215655 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -62,7 +62,7 @@ internal class SyncThread @Inject constructor( private val backgroundDetectionObserver: BackgroundDetectionObserver, private val activeCallHandler: ActiveCallHandler, private val lightweightSettingsStorage: DefaultLightweightSettingsStorage -) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { +) : Thread("Matrix-SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.Idle private var liveState = MutableLiveData(state) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index 0cc7944d58..a04bc74628 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.WorkerParamsFactory +import org.matrix.android.sdk.internal.worker.startChain import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -136,6 +137,7 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, .setConstraints(WorkManagerProvider.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .setInputData(data) + .startChain(true) .build() workManagerProvider.workManager .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt index 901d0eca8f..dea5f131b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt @@ -49,13 +49,13 @@ internal class DefaultBackgroundDetectionObserver : BackgroundDetectionObserver } override fun onStart(owner: LifecycleOwner) { - Timber.v("App returning to foreground…") + Timber.d("App returning to foreground…") isInBackground = false listeners.forEach { it.onMoveToForeground() } } override fun onStop(owner: LifecycleOwner) { - Timber.v("App going to background…") + Timber.d("App going to background…") isInBackground = true listeners.forEach { it.onMoveToBackground() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt index 030f51428b..b98b61c9f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt @@ -55,14 +55,16 @@ internal abstract class SessionSafeCoroutineWorker( // Make sure to inject before handling error as you may need some dependencies to process them. injectWith(sessionComponent) - if (params.lastFailureMessage != null) { - // Forward error to the next workers - doOnError(params) - } else { - doSafeWork(params) + + when (val lastFailureMessage = params.lastFailureMessage) { + null -> doSafeWork(params) + else -> { + // Forward error to the next workers + doOnError(params, lastFailureMessage) + } } } catch (throwable: Throwable) { - buildErrorResult(params, throwable.localizedMessage ?: "error") + buildErrorResult(params, "${throwable::class.java.name}: ${throwable.localizedMessage ?: "N/A error message"}") } } @@ -89,10 +91,10 @@ internal abstract class SessionSafeCoroutineWorker( * This is called when the input parameters are correct, but contain an error from the previous worker. */ @CallSuper - open fun doOnError(params: PARAM): Result { + open fun doOnError(params: PARAM, failureMessage: String): Result { // Forward the error return Result.success(inputData) - .also { Timber.e("Work cancelled due to input error from parent") } + .also { Timber.e("Work cancelled due to input error from parent: $failureMessage") } } companion object { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt new file mode 100644 index 0000000000..1c2cf293b6 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotBeNull +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns.getServerName +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.extensions.orFalse +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.content.EncryptionEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +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.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_EMAIL +import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_MSISDN +import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid +import org.matrix.android.sdk.internal.util.time.DefaultClock + +private const val MY_USER_ID = "my-user-id" +private const val MY_USER_DISPLAY_NAME = "my-user-display-name" +private const val MY_USER_AVATAR = "my-user-avatar" + +@ExperimentalCoroutinesApi +internal class DefaultCreateLocalRoomStateEventsTaskTest { + + private val clock = DefaultClock() + private val userService = mockk() + + private val defaultCreateLocalRoomStateEventsTask = DefaultCreateLocalRoomStateEventsTask( + myUserId = MY_USER_ID, + userService = userService, + clock = clock + ) + + lateinit var createRoomBody: CreateRoomBody + + @Before + fun setup() { + createRoomBody = mockk { + every { roomVersion } returns null + every { creationContent } returns null + every { roomAliasName } returns null + every { topic } returns null + every { name } returns null + every { powerLevelContentOverride } returns null + every { initialStates } returns null + every { invite3pids } returns null + every { preset } returns null + every { isDirect } returns null + every { invitedUserIds } returns null + } + coEvery { userService.resolveUser(any()) } answers { User(firstArg()) } + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room create state event`() = runTest { + // Given + val aRoomVersion = "a_room_version" + + every { createRoomBody.roomVersion } returns aRoomVersion + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomCreateEvent = result.find { it.type == EventType.STATE_ROOM_CREATE } + val roomCreateContent = roomCreateEvent?.content.toModel() + + roomCreateContent?.creator shouldBeEqualTo MY_USER_ID + roomCreateContent?.roomVersion shouldBeEqualTo aRoomVersion + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct name and topic state events`() = runTest { + // Given + val aRoomName = "a_room_name" + val aRoomTopic = "a_room_topic" + + every { createRoomBody.name } returns aRoomName + every { createRoomBody.topic } returns aRoomTopic + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomNameEvent = result.find { it.type == EventType.STATE_ROOM_NAME } + val roomTopicEvent = result.find { it.type == EventType.STATE_ROOM_TOPIC } + + roomNameEvent?.content.toModel()?.name shouldBeEqualTo aRoomName + roomTopicEvent?.content.toModel()?.topic shouldBeEqualTo aRoomTopic + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room member events`() = runTest { + // Given + data class RoomMember(val user: User, val membership: Membership) + + val aRoomMemberList: List = listOf( + RoomMember(User(MY_USER_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR), Membership.JOIN), + RoomMember(User("userA_id", "userA_display_name", "userA_avatar"), Membership.INVITE), + RoomMember(User("userB_id", "userB_display_name", "userB_avatar"), Membership.INVITE) + ) + + every { createRoomBody.invitedUserIds } returns aRoomMemberList.filter { it.membership == Membership.INVITE }.map { it.user.userId } + coEvery { userService.resolveUser(any()) } answers { + aRoomMemberList.map { it.user }.find { it.userId == firstArg() } ?: User(firstArg()) + } + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomMemberEvents = result.filter { it.type == EventType.STATE_ROOM_MEMBER } + + roomMemberEvents.map { it.stateKey } shouldBeEqualTo aRoomMemberList.map { it.user.userId } + roomMemberEvents.forEach { event -> + val roomMemberContent = event.content.toModel() + val roomMember = aRoomMemberList.find { it.user.userId == event.stateKey } + + roomMember.shouldNotBeNull() + roomMemberContent?.avatarUrl shouldBeEqualTo roomMember.user.avatarUrl + roomMemberContent?.displayName shouldBeEqualTo roomMember.user.displayName + roomMemberContent?.membership shouldBeEqualTo roomMember.membership + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct power levels event`() = runTest { + // Given + val aPowerLevelsContent = PowerLevelsContent( + ban = 1, + kick = 2, + invite = 3, + redact = 4, + eventsDefault = 5, + events = null, + usersDefault = 6, + users = null, + stateDefault = 7, + notifications = null + ) + + every { createRoomBody.powerLevelContentOverride } returns aPowerLevelsContent + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomPowerLevelsEvent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS } + roomPowerLevelsEvent?.content.toModel() shouldBeEqualTo aPowerLevelsContent + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct canonical alias event`() = runTest { + // Given + val aRoomAlias = "a_room_alias" + val expectedCanonicalAlias = "$aRoomAlias:${MY_USER_ID.getServerName()}" + + every { createRoomBody.roomAliasName } returns aRoomAlias + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomPowerLevelsEvent = result.find { it.type == EventType.STATE_ROOM_CANONICAL_ALIAS } + roomPowerLevelsEvent?.content.toModel()?.canonicalAlias shouldBeEqualTo expectedCanonicalAlias + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct preset related events`() = runTest { + data class ExpectedResult(val joinRules: RoomJoinRules, val historyVisibility: RoomHistoryVisibility, val guestAccess: GuestAccess) + data class Case(val preset: CreateRoomPreset, val expectedResult: ExpectedResult) + + CreateRoomPreset.values().forEach { aRoomPreset -> + // Given + val case = when (aRoomPreset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT -> Case( + CreateRoomPreset.PRESET_PRIVATE_CHAT, + ExpectedResult(RoomJoinRules.INVITE, RoomHistoryVisibility.SHARED, GuestAccess.CanJoin) + ) + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> Case( + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT, + ExpectedResult(RoomJoinRules.INVITE, RoomHistoryVisibility.SHARED, GuestAccess.CanJoin) + ) + CreateRoomPreset.PRESET_PUBLIC_CHAT -> Case( + CreateRoomPreset.PRESET_PUBLIC_CHAT, + ExpectedResult(RoomJoinRules.PUBLIC, RoomHistoryVisibility.SHARED, GuestAccess.Forbidden) + ) + } + every { createRoomBody.preset } returns case.preset + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.find { it.type == EventType.STATE_ROOM_JOIN_RULES } + ?.content.toModel() + ?.joinRules shouldBeEqualTo case.expectedResult.joinRules + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel() + ?.historyVisibility shouldBeEqualTo case.expectedResult.historyVisibility + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel() + ?.guestAccess shouldBeEqualTo case.expectedResult.guestAccess + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the initial state events`() = runTest { + // Given + val aListOfInitialStateEvents = listOf( + Event( + type = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + content = EncryptionEventContent(MXCRYPTO_ALGORITHM_MEGOLM).toContent() + ), + Event( + type = "a_custom_type", + content = mapOf("a_custom_map_to_integer" to 42), + stateKey = "a_state_key" + ), + Event( + type = "another_custom_type", + content = mapOf("a_custom_map_to_boolean" to false), + stateKey = "another_state_key" + ) + ) + + every { createRoomBody.initialStates } returns aListOfInitialStateEvents + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + aListOfInitialStateEvents.forEach { expected -> + val found = result.find { it.type == expected.type } + found.shouldNotBeNull() + found.content shouldBeEqualTo expected.content + found.stateKey shouldBeEqualTo expected.stateKey + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct third party invite events`() = runTest { + // Given + val aListOfThreePids = listOf( + ThreePid.Email("bob@matrix.org"), + ThreePid.Msisdn("+11111111111"), + ThreePid.Email("alice@matrix.org"), + ThreePid.Msisdn("+22222222222"), + ) + val aListOf3pids = aListOfThreePids.mapIndexed { index, threePid -> + ThreePidInviteBody( + idServer = "an_id_server_$index", + idAccessToken = "an_id_access_token_$index", + medium = when (threePid) { + is ThreePid.Email -> MEDIUM_EMAIL + is ThreePid.Msisdn -> MEDIUM_MSISDN + }, + address = threePid.value + ) + } + every { createRoomBody.invite3pids } returns aListOf3pids + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val thirdPartyInviteEvents = result.filter { it.type == EventType.STATE_ROOM_THIRD_PARTY_INVITE } + val thirdPartyInviteContents = thirdPartyInviteEvents.map { it.content.toModel() } + val localThirdPartyInviteEvents = result.filter { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + val localThirdPartyInviteContents = localThirdPartyInviteEvents.map { it.content.toModel() } + + thirdPartyInviteEvents.size shouldBeEqualTo aListOf3pids.size + localThirdPartyInviteEvents.size shouldBeEqualTo aListOf3pids.size + + aListOf3pids.forEach { expected -> + thirdPartyInviteContents.find { it?.displayName == expected.address }.shouldNotBeNull() + + val localThirdPartyInviteContent = localThirdPartyInviteContents.find { it?.thirdPartyInvite == expected.toThreePid() } + localThirdPartyInviteContent.shouldNotBeNull() + localThirdPartyInviteContent.membership shouldBeEqualTo Membership.INVITE + localThirdPartyInviteContent.isDirect shouldBeEqualTo createRoomBody.isDirect.orFalse() + localThirdPartyInviteContent.displayName shouldBeEqualTo expected.address + } + } + + @Test + fun `given a CreateRoomBody with default values when execute then the resulting list of events is correct`() = runTest { + // Given + // map of expected event types to occurrences + val expectedEventTypes = mapOf( + EventType.STATE_ROOM_CREATE to 1, + EventType.STATE_ROOM_POWER_LEVELS to 1, + EventType.STATE_ROOM_MEMBER to 1, + EventType.STATE_ROOM_GUEST_ACCESS to 1, + EventType.STATE_ROOM_HISTORY_VISIBILITY to 1, + ) + coEvery { userService.resolveUser(any()) } answers { + if (firstArg() == MY_USER_ID) User(MY_USER_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR) else User(firstArg()) + } + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.size shouldBeEqualTo expectedEventTypes.values.sum() + result.map { it.type }.toSet() shouldBeEqualTo expectedEventTypes.keys + + // Room create + result.find { it.type == EventType.STATE_ROOM_CREATE }.shouldNotBeNull() + // Room member + result.singleOrNull { it.type == EventType.STATE_ROOM_MEMBER }?.stateKey shouldBeEqualTo MY_USER_ID + // Power levels + val powerLevelsContent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS }?.content.toModel() + powerLevelsContent.shouldNotBeNull() + powerLevelsContent.ban shouldBeEqualTo Role.Moderator.value + powerLevelsContent.kick shouldBeEqualTo Role.Moderator.value + powerLevelsContent.invite shouldBeEqualTo Role.Moderator.value + powerLevelsContent.redact shouldBeEqualTo Role.Moderator.value + powerLevelsContent.eventsDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.usersDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.stateDefault shouldBeEqualTo Role.Moderator.value + // Guest access + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel()?.guestAccess shouldBeEqualTo GuestAccess.Forbidden + // History visibility + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel()?.historyVisibility shouldBeEqualTo RoomHistoryVisibility.SHARED + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events is correctly ordered with the right values`() = runTest { + // Given + val expectedIsDirect = true + val expectedHistoryVisibility = RoomHistoryVisibility.WORLD_READABLE + + every { createRoomBody.roomVersion } returns "a_room_version" + every { createRoomBody.roomAliasName } returns "a_room_alias_name" + every { createRoomBody.name } returns "a_name" + every { createRoomBody.topic } returns "a_topic" + every { createRoomBody.powerLevelContentOverride } returns PowerLevelsContent( + ban = 1, + kick = 2, + invite = 3, + redact = 4, + eventsDefault = 5, + events = null, + usersDefault = 6, + users = null, + stateDefault = 7, + notifications = null + ) + every { createRoomBody.invite3pids } returns listOf( + ThreePidInviteBody( + idServer = "an_id_server", + idAccessToken = "an_id_access_token", + medium = MEDIUM_EMAIL, + address = "an_email@example.org" + ) + ) + every { createRoomBody.preset } returns CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + every { createRoomBody.initialStates } returns listOf( + Event(type = "a_custom_type", stateKey = ""), + // override the value from the preset + Event( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + content = RoomHistoryVisibilityContent(expectedHistoryVisibility.value).toContent() + ) + ) + every { createRoomBody.isDirect } returns expectedIsDirect + every { createRoomBody.invitedUserIds } returns listOf("a_user_id") + + val orderedExpectedEventType = listOf( + EventType.STATE_ROOM_CREATE, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_POWER_LEVELS, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_JOIN_RULES, + EventType.STATE_ROOM_GUEST_ACCESS, + "a_custom_type", + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, + ) + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.map { it.type } shouldBeEqualTo orderedExpectedEventType + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel()?.historyVisibility shouldBeEqualTo expectedHistoryVisibility + result.lastOrNull { it.type == EventType.STATE_ROOM_MEMBER } + ?.content.toModel()?.isDirect shouldBeEqualTo expectedIsDirect + result.lastOrNull { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + ?.content.toModel()?.isDirect shouldBeEqualTo expectedIsDirect + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt new file mode 100644 index 0000000000..d3732363b5 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.realm.kotlin.where +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.util.time.DefaultClock +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource + +private const val A_LOCAL_ROOM_ID = "local.a-local-room-id" +private const val AN_EXISTING_ROOM_ID = "an-existing-room-id" +private const val A_ROOM_ID = "a-room-id" +private const val MY_USER_ID = "my-user-id" + +@ExperimentalCoroutinesApi +internal class DefaultCreateRoomFromLocalRoomTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val clock = DefaultClock() + private val createRoomTask = mockk() + private val fakeStateEventDataSource = FakeStateEventDataSource() + + private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask( + userId = MY_USER_ID, + monarchy = fakeMonarchy.instance, + createRoomTask = createRoomTask, + stateEventDataSource = fakeStateEventDataSource.instance, + clock = clock + ) + + @Before + fun setup() { + mockkStatic("org.matrix.android.sdk.internal.database.RealmQueryLatchKt") + coJustRun { awaitNotEmptyResult(realmConfiguration = any(), timeoutMillis = any(), builder = any()) } + + mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") + coEvery { any().copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, any()) } answers { firstArg() } + + mockkStatic("org.matrix.android.sdk.internal.database.query.CurrentStateEventEntityQueriesKt") + every { CurrentStateEventEntity.getOrCreate(fakeMonarchy.fakeRealm.instance, any(), any(), any()) } answers { + CurrentStateEventEntity(roomId = arg(2), stateKey = arg(3), type = arg(4)) + } + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a local room id when execute then the existing room id is kept`() = runTest { + // Given + givenATombstoneEvent( + Event( + roomId = A_LOCAL_ROOM_ID, + type = EventType.STATE_ROOM_TOMBSTONE, + stateKey = "", + content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent() + ) + ) + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + val result = defaultCreateRoomFromLocalRoomTask.execute(params) + + // Then + verifyTombstoneEvent(AN_EXISTING_ROOM_ID) + result shouldBeEqualTo AN_EXISTING_ROOM_ID + } + + @Test + fun `given a local room id when execute then it is correctly executed`() = runTest { + // Given + val aCreateRoomParams = mockk() + val aLocalRoomSummaryEntity = mockk { + every { roomSummaryEntity } returns mockk(relaxed = true) + every { createRoomParams } returns aCreateRoomParams + } + givenATombstoneEvent(null) + givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity) + + coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + val result = defaultCreateRoomFromLocalRoomTask.execute(params) + + // Then + verifyTombstoneEvent(null) + // CreateRoomTask has been called with the initial CreateRoomParams + coVerify { createRoomTask.execute(aCreateRoomParams) } + // The resulting roomId matches the roomId returned by the createRoomTask + result shouldBeEqualTo A_ROOM_ID + // A tombstone state event has been created + coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) } + } + + private fun givenATombstoneEvent(event: Event?) { + fakeStateEventDataSource.givenGetStateEventReturns(event) + } + + private fun givenALocalRoomSummaryEntity(localRoomSummaryEntity: LocalRoomSummaryEntity) { + every { + fakeMonarchy.fakeRealm.instance + .where() + .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID) + .findFirst() + } returns localRoomSummaryEntity + } + + private fun verifyTombstoneEvent(expectedRoomId: String?) { + fakeStateEventDataSource.verifyGetStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + fakeStateEventDataSource.instance.getStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + ?.content.toModel() + ?.replacementRoomId shouldBeEqualTo expectedRoomId + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt index 588bfaa979..d51ed77399 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent @@ -69,7 +70,7 @@ class DefaultGetActiveBeaconInfoForUserTaskTest { fakeStateEventDataSource.verifyGetStateEvent( roomId = params.roomId, eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - stateKey = A_USER_ID + stateKey = QueryStringValue.Equals(A_USER_ID) ) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt index d77084fe3b..2d501f12af 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction internal class FakeMonarchy { val instance = mockk() - private val fakeRealm = FakeRealm() + val fakeRealm = FakeRealm() init { mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") @@ -42,6 +42,12 @@ internal class FakeMonarchy { } coAnswers { secondArg Any>().invoke(fakeRealm.instance) } + coEvery { + instance.doWithRealm(any()) + } coAnswers { + firstArg().doWithRealm(fakeRealm.instance) + } + every { instance.realmConfiguration } returns mockk() } inline fun givenWhere(): RealmQuery { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt index ca03316fa7..ebb2a1d7a0 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.test.fakes import io.mockk.every import io.mockk.mockk import io.mockk.verify -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -37,12 +37,12 @@ internal class FakeStateEventDataSource { } returns event } - fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: String) { + fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: QueryStateEventValue) { verify { instance.getStateEvent( roomId = roomId, eventType = eventType, - stateKey = QueryStringValue.Equals(stateKey) + stateKey = stateKey ) } } diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index b12f15fa5d..b4d7ebae1f 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -185,3 +185,6 @@ System\.currentTimeMillis\(\)===2 onCreateOptionsMenu onOptionsItemSelected onPrepareOptionsMenu + +### Suspicious String template. Please check that the string template will behave as expected, i.e. the class field and not the whole object will be used. For instance `Timber.d("$event.type")` is not correct, you should write `Timber.d("${event.type}")`. In the former the whole event content will be logged, since it's a data class. If this is expected (i.e. to fix false positive), please add explicit curly braces (`{` and `}`) around the variable, for instance `"elementLogs.${i}.txt"` +\$[a-zA-Z_]\w*\??\.[a-zA-Z_] diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json index a1b944a7eb..41465a442f 100644 --- a/tools/emojis/emoji_picker_datasource_formatted.json +++ b/tools/emojis/emoji_picker_datasource_formatted.json @@ -2634,7 +2634,8 @@ "mask", "sick", "ill", - "disease" + "disease", + "covid" ] }, "face-with-thermometer": { @@ -2647,7 +2648,8 @@ "thermometer", "temperature", "cold", - "fever" + "fever", + "covid" ] }, "face-with-headbandage": { @@ -4481,7 +4483,9 @@ "hope", "wish", "namaste", - "highfive" + "highfive", + "thank you", + "appreciate" ] }, "writing-hand": { @@ -9581,7 +9585,8 @@ "amoeba", "bacteria", "virus", - "germs" + "germs", + "covid" ] }, "bouquet": { @@ -10260,7 +10265,9 @@ "baguette", "bread", "food", - "french" + "french", + "france", + "bakery" ] }, "flatbread": { @@ -10272,7 +10279,8 @@ "naan", "pita", "flour", - "food" + "food", + "bakery" ] }, "pretzel": { @@ -10282,7 +10290,9 @@ "twisted", "convoluted", "food", - "bread" + "bread", + "germany", + "bakery" ] }, "bagel": { @@ -10293,7 +10303,8 @@ "breakfast", "schmear", "food", - "bread" + "bread", + "jewish" ] }, "pancakes": { @@ -10306,7 +10317,8 @@ "hotcake", "pancake", "flapjacks", - "hotcakes" + "hotcakes", + "brunch" ] }, "waffle": { @@ -10316,7 +10328,8 @@ "breakfast", "indecisive", "iron", - "food" + "food", + "brunch" ] }, "cheese-wedge": { @@ -10325,7 +10338,8 @@ "j": [ "cheese", "food", - "chadder" + "chadder", + "swiss" ] }, "meat-on-bone": { @@ -10376,7 +10390,8 @@ "food", "meat", "pork", - "pig" + "pig", + "brunch" ] }, "hamburger": { @@ -10400,7 +10415,8 @@ "fries", "chips", "snack", - "fast food" + "fast food", + "potato" ] }, "pizza": { @@ -10410,7 +10426,8 @@ "cheese", "slice", "food", - "party" + "party", + "italy" ] }, "hot-dog": { @@ -10420,7 +10437,8 @@ "frankfurter", "hotdog", "sausage", - "food" + "food", + "america" ] }, "sandwich": { @@ -10429,7 +10447,9 @@ "j": [ "bread", "food", - "lunch" + "lunch", + "toast", + "bakery" ] }, "taco": { @@ -10468,7 +10488,8 @@ "food", "gyro", "kebab", - "stuffed" + "stuffed", + "mediterranean" ] }, "falafel": { @@ -10477,7 +10498,8 @@ "j": [ "chickpea", "meatball", - "food" + "food", + "mediterranean" ] }, "egg": { @@ -10498,7 +10520,8 @@ "frying", "pan", "food", - "kitchen" + "kitchen", + "skillet" ] }, "shallow-pan-of-food": { @@ -10510,7 +10533,8 @@ "paella", "pan", "shallow", - "cooking" + "cooking", + "skillet" ] }, "pot-of-food": { @@ -10521,7 +10545,8 @@ "stew", "food", "meat", - "soup" + "soup", + "hot pot" ] }, "fondue": { @@ -10556,7 +10581,8 @@ "green", "salad", "healthy", - "lettuce" + "lettuce", + "vegetable" ] }, "popcorn": { @@ -10566,7 +10592,8 @@ "food", "movie theater", "films", - "snack" + "snack", + "drama" ] }, "butter": { @@ -10592,7 +10619,8 @@ "j": [ "can", "food", - "soup" + "soup", + "tomatoes" ] }, "bento-box": { @@ -10602,7 +10630,8 @@ "bento", "box", "food", - "japanese" + "japanese", + "lunch" ] }, "rice-cracker": { @@ -10612,7 +10641,8 @@ "cracker", "rice", "food", - "japanese" + "japanese", + "snack" ] }, "rice-ball": { @@ -10633,7 +10663,6 @@ "cooked", "rice", "food", - "china", "asian" ] }, @@ -10680,7 +10709,8 @@ "roasted", "sweet", "food", - "nature" + "nature", + "plant" ] }, "oden": { @@ -10745,7 +10775,8 @@ "autumn", "festival", "yuèbǐng", - "food" + "food", + "dessert" ] }, "dango": { @@ -10772,7 +10803,8 @@ "jiaozi", "pierogi", "potsticker", - "food" + "food", + "gyoza" ] }, "fortune-cookie": { @@ -10780,7 +10812,8 @@ "b": "1F960", "j": [ "prophecy", - "food" + "food", + "dessert" ] }, "takeout-box": { @@ -22169,7 +22202,9 @@ "nation", "country", "banner", - "japan" + "japan", + "jp", + "ja" ] }, "flag-kenya": { diff --git a/tools/release/pushPlayStoreMetaData.sh b/tools/release/pushPlayStoreMetaData.sh index 5c2f69cb7b..2d8fd9b36a 100755 --- a/tools/release/pushPlayStoreMetaData.sh +++ b/tools/release/pushPlayStoreMetaData.sh @@ -28,7 +28,6 @@ mv ./fastlane/metadata/android/fy ./fastlane_tmp mv ./fastlane/metadata/android/ga ./fastlane_tmp mv ./fastlane/metadata/android/kab ./fastlane_tmp mv ./fastlane/metadata/android/nb ./fastlane_tmp -mv ./fastlane/metadata/android/gl ./fastlane_tmp # Fastlane / PlayStore require longDescription and shortDescription file to be set, so copy the default # one for languages where they are missing diff --git a/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl index 0f01b347c0..133faa6821 100644 --- a/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl @@ -18,10 +18,10 @@ import javax.inject.Inject data class ${fragmentArgsClass}() : Parcelable -//TODO add this fragment into FragmentModule -class ${fragmentClass} @Inject constructor( - private val viewModelFactory: ${viewModelClass}.Factory -) : VectorBaseFragment(), ${viewModelClass}.Factory by viewModelFactory { +@AndroidEntryPoint +class ${fragmentClass}() : + VectorBaseFragment(), + ${viewModelClass}.Factory by viewModelFactory { <#if createFragmentArgs> private val fragmentArgs: ${fragmentArgsClass} by args() diff --git a/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt b/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt index ae8cfd1172..8821c8187e 100644 --- a/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt +++ b/vector-config/src/main/java/im/vector/app/config/OnboardingVariant.kt @@ -18,6 +18,5 @@ package im.vector.app.config enum class OnboardingVariant { LEGACY, - LOGIN_2, FTUE_AUTH } diff --git a/vector/build.gradle b/vector/build.gradle index 629b7c4135..83d322946b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 34 +ext.versionPatch = 36 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -391,7 +391,7 @@ dependencies { implementation libs.androidx.biometric implementation "org.threeten:threetenbp:1.4.0:no-tzdb" - implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0" + implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.11.0" implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin @@ -413,7 +413,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54' // FlowBinding implementation libs.github.flowBinding diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt index b01c1a895f..068c9fb646 100644 --- a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt @@ -83,7 +83,7 @@ private fun useMediaStoreScreenshotStorage( screenshotLocation: String, bitmap: Bitmap ) { - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "$screenshotName.jpeg") + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "${screenshotName}.jpeg") contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, screenshotLocation) val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) if (uri != null) { @@ -104,7 +104,7 @@ private fun usePublicExternalScreenshotStorage( if (!directory.exists()) { directory.mkdirs() } - val file = File(directory, "$screenshotName.jpeg") + val file = File(directory, "${screenshotName}.jpeg") saveScreenshotToStream(bitmap, FileOutputStream(file)) contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) } diff --git a/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt b/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt index a880b17e0c..3517f806d6 100644 --- a/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt @@ -76,7 +76,7 @@ class EmojiDataSourceTest : InstrumentedTest { fun searchTestOneResult() { val emojiDataSource = createEmojiDataSource() val result = runBlocking { - emojiDataSource.filterWith("france") + emojiDataSource.filterWith("flag-france") } assertEquals("Should have 1 result", 1, result.size) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt index d3e70e26e6..2abf6487e2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt @@ -28,7 +28,8 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentDebugMemoryLeaksBinding @AndroidEntryPoint -class DebugMemoryLeaksFragment : VectorBaseFragment() { +class DebugMemoryLeaksFragment : + VectorBaseFragment() { private val viewModel: DebugMemoryLeaksViewModel by fragmentViewModel() diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index beee800b4a..ea62aa1b58 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -348,6 +348,8 @@ + + diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 4655de7377..46cb6ec79b 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -266,7 +266,7 @@ class VectorApplication : } private fun createFontThreadHandler(): Handler { - val handlerThread = HandlerThread("fonts") + val handlerThread = HandlerThread("Vector-fonts") handlerThread.start() return Handler(handlerThread.looper) } diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt index 7a1d613ab9..3f0507305a 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt @@ -20,6 +20,7 @@ import android.content.Context import arrow.core.Option import im.vector.app.ActiveSessionDataSource import im.vector.app.core.extensions.configureAndStart +import im.vector.app.core.extensions.startSyncing import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.services.GuardServiceStarter import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -69,7 +70,7 @@ class ActiveSessionHolder @Inject constructor( suspend fun clearActiveSession() { // Do some cleanup first - getSafeActiveSession()?.let { + getSafeActiveSession(startSync = false)?.let { Timber.w("clearActiveSession of ${it.myUserId}") it.callSignalingService().removeCallListener(callManager) it.removeListener(sessionListener) @@ -90,8 +91,8 @@ class ActiveSessionHolder @Inject constructor( return activeSessionReference.get() != null || authenticationService.hasAuthenticatedSessions() } - fun getSafeActiveSession(): Session? { - return runBlocking { getOrInitializeSession(startSync = true) } + fun getSafeActiveSession(startSync: Boolean = true): Session? { + return runBlocking { getOrInitializeSession(startSync = startSync) } } fun getActiveSession(): Session { @@ -100,10 +101,16 @@ class ActiveSessionHolder @Inject constructor( } suspend fun getOrInitializeSession(startSync: Boolean): Session? { - return activeSessionReference.get() ?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session -> - setActiveSession(session) - session.configureAndStart(applicationContext, startSyncing = startSync) - } + return activeSessionReference.get() + ?.also { + if (startSync && !it.syncService().isSyncThreadAlive()) { + it.startSyncing(applicationContext) + } + } + ?: sessionInitializer.tryInitialize(readCurrentSession = { activeSessionReference.get() }) { session -> + setActiveSession(session) + session.configureAndStart(applicationContext, startSyncing = startSync) + } } fun isWaitingForSessionInitialization() = activeSessionReference.get() == null && authenticationService.hasAuthenticatedSessions() diff --git a/vector/src/main/java/im/vector/app/core/di/ActivityEntryPoint.kt b/vector/src/main/java/im/vector/app/core/di/ActivityEntryPoint.kt index c5f7317ebe..4b8b23489b 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActivityEntryPoint.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActivityEntryPoint.kt @@ -16,7 +16,6 @@ package im.vector.app.core.di -import androidx.fragment.app.FragmentFactory import androidx.lifecycle.ViewModelProvider import dagger.hilt.EntryPoint import dagger.hilt.InstallIn @@ -25,6 +24,5 @@ import dagger.hilt.android.components.ActivityComponent @InstallIn(ActivityComponent::class) @EntryPoint interface ActivityEntryPoint { - fun fragmentFactory(): FragmentFactory fun viewModelFactory(): ViewModelProvider.Factory } diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentKey.kt b/vector/src/main/java/im/vector/app/core/di/FragmentKey.kt deleted file mode 100644 index bc2dc40a15..0000000000 --- a/vector/src/main/java/im/vector/app/core/di/FragmentKey.kt +++ /dev/null @@ -1,27 +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.core.di - -import androidx.fragment.app.Fragment -import dagger.MapKey -import kotlin.reflect.KClass - -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) -@Retention(AnnotationRetention.RUNTIME) -@MapKey -annotation class FragmentKey(val value: KClass) 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 deleted file mode 100644 index e86b350534..0000000000 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ /dev/null @@ -1,1056 +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.core.di - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentFactory -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.multibindings.IntoMap -import im.vector.app.features.analytics.ui.consent.AnalyticsOptInFragment -import im.vector.app.features.attachments.preview.AttachmentsPreviewFragment -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.BootstrapConclusionFragment -import im.vector.app.features.crypto.recover.BootstrapConfirmPassphraseFragment -import im.vector.app.features.crypto.recover.BootstrapEnterPassphraseFragment -import im.vector.app.features.crypto.recover.BootstrapMigrateBackupFragment -import im.vector.app.features.crypto.recover.BootstrapReAuthFragment -import im.vector.app.features.crypto.recover.BootstrapSaveRecoveryKeyFragment -import im.vector.app.features.crypto.recover.BootstrapSetupRecoveryKeyFragment -import im.vector.app.features.crypto.recover.BootstrapWaitingFragment -import im.vector.app.features.crypto.verification.QuadSLoadingFragment -import im.vector.app.features.crypto.verification.cancel.VerificationCancelFragment -import im.vector.app.features.crypto.verification.cancel.VerificationNotMeFragment -import im.vector.app.features.crypto.verification.choose.VerificationChooseMethodFragment -import im.vector.app.features.crypto.verification.conclusion.VerificationConclusionFragment -import im.vector.app.features.crypto.verification.emoji.VerificationEmojiCodeFragment -import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment -import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment -import im.vector.app.features.crypto.verification.request.VerificationRequestFragment -import im.vector.app.features.devtools.RoomDevToolEditFragment -import im.vector.app.features.devtools.RoomDevToolFragment -import im.vector.app.features.devtools.RoomDevToolSendFormFragment -import im.vector.app.features.devtools.RoomDevToolStateEventListFragment -import im.vector.app.features.discovery.DiscoverySettingsFragment -import im.vector.app.features.discovery.change.SetIdentityServerFragment -import im.vector.app.features.home.HomeDetailFragment -import im.vector.app.features.home.HomeDrawerFragment -import im.vector.app.features.home.LoadingFragment -import im.vector.app.features.home.NewHomeDetailFragment -import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment -import im.vector.app.features.home.room.detail.TimelineFragment -import im.vector.app.features.home.room.detail.search.SearchFragment -import im.vector.app.features.home.room.list.RoomListFragment -import im.vector.app.features.home.room.list.home.HomeRoomListFragment -import im.vector.app.features.home.room.threads.list.views.ThreadListFragment -import im.vector.app.features.location.LocationSharingFragment -import im.vector.app.features.location.preview.LocationPreviewFragment -import im.vector.app.features.login.LoginCaptchaFragment -import im.vector.app.features.login.LoginFragment -import im.vector.app.features.login.LoginGenericTextInputFormFragment -import im.vector.app.features.login.LoginResetPasswordFragment -import im.vector.app.features.login.LoginResetPasswordMailConfirmationFragment -import im.vector.app.features.login.LoginResetPasswordSuccessFragment -import im.vector.app.features.login.LoginServerSelectionFragment -import im.vector.app.features.login.LoginServerUrlFormFragment -import im.vector.app.features.login.LoginSignUpSignInSelectionFragment -import im.vector.app.features.login.LoginSplashFragment -import im.vector.app.features.login.LoginWaitForEmailFragment -import im.vector.app.features.login.LoginWebFragment -import im.vector.app.features.login.terms.LoginTermsFragment -import im.vector.app.features.login2.LoginCaptchaFragment2 -import im.vector.app.features.login2.LoginFragmentSigninPassword2 -import im.vector.app.features.login2.LoginFragmentSigninUsername2 -import im.vector.app.features.login2.LoginFragmentSignupPassword2 -import im.vector.app.features.login2.LoginFragmentSignupUsername2 -import im.vector.app.features.login2.LoginFragmentToAny2 -import im.vector.app.features.login2.LoginGenericTextInputFormFragment2 -import im.vector.app.features.login2.LoginResetPasswordFragment2 -import im.vector.app.features.login2.LoginResetPasswordMailConfirmationFragment2 -import im.vector.app.features.login2.LoginResetPasswordSuccessFragment2 -import im.vector.app.features.login2.LoginServerSelectionFragment2 -import im.vector.app.features.login2.LoginServerUrlFormFragment2 -import im.vector.app.features.login2.LoginSplashSignUpSignInSelectionFragment2 -import im.vector.app.features.login2.LoginSsoOnlyFragment2 -import im.vector.app.features.login2.LoginWaitForEmailFragment2 -import im.vector.app.features.login2.LoginWebFragment2 -import im.vector.app.features.login2.created.AccountCreatedFragment -import im.vector.app.features.login2.terms.LoginTermsFragment2 -import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment -import im.vector.app.features.matrixto.MatrixToUserFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedLoginFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedRegisterFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedServerSelectionFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthEmailEntryFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyWaitForEmailFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthPhoneConfirmationFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthPhoneEntryFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordMailConfirmationFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordSuccessFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashCarouselFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthUseCaseFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment -import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment -import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment -import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment -import im.vector.app.features.pin.PinFragment -import im.vector.app.features.poll.create.CreatePollFragment -import im.vector.app.features.qrcode.QrCodeScannerFragment -import im.vector.app.features.reactions.EmojiChooserFragment -import im.vector.app.features.reactions.EmojiSearchResultFragment -import im.vector.app.features.roomdirectory.PublicRoomsFragment -import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment -import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment -import im.vector.app.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment -import im.vector.app.features.roommemberprofile.RoomMemberProfileFragment -import im.vector.app.features.roommemberprofile.devices.DeviceListFragment -import im.vector.app.features.roommemberprofile.devices.DeviceTrustInfoActionFragment -import im.vector.app.features.roomprofile.RoomProfileFragment -import im.vector.app.features.roomprofile.alias.RoomAliasFragment -import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment -import im.vector.app.features.roomprofile.members.RoomMemberListFragment -import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment -import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment -import im.vector.app.features.roomprofile.settings.RoomSettingsFragment -import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment -import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment -import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment -import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment -import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment -import im.vector.app.features.settings.VectorSettingsGeneralFragment -import im.vector.app.features.settings.VectorSettingsHelpAboutFragment -import im.vector.app.features.settings.VectorSettingsLabsFragment -import im.vector.app.features.settings.VectorSettingsPinFragment -import im.vector.app.features.settings.VectorSettingsPreferencesFragment -import im.vector.app.features.settings.VectorSettingsSecurityPrivacyFragment -import im.vector.app.features.settings.account.deactivation.DeactivateAccountFragment -import im.vector.app.features.settings.crosssigning.CrossSigningSettingsFragment -import im.vector.app.features.settings.devices.VectorSettingsDevicesFragment -import im.vector.app.features.settings.devtools.AccountDataFragment -import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailFragment -import im.vector.app.features.settings.devtools.IncomingKeyRequestListFragment -import im.vector.app.features.settings.devtools.KeyRequestsFragment -import im.vector.app.features.settings.devtools.OutgoingKeyRequestListFragment -import im.vector.app.features.settings.font.FontScaleSettingFragment -import im.vector.app.features.settings.homeserver.HomeserverSettingsFragment -import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragment -import im.vector.app.features.settings.legals.LegalsFragment -import im.vector.app.features.settings.locale.LocalePickerFragment -import im.vector.app.features.settings.notifications.VectorSettingsAdvancedNotificationPreferenceFragment -import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment -import im.vector.app.features.settings.notifications.VectorSettingsNotificationsTroubleshootFragment -import im.vector.app.features.settings.push.PushGatewaysFragment -import im.vector.app.features.settings.push.PushRulesFragment -import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment -import im.vector.app.features.share.IncomingShareFragment -import im.vector.app.features.signout.soft.SoftLogoutFragment -import im.vector.app.features.spaces.SpaceListFragment -import im.vector.app.features.spaces.create.ChoosePrivateSpaceTypeFragment -import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment -import im.vector.app.features.spaces.create.CreateSpaceAdd3pidInvitesFragment -import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment -import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment -import im.vector.app.features.spaces.explore.SpaceDirectoryFragment -import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedFragment -import im.vector.app.features.spaces.manage.SpaceAddRoomFragment -import im.vector.app.features.spaces.manage.SpaceManageRoomsFragment -import im.vector.app.features.spaces.manage.SpaceSettingsFragment -import im.vector.app.features.spaces.people.SpacePeopleFragment -import im.vector.app.features.spaces.preview.SpacePreviewFragment -import im.vector.app.features.terms.ReviewTermsFragment -import im.vector.app.features.usercode.ShowUserCodeFragment -import im.vector.app.features.userdirectory.UserListFragment -import im.vector.app.features.widgets.WidgetFragment - -@InstallIn(ActivityComponent::class) -@Module -interface FragmentModule { - /** - * Fragments with @IntoMap will be injected by this factory. - */ - @Binds - fun bindFragmentFactory(factory: VectorFragmentFactory): FragmentFactory - - @Binds - @IntoMap - @FragmentKey(RoomListFragment::class) - fun bindRoomListFragment(fragment: RoomListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LocalePickerFragment::class) - fun bindLocalePickerFragment(fragment: LocalePickerFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpaceListFragment::class) - fun bindSpaceListFragment(fragment: SpaceListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(TimelineFragment::class) - fun bindTimelineFragment(fragment: TimelineFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomDirectoryPickerFragment::class) - fun bindRoomDirectoryPickerFragment(fragment: RoomDirectoryPickerFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(CreateRoomFragment::class) - fun bindCreateRoomFragment(fragment: CreateRoomFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomPreviewNoPreviewFragment::class) - fun bindRoomPreviewNoPreviewFragment(fragment: RoomPreviewNoPreviewFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(KeysBackupSettingsFragment::class) - fun bindKeysBackupSettingsFragment(fragment: KeysBackupSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoadingFragment::class) - fun bindLoadingFragment(fragment: LoadingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(HomeDrawerFragment::class) - fun bindHomeDrawerFragment(fragment: HomeDrawerFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(HomeDetailFragment::class) - fun bindHomeDetailFragment(fragment: HomeDetailFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(NewHomeDetailFragment::class) - fun bindNewHomeDetailFragment(fragment: NewHomeDetailFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(EmojiSearchResultFragment::class) - fun bindEmojiSearchResultFragment(fragment: EmojiSearchResultFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragment::class) - fun bindLoginFragment(fragment: LoginFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginCaptchaFragment::class) - fun bindLoginCaptchaFragment(fragment: LoginCaptchaFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginTermsFragment::class) - fun bindLoginTermsFragment(fragment: LoginTermsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginServerUrlFormFragment::class) - fun bindLoginServerUrlFormFragment(fragment: LoginServerUrlFormFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordMailConfirmationFragment::class) - fun bindLoginResetPasswordMailConfirmationFragment(fragment: LoginResetPasswordMailConfirmationFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordFragment::class) - fun bindLoginResetPasswordFragment(fragment: LoginResetPasswordFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordSuccessFragment::class) - fun bindLoginResetPasswordSuccessFragment(fragment: LoginResetPasswordSuccessFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginServerSelectionFragment::class) - fun bindLoginServerSelectionFragment(fragment: LoginServerSelectionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginSignUpSignInSelectionFragment::class) - fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginSplashFragment::class) - fun bindLoginSplashFragment(fragment: LoginSplashFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginWebFragment::class) - fun bindLoginWebFragment(fragment: LoginWebFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginGenericTextInputFormFragment::class) - fun bindLoginGenericTextInputFormFragment(fragment: LoginGenericTextInputFormFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginWaitForEmailFragment::class) - fun bindLoginWaitForEmailFragment(fragment: LoginWaitForEmailFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSigninUsername2::class) - fun bindLoginFragmentSigninUsername2(fragment: LoginFragmentSigninUsername2): Fragment - - @Binds - @IntoMap - @FragmentKey(AccountCreatedFragment::class) - fun bindAccountCreatedFragment(fragment: AccountCreatedFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSignupUsername2::class) - fun bindLoginFragmentSignupUsername2(fragment: LoginFragmentSignupUsername2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSigninPassword2::class) - fun bindLoginFragmentSigninPassword2(fragment: LoginFragmentSigninPassword2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentSignupPassword2::class) - fun bindLoginFragmentSignupPassword2(fragment: LoginFragmentSignupPassword2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginCaptchaFragment2::class) - fun bindLoginCaptchaFragment2(fragment: LoginCaptchaFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginFragmentToAny2::class) - fun bindLoginFragmentToAny2(fragment: LoginFragmentToAny2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginTermsFragment2::class) - fun bindLoginTermsFragment2(fragment: LoginTermsFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginServerUrlFormFragment2::class) - fun bindLoginServerUrlFormFragment2(fragment: LoginServerUrlFormFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordMailConfirmationFragment2::class) - fun bindLoginResetPasswordMailConfirmationFragment2(fragment: LoginResetPasswordMailConfirmationFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordFragment2::class) - fun bindLoginResetPasswordFragment2(fragment: LoginResetPasswordFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginResetPasswordSuccessFragment2::class) - fun bindLoginResetPasswordSuccessFragment2(fragment: LoginResetPasswordSuccessFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginServerSelectionFragment2::class) - fun bindLoginServerSelectionFragment2(fragment: LoginServerSelectionFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginSsoOnlyFragment2::class) - fun bindLoginSsoOnlyFragment2(fragment: LoginSsoOnlyFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginSplashSignUpSignInSelectionFragment2::class) - fun bindLoginSplashSignUpSignInSelectionFragment2(fragment: LoginSplashSignUpSignInSelectionFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginWebFragment2::class) - fun bindLoginWebFragment2(fragment: LoginWebFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginGenericTextInputFormFragment2::class) - fun bindLoginGenericTextInputFormFragment2(fragment: LoginGenericTextInputFormFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(LoginWaitForEmailFragment2::class) - fun bindLoginWaitForEmailFragment2(fragment: LoginWaitForEmailFragment2): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthLegacyStyleCaptchaFragment::class) - fun bindFtueAuthLegacyStyleCaptchaFragment(fragment: FtueAuthLegacyStyleCaptchaFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthCaptchaFragment::class) - fun bindFtueAuthCaptchaFragment(fragment: FtueAuthCaptchaFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthGenericTextInputFormFragment::class) - fun bindFtueAuthGenericTextInputFormFragment(fragment: FtueAuthGenericTextInputFormFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthLoginFragment::class) - fun bindFtueAuthLoginFragment(fragment: FtueAuthLoginFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthResetPasswordFragment::class) - fun bindFtueAuthResetPasswordFragment(fragment: FtueAuthResetPasswordFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthResetPasswordMailConfirmationFragment::class) - fun bindFtueAuthResetPasswordMailConfirmationFragment(fragment: FtueAuthResetPasswordMailConfirmationFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthResetPasswordSuccessFragment::class) - fun bindFtueAuthResetPasswordSuccessFragment(fragment: FtueAuthResetPasswordSuccessFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthServerSelectionFragment::class) - fun bindFtueAuthServerSelectionFragment(fragment: FtueAuthServerSelectionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthSignUpSignInSelectionFragment::class) - fun bindFtueAuthSignUpSignInSelectionFragment(fragment: FtueAuthSignUpSignInSelectionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthSplashFragment::class) - fun bindFtueAuthSplashFragment(fragment: FtueAuthSplashFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthSplashCarouselFragment::class) - fun bindFtueAuthSplashCarouselFragment(fragment: FtueAuthSplashCarouselFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthUseCaseFragment::class) - fun bindFtueAuthUseCaseFragment(fragment: FtueAuthUseCaseFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthWaitForEmailFragment::class) - fun bindFtueAuthWaitForEmailFragment(fragment: FtueAuthWaitForEmailFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthLegacyWaitForEmailFragment::class) - fun bindFtueAuthLegacyWaitForEmailFragment(fragment: FtueAuthLegacyWaitForEmailFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthWebFragment::class) - fun bindFtueAuthWebFragment(fragment: FtueAuthWebFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthLegacyStyleTermsFragment::class) - fun bindFtueAuthLegacyStyleTermsFragment(fragment: FtueAuthLegacyStyleTermsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthTermsFragment::class) - fun bindFtueAuthTermsFragment(fragment: FtueAuthTermsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthAccountCreatedFragment::class) - fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthEmailEntryFragment::class) - fun bindFtueAuthEmailEntryFragment(fragment: FtueAuthEmailEntryFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthPhoneEntryFragment::class) - fun bindFtueAuthPhoneEntryFragment(fragment: FtueAuthPhoneEntryFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthPhoneConfirmationFragment::class) - fun bindFtueAuthPhoneConfirmationFragment(fragment: FtueAuthPhoneConfirmationFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthChooseDisplayNameFragment::class) - fun bindFtueAuthChooseDisplayNameFragment(fragment: FtueAuthChooseDisplayNameFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthChooseProfilePictureFragment::class) - fun bindFtueAuthChooseProfilePictureFragment(fragment: FtueAuthChooseProfilePictureFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthPersonalizationCompleteFragment::class) - fun bindFtueAuthPersonalizationCompleteFragment(fragment: FtueAuthPersonalizationCompleteFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthCombinedLoginFragment::class) - fun bindFtueAuthCombinedLoginFragment(fragment: FtueAuthCombinedLoginFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthCombinedRegisterFragment::class) - fun bindFtueAuthCombinedRegisterFragment(fragment: FtueAuthCombinedRegisterFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FtueAuthCombinedServerSelectionFragment::class) - fun bindFtueAuthCombinedServerSelectionFragment(fragment: FtueAuthCombinedServerSelectionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(UserListFragment::class) - fun bindUserListFragment(fragment: UserListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(PushGatewaysFragment::class) - fun bindPushGatewaysFragment(fragment: PushGatewaysFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsNotificationsTroubleshootFragment::class) - fun bindVectorSettingsNotificationsTroubleshootFragment(fragment: VectorSettingsNotificationsTroubleshootFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsAdvancedNotificationPreferenceFragment::class) - fun bindVectorSettingsAdvancedNotificationPreferenceFragment(fragment: VectorSettingsAdvancedNotificationPreferenceFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsNotificationPreferenceFragment::class) - fun bindVectorSettingsNotificationPreferenceFragment(fragment: VectorSettingsNotificationPreferenceFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsLabsFragment::class) - fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(HomeserverSettingsFragment::class) - fun bindHomeserverSettingsFragment(fragment: HomeserverSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(FontScaleSettingFragment::class) - fun bindFontScaleSettingFragment(fragment: FontScaleSettingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsPinFragment::class) - fun bindVectorSettingsPinFragment(fragment: VectorSettingsPinFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsGeneralFragment::class) - fun bindVectorSettingsGeneralFragment(fragment: VectorSettingsGeneralFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(PushRulesFragment::class) - fun bindPushRulesFragment(fragment: PushRulesFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsPreferencesFragment::class) - fun bindVectorSettingsPreferencesFragment(fragment: VectorSettingsPreferencesFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsSecurityPrivacyFragment::class) - fun bindVectorSettingsSecurityPrivacyFragment(fragment: VectorSettingsSecurityPrivacyFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsHelpAboutFragment::class) - fun bindVectorSettingsHelpAboutFragment(fragment: VectorSettingsHelpAboutFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsIgnoredUsersFragment::class) - fun bindVectorSettingsIgnoredUsersFragment(fragment: VectorSettingsIgnoredUsersFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VectorSettingsDevicesFragment::class) - fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ThreePidsSettingsFragment::class) - fun bindThreePidsSettingsFragment(fragment: ThreePidsSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(PublicRoomsFragment::class) - fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomProfileFragment::class) - fun bindRoomProfileFragment(fragment: RoomProfileFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomMemberListFragment::class) - fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomUploadsFragment::class) - fun bindRoomUploadsFragment(fragment: RoomUploadsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomUploadsMediaFragment::class) - fun bindRoomUploadsMediaFragment(fragment: RoomUploadsMediaFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomUploadsFilesFragment::class) - fun bindRoomUploadsFilesFragment(fragment: RoomUploadsFilesFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomSettingsFragment::class) - fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomAliasFragment::class) - fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomPermissionsFragment::class) - fun bindRoomPermissionsFragment(fragment: RoomPermissionsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomMemberProfileFragment::class) - fun bindRoomMemberProfileFragment(fragment: RoomMemberProfileFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BreadcrumbsFragment::class) - fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(AnalyticsOptInFragment::class) - fun bindAnalyticsOptInFragment(fragment: AnalyticsOptInFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(EmojiChooserFragment::class) - fun bindEmojiChooserFragment(fragment: EmojiChooserFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SoftLogoutFragment::class) - fun bindSoftLogoutFragment(fragment: SoftLogoutFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationRequestFragment::class) - fun bindVerificationRequestFragment(fragment: VerificationRequestFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationChooseMethodFragment::class) - fun bindVerificationChooseMethodFragment(fragment: VerificationChooseMethodFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationEmojiCodeFragment::class) - fun bindVerificationEmojiCodeFragment(fragment: VerificationEmojiCodeFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationQrScannedByOtherFragment::class) - fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationQRWaitingFragment::class) - fun bindVerificationQRWaitingFragment(fragment: VerificationQRWaitingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationConclusionFragment::class) - fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationCancelFragment::class) - fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(QuadSLoadingFragment::class) - fun bindQuadSLoadingFragment(fragment: QuadSLoadingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(VerificationNotMeFragment::class) - fun bindVerificationNotMeFragment(fragment: VerificationNotMeFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(QrCodeScannerFragment::class) - fun bindQrCodeScannerFragment(fragment: QrCodeScannerFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(DeviceListFragment::class) - fun bindDeviceListFragment(fragment: DeviceListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(DeviceTrustInfoActionFragment::class) - fun bindDeviceTrustInfoActionFragment(fragment: DeviceTrustInfoActionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(CrossSigningSettingsFragment::class) - fun bindCrossSigningSettingsFragment(fragment: CrossSigningSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(AttachmentsPreviewFragment::class) - fun bindAttachmentsPreviewFragment(fragment: AttachmentsPreviewFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(IncomingShareFragment::class) - fun bindIncomingShareFragment(fragment: IncomingShareFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(AccountDataFragment::class) - fun bindAccountDataFragment(fragment: AccountDataFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(OutgoingKeyRequestListFragment::class) - fun bindOutgoingKeyRequestListFragment(fragment: OutgoingKeyRequestListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(IncomingKeyRequestListFragment::class) - fun bindIncomingKeyRequestListFragment(fragment: IncomingKeyRequestListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(KeyRequestsFragment::class) - fun bindKeyRequestsFragment(fragment: KeyRequestsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(GossipingEventsPaperTrailFragment::class) - fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapEnterPassphraseFragment::class) - fun bindBootstrapEnterPassphraseFragment(fragment: BootstrapEnterPassphraseFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapConfirmPassphraseFragment::class) - fun bindBootstrapConfirmPassphraseFragment(fragment: BootstrapConfirmPassphraseFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapWaitingFragment::class) - fun bindBootstrapWaitingFragment(fragment: BootstrapWaitingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapSetupRecoveryKeyFragment::class) - fun bindBootstrapSetupRecoveryKeyFragment(fragment: BootstrapSetupRecoveryKeyFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapSaveRecoveryKeyFragment::class) - fun bindBootstrapSaveRecoveryKeyFragment(fragment: BootstrapSaveRecoveryKeyFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapConclusionFragment::class) - fun bindBootstrapConclusionFragment(fragment: BootstrapConclusionFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapReAuthFragment::class) - fun bindBootstrapReAuthFragment(fragment: BootstrapReAuthFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapMigrateBackupFragment::class) - fun bindBootstrapMigrateBackupFragment(fragment: BootstrapMigrateBackupFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(DeactivateAccountFragment::class) - fun bindDeactivateAccountFragment(fragment: DeactivateAccountFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SharedSecuredStoragePassphraseFragment::class) - fun bindSharedSecuredStoragePassphraseFragment(fragment: SharedSecuredStoragePassphraseFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SharedSecuredStorageKeyFragment::class) - fun bindSharedSecuredStorageKeyFragment(fragment: SharedSecuredStorageKeyFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SharedSecuredStorageResetAllFragment::class) - fun bindSharedSecuredStorageResetAllFragment(fragment: SharedSecuredStorageResetAllFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SetIdentityServerFragment::class) - fun bindSetIdentityServerFragment(fragment: SetIdentityServerFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(DiscoverySettingsFragment::class) - fun bindDiscoverySettingsFragment(fragment: DiscoverySettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LegalsFragment::class) - fun bindLegalsFragment(fragment: LegalsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ReviewTermsFragment::class) - fun bindReviewTermsFragment(fragment: ReviewTermsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(WidgetFragment::class) - fun bindWidgetFragment(fragment: WidgetFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ContactsBookFragment::class) - fun bindPhoneBookFragment(fragment: ContactsBookFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(PinFragment::class) - fun bindPinFragment(fragment: PinFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomBannedMemberListFragment::class) - fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomNotificationSettingsFragment::class) - fun bindRoomNotificationSettingsFragment(fragment: RoomNotificationSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SearchFragment::class) - fun bindSearchFragment(fragment: SearchFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ShowUserCodeFragment::class) - fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomDevToolFragment::class) - fun bindRoomDevToolFragment(fragment: RoomDevToolFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomDevToolStateEventListFragment::class) - fun bindRoomDevToolStateEventListFragment(fragment: RoomDevToolStateEventListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomDevToolEditFragment::class) - fun bindRoomDevToolEditFragment(fragment: RoomDevToolEditFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomDevToolSendFormFragment::class) - fun bindRoomDevToolSendFormFragment(fragment: RoomDevToolSendFormFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpacePreviewFragment::class) - fun bindSpacePreviewFragment(fragment: SpacePreviewFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ChooseSpaceTypeFragment::class) - fun bindChooseSpaceTypeFragment(fragment: ChooseSpaceTypeFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(CreateSpaceDetailsFragment::class) - fun bindCreateSpaceDetailsFragment(fragment: CreateSpaceDetailsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(CreateSpaceDefaultRoomsFragment::class) - fun bindCreateSpaceDefaultRoomsFragment(fragment: CreateSpaceDefaultRoomsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(MatrixToUserFragment::class) - fun bindMatrixToUserFragment(fragment: MatrixToUserFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(MatrixToRoomSpaceFragment::class) - fun bindMatrixToRoomSpaceFragment(fragment: MatrixToRoomSpaceFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpaceDirectoryFragment::class) - fun bindSpaceDirectoryFragment(fragment: SpaceDirectoryFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ChoosePrivateSpaceTypeFragment::class) - fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(CreateSpaceAdd3pidInvitesFragment::class) - fun bindCreateSpaceAdd3pidInvitesFragment(fragment: CreateSpaceAdd3pidInvitesFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpaceAddRoomFragment::class) - fun bindSpaceAddRoomFragment(fragment: SpaceAddRoomFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpacePeopleFragment::class) - fun bindSpacePeopleFragment(fragment: SpacePeopleFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpaceSettingsFragment::class) - fun bindSpaceSettingsFragment(fragment: SpaceSettingsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpaceManageRoomsFragment::class) - fun bindSpaceManageRoomsFragment(fragment: SpaceManageRoomsFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomJoinRuleFragment::class) - fun bindRoomJoinRuleFragment(fragment: RoomJoinRuleFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(RoomJoinRuleChooseRestrictedFragment::class) - fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SpaceLeaveAdvancedFragment::class) - fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(ThreadListFragment::class) - fun bindThreadListFragment(fragment: ThreadListFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(CreatePollFragment::class) - fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LocationSharingFragment::class) - fun bindLocationSharingFragment(fragment: LocationSharingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(LocationPreviewFragment::class) - fun bindLocationPreviewFragment(fragment: LocationPreviewFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(HomeRoomListFragment::class) - fun binHomeRoomListFragment(fragment: HomeRoomListFragment): Fragment -} diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 331b4afa18..b21b4778e3 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -52,14 +52,13 @@ import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsV import im.vector.app.features.home.room.detail.upgrade.MigrateRoomViewModel import im.vector.app.features.home.room.list.RoomListViewModel import im.vector.app.features.home.room.list.home.HomeRoomListViewModel +import im.vector.app.features.home.room.list.home.invites.InvitesViewModel import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel import im.vector.app.features.invite.InviteUsersToRoomViewModel import im.vector.app.features.location.LocationSharingViewModel import im.vector.app.features.location.live.map.LiveLocationMapViewModel import im.vector.app.features.location.preview.LocationPreviewViewModel import im.vector.app.features.login.LoginViewModel -import im.vector.app.features.login2.LoginViewModel2 -import im.vector.app.features.login2.created.AccountCreatedViewModel import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel import im.vector.app.features.media.VectorAttachmentViewerViewModel import im.vector.app.features.onboarding.OnboardingViewModel @@ -456,21 +455,11 @@ interface MavericksViewModelModule { @MavericksViewModelKey(MatrixToBottomSheetViewModel::class) fun matrixToBottomSheetViewModelFactory(factory: MatrixToBottomSheetViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds - @IntoMap - @MavericksViewModelKey(AccountCreatedViewModel::class) - fun accountCreatedViewModelFactory(factory: AccountCreatedViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds @IntoMap @MavericksViewModelKey(OnboardingViewModel::class) fun onboardingViewModelFactory(factory: OnboardingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds - @IntoMap - @MavericksViewModelKey(LoginViewModel2::class) - fun loginViewModel2Factory(factory: LoginViewModel2.Factory): MavericksAssistedViewModelFactory<*, *> - @Binds @IntoMap @MavericksViewModelKey(LoginViewModel::class) @@ -630,4 +619,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(HomeRoomListViewModel::class) fun homeRoomListViewModel(factory: HomeRoomListViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(InvitesViewModel::class) + fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/di/VectorFragmentFactory.kt b/vector/src/main/java/im/vector/app/core/di/VectorFragmentFactory.kt deleted file mode 100644 index f761d99114..0000000000 --- a/vector/src/main/java/im/vector/app/core/di/VectorFragmentFactory.kt +++ /dev/null @@ -1,43 +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.core.di - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentFactory -import timber.log.Timber -import javax.inject.Inject -import javax.inject.Provider - -/** - * FragmentFactory which uses Dagger to create the instances. - */ -class VectorFragmentFactory @Inject constructor( - private val creators: @JvmSuppressWildcards Map, Provider> -) : FragmentFactory() { - - override fun instantiate(classLoader: ClassLoader, className: String): Fragment { - val fragmentClass = loadFragmentClass(classLoader, className) - val creator: Provider? = creators[fragmentClass] - return if (creator == null) { - Timber.v("Unknown model class: $className, fallback to default instance") - super.instantiate(classLoader, className) - } else { - creator.get() - } - } -} diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index d90e934d0a..c08f939524 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -34,6 +34,7 @@ import im.vector.app.features.home.HomeSharedActionViewModel import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import im.vector.app.features.reactions.EmojiChooserViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel @@ -157,4 +158,9 @@ interface ViewModelModule { @IntoMap @ViewModelKey(SpacePeopleSharedActionViewModel::class) fun bindSpacePeopleSharedActionViewModel(viewModel: SpacePeopleSharedActionViewModel): ViewModel + + @Binds + @IntoMap + @ViewModelKey(RoomListSharedActionViewModel::class) + fun bindRoomListSharedActionViewModel(viewModel: RoomListSharedActionViewModel): ViewModel } diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt new file mode 100644 index 0000000000..0e8dc1d0d1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.dialogs + +import androidx.fragment.app.Fragment +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.time.Clock +import javax.inject.Inject + +/** + * Factory for [GalleryOrCameraDialogHelper]. + */ +class GalleryOrCameraDialogHelperFactory @Inject constructor( + private val colorProvider: ColorProvider, + private val clock: Clock, +) { + fun create(fragment: Fragment): GalleryOrCameraDialogHelper { + return GalleryOrCameraDialogHelper(fragment, colorProvider, clock) + } +} 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 61c4fe2174..f3aef54062 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 @@ -175,7 +175,7 @@ fun Fragment.queryExportKeys(userId: String, activityResultLauncher: ActivityRes selectTxtFileToWrite( activity = requireActivity(), activityResultLauncher = activityResultLauncher, - defaultFileName = "$appName-megolm-export-$userId-$timestamp.txt", + defaultFileName = "$appName-megolm-export-$userId-${timestamp}.txt", chooserHint = getString(R.string.keys_backup_setup_step1_manual_export) ) } @@ -187,7 +187,7 @@ fun Activity.queryExportKeys(userId: String, activityResultLauncher: ActivityRes selectTxtFileToWrite( activity = this, activityResultLauncher = activityResultLauncher, - defaultFileName = "$appName-megolm-export-$userId-$timestamp.txt", + defaultFileName = "$appName-megolm-export-$userId-${timestamp}.txt", chooserHint = getString(R.string.keys_backup_setup_step1_manual_export) ) } diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt index caed413e2b..cb1d46efce 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.session.sync.FilterService import timber.log.Timber fun Session.configureAndStart(context: Context, startSyncing: Boolean = true) { - Timber.i("Configure and start session for $myUserId") + Timber.i("Configure and start session for $myUserId. startSyncing: $startSyncing") open() filterService().setFilter(FilterService.FilterPreset.ElementFilter) if (startSyncing) { diff --git a/vector/src/main/java/im/vector/app/core/extensions/Throwable.kt b/vector/src/main/java/im/vector/app/core/extensions/Throwable.kt new file mode 100644 index 0000000000..0aa9039dcb --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/extensions/Throwable.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.extensions + +/** + * Recursive through the throwable and its causes for the given predicate. + * + * @return true when the predicate finds a match. + */ +tailrec fun Throwable?.crawlCausesFor(predicate: (Throwable) -> Boolean): Boolean { + return when { + this == null -> false + else -> { + when (predicate(this)) { + true -> true + else -> this.cause.crawlCausesFor(predicate) + } + } + } +} 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 24a65e1071..4e7b174772 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 @@ -21,7 +21,6 @@ import android.app.Activity import android.content.Context import android.os.Build import android.os.Bundle -import android.os.Parcelable import android.view.Menu import android.view.MenuInflater import android.view.MenuItem @@ -39,8 +38,6 @@ import androidx.core.content.ContextCompat import androidx.core.util.Consumer import androidx.core.view.MenuProvider import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentFactory import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider @@ -67,7 +64,6 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.singletonEntryPoint -import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.resources.BuildMeta import im.vector.app.core.utils.AndroidSystemSettingsProvider import im.vector.app.core.utils.ToolbarConfig @@ -169,7 +165,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver lateinit var navigator: Navigator private set - private lateinit var fragmentFactory: FragmentFactory private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var vectorPreferences: VectorPreferences @@ -210,8 +205,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver val singletonEntryPoint = singletonEntryPoint() val activityEntryPoint = EntryPointAccessors.fromActivity(this, ActivityEntryPoint::class.java) ThemeUtils.setActivityTheme(this, getOtherThemes()) - fragmentFactory = activityEntryPoint.fragmentFactory() - supportFragmentManager.fragmentFactory = fragmentFactory viewModelFactory = activityEntryPoint.viewModelFactory() super.onCreate(savedInstanceState) addOnMultiWindowModeChangedListener(onMultiWindowModeChangedListener) @@ -464,12 +457,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver bugReporter.inMultiWindowMode = it.isInMultiWindowMode } - protected fun createFragment(fragmentClass: Class, argsParcelable: Parcelable? = null): Fragment { - return fragmentFactory.instantiate(classLoader, fragmentClass.name).apply { - arguments = argsParcelable?.toMvRxBundle() - } - } - /* ========================================================================================== * PRIVATE METHODS * ========================================================================================== */ 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 340c906a6d..8fe2d33f6a 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 @@ -123,7 +123,6 @@ abstract class VectorBaseFragment : Fragment(), MavericksView analyticsTracker = singletonEntryPoint.analyticsTracker() unrecognizedCertificateDialog = singletonEntryPoint.unrecognizedCertificateDialog() viewModelFactory = activityEntryPoint.viewModelFactory() - childFragmentManager.fragmentFactory = activityEntryPoint.fragmentFactory() super.onAttach(context) } diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt index 1f44ab3686..0993485471 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt @@ -92,8 +92,6 @@ class UnifiedPushHelper @Inject constructor( return@launch } - // By default, use internal solution (fcm/background sync) - UnifiedPush.saveDistributor(context, context.packageName) val distributors = UnifiedPush.getDistributors(context) if (distributors.size == 1 && !force) { @@ -101,7 +99,14 @@ class UnifiedPushHelper @Inject constructor( UnifiedPush.registerApp(context) onDoneRunnable?.run() } else { - openDistributorDialogInternal(activity, pushersManager, onDoneRunnable, distributors, !force, !force) + openDistributorDialogInternal( + activity = activity, + pushersManager = pushersManager, + onDoneRunnable = onDoneRunnable, + distributors = distributors, + unregisterFirst = force, + cancellable = !force + ) } } } @@ -165,6 +170,12 @@ class UnifiedPushHelper @Inject constructor( onDoneRunnable?.run() } } + .setOnCancelListener { + // By default, use internal solution (fcm/background sync) + UnifiedPush.saveDistributor(context, context.packageName) + UnifiedPush.registerApp(context) + onDoneRunnable?.run() + } .setCancelable(cancellable) .show() } 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 14fae80325..e1e7764f19 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -38,6 +38,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.deleteAllFiles import im.vector.app.databinding.ActivityMainBinding import im.vector.app.features.analytics.VectorAnalytics +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.ShortcutsHandler import im.vector.app.features.notifications.NotificationDrawerManager @@ -186,8 +187,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } else if (intent.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) { val roomId = intent.getStringExtra(EXTRA_ROOM_ID) if (roomId?.isNotEmpty() == true) { - // TODO Add a trigger Shortcut to the analytics. - navigator.openRoom(this, roomId) + navigator.openRoom(this, roomId, trigger = ViewRoom.Trigger.Shortcut) } finish() } else { diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt index deb9088259..6336faa74c 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt @@ -117,6 +117,12 @@ data class Interaction( */ WebLeftPanelExploreRoomsButton, + /** + * User clicked on the avatar uploader in the profile settings of + * Element Web/Desktop. + */ + WebProfileSettingsAvatarUploadButton, + /** * User interacted with pin to sidebar checkboxes in the quick settings * menu of Element Web/Desktop. @@ -279,6 +285,18 @@ data class Interaction( */ WebRoomListRoomsSublistPlusMenuExploreRoomsItem, + /** + * User clicked on the button to return to the user onboarding list in + * the room list in Element Web/Desktop. + */ + WebRoomListUserOnboardingButton, + + /** + * User clicked on the button to close the user onboarding button in the + * room list in Element Web/Desktop. + */ + WebRoomListUserOnboardingIgnoreButton, + /** * User interacted with leave action in the general tab of the room * settings dialog in Element Web/Desktop. @@ -349,6 +367,36 @@ data class Interaction( * Web/Desktop. */ WebUserMenuThemeToggleButton, + + /** + * User clicked on the send DM CTA in the header of the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingHeaderSendDm, + + /** + * User clicked on the action of the download apps task on the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskDownloadApps, + + /** + * User clicked on the action of the enable notifications task on the + * new user onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskEnableNotifications, + + /** + * User clicked on the action of the find people task on the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskSendDm, + + /** + * User clicked on the action of the your profile task on the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskSetupProfile, } enum class InteractionType { diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/PermissionChanged.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/PermissionChanged.kt new file mode 100644 index 0000000000..9f463a4107 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/PermissionChanged.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.analytics.plan + +import im.vector.app.features.analytics.itf.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user changes a permission status. + */ +data class PermissionChanged( + /** + * Whether the permission has been granted by the user. + */ + val granted: Boolean, + /** + * The name of the permission. + */ + val permission: Permission, +) : VectorAnalyticsEvent { + + enum class Permission { + /** + * Permissions related to sending notifications have changed. + */ + Notification, + } + + override fun getName() = "PermissionChanged" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("granted", granted) + put("permission", permission.name) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt index d2f30eec9b..f6a724304b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt @@ -152,6 +152,11 @@ data class ViewRoom( */ RoomList, + /** + * Room accessed via a shortcut. + */ + Shortcut, + /** * Room accessed via a slash command in Element Web/Desktop like /goto. */ diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt index a5bafa2ee6..fbeeab9ec3 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.setTextWithColoredPart import im.vector.app.core.platform.OnBackPressed @@ -30,9 +31,12 @@ import im.vector.app.databinding.FragmentAnalyticsOptinBinding import im.vector.app.features.analytics.AnalyticsConfig import javax.inject.Inject -class AnalyticsOptInFragment @Inject constructor( - private val analyticsConfig: AnalyticsConfig, -) : VectorBaseFragment(), OnBackPressed { +@AndroidEntryPoint +class AnalyticsOptInFragment : + VectorBaseFragment(), + OnBackPressed { + + @Inject lateinit var analyticsConfig: AnalyticsConfig // Share the view model with the Activity so that the Activity // can decide what to do when the data has been saved 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 c3a4ae7df2..47b19a435e 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 @@ -39,6 +39,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.yalantis.ucrop.UCrop +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.insertBeforeLast @@ -63,15 +64,17 @@ data class AttachmentsPreviewArgs( val attachments: List ) : Parcelable -class AttachmentsPreviewFragment @Inject constructor( - private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController, - private val attachmentBigPreviewController: AttachmentBigPreviewController, - private val colorProvider: ColorProvider, - private val clock: Clock, -) : VectorBaseFragment(), +@AndroidEntryPoint +class AttachmentsPreviewFragment : + VectorBaseFragment(), AttachmentMiniaturePreviewController.Callback, VectorMenuProvider { + @Inject lateinit var attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController + @Inject lateinit var attachmentBigPreviewController: AttachmentBigPreviewController + @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var clock: Clock + private val fragmentArgs: AttachmentsPreviewArgs by args() private val viewModel: AttachmentsPreviewViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt new file mode 100644 index 0000000000..a0d6e29849 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022 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.call.dialpad + +import android.os.Bundle +import androidx.appcompat.app.AppCompatDialog +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.createdirect.DirectRoomHelper +import im.vector.app.features.settings.VectorLocale +import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +@AndroidEntryPoint +class PstnDialActivity : SimpleFragmentActivity() { + + @Inject lateinit var callManager: WebRtcCallManager + @Inject lateinit var directRoomHelper: DirectRoomHelper + @Inject lateinit var session: Session + @Inject lateinit var errorFormatter: ErrorFormatter + + private var progress: AppCompatDialog? = null + + override fun getTitleRes(): Int = R.string.call + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (isFirstCreation()) { + addFragment( + views.container, + createDialPadFragment() + ) + } + } + + private fun handleStartCallWithPhoneNumber(rawNumber: String) { + lifecycleScope.launch { + try { + showLoadingDialog() + val result = DialPadLookup(session, callManager, directRoomHelper).lookupPhoneNumber(rawNumber) + callManager.startOutgoingCall(result.roomId, result.userId, isVideoCall = false) + dismissLoadingDialog() + finish() + } catch (failure: Throwable) { + dismissLoadingDialog() + displayErrorDialog(failure) + } + } + } + + private fun createDialPadFragment(): Fragment { + val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, DialPadFragment::class.java.name) + return (fragment as DialPadFragment).apply { + arguments = Bundle().apply { + putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) + putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true) + putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) + } + callback = object : DialPadFragment.Callback { + override fun onOkClicked(formatted: String?, raw: String?) { + if (raw.isNullOrEmpty()) return + handleStartCallWithPhoneNumber(raw) + } + } + } + } + + private fun showLoadingDialog() { + progress?.dismiss() + progress = MaterialProgressDialog(this) + .show(getString(R.string.please_wait)) + } + + private fun dismissLoadingDialog() { + progress?.dismiss() + } + + private fun displayErrorDialog(throwable: Throwable) { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(throwable)) + .setPositiveButton(R.string.ok, null) + .show() + } +} 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 8cd7f2de45..4677dce7d6 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 @@ -24,6 +24,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.hideKeyboard @@ -44,9 +45,12 @@ import reactivecircus.flowbinding.android.widget.checkedChanges import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject -class ContactsBookFragment @Inject constructor( - private val contactsBookController: ContactsBookController -) : VectorBaseFragment(), ContactsBookController.Callback { +@AndroidEntryPoint +class ContactsBookFragment : + VectorBaseFragment(), + ContactsBookController.Callback { + + @Inject lateinit var contactsBookController: ContactsBookController override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentContactsBookBinding { return FragmentContactsBookBinding.inflate(inflater, container, false) 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 b306cb6e03..36ee47ca06 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 @@ -132,10 +132,10 @@ class CreateDirectRoomViewModel @AssistedInject constructor( if (vectorFeatures.shouldStartDmOnFirstMessage()) { session.roomService().createLocalRoom(roomParams) } else { + analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) session.roomService().createRoom(roomParams) } } - analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt index de2027f2a5..c2cc13920f 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt @@ -16,6 +16,7 @@ package im.vector.app.features.createdirect +import im.vector.app.features.VectorFeatures import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.CreatedRoom import im.vector.app.features.raw.wellknown.getElementWellknown @@ -30,7 +31,8 @@ import javax.inject.Inject class DirectRoomHelper @Inject constructor( private val rawService: RawService, private val session: Session, - private val analyticsTracker: AnalyticsTracker + private val analyticsTracker: AnalyticsTracker, + private val vectorFeatures: VectorFeatures, ) { suspend fun ensureDMExists(userId: String): String { @@ -48,8 +50,12 @@ class DirectRoomHelper @Inject constructor( setDirectMessage() enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - roomId = session.roomService().createRoom(roomParams) - analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) + roomId = if (vectorFeatures.shouldStartDmOnFirstMessage()) { + session.roomService().createLocalRoom(roomParams) + } else { + analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) + session.roomService().createRoom(roomParams) + } } return roomId } 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 42605a850b..9a3d37eed7 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 @@ -22,15 +22,16 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint 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 im.vector.app.databinding.FragmentKeysBackupRestoreFromKeyBinding import org.matrix.android.sdk.api.extensions.tryOrNull -import javax.inject.Inject -class KeysBackupRestoreFromKeyFragment @Inject constructor() : +@AndroidEntryPoint +class KeysBackupRestoreFromKeyFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreFromKeyBinding { 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 631bc9ff4f..cf98bc7e4d 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 @@ -24,12 +24,14 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.core.text.set import androidx.core.widget.doOnTextChanged +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentKeysBackupRestoreFromPassphraseBinding -import javax.inject.Inject -class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class KeysBackupRestoreFromPassphraseFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreFromPassphraseBinding { return FragmentKeysBackupRestoreFromPassphraseBinding.inflate(inflater, container, false) 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 d26c1e2134..4a1bc49e27 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 @@ -20,13 +20,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.LiveEvent import im.vector.app.databinding.FragmentKeysBackupRestoreSuccessBinding -import javax.inject.Inject -class KeysBackupRestoreSuccessFragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class KeysBackupRestoreSuccessFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreSuccessBinding { return FragmentKeysBackupRestoreSuccessBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index edc44fa796..c0001501d5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt @@ -22,6 +22,7 @@ import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -30,10 +31,13 @@ import im.vector.app.databinding.FragmentKeysBackupSettingsBinding import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import javax.inject.Inject -class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController) : +@AndroidEntryPoint +class KeysBackupSettingsFragment : VectorBaseFragment(), KeysBackupSettingsRecyclerViewController.Listener { + @Inject lateinit var keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSettingsBinding { return FragmentKeysBackupSettingsBinding.inflate(inflater, container, false) } 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 7d8feba942..9b8386c741 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 @@ -20,12 +20,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.LiveEvent import im.vector.app.databinding.FragmentKeysBackupSetupStep1Binding -import javax.inject.Inject -class KeysBackupSetupStep1Fragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class KeysBackupSetupStep1Fragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep1Binding { return FragmentKeysBackupSetupStep1Binding.inflate(inflater, container, false) 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 706076dae0..cf92afcc2e 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 @@ -24,6 +24,7 @@ import androidx.core.widget.doOnTextChanged import androidx.lifecycle.viewModelScope import androidx.transition.TransitionManager import com.nulabinc.zxcvbn.Zxcvbn +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hidePassword import im.vector.app.core.platform.VectorBaseFragment @@ -31,9 +32,10 @@ import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding import im.vector.app.features.settings.VectorLocale import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import javax.inject.Inject -class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class KeysBackupSetupStep2Fragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep2Binding { return FragmentKeysBackupSetupStep2Binding.inflate(inflater, container, false) 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 b8b84ea322..7e0fb7e417 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 @@ -28,6 +28,7 @@ import androidx.lifecycle.lifecycleScope import arrow.core.Try import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.safeOpenOutputStream @@ -44,9 +45,10 @@ import java.io.IOException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -import javax.inject.Inject -class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class KeysBackupSetupStep3Fragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep3Binding { return FragmentKeysBackupSetupStep3Binding.inflate(inflater, container, false) @@ -134,7 +136,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class SharedSecuredStorageKeyFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssAccessFromKeyBinding { return FragmentSsssAccessFromKeyBinding.inflate(inflater, container, false) 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 5af5480573..877e4aa164 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 @@ -24,20 +24,19 @@ import android.view.inputmethod.EditorInfo import androidx.core.text.toSpannable import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import im.vector.lib.core.utils.flow.throttleFirst import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -class SharedSecuredStoragePassphraseFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +@AndroidEntryPoint +class SharedSecuredStoragePassphraseFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssAccessFromPassphraseBinding { return FragmentSsssAccessFromPassphraseBinding.inflate(inflater, container, false) 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 index c0d0aa8e76..66344107a4 100644 --- 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 @@ -22,14 +22,15 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSsssResetAllBinding import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet -import javax.inject.Inject -class SharedSecuredStorageResetAllFragment @Inject constructor() : +@AndroidEntryPoint +class SharedSecuredStorageResetAllFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssResetAllBinding { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt index 555f2d7c1c..22ecd3dafd 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.core.text.toSpannable import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider @@ -30,9 +31,11 @@ import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.databinding.FragmentBootstrapConclusionBinding import javax.inject.Inject -class BootstrapConclusionFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +@AndroidEntryPoint +class BootstrapConclusionFragment : + VectorBaseFragment() { + + @Inject lateinit var colorProvider: ColorProvider override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapConclusionBinding { return FragmentBootstrapConclusionBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt index 3c8137d087..285721ee75 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt @@ -25,6 +25,7 @@ import androidx.core.view.isGone import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment @@ -34,9 +35,9 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -class BootstrapConfirmPassphraseFragment @Inject constructor() : +@AndroidEntryPoint +class BootstrapConfirmPassphraseFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt index ff6d109b3c..43cc25f195 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -24,6 +24,7 @@ import android.view.inputmethod.EditorInfo import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding @@ -33,9 +34,9 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -class BootstrapEnterPassphraseFragment @Inject constructor() : +@AndroidEntryPoint +class BootstrapEnterPassphraseFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding { 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 2c0ccec9fb..2802c872ee 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 @@ -30,6 +30,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult @@ -47,9 +48,11 @@ import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject -class BootstrapMigrateBackupFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +@AndroidEntryPoint +class BootstrapMigrateBackupFragment : + VectorBaseFragment() { + + @Inject lateinit var colorProvider: ColorProvider override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapMigrateBackupBinding { return FragmentBootstrapMigrateBackupBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt index c46ccbf08e..d5e60631a5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt @@ -23,15 +23,14 @@ import android.view.ViewGroup import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentBootstrapReauthBinding -import javax.inject.Inject -class BootstrapReAuthFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +@AndroidEntryPoint +class BootstrapReAuthFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapReauthBinding { return FragmentBootstrapReauthBinding.inflate(inflater, container, false) 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 db24807c1b..21d68dfe99 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 @@ -27,21 +27,20 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentBootstrapSaveKeyBinding import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import javax.inject.Inject -class BootstrapSaveRecoveryKeyFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +@AndroidEntryPoint +class BootstrapSaveRecoveryKeyFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapSaveKeyBinding { return FragmentBootstrapSaveKeyBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt index 3d078a82ed..b03cbe87a8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -23,13 +23,14 @@ import android.view.ViewGroup import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapSetupRecoveryBinding import im.vector.app.features.raw.wellknown.SecureBackupMethod -import javax.inject.Inject -class BootstrapSetupRecoveryKeyFragment @Inject constructor() : +@AndroidEntryPoint +class BootstrapSetupRecoveryKeyFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapSetupRecoveryBinding { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt index e0965e69f9..310bb5ac37 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt @@ -21,11 +21,12 @@ import android.view.ViewGroup import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapWaitingBinding -import javax.inject.Inject -class BootstrapWaitingFragment @Inject constructor() : +@AndroidEntryPoint +class BootstrapWaitingFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapWaitingBinding { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt index 366348120b..5b6902df98 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt @@ -18,11 +18,13 @@ package im.vector.app.features.crypto.verification import android.view.LayoutInflater import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentProgressBinding -import javax.inject.Inject -class QuadSLoadingFragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class QuadSLoadingFragment : + VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentProgressBinding { return FragmentProgressBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelFragment.kt index 62bab05e42..c972f7834d 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationCancelFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -29,11 +30,13 @@ import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import javax.inject.Inject -class VerificationCancelFragment @Inject constructor( - val controller: VerificationCancelController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationCancelFragment : + VectorBaseFragment(), VerificationCancelController.Listener { + @Inject lateinit var controller: VerificationCancelController + private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeFragment.kt index 635a56a6c1..05632bdfc3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/cancel/VerificationNotMeFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -29,11 +30,13 @@ import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import javax.inject.Inject -class VerificationNotMeFragment @Inject constructor( - val controller: VerificationNotMeController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationNotMeFragment : + VectorBaseFragment(), VerificationNotMeController.Listener { + @Inject lateinit var controller: VerificationNotMeController + private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding { 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 3d3766f430..45f7908446 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 @@ -23,6 +23,7 @@ import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -39,11 +40,12 @@ import im.vector.app.features.qrcode.QrCodeScannerActivity import timber.log.Timber import javax.inject.Inject -class VerificationChooseMethodFragment @Inject constructor( - val controller: VerificationChooseMethodController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationChooseMethodFragment : + VectorBaseFragment(), VerificationChooseMethodController.Listener { + @Inject lateinit var controller: VerificationChooseMethodController private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class) private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionFragment.kt index 85b90e6004..dd559e8a1b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -32,11 +33,13 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheetViewMod import kotlinx.parcelize.Parcelize import javax.inject.Inject -class VerificationConclusionFragment @Inject constructor( - val controller: VerificationConclusionController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationConclusionFragment : + VectorBaseFragment(), VerificationConclusionController.Listener { + @Inject lateinit var controller: VerificationConclusionController + @Parcelize data class Args( val isSuccessFull: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt index 3f4eaf8ac9..1e1a8d0710 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt @@ -22,6 +22,7 @@ import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -30,11 +31,13 @@ import im.vector.app.features.crypto.verification.VerificationAction import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import javax.inject.Inject -class VerificationEmojiCodeFragment @Inject constructor( - val controller: VerificationEmojiCodeController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationEmojiCodeFragment : + VectorBaseFragment(), VerificationEmojiCodeController.Listener { + @Inject lateinit var controller: VerificationEmojiCodeController + private val viewModel by fragmentViewModel(VerificationEmojiCodeViewModel::class) private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt index 441885dd10..e5402424d0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.Mavericks +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -29,9 +30,11 @@ import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding import kotlinx.parcelize.Parcelize import javax.inject.Inject -class VerificationQRWaitingFragment @Inject constructor( - val controller: VerificationQRWaitingController -) : VectorBaseFragment() { +@AndroidEntryPoint +class VerificationQRWaitingFragment : + VectorBaseFragment() { + + @Inject lateinit var controller: VerificationQRWaitingController @Parcelize data class Args( diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt index 9e77506e3b..0fdbd03174 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt @@ -21,6 +21,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -29,11 +30,13 @@ import im.vector.app.features.crypto.verification.VerificationAction import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import javax.inject.Inject -class VerificationQrScannedByOtherFragment @Inject constructor( - val controller: VerificationQrScannedByOtherController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationQrScannedByOtherFragment : + VectorBaseFragment(), VerificationQrScannedByOtherController.Listener { + @Inject lateinit var controller: VerificationQrScannedByOtherController + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestFragment.kt index 238249683f..6887451a76 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestFragment.kt @@ -21,6 +21,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -29,11 +30,13 @@ import im.vector.app.features.crypto.verification.VerificationAction import im.vector.app.features.crypto.verification.VerificationBottomSheetViewModel import javax.inject.Inject -class VerificationRequestFragment @Inject constructor( - val controller: VerificationRequestController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VerificationRequestFragment : + VectorBaseFragment(), VerificationRequestController.Listener { + @Inject lateinit var controller: VerificationRequestController + private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding { diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt index 774460eb1f..011deb612f 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt @@ -109,15 +109,15 @@ class RoomDevToolActivity : } RoomDevToolViewState.Mode.StateEventList, RoomDevToolViewState.Mode.StateEventListByType -> { - val frag = createFragment(RoomDevToolStateEventListFragment::class.java) + val frag = RoomDevToolStateEventListFragment() navigateTo(frag) } RoomDevToolViewState.Mode.EditEventContent -> { - val frag = createFragment(RoomDevToolEditFragment::class.java) + val frag = RoomDevToolEditFragment() navigateTo(frag) } is RoomDevToolViewState.Mode.SendEventForm -> { - val frag = createFragment(RoomDevToolSendFormFragment::class.java) + val frag = RoomDevToolSendFormFragment() navigateTo(frag) } } diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt index 1b6fbb7359..3bbb43013d 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt @@ -23,15 +23,16 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentDevtoolsEditorBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -class RoomDevToolEditFragment @Inject constructor() : +@AndroidEntryPoint +class RoomDevToolEditFragment : VectorBaseFragment() { private val sharedViewModel: RoomDevToolViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolFragment.kt index 0e1a2418b8..0ebbf7d8bf 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolFragment.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -28,11 +29,13 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject -class RoomDevToolFragment @Inject constructor( - private val epoxyController: RoomDevToolRootController -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomDevToolFragment : + VectorBaseFragment(), DevToolsInteractionListener { + @Inject lateinit var epoxyController: RoomDevToolRootController + private val sharedViewModel: RoomDevToolViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolSendFormFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolSendFormFragment.kt index 6b7dea7d53..3ec49e25f6 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolSendFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolSendFormFragment.kt @@ -22,15 +22,19 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject -class RoomDevToolSendFormFragment @Inject constructor( - private val epoxyController: RoomDevToolSendFormController -) : VectorBaseFragment(), DevToolsInteractionListener { +@AndroidEntryPoint +class RoomDevToolSendFormFragment : + VectorBaseFragment(), + DevToolsInteractionListener { + + @Inject lateinit var epoxyController: RoomDevToolSendFormController val sharedViewModel: RoomDevToolViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolStateEventListFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolStateEventListFragment.kt index 728fb62d66..dfc74e7e74 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolStateEventListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolStateEventListFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -29,9 +30,12 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject -class RoomDevToolStateEventListFragment @Inject constructor( - private val epoxyController: RoomStateListController -) : VectorBaseFragment(), DevToolsInteractionListener { +@AndroidEntryPoint +class RoomDevToolStateEventListFragment : + VectorBaseFragment(), + DevToolsInteractionListener { + + @Inject lateinit var epoxyController: RoomStateListController val sharedViewModel: RoomDevToolViewModel by activityViewModel() 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 285d0f728f..8c801bdf89 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 @@ -25,6 +25,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -43,11 +44,13 @@ import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.terms.TermsService import javax.inject.Inject -class DiscoverySettingsFragment @Inject constructor( - private val controller: DiscoverySettingsController -) : VectorBaseFragment(), +@AndroidEntryPoint +class DiscoverySettingsFragment : + VectorBaseFragment(), DiscoverySettingsController.Listener { + @Inject lateinit var controller: DiscoverySettingsController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } 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 ee36345418..f8499219aa 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 @@ -28,6 +28,7 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.toReducedUrl @@ -42,9 +43,11 @@ import org.matrix.android.sdk.api.session.terms.TermsService import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject -class SetIdentityServerFragment @Inject constructor( - val colorProvider: ColorProvider -) : VectorBaseFragment() { +@AndroidEntryPoint +class SetIdentityServerFragment : + VectorBaseFragment() { + + @Inject lateinit var colorProvider: ColorProvider override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSetIdentityServerBinding { return FragmentSetIdentityServerBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt index 816b9acb24..8ca217636a 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt @@ -37,6 +37,7 @@ import im.vector.app.features.themes.ThemeUtils @EpoxyModelClass abstract class HomeSpaceSummaryItem : VectorEpoxyModel(R.layout.item_space) { + @EpoxyAttribute var text: String = "" @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) diff --git a/vector/src/main/java/im/vector/app/features/grouplist/NewHomeSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/NewHomeSpaceSummaryItem.kt new file mode 100644 index 0000000000..1f967db7ad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/grouplist/NewHomeSpaceSummaryItem.kt @@ -0,0 +1,69 @@ +/* + * 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.grouplist + +import android.content.res.ColorStateList +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +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.platform.CheckableConstraintLayout +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import im.vector.app.features.themes.ThemeUtils + +@EpoxyModelClass +abstract class NewHomeSpaceSummaryItem : VectorEpoxyModel(R.layout.item_new_space) { + + @EpoxyAttribute var text: String = "" + @EpoxyAttribute var selected: Boolean = false + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + @EpoxyAttribute var showSeparator: Boolean = false + + override fun getViewType() = R.id.space_item_home + + override fun bind(holder: Holder) { + super.bind(holder) + holder.root.onClick(listener) + holder.name.text = holder.view.context.getString(R.string.all_chats) + holder.root.isChecked = selected + holder.root.context.resources + holder.avatar.background = ContextCompat.getDrawable(holder.view.context, R.drawable.new_space_home_background) + holder.avatar.backgroundTintList = ColorStateList.valueOf( + ColorUtils.setAlphaComponent(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_tertiary), (255 * 0.3).toInt())) + holder.avatar.setImageResource(R.drawable.ic_space_home) + holder.avatar.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary)) + holder.avatar.scaleType = ImageView.ScaleType.CENTER_INSIDE + + holder.unreadCounter.render(countState) + } + + class Holder : VectorEpoxyHolder() { + val root by bind(R.id.root) + val avatar by bind(R.id.avatar) + val name by bind(R.id.name) + val unreadCounter by bind(R.id.unread_counter) + } +} 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 fe57b9f735..553b45ad81 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 @@ -56,6 +56,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator @@ -283,6 +284,11 @@ class HomeActivity : .show(supportFragmentManager, "SPACE_SETTINGS") } + private fun showLayoutSettings() { + HomeLayoutSettingBottomDialogFragment() + .show(supportFragmentManager, "LAYOUT_SETTINGS") + } + private fun openSpaceInvite(spaceId: String) { SpaceInviteBottomSheet.newInstance(spaceId) .show(supportFragmentManager, "SPACE_INVITE") @@ -596,6 +602,10 @@ class HomeActivity : navigator.openSettings(this) true } + R.id.menu_home_layout_settings -> { + showLayoutSettings() + true + } R.id.menu_home_invite_friends -> { launchInviteFriends() true diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index d4c89c1bca..cdc16f1f33 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -28,6 +28,7 @@ import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.badge.BadgeDrawable +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.core.extensions.commitTransaction @@ -60,19 +61,21 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject -class HomeDetailFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val colorProvider: ColorProvider, - private val alertManager: PopupAlertManager, - private val callManager: WebRtcCallManager, - private val vectorPreferences: VectorPreferences, - private val spaceStateHandler: SpaceStateHandler, -) : VectorBaseFragment(), +@AndroidEntryPoint +class HomeDetailFragment : + VectorBaseFragment(), KeysBackupBanner.Delegate, CurrentCallsView.Callback, OnBackPressed, VectorMenuProvider { + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var alertManager: PopupAlertManager + @Inject lateinit var callManager: WebRtcCallManager + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var spaceStateHandler: SpaceStateHandler + private val viewModel: HomeDetailViewModel by fragmentViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() private val unreadMessagesSharedViewModel: UnreadMessagesSharedViewModel by activityViewModel() 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 535f38e68e..011a7c4537 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 @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.core.app.ActivityOptionsCompat import androidx.core.view.ViewCompat import androidx.core.view.isVisible +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.observeK import im.vector.app.core.extensions.replaceChildFragment @@ -40,12 +41,14 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class HomeDrawerFragment @Inject constructor( - private val session: Session, - private val vectorPreferences: VectorPreferences, - private val avatarRenderer: AvatarRenderer, - private val buildMeta: BuildMeta, -) : VectorBaseFragment() { +@AndroidEntryPoint +class HomeDrawerFragment : + VectorBaseFragment() { + + @Inject lateinit var session: Session + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var buildMeta: BuildMeta private lateinit var sharedActionViewModel: HomeSharedActionViewModel 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 47cfd1c28f..c5e33abf34 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 @@ -21,11 +21,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLoadingBinding -import javax.inject.Inject -class LoadingFragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class LoadingFragment : VectorBaseFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoadingBinding { return FragmentLoadingBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index 2e36748069..4b14fecae9 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -16,18 +16,18 @@ package im.vector.app.features.home +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.google.android.material.badge.BadgeDrawable +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.core.extensions.commitTransaction @@ -42,15 +42,13 @@ import im.vector.app.core.ui.views.KeysBackupBanner import im.vector.app.databinding.FragmentNewHomeDetailBinding import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity -import im.vector.app.features.call.dialpad.DialPadFragment +import im.vector.app.features.call.dialpad.PstnDialActivity import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.list.home.HomeRoomListFragment import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert -import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS -import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.BannerState import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import kotlinx.coroutines.Dispatchers @@ -61,23 +59,24 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class NewHomeDetailFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val colorProvider: ColorProvider, - private val alertManager: PopupAlertManager, - private val callManager: WebRtcCallManager, - private val vectorPreferences: VectorPreferences, - private val appStateHandler: SpaceStateHandler, - private val session: Session, -) : VectorBaseFragment(), +@AndroidEntryPoint +class NewHomeDetailFragment : + VectorBaseFragment(), KeysBackupBanner.Delegate, CurrentCallsView.Callback, OnBackPressed, VectorMenuProvider { + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var alertManager: PopupAlertManager + @Inject lateinit var callManager: WebRtcCallManager + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var spaceStateHandler: SpaceStateHandler + @Inject lateinit var session: Session + private val viewModel: HomeDetailViewModel by fragmentViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() - private val unreadMessagesSharedViewModel: UnreadMessagesSharedViewModel by activityViewModel() private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel() private lateinit var sharedActionViewModel: HomeSharedActionViewModel @@ -99,6 +98,10 @@ class NewHomeDetailFragment @Inject constructor( viewModel.handle(HomeDetailAction.MarkAllRoomsRead) true } + R.id.menu_home_dialpad -> { + startActivity(Intent(requireContext(), PstnDialActivity::class.java)) + true + } else -> false } } @@ -107,6 +110,7 @@ class NewHomeDetailFragment @Inject constructor( withState(viewModel) { state -> val isRoomList = state.currentTab is HomeTab.RoomList menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = isRoomList && hasUnreadRooms + menu.findItem(R.id.menu_home_dialpad).isVisible = state.showDialPadTab } } @@ -120,32 +124,22 @@ class NewHomeDetailFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) - setupBottomNavigationView() setupToolbar() setupKeysBackupBanner() setupActiveCallView() - withState(viewModel) { - // Update the navigation view if needed (for when we restore the tabs) - views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId() + childFragmentManager.commitTransaction { + add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, HOME_ROOM_LIST_FRAGMENT_TAG) } viewModel.onEach(HomeDetailViewState::selectedSpace) { selectedSpace -> onSpaceChange(selectedSpace) } - viewModel.onEach(HomeDetailViewState::currentTab) { currentTab -> - updateUIForTab(currentTab) - } - - viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab -> - updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab) - } - viewModel.observeViewEvents { viewEvent -> when (viewEvent) { - HomeDetailViewEvents.CallStarted -> handleCallStarted() - is HomeDetailViewEvents.FailToCall -> showFailure(viewEvent.failure) + HomeDetailViewEvents.CallStarted -> Unit + is HomeDetailViewEvents.FailToCall -> Unit HomeDetailViewEvents.Loading -> showLoadingDialog() } } @@ -176,22 +170,16 @@ class NewHomeDetailFragment @Inject constructor( } private fun navigateBack() { - val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull() - val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull() + val previousSpaceId = spaceStateHandler.getSpaceBackstack().removeLastOrNull() + val parentSpaceId = spaceStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull() setCurrentSpace(previousSpaceId ?: parentSpaceId) } private fun setCurrentSpace(spaceId: String?) { - appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false) + spaceStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false) sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace) } - private fun handleCallStarted() { - dismissLoadingDialog() - val fragmentTag = HomeTab.DialPad.toFragmentTag() - (childFragmentManager.findFragmentByTag(fragmentTag) as? DialPadFragment)?.clear() - } - override fun onDestroyView() { currentCallsViewPresenter.unBind() super.onDestroyView() @@ -199,13 +187,12 @@ class NewHomeDetailFragment @Inject constructor( override fun onResume() { super.onResume() - updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab()) callManager.checkForProtocolsSupportIfNeeded() refreshSpaceState() } private fun refreshSpaceState() { - appStateHandler.getCurrentSpace()?.let { + spaceStateHandler.getCurrentSpace()?.let { onSpaceChange(it) } } @@ -268,8 +255,7 @@ class NewHomeDetailFragment @Inject constructor( } private fun onSpaceChange(spaceSummary: RoomSummary?) { - // Reimplement in next PR - println(spaceSummary) + views.collapsingToolbar.title = (spaceSummary?.displayName ?: getString(R.string.all_chats)) } private fun setupKeysBackupBanner() { @@ -298,82 +284,17 @@ class NewHomeDetailFragment @Inject constructor( } } + views.collapsingToolbar.debouncedClicks(::openSpaceSettings) + views.toolbar.debouncedClicks(::openSpaceSettings) + views.avatar.debouncedClicks { navigator.openSettings(requireContext()) } } - private fun setupBottomNavigationView() { - views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab() - views.bottomNavigationView.setOnItemSelectedListener { - val tab = when (it.itemId) { - R.id.bottom_action_people -> HomeTab.RoomList(RoomListDisplayMode.PEOPLE) - R.id.bottom_action_rooms -> HomeTab.RoomList(RoomListDisplayMode.ROOMS) - R.id.bottom_action_notification -> HomeTab.RoomList(RoomListDisplayMode.NOTIFICATIONS) - else -> HomeTab.DialPad - } - viewModel.handle(HomeDetailAction.SwitchTab(tab)) - true - } - } - - private fun updateUIForTab(tab: HomeTab) { - views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true - updateSelectedFragment(tab) - invalidateOptionsMenu() - } - - private fun HomeTab.toFragmentTag() = "FRAGMENT_TAG_$this" - - private fun updateSelectedFragment(tab: HomeTab) { - val fragmentTag = tab.toFragmentTag() - val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag) - childFragmentManager.commitTransaction { - childFragmentManager.fragments - .filter { it != fragmentToShow } - .forEach { - detach(it) - } - if (fragmentToShow == null) { - when (tab) { - is HomeTab.RoomList -> { - add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, fragmentTag) - } - is HomeTab.DialPad -> { - add(R.id.roomListContainer, createDialPadFragment(), fragmentTag) - } - } - } else { - if (tab is HomeTab.DialPad) { - (fragmentToShow as? DialPadFragment)?.applyCallback() - } - attach(fragmentToShow) - } - } - } - - private fun createDialPadFragment(): Fragment { - val fragment = childFragmentManager.fragmentFactory.instantiate(vectorBaseActivity.classLoader, DialPadFragment::class.java.name) - return (fragment as DialPadFragment).apply { - arguments = Bundle().apply { - putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true) - putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true) - putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country) - } - applyCallback() - } - } - - private fun updateTabVisibilitySafely(tabId: Int, isVisible: Boolean) { - val wasVisible = views.bottomNavigationView.menu.findItem(tabId).isVisible - views.bottomNavigationView.menu.findItem(tabId).isVisible = isVisible - if (wasVisible && !isVisible) { - // As we hide it check if it's not the current item! - withState(viewModel) { - if (it.currentTab.toMenuId() == tabId) { - viewModel.handle(HomeDetailAction.SwitchTab(HomeTab.RoomList(RoomListDisplayMode.PEOPLE))) - } - } + private fun openSpaceSettings() = withState(viewModel) { viewState -> + viewState.selectedSpace?.let { + sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId)) } } @@ -390,9 +311,6 @@ class NewHomeDetailFragment @Inject constructor( } override fun invalidate() = withState(viewModel) { - views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople) - views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms) - views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup) views.syncStateView.render( it.syncState, it.incrementalSyncRequestState, @@ -403,27 +321,6 @@ class NewHomeDetailFragment @Inject constructor( hasUnreadRooms = it.hasUnreadMessages } - private fun BadgeDrawable.render(count: Int, highlight: Boolean) { - isVisible = count > 0 - number = count - maxCharacterCount = 3 - badgeTextColor = ThemeUtils.getColor(requireContext(), R.attr.colorOnPrimary) - backgroundColor = if (highlight) { - ThemeUtils.getColor(requireContext(), R.attr.colorError) - } else { - ThemeUtils.getColor(requireContext(), R.attr.vctr_unread_background) - } - } - - private fun HomeTab.toMenuId() = when (this) { - is HomeTab.DialPad -> R.id.bottom_action_dial_pad - is HomeTab.RoomList -> when (displayMode) { - RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people - RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms - else -> R.id.bottom_action_notification - } - } - override fun onTapToReturnToCall() { callManager.getCurrentCall()?.let { call -> VectorCallActivity.newIntent( @@ -440,20 +337,14 @@ class NewHomeDetailFragment @Inject constructor( } } - private fun DialPadFragment.applyCallback(): DialPadFragment { - callback = object : DialPadFragment.Callback { - override fun onOkClicked(formatted: String?, raw: String?) { - if (raw.isNullOrEmpty()) return - viewModel.handle(HomeDetailAction.StartCallWithPhoneNumber(raw)) - } - } - return this - } - - override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) { + override fun onBackPressed(toolbarButton: Boolean) = if (spaceStateHandler.getCurrentSpace() != null) { navigateBack() true } else { false } + + companion object { + private const val HOME_ROOM_LIST_FRAGMENT_TAG = "TAG_HOME_ROOM_LIST" + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt index 4d44ff775a..c5d7d76322 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -30,11 +31,13 @@ import im.vector.app.features.home.room.detail.RoomDetailSharedAction import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel import javax.inject.Inject -class BreadcrumbsFragment @Inject constructor( - private val breadcrumbsController: BreadcrumbsController -) : VectorBaseFragment(), +@AndroidEntryPoint +class BreadcrumbsFragment : + VectorBaseFragment(), BreadcrumbsController.Listener { + @Inject lateinit var breadcrumbsController: BreadcrumbsController + private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel private val breadcrumbsViewModel: BreadcrumbsViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index f164183914..b5eb0608d4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -67,10 +67,12 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.vanniktech.emoji.EmojiPopup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.animations.play import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.error.fatalError import im.vector.app.core.extensions.cleanup @@ -255,31 +257,8 @@ import java.net.URL import java.util.UUID import javax.inject.Inject -class TimelineFragment @Inject constructor( - private val session: Session, - private val avatarRenderer: AvatarRenderer, - private val timelineEventController: TimelineEventController, - autoCompleterFactory: AutoCompleter.Factory, - private val permalinkHandler: PermalinkHandler, - private val notificationDrawerManager: NotificationDrawerManager, - private val eventHtmlRenderer: EventHtmlRenderer, - private val vectorPreferences: VectorPreferences, - private val threadsManager: ThreadsManager, - private val colorProvider: ColorProvider, - private val dimensionConverter: DimensionConverter, - private val userPreferencesProvider: UserPreferencesProvider, - private val notificationUtils: NotificationUtils, - private val matrixItemColorProvider: MatrixItemColorProvider, - private val imageContentRenderer: ImageContentRenderer, - private val roomDetailPendingActionStore: RoomDetailPendingActionStore, - private val pillsPostProcessorFactory: PillsPostProcessor.Factory, - private val callManager: WebRtcCallManager, - private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, - private val shareIntentHandler: ShareIntentHandler, - private val clock: Clock, - private val vectorFeatures: VectorFeatures, - private val buildMeta: BuildMeta, -) : +@AndroidEntryPoint +class TimelineFragment : VectorBaseFragment(), TimelineEventController.Callback, VectorInviteView.Callback, @@ -289,6 +268,31 @@ class TimelineFragment @Inject constructor( CurrentCallsView.Callback, VectorMenuProvider { + @Inject lateinit var session: Session + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var timelineEventController: TimelineEventController + @Inject lateinit var autoCompleterFactory: AutoCompleter.Factory + @Inject lateinit var permalinkHandler: PermalinkHandler + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + @Inject lateinit var eventHtmlRenderer: EventHtmlRenderer + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var threadsManager: ThreadsManager + @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var dimensionConverter: DimensionConverter + @Inject lateinit var userPreferencesProvider: UserPreferencesProvider + @Inject lateinit var notificationUtils: NotificationUtils + @Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider + @Inject lateinit var imageContentRenderer: ImageContentRenderer + @Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore + @Inject lateinit var pillsPostProcessorFactory: PillsPostProcessor.Factory + @Inject lateinit var callManager: WebRtcCallManager + @Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker + @Inject lateinit var shareIntentHandler: ShareIntentHandler + @Inject lateinit var clock: Clock + @Inject lateinit var vectorFeatures: VectorFeatures + @Inject lateinit var buildMeta: BuildMeta + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + companion object { /** @@ -309,7 +313,7 @@ class TimelineFragment @Inject constructor( private const val ircPattern = " (IRC)" } - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper private val timelineArgs: TimelineArgs by args() private val glideRequests by lazy { @@ -362,6 +366,7 @@ class TimelineFragment @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) analyticsScreenName = MobileScreen.ScreenName.Room + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle -> bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId -> timelineViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index c0f90aba7a..cea845a490 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -82,6 +82,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError +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.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType @@ -1269,11 +1270,26 @@ class TimelineViewModel @AssistedInject constructor( } } room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also { - setState { copy(tombstoneEvent = it) } + onRoomTombstoneUpdated(it) } } } + private var roomTombstoneHandled = false + private fun onRoomTombstoneUpdated(tombstoneEvent: Event) = withState { state -> + if (roomTombstoneHandled) return@withState + if (state.isLocalRoom()) { + // Local room has been replaced, so navigate to the new room + val roomId = tombstoneEvent.getClearContent()?.toModel() + ?.replacementRoomId + ?: return@withState + _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true)) + roomTombstoneHandled = true + } else { + setState { copy(tombstoneEvent = tombstoneEvent) } + } + } + /** * Navigates to the appropriate event (by paginating the thread timeline until the event is found * in the snapshot. The main reason for this function is to support the /relations api 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 index de51adf05a..713ca80089 100644 --- 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 @@ -29,6 +29,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -51,12 +52,13 @@ data class SearchArgs( val roomAvatarUrl: String? ) : Parcelable -class SearchFragment @Inject constructor( - private val controller: SearchResultController -) : VectorBaseFragment(), +@AndroidEntryPoint +class SearchFragment : + VectorBaseFragment(), StateView.EventCallback, SearchResultController.Listener { + @Inject lateinit var controller: SearchResultController private val fragmentArgs: SearchArgs by args() private val searchViewModel: SearchViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt index 655d46194d..87c6a1efda 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineAsyncHelper.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.helper import android.os.Handler import android.os.HandlerThread -private const val THREAD_NAME = "Timeline_Building_Thread" +private const val THREAD_NAME = "Vector-Timeline_Building_Thread" object TimelineAsyncHelper { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index e6765bf35a..d22b649b36 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -229,8 +229,9 @@ class TimelineEventVisibilityHelper @Inject constructor( // Hide fake events for local rooms if (RoomLocalEcho.isLocalEchoId(roomId) && - root.getClearType() == EventType.STATE_ROOM_MEMBER || - root.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY) { + (root.getClearType() == EventType.STATE_ROOM_MEMBER || + root.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY || + root.getClearType() == EventType.STATE_ROOM_THIRD_PARTY_INVITE)) { return true } 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 c25fe546c3..de51101804 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 @@ -35,6 +35,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.extensions.cleanup @@ -70,17 +71,19 @@ data class RoomListParams( val displayMode: RoomListDisplayMode ) : Parcelable -class RoomListFragment @Inject constructor( - private val pagedControllerFactory: RoomSummaryPagedControllerFactory, - private val notificationDrawerManager: NotificationDrawerManager, - private val footerController: RoomListFooterController, - private val userPreferencesProvider: UserPreferencesProvider -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomListFragment : + VectorBaseFragment(), RoomListListener, OnBackPressed, FilteredRoomFooterItem.Listener, NotifsFabMenuView.Listener { + @Inject lateinit var pagedControllerFactory: RoomSummaryPagedControllerFactory + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + @Inject lateinit var footerController: RoomListFooterController + @Inject lateinit var userPreferencesProvider: UserPreferencesProvider + private var modelBuildListener: OnModelBuildFinishedListener? = null private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel private val roomListParams: RoomListParams by args() 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 4b76daf502..2e0a6ae347 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 @@ -146,17 +146,17 @@ class RoomListViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() private val roomListSectionBuilder = RoomListSectionBuilder( - session, - stringProvider, - spaceStateHandler, - viewModelScope, - autoAcceptInvites, - { - updatableQuery = it - }, - suggestedRoomJoiningState, - !vectorPreferences.prefSpacesShowAllRoomInHome() - ) + session, + stringProvider, + spaceStateHandler, + viewModelScope, + autoAcceptInvites, + { + updatableQuery = it + }, + suggestedRoomJoiningState, + !vectorPreferences.prefSpacesShowAllRoomInHome() + ) val sections: List by lazy { roomListSectionBuilder.buildSections(initialState.displayMode) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt index 0423a8fffc..8c84aa55e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt @@ -25,8 +25,7 @@ sealed class RoomListQuickActionsSharedAction( @StringRes val titleRes: Int, @DrawableRes val iconResId: Int?, val destructive: Boolean = false -) : - VectorSharedAction { +) : VectorSharedAction { data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActionsSharedAction( R.string.room_list_quick_actions_notifications_all_noisy, diff --git a/vector/src/main/java/im/vector/app/features/login2/SignMode2.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListSharedAction.kt similarity index 69% rename from vector/src/main/java/im/vector/app/features/login2/SignMode2.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListSharedAction.kt index f3d59837e7..766bc6eea1 100644 --- a/vector/src/main/java/im/vector/app/features/login2/SignMode2.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListSharedAction.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2022 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. @@ -14,14 +14,11 @@ * limitations under the License. */ -package im.vector.app.features.login2 +package im.vector.app.features.home.room.list.actions -enum class SignMode2 { - Unknown, +import im.vector.app.core.platform.VectorSharedAction - // Account creation - SignUp, +sealed class RoomListSharedAction : VectorSharedAction { - // Login - SignIn + object CloseBottomSheet : RoomListSharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListSharedActionViewModel.kt similarity index 58% rename from vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListSharedActionViewModel.kt index 6a23409f5c..e2545accc8 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListSharedActionViewModel.kt @@ -1,27 +1,22 @@ /* - * Copyright 2021 New Vector Ltd + * Copyright (c) 2022 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 + * 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.login2.created +package im.vector.app.features.home.room.list.actions -import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject -/** - * Transient events for Account Created. - */ -sealed class AccountCreatedViewEvents : VectorViewEvents { - data class Failure(val throwable: Throwable) : AccountCreatedViewEvents() -} +class RoomListSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt new file mode 100644 index 0000000000..11cd892406 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 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.list.home + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.extensions.orFalse +import javax.inject.Inject + +private val Context.dataStore: DataStore by preferencesDataStore(name = "layout_preferences") + +class HomeLayoutPreferencesStore @Inject constructor( + private val context: Context +) { + + private val areRecentsEnbabled = booleanPreferencesKey("SETTINGS_PREFERENCES_HOME_RECENTS") + private val areFiltersEnabled = booleanPreferencesKey("SETTINGS_PREFERENCES_HOME_FILTERS") + private val isAZOrderingEnabled = booleanPreferencesKey("SETTINGS_PREFERENCES_USE_AZ_ORDER") + + val areRecentsEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[areRecentsEnbabled].orFalse() } + .distinctUntilChanged() + + val areFiltersEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[areFiltersEnabled].orFalse() } + .distinctUntilChanged() + + val isAZOrderingEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[isAZOrderingEnabled].orFalse() } + .distinctUntilChanged() + + suspend fun setRecentsEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[areRecentsEnbabled] = isEnabled + } + } + + suspend fun setFiltersEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[areFiltersEnabled] = isEnabled + } + } + + suspend fun setAZOrderingEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[isAZOrderingEnabled] = isEnabled + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index c98d22a34f..3e8c2b5dcd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.list.home +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -29,6 +30,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.extensions.cleanup @@ -43,9 +45,14 @@ import im.vector.app.features.home.room.list.RoomSummaryItemFactory import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import im.vector.app.features.home.room.list.actions.RoomListSharedAction +import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import im.vector.app.features.home.room.list.home.filter.HomeFilteredRoomsController import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter +import im.vector.app.features.home.room.list.home.invites.InvitesActivity +import im.vector.app.features.home.room.list.home.invites.InvitesCounterController import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController +import im.vector.app.features.spaces.SpaceListBottomSheet import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -54,35 +61,53 @@ import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import javax.inject.Inject -class HomeRoomListFragment @Inject constructor( - private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val userPreferencesProvider: UserPreferencesProvider, - private val recentRoomCarouselController: RecentRoomCarouselController -) : VectorBaseFragment(), +@AndroidEntryPoint +class HomeRoomListFragment : + VectorBaseFragment(), RoomListListener { + @Inject lateinit var roomSummaryItemFactory: RoomSummaryItemFactory + @Inject lateinit var userPreferencesProvider: UserPreferencesProvider + @Inject lateinit var recentRoomCarouselController: RecentRoomCarouselController + @Inject lateinit var invitesCounterController: InvitesCounterController + private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel() - private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel + private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel + private lateinit var sharedActionViewModel: RoomListSharedActionViewModel private var concatAdapter = ConcatAdapter() private var modelBuildListener: OnModelBuildFinishedListener? = null + private val spaceListBottomSheet = SpaceListBottomSheet() + private lateinit var stateRestorer: LayoutManagerStateRestorer + private val newChatBottomSheet = NewChatBottomSheet() + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomListBinding { return FragmentRoomListBinding.inflate(inflater, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - sharedActionViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java] - sharedActionViewModel - .stream() - .onEach { handleQuickActions(it) } - .launchIn(viewLifecycleOwner.lifecycleScope) - views.stateView.contentView = views.roomListView views.stateView.state = StateView.State.Loading + setupObservers() + setupRecyclerView() + setupFabs() + } + + private fun setupObservers() { + sharedQuickActionsViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java] + sharedActionViewModel = activityViewModelProvider[RoomListSharedActionViewModel::class.java] + + sharedActionViewModel + .stream() + .onEach(::handleSharedAction) + .launchIn(viewLifecycleOwner.lifecycleScope) + sharedQuickActionsViewModel + .stream() + .onEach(::handleQuickActions) + .launchIn(viewLifecycleOwner.lifecycleScope) roomListViewModel.observeViewEvents { when (it) { @@ -92,69 +117,11 @@ class HomeRoomListFragment @Inject constructor( is HomeRoomListViewEvents.Done -> Unit } } - - setupRecyclerView() - setupFabs() } - private fun setupRecyclerView() { - val layoutManager = LinearLayoutManager(context) - stateRestorer = LayoutManagerStateRestorer(layoutManager).register() - views.roomListView.layoutManager = layoutManager - views.roomListView.itemAnimator = RoomListAnimator() - layoutManager.recycleChildrenOnDetach = true - - modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } - - roomListViewModel.sections.onEach { sections -> - setUpAdapters(sections) - }.launchIn(lifecycleScope) - - views.roomListView.adapter = concatAdapter - } - - private fun setupFabs() { - showFABs() - - views.newLayoutCreateChatButton.setOnClickListener { - // Click action for create chat modal goes here (Issue #6717) - } - - views.newLayoutOpenSpacesButton.setOnClickListener { - // Click action for open spaces modal goes here (Issue #6499) - } - - // Hide FABs when list is scrolling - views.roomListView.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - views.createChatFabMenu.handler.removeCallbacksAndMessages(null) - - when (newState) { - RecyclerView.SCROLL_STATE_IDLE -> views.createChatFabMenu.postDelayed(::showFABs, 250) - RecyclerView.SCROLL_STATE_DRAGGING, - RecyclerView.SCROLL_STATE_SETTLING -> hideFABs() - } - } - }) - } - - private fun showFABs() { - views.newLayoutCreateChatButton.show() - views.newLayoutOpenSpacesButton.show() - } - - private fun hideFABs() { - views.newLayoutCreateChatButton.hide() - views.newLayoutOpenSpacesButton.hide() - } - - override fun invalidate() = withState(roomListViewModel) { state -> - views.stateView.state = state.state - } - - private fun setUpAdapters(sections: Set) { - sections.forEach { - concatAdapter.addAdapter(getAdapterForData(it)) + private fun handleSharedAction(action: RoomListSharedAction) { + when (action) { + RoomListSharedAction.CloseBottomSheet -> spaceListBottomSheet.dismiss() } } @@ -188,6 +155,80 @@ class HomeRoomListFragment @Inject constructor( } } + private fun setupRecyclerView() { + val layoutManager = LinearLayoutManager(context) + stateRestorer = LayoutManagerStateRestorer(layoutManager).register() + views.roomListView.layoutManager = layoutManager + views.roomListView.itemAnimator = RoomListAnimator() + layoutManager.recycleChildrenOnDetach = true + + modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } + + roomListViewModel.sections.onEach { sections -> + setUpAdapters(sections) + }.launchIn(lifecycleScope) + + views.roomListView.adapter = concatAdapter + + // we need to force scroll when recents/filter tabs are added to make them visible + concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (positionStart == 0) { + layoutManager.scrollToPosition(0) + } + } + }) + } + + private fun setupFabs() { + showFABs() + + views.newLayoutCreateChatButton.setOnClickListener { + newChatBottomSheet.show(requireActivity().supportFragmentManager, NewChatBottomSheet.TAG) + } + + views.newLayoutOpenSpacesButton.setOnClickListener { + // Click action for open spaces modal goes here + spaceListBottomSheet.show(requireActivity().supportFragmentManager, SpaceListBottomSheet.TAG) + } + + // Hide FABs when list is scrolling + views.roomListView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + views.createChatFabMenu.handler.removeCallbacksAndMessages(null) + + when (newState) { + RecyclerView.SCROLL_STATE_IDLE -> views.createChatFabMenu.postDelayed(::showFABs, 250) + RecyclerView.SCROLL_STATE_DRAGGING, + RecyclerView.SCROLL_STATE_SETTLING -> hideFABs() + } + } + }) + } + + private fun showFABs() { + views.newLayoutCreateChatButton.show() + views.newLayoutOpenSpacesButton.show() + } + + private fun hideFABs() { + views.newLayoutCreateChatButton.hide() + views.newLayoutOpenSpacesButton.hide() + } + + override fun invalidate() = withState(roomListViewModel) { state -> + views.stateView.state = state.state + } + + private fun setUpAdapters(sections: Set) { + concatAdapter.adapters.forEach { + concatAdapter.removeAdapter(it) + } + sections.forEach { + concatAdapter.addAdapter(getAdapterForData(it)) + } + } + private fun promptLeaveRoom(roomId: String) { val isPublicRoom = roomListViewModel.isPublicRoom(roomId) val message = buildString { @@ -212,12 +253,11 @@ class HomeRoomListFragment @Inject constructor( is HomeRoomSection.RoomSummaryData -> { HomeFilteredRoomsController( roomSummaryItemFactory, - showFilters = section.showFilters, ).also { controller -> controller.listener = this controller.onFilterChanged = ::onRoomFilterChanged section.filtersData.onEach { - controller.submitFiltersData(it) + controller.submitFiltersData(it.getOrNull()) }.launchIn(lifecycleScope) section.list.observe(viewLifecycleOwner) { list -> controller.submitList(list) @@ -230,9 +270,19 @@ class HomeRoomListFragment @Inject constructor( controller.submitList(list) } }.adapter + is HomeRoomSection.InvitesCountData -> invitesCounterController.also { controller -> + controller.clickListener = ::onInvitesCounterClicked + section.count.observe(viewLifecycleOwner) { count -> + controller.submitData(count) + } + }.adapter } } + private fun onInvitesCounterClicked() { + startActivity(Intent(activity, InvitesActivity::class.java)) + } + private fun onRoomFilterChanged(filter: HomeRoomFilter) { roomListViewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter)) } @@ -249,6 +299,7 @@ class HomeRoomListFragment @Inject constructor( override fun onDestroyView() { views.roomListView.cleanup() recentRoomCarouselController.listener = null + invitesCounterController.clickListener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 1fed9eba86..5ecf9d6d96 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.list.home +import androidx.lifecycle.map import androidx.paging.PagedList import arrow.core.toOption import com.airbnb.mvrx.MavericksViewModelFactory @@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -53,12 +55,14 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.flow.flow class HomeRoomListViewModel @AssistedInject constructor( @Assisted initialState: HomeRoomListViewState, private val session: Session, private val spaceStateHandler: SpaceStateHandler, + private val preferencesStore: HomeLayoutPreferencesStore, ) : VectorViewModel(initialState) { @AssistedFactory @@ -82,17 +86,30 @@ class HomeRoomListViewModel @AssistedInject constructor( init { configureSections() + observePreferences() } - private fun configureSections() { - val newSections = mutableSetOf() + private fun observePreferences() { + preferencesStore.areRecentsEnabledFlow.onEach { + configureSections() + }.launchIn(viewModelScope) - newSections.add(getRecentRoomsSection()) + preferencesStore.isAZOrderingEnabledFlow.onEach { + configureSections() + }.launchIn(viewModelScope) + } + + private fun configureSections() = viewModelScope.launch { + val newSections = mutableSetOf() + newSections.add(getInvitesCountSection()) + + val areSettingsEnabled = preferencesStore.areRecentsEnabledFlow.first() + if (areSettingsEnabled) { + newSections.add(getRecentRoomsSection()) + } newSections.add(getFilteredRoomsSection()) - viewModelScope.launch { - _sections.emit(newSections) - } + _sections.emit(newSections) setState { copy(state = StateView.State.Content) @@ -111,13 +128,30 @@ class HomeRoomListViewModel @AssistedInject constructor( ) } - private fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { + private fun getInvitesCountSection(): HomeRoomSection.InvitesCountData { + val builder = RoomSummaryQueryParams.Builder().also { + it.memberships = listOf(Membership.INVITE) + } + + val liveCount = session.roomService().getRoomSummariesLive( + builder.build(), + RoomSortOrder.ACTIVITY + ).map { it.count() } + + return HomeRoomSection.InvitesCountData(liveCount) + } + + private suspend fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) } val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build()) - val sortOrder = RoomSortOrder.ACTIVITY // #6506 + val sortOrder = if (preferencesStore.isAZOrderingEnabledFlow.first()) { + RoomSortOrder.NAME + } else { + RoomSortOrder.ACTIVITY + } val liveResults = session.roomService().getFilteredPagedRoomSummariesLive( params, @@ -135,19 +169,18 @@ class HomeRoomListViewModel @AssistedInject constructor( .onEach { selectedSpaceOption -> val selectedSpace = selectedSpaceOption.orNull() liveResults.queryParams = liveResults.queryParams.copy( - spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() + spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() ) }.launchIn(viewModelScope) return HomeRoomSection.RoomSummaryData( list = liveResults.livePagedList, - showFilters = true, // #6506 filtersData = getFiltersDataFlow() ) } - private fun getFiltersDataFlow(): SharedFlow> { - val flow = MutableSharedFlow>(replay = 1) + private fun getFiltersDataFlow(): SharedFlow>> { + val flow = MutableSharedFlow>>(replay = 1) val favouritesFlow = session.flow() .liveRoomSummaries( @@ -168,25 +201,28 @@ class HomeRoomListViewModel @AssistedInject constructor( .map { it.isNotEmpty() } .distinctUntilChanged() - favouritesFlow.combine(dmsFLow) { hasFavourite, hasDm -> - hasFavourite to hasDm - }.onEach { (hasFavourite, hasDm) -> - val filtersData = mutableListOf( - HomeRoomFilter.ALL, - HomeRoomFilter.UNREADS - ) - if (hasFavourite) { - filtersData.add( - HomeRoomFilter.FAVOURITES + combine(favouritesFlow, dmsFLow, preferencesStore.areFiltersEnabledFlow) { hasFavourite, hasDm, areFiltersEnabled -> + Triple(hasFavourite, hasDm, areFiltersEnabled) + }.onEach { (hasFavourite, hasDm, areFiltersEnabled) -> + if (areFiltersEnabled) { + val filtersData = mutableListOf( + HomeRoomFilter.ALL, + HomeRoomFilter.UNREADS ) + if (hasFavourite) { + filtersData.add( + HomeRoomFilter.FAVOURITES + ) + } + if (hasDm) { + filtersData.add( + HomeRoomFilter.PEOPlE + ) + } + flow.emit(Optional.from(filtersData)) + } else { + flow.emit(Optional.empty()) } - if (hasDm) { - filtersData.add( - HomeRoomFilter.PEOPlE - ) - } - - flow.emit(filtersData) }.launchIn(viewModelScope) return flow diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index f51b479d37..29df594d06 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -21,15 +21,19 @@ import androidx.paging.PagedList import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter import kotlinx.coroutines.flow.SharedFlow import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.Optional sealed class HomeRoomSection { data class RoomSummaryData( val list: LiveData>, - val showFilters: Boolean, - val filtersData: SharedFlow> + val filtersData: SharedFlow>>, ) : HomeRoomSection() data class RecentRoomsData( val list: LiveData> ) : HomeRoomSection() + + data class InvitesCountData( + val count: LiveData + ) : HomeRoomSection() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt new file mode 100644 index 0000000000..05b86f7393 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/NewChatBottomSheet.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 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.list.home + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.databinding.FragmentNewChatBottomSheetBinding +import im.vector.app.features.navigation.Navigator +import javax.inject.Inject + +@AndroidEntryPoint +class NewChatBottomSheet @Inject constructor() : BottomSheetDialogFragment() { + + @Inject lateinit var navigator: Navigator + + private lateinit var binding: FragmentNewChatBottomSheetBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentNewChatBottomSheetBinding.inflate(inflater, container, false) + initFABs() + return binding.root + } + + private fun initFABs() { + binding.startChat.setOnClickListener { + navigator.openCreateDirectRoom(requireActivity()) + } + + binding.createRoom.setOnClickListener { + navigator.openCreateRoom(requireActivity()) + } + + binding.exploreRooms.setOnClickListener { + navigator.openRoomDirectory(requireContext()) + } + } + + companion object { + const val TAG = "NewChatBottomSheet" + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt index 7c1f154d52..2d673bc089 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary class HomeFilteredRoomsController( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val showFilters: Boolean, ) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -48,7 +47,7 @@ class HomeFilteredRoomsController( override fun addModels(models: List>) { val host = this - if (showFilters) { + if (host.filtersData != null) { roomFilterHeaderItem { id("filter_header") filtersData(host.filtersData) @@ -58,7 +57,7 @@ class HomeFilteredRoomsController( super.addModels(models) } - fun submitFiltersData(data: List) { + fun submitFiltersData(data: List?) { this.filtersData = data requestForcedModelBuild() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InviteCounterItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InviteCounterItem.kt new file mode 100644 index 0000000000..4bc292be27 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InviteCounterItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +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.features.home.room.list.UnreadCounterBadgeView + +@EpoxyModelClass +abstract class InviteCounterItem : VectorEpoxyModel(R.layout.item_invites_count) { + + @EpoxyAttribute var invitesCount: Int = 0 + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener(listener) + holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(invitesCount, true)) + } + + class Holder : VectorEpoxyHolder() { + val unreadCounterBadgeView by bind(R.id.invites_count_badge) + } +} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesAction.kt similarity index 57% rename from vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesAction.kt index f108bfa886..ed6ed23c9d 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesAction.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,12 +14,12 @@ * limitations under the License. */ -package im.vector.app.features.login2.created +package im.vector.app.features.home.room.list.home.invites -import android.net.Uri import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.model.RoomSummary -sealed class AccountCreatedAction : VectorViewModelAction { - data class SetDisplayName(val displayName: String) : AccountCreatedAction() - data class SetAvatar(val avatarUri: Uri, val filename: String) : AccountCreatedAction() +sealed class InvitesAction : VectorViewModelAction { + data class AcceptInvitation(val roomSummary: RoomSummary) : InvitesAction() + data class RejectInvitation(val roomSummary: RoomSummary) : InvitesAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt new file mode 100644 index 0000000000..b590caab42 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding + +@AndroidEntryPoint +class InvitesActivity : VectorBaseActivity() { + + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + + override fun initUiAndData() { + if (isFirstCreation()) { + addFragment(views.simpleFragmentContainer, InvitesFragment::class.java) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt new file mode 100644 index 0000000000..1511b97c3c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.utils.createUIHandler +import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.home.room.list.RoomListListener +import im.vector.app.features.home.room.list.RoomSummaryItemFactory +import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_ +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import javax.inject.Inject + +class InvitesController @Inject constructor( + private val roomSummaryItemFactory: RoomSummaryItemFactory, +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + var roomChangeMembershipStates: Map? = null + set(value) { + field = value + requestForcedModelBuild() + } + + var listener: RoomListListener? = null + + override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { + item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) } + return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesCounterController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesCounterController.kt new file mode 100644 index 0000000000..82a31d30a9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesCounterController.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import com.airbnb.epoxy.EpoxyController +import im.vector.app.core.resources.StringProvider +import javax.inject.Inject + +class InvitesCounterController @Inject constructor( + val stringProvider: StringProvider +) : EpoxyController() { + + private var count = 0 + var clickListener: (() -> Unit)? = null + + override fun buildModels() { + val host = this + if (count != 0) { + inviteCounterItem { + id("invites_counter") + invitesCount(host.count) + listener { host.clickListener?.invoke() } + } + } + } + + fun submitData(count: Int?) { + this.count = count ?: 0 + requestModelBuild() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt new file mode 100644 index 0000000000..74b46cec33 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentInvitesBinding +import im.vector.app.features.analytics.plan.ViewRoom +import im.vector.app.features.home.room.list.RoomListListener +import im.vector.app.features.notifications.NotificationDrawerManager +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import javax.inject.Inject + +@AndroidEntryPoint +class InvitesFragment : VectorBaseFragment(), RoomListListener { + + @Inject lateinit var controller: InvitesController + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + + private val viewModel by fragmentViewModel(InvitesViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentInvitesBinding { + return FragmentInvitesBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar(views.invitesToolbar) + .allowBack() + + views.invitesRecycler.configureWith(controller) + controller.listener = this + + viewModel.onEach(InvitesViewState::roomMembershipChanges) { + controller.roomChangeMembershipStates = it + } + + viewModel.observeViewEvents { + when (it) { + is InvitesViewEvents.Failure -> showFailure(it.throwable) + is InvitesViewEvents.OpenRoom -> handleOpenRoom(it.roomSummary, it.shouldCloseInviteView) + InvitesViewEvents.Close -> handleClose() + } + } + } + + private fun handleClose() { + requireActivity().finish() + } + + private fun handleOpenRoom(roomSummary: RoomSummary, shouldCloseInviteView: Boolean) { + navigator.openRoom( + context = requireActivity(), + roomId = roomSummary.roomId, + isInviteAlreadyAccepted = true, + trigger = ViewRoom.Trigger.RoomList // #6508 + ) + if (shouldCloseInviteView) { + requireActivity().finish() + } + } + + override fun invalidate(): Unit = withState(viewModel) { state -> + super.invalidate() + + state.pagedList?.observe(viewLifecycleOwner) { list -> + controller.submitList(list) + } + } + + override fun onRejectRoomInvitation(room: RoomSummary) { + notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) } + viewModel.handle(InvitesAction.RejectInvitation(room)) + } + + override fun onAcceptRoomInvitation(room: RoomSummary) { + notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) } + viewModel.handle(InvitesAction.AcceptInvitation(room)) + } + + override fun onJoinSuggestedRoom(room: SpaceChildInfo) = Unit + + override fun onSuggestedRoomClicked(room: SpaceChildInfo) = Unit + + override fun onRoomClicked(room: RoomSummary) = Unit + + override fun onRoomLongClicked(room: RoomSummary): Boolean = false +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt new file mode 100644 index 0000000000..d68577cf95 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import im.vector.app.core.platform.VectorViewEvents +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +sealed class InvitesViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : InvitesViewEvents() + data class OpenRoom(val roomSummary: RoomSummary, val shouldCloseInviteView: Boolean) : InvitesViewEvents() + object Close : InvitesViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt new file mode 100644 index 0000000000..b0d854be66 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import androidx.paging.PagedList +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomSortOrder +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership +import timber.log.Timber + +class InvitesViewModel @AssistedInject constructor( + @Assisted val initialState: InvitesViewState, + private val session: Session, +) : VectorViewModel(initialState) { + + private val pagedListConfig = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(true) + .setPrefetchDistance(10) + .build() + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: InvitesViewState): InvitesViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + observeInvites() + } + + override fun handle(action: InvitesAction) { + when (action) { + is InvitesAction.AcceptInvitation -> handleAcceptInvitation(action) + is InvitesAction.RejectInvitation -> handleRejectInvitation(action) + } + } + + private fun handleRejectInvitation(action: InvitesAction.RejectInvitation) = withState { state -> + val roomId = action.roomSummary.roomId + val roomMembershipChange = state.roomMembershipChanges[roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { + // Request already sent, should not happen + Timber.w("Try to left an already leaving or joining room. Should not happen") + return@withState + } + + val shouldCloseInviteView = state.pagedList?.value?.size == 1 + + viewModelScope.launch { + try { + session.roomService().leaveRoom(roomId) + // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. + // Instead, we wait for the room to be rejected + // Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons. + // If we update the state, the button will be displayed again, so it's not ideal... + if (shouldCloseInviteView) { + _viewEvents.post(InvitesViewEvents.Close) + } + } catch (failure: Throwable) { + // Notify the user + _viewEvents.post(InvitesViewEvents.Failure(failure)) + } + } + } + + private fun handleAcceptInvitation(action: InvitesAction.AcceptInvitation) = withState { state -> + val roomId = action.roomSummary.roomId + val roomMembershipChange = state.roomMembershipChanges[roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { + // Request already sent, should not happen + Timber.w("Try to join an already joining room. Should not happen") + return@withState + } + // close invites view when navigate to a room from the last one invite + + val shouldCloseInviteView = state.pagedList?.value?.size == 1 + + _viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView)) + + // quick echo + setState { + copy( + roomMembershipChanges = roomMembershipChanges.mapValues { + if (it.key == roomId) { + ChangeMembershipState.Joining + } else { + it.value + } + } + ) + } + } + + private fun observeInvites() { + val builder = RoomSummaryQueryParams.Builder().also { + it.memberships = listOf(Membership.INVITE) + } + val pagedList = session.roomService().getPagedRoomSummariesLive( + queryParams = builder.build(), + pagedListConfig = pagedListConfig, + sortOrder = RoomSortOrder.ACTIVITY + ) + + setState { + copy(pagedList = pagedList) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt new file mode 100644 index 0000000000..708db29604 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 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.list.home.invites + +import androidx.lifecycle.LiveData +import androidx.paging.PagedList +import com.airbnb.mvrx.MavericksState +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +data class InvitesViewState( + val pagedList: LiveData>? = null, + val roomMembershipChanges: Map = emptyMap(), +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt new file mode 100644 index 0000000000..0c4d64a1cc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 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.list.home.layout + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetHomeLayoutSettingsBinding +import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class HomeLayoutSettingBottomDialogFragment : VectorBaseBottomSheetDialogFragment() { + + @Inject lateinit var preferencesStore: HomeLayoutPreferencesStore + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetHomeLayoutSettingsBinding { + return BottomSheetHomeLayoutSettingsBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewLifecycleOwner.lifecycleScope.launch { + views.homeLayoutSettingsRecents.isChecked = preferencesStore.areRecentsEnabledFlow.first() + views.homeLayoutSettingsFilters.isChecked = preferencesStore.areFiltersEnabledFlow.first() + + if (preferencesStore.isAZOrderingEnabledFlow.first()) { + views.homeLayoutSettingsSortName.isChecked = true + } else { + views.homeLayoutSettingsSortActivity.isChecked = true + } + } + + views.homeLayoutSettingsRecents.setOnCheckedChangeListener { _, isChecked -> + setRecentsEnabled(isChecked) + } + views.homeLayoutSettingsFilters.setOnCheckedChangeListener { _, isChecked -> + setFiltersEnabled(isChecked) + } + views.homeLayoutSettingsSortGroup.setOnCheckedChangeListener { _, checkedId -> + setAzOrderingEnabled(checkedId == R.id.home_layout_settings_sort_name) + } + } + + private fun setRecentsEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setRecentsEnabled(isEnabled) + } + + private fun setFiltersEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setFiltersEnabled(isEnabled) + } + + private fun setAzOrderingEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setAZOrderingEnabled(isEnabled) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt index 53832bbc74..ebec912779 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt @@ -59,7 +59,13 @@ class RecentRoomCarouselController @Inject constructor( data?.let { data -> carousel { id("recents_carousel") - padding(Carousel.Padding(host.hPadding, host.itemSpacing)) + padding(Carousel.Padding( + host.hPadding, + 0, + host.hPadding, + 0, + host.itemSpacing) + ) withModelsFrom(data) { roomSummary -> val onClick = host.listener?.let { it::onRoomClicked } val onLongClick = host.listener?.let { it::onRoomLongClicked } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index aaa9846c39..ef07067bac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -26,6 +26,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -48,15 +49,17 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject -class ThreadListFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val bugReporter: BugReporter, - private val threadListController: ThreadListController, - val threadListViewModelFactory: ThreadListViewModel.Factory -) : VectorBaseFragment(), +@AndroidEntryPoint +class ThreadListFragment : + VectorBaseFragment(), ThreadListController.Listener, VectorMenuProvider { + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var bugReporter: BugReporter + @Inject lateinit var threadListController: ThreadListController + @Inject lateinit var threadListViewModelFactory: ThreadListViewModel.Factory + private val threadListViewModel: ThreadListViewModel by fragmentViewModel() private val threadListArgs: ThreadListArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index d96410010e..779818b3d6 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -31,6 +31,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mapbox.mapboxsdk.maps.MapView +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseFragment @@ -53,15 +54,17 @@ import javax.inject.Inject /** * We should consider using SupportMapFragment for a out of the box lifecycle handling. */ -class LocationSharingFragment @Inject constructor( - private val urlMapProvider: UrlMapProvider, - private val avatarRenderer: AvatarRenderer, - private val matrixItemColorProvider: MatrixItemColorProvider, - private val vectorPreferences: VectorPreferences, -) : VectorBaseFragment(), +@AndroidEntryPoint +class LocationSharingFragment : + VectorBaseFragment(), LocationTargetChangeListener, VectorBaseBottomSheetDialogFragment.ResultListener { + @Inject lateinit var urlMapProvider: UrlMapProvider + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider + @Inject lateinit var vectorPreferences: VectorPreferences + private val viewModel: LocationSharingViewModel by fragmentViewModel() private val locationSharingNavigator: LocationSharingNavigator by lazy { DefaultLocationSharingNavigator(activity) } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt index 85095e7c9f..942021dd64 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewFragment.kt @@ -62,7 +62,8 @@ import javax.inject.Inject * Screen showing a map with all the current users sharing their live location in a room. */ @AndroidEntryPoint -class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment() { +class LiveLocationMapViewFragment : + VectorBaseFragment() { @Inject lateinit var urlMapProvider: UrlMapProvider @Inject lateinit var bottomSheetController: LiveLocationBottomSheetController diff --git a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt index 8285d0156b..082cee02f0 100644 --- a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt @@ -27,6 +27,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.mapbox.mapboxsdk.maps.MapView +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider @@ -43,12 +44,14 @@ import javax.inject.Inject /* * TODO Move locationPinProvider to a ViewModel */ -class LocationPreviewFragment @Inject constructor( - private val urlMapProvider: UrlMapProvider, - private val locationPinProvider: LocationPinProvider -) : VectorBaseFragment(), +@AndroidEntryPoint +class LocationPreviewFragment : + VectorBaseFragment(), VectorMenuProvider { + @Inject lateinit var urlMapProvider: UrlMapProvider + @Inject lateinit var locationPinProvider: LocationPinProvider + private val args: LocationSharingArgs by args() private val viewModel: LocationPreviewViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt index b18df6c9cf..ddab65d981 100644 --- a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt @@ -85,7 +85,7 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragmen private fun prefetchIfNeeded() { withState(loginViewModel) { state -> - if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { + if (state.loginMode.hasSso() && state.loginMode.ssoState().isFallback()) { // in this case we can prefetch (not other cases for privacy concerns) loginViewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, diff --git a/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt b/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt index 955c3f7290..253c514e5a 100644 --- a/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt +++ b/vector/src/main/java/im/vector/app/features/login/HomeServerConnectionConfigFactory.kt @@ -17,12 +17,13 @@ package im.vector.app.features.login import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.network.ssl.Fingerprint import timber.log.Timber import javax.inject.Inject class HomeServerConnectionConfigFactory @Inject constructor() { - fun create(url: String?): HomeServerConnectionConfig? { + fun create(url: String?, fingerprint: Fingerprint? = null): HomeServerConnectionConfig? { if (url == null) { return null } @@ -30,6 +31,13 @@ class HomeServerConnectionConfigFactory @Inject constructor() { return try { HomeServerConnectionConfig.Builder() .withHomeServerUri(url) + .run { + if (fingerprint == null) { + this + } else { + withAllowedFingerPrints(listOf(fingerprint)) + } + } .build() } catch (t: Throwable) { Timber.e(t) 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 1b49f9bfa1..25403b06f3 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,6 +33,7 @@ import android.webkit.WebViewClient import androidx.core.view.isVisible import com.airbnb.mvrx.args import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.utils.AssetReader import im.vector.app.databinding.FragmentLoginCaptchaBinding @@ -51,9 +52,11 @@ data class LoginCaptchaFragmentArgument( /** * In this screen, the user is asked to confirm he is not a robot. */ -class LoginCaptchaFragment @Inject constructor( - private val assetReader: AssetReader -) : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginCaptchaFragment : + AbstractLoginFragment() { + + @Inject lateinit var assetReader: AssetReader override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding { return FragmentLoginCaptchaBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 9c598c400b..d61044d101 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -28,6 +28,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -37,12 +38,10 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject /** * In this screen: @@ -52,7 +51,9 @@ import javax.inject.Inject * In signup mode: * - the user is asked for login and password */ -class LoginFragment @Inject constructor() : AbstractSSOLoginFragment() { +@AndroidEntryPoint +class LoginFragment : + AbstractSSOLoginFragment() { private var isSignupMode = false @@ -100,13 +101,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment error("developer error") - SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP - SignMode.SignIn, - SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN - } + private fun ssoMode(state: LoginViewState) = when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP + SignMode.SignIn, + SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN } private fun submit() { @@ -201,16 +200,13 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment + loginViewModel.getSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + providerId = provider?.id + ) + ?.let { openInCustomTab(it) } } } else { views.loginSocialLoginContainer.isVisible = false @@ -272,7 +268,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment() { +@AndroidEntryPoint +class LoginGenericTextInputFormFragment : + AbstractLoginFragment() { private val params: LoginGenericTextInputFormFragmentArgument by args() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt index dd479c89c5..944b159441 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt @@ -18,22 +18,21 @@ package im.vector.app.features.login import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider sealed class LoginMode : Parcelable { // Parcelable because persist state @Parcelize object Unknown : LoginMode() @Parcelize object Password : LoginMode() - @Parcelize data class Sso(val ssoIdentityProviders: List?) : LoginMode() - @Parcelize data class SsoAndPassword(val ssoIdentityProviders: List?) : LoginMode() + @Parcelize data class Sso(val ssoState: SsoState) : LoginMode() + @Parcelize data class SsoAndPassword(val ssoState: SsoState) : LoginMode() @Parcelize object Unsupported : LoginMode() } -fun LoginMode.ssoIdentityProviders(): List? { +fun LoginMode.ssoState(): SsoState { return when (this) { - is LoginMode.Sso -> ssoIdentityProviders - is LoginMode.SsoAndPassword -> ssoIdentityProviders - else -> null + is LoginMode.Sso -> ssoState + is LoginMode.SsoAndPassword -> ssoState + else -> SsoState.Fallback } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt index 1ca0774f54..87df2d9483 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -36,12 +37,13 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject /** * In this screen, the user is asked for email and new password to reset his password. */ -class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginResetPasswordFragment : + AbstractLoginFragment() { // Show warning only once private var showWarning = true 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 689e8ef6b7..c95a778860 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 @@ -22,15 +22,17 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.Fail import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject /** * In this screen, the user is asked to check their email and to click on a button once it's done. */ -class LoginResetPasswordMailConfirmationFragment @Inject constructor() : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginResetPasswordMailConfirmationFragment : + AbstractLoginFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordMailConfirmationBinding { return FragmentLoginResetPasswordMailConfirmationBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordSuccessFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordSuccessFragment.kt index d2f1f620bd..e601f0512d 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordSuccessFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordSuccessFragment.kt @@ -20,13 +20,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.databinding.FragmentLoginResetPasswordSuccessBinding -import javax.inject.Inject /** * In this screen, we confirm to the user that his password has been reset. */ -class LoginResetPasswordSuccessFragment @Inject constructor() : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginResetPasswordSuccessFragment : + AbstractLoginFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordSuccessBinding { return FragmentLoginResetPasswordSuccessBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt index 6c49bafbba..0813957e99 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginServerSelectionFragment.kt @@ -20,16 +20,18 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.FragmentLoginServerSelectionBinding import me.gujun.android.span.span -import javax.inject.Inject /** * In this screen, the user will choose between matrix.org, modular or other type of homeserver. */ -class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginServerSelectionFragment : + AbstractLoginFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerSelectionBinding { return FragmentLoginServerSelectionBinding.inflate(inflater, container, false) 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 937e1bdf55..aabe0c2f2f 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 @@ -27,6 +27,7 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.resources.BuildMeta @@ -43,9 +44,11 @@ import javax.inject.Inject /** * In this screen, the user is prompted to enter a homeserver url. */ -class LoginServerUrlFormFragment @Inject constructor( - private val buildMeta: BuildMeta, -) : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginServerUrlFormFragment : + AbstractLoginFragment() { + + @Inject lateinit var buildMeta: BuildMeta override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlFormBinding { return FragmentLoginServerUrlFormBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt index 1325ea37af..dbcf674847 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt @@ -22,16 +22,18 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import javax.inject.Inject +import im.vector.app.features.login.SocialLoginButtonsView.Mode /** * In this screen, the user is asked to sign up or to sign in to the homeserver. */ -class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLoginFragment() { +@AndroidEntryPoint +class LoginSignUpSignInSelectionFragment : + AbstractSSOLoginFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupSigninSelectionBinding { return FragmentLoginSignupSigninSelectionBinding.inflate(inflater, container, false) @@ -73,16 +75,13 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi when (state.loginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted() - views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = provider?.id - ) - ?.let { openInCustomTab(it) } - } + views.loginSignupSigninSocialLoginButtons.render(state.loginMode.ssoState(), Mode.MODE_CONTINUE) { provider -> + loginViewModel.getSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + providerId = provider?.id + ) + ?.let { openInCustomTab(it) } } } else -> { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt index 7f5e87967b..6a8de819fd 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.resources.BuildMeta import im.vector.app.databinding.FragmentLoginSplashBinding @@ -35,10 +36,12 @@ import javax.inject.Inject /** * In this screen, the user is viewing an introduction to what he can do with this application. */ -class LoginSplashFragment @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val buildMeta: BuildMeta, -) : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginSplashFragment : + AbstractLoginFragment() { + + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var buildMeta: BuildMeta override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplashBinding { return FragmentLoginSplashBinding.inflate(inflater, container, false) 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 40f72ccc99..79d06a0864 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 @@ -223,7 +223,7 @@ class LoginViewModel @AssistedInject constructor( setState { copy( signMode = SignMode.SignIn, - loginMode = LoginMode.Sso(action.ssoIdentityProviders), + loginMode = LoginMode.Sso(action.ssoIdentityProviders.toSsoState()), homeServerUrlFromUser = action.homeServerUrl, homeServerUrl = action.homeServerUrl, deviceId = action.deviceId @@ -816,8 +816,8 @@ class LoginViewModel @AssistedInject constructor( val loginMode = when { // SSO login is taken first data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState()) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState()) data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } 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 07251f52a2..c13769e9e9 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 @@ -22,11 +22,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.databinding.FragmentLoginWaitForEmailBinding import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject @Parcelize data class LoginWaitForEmailFragmentArgument( @@ -36,7 +36,9 @@ data class LoginWaitForEmailFragmentArgument( /** * In this screen, the user is asked to check their emails. */ -class LoginWaitForEmailFragment @Inject constructor() : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginWaitForEmailFragment : + AbstractLoginFragment() { private val params: LoginWaitForEmailFragmentArgument by args() 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 0940eb50ee..b89018ccff 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 @@ -32,6 +32,7 @@ import android.webkit.WebView import android.webkit.WebViewClient import com.airbnb.mvrx.activityViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.utils.AssetReader import im.vector.app.databinding.FragmentLoginWebBinding @@ -47,9 +48,11 @@ import javax.inject.Inject * This screen is displayed when the application does not support login flow or registration flow * of the homeserver, as a fallback to login or to create an account. */ -class LoginWebFragment @Inject constructor( - private val assetReader: AssetReader -) : AbstractLoginFragment() { +@AndroidEntryPoint +class LoginWebFragment : + AbstractLoginFragment() { + + @Inject lateinit var assetReader: AssetReader private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt index 4f3b1237f2..816050420e 100644 --- a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt +++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt @@ -160,8 +160,11 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: } } -fun SocialLoginButtonsView.render(ssoProviders: List?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { +fun SocialLoginButtonsView.render(state: SsoState, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) { this.mode = mode - this.ssoIdentityProviders = ssoProviders?.sorted() + this.ssoIdentityProviders = when (state) { + SsoState.Fallback -> null + is SsoState.IdentityProviders -> state.providers.sorted() + } this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } } diff --git a/vector/src/main/java/im/vector/app/features/login/SsoState.kt b/vector/src/main/java/im/vector/app/features/login/SsoState.kt new file mode 100644 index 0000000000..5f57780bd7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login/SsoState.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.login + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider + +sealed interface SsoState : Parcelable { + @Parcelize + data class IdentityProviders(val providers: List) : SsoState + + @Parcelize + object Fallback : SsoState + + fun isFallback() = this == Fallback + + fun providersOrNull() = when (this) { + Fallback -> null + is IdentityProviders -> providers.takeIf { it.isNotEmpty() } + } +} + +fun List?.toSsoState() = this + ?.takeIf { it.isNotEmpty() } + ?.let { SsoState.IdentityProviders(it) } + ?: SsoState.Fallback 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 ce499f290b..a7a4274876 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 @@ -42,11 +42,12 @@ data class LoginTermsFragmentArgument( /** * LoginTermsFragment displays the list of policies the user has to accept. */ -class LoginTermsFragment @Inject constructor( - private val policyController: PolicyController -) : AbstractLoginFragment(), +class LoginTermsFragment : + AbstractLoginFragment(), PolicyController.PolicyControllerListener { + @Inject lateinit var policyController: PolicyController + private val params: LoginTermsFragmentArgument by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTermsBinding { diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt deleted file mode 100644 index aae51fa959..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt +++ /dev/null @@ -1,165 +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.login2 - -import android.os.Bundle -import android.view.View -import androidx.annotation.CallSuper -import androidx.transition.TransitionInflater -import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.activityViewModel -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.dialogs.UnrecognizedCertificateDialog -import im.vector.app.core.platform.OnBackPressed -import im.vector.app.core.platform.VectorBaseFragment -import kotlinx.coroutines.CancellationException -import org.matrix.android.sdk.api.failure.Failure - -/** - * Parent Fragment for all the login/registration screens. - */ -abstract class AbstractLoginFragment2 : VectorBaseFragment(), OnBackPressed { - - protected val loginViewModel: LoginViewModel2 by activityViewModel() - - private var isResetPasswordStarted = false - - // Due to async, we keep a boolean to avoid displaying twice the cancellation dialog - private var displayCancelDialog = true - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - context?.let { - sharedElementEnterTransition = TransitionInflater.from(it).inflateTransition(android.R.transition.move) - } - } - - @CallSuper - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - loginViewModel.observeViewEvents { - handleLoginViewEvents(it) - } - } - - private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents2) { - when (loginViewEvents) { - is LoginViewEvents2.Failure -> showFailure(loginViewEvents.throwable) - else -> - // This is handled by the Activity - Unit - } - } - - override fun showFailure(throwable: Throwable) { - // Only the resumed Fragment can eventually show the error, to avoid multiple dialog display - if (!isResumed) { - return - } - - when (throwable) { - is CancellationException -> - /* Ignore this error, user has cancelled the action */ - Unit - is Failure.UnrecognizedCertificateFailure -> - showUnrecognizedCertificateFailure(throwable) - else -> - onError(throwable) - } - } - - private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) { - // Ask the user to accept the certificate - unrecognizedCertificateDialog.show(requireActivity(), - failure.fingerprint, - failure.url, - object : UnrecognizedCertificateDialog.Callback { - override fun onAccept() { - // User accept the certificate - loginViewModel.handle(LoginAction2.UserAcceptCertificate(failure.fingerprint)) - } - - override fun onIgnore() { - // Cannot happen in this case - } - - override fun onReject() { - // Nothing to do in this case - } - }) - } - - open fun onError(throwable: Throwable) { - super.showFailure(throwable) - } - - override fun onBackPressed(toolbarButton: Boolean): Boolean { - return when { - displayCancelDialog && loginViewModel.isRegistrationStarted -> { - // Ask for confirmation before cancelling the registration - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.login_signup_cancel_confirmation_title) - .setMessage(R.string.login_signup_cancel_confirmation_content) - .setPositiveButton(R.string.yes) { _, _ -> - displayCancelDialog = false - vectorBaseActivity.onBackPressed() - } - .setNegativeButton(R.string.no, null) - .show() - - true - } - displayCancelDialog && isResetPasswordStarted -> { - // Ask for confirmation before cancelling the reset password - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.login_reset_password_cancel_confirmation_title) - .setMessage(R.string.login_reset_password_cancel_confirmation_content) - .setPositiveButton(R.string.yes) { _, _ -> - displayCancelDialog = false - vectorBaseActivity.onBackPressed() - } - .setNegativeButton(R.string.no, null) - .show() - - true - } - else -> { - resetViewModel() - // Do not consume the Back event - false - } - } - } - - final override fun invalidate() = withState(loginViewModel) { state -> - // True when email is sent with success to the homeserver - isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not() - - updateWithState(state) - } - - open fun updateWithState(state: LoginViewState2) { - // No op by default - } - - // Reset any modification on the loginViewModel by the current fragment - abstract fun resetViewModel() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt deleted file mode 100644 index 8bc531b25d..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2 - -import android.content.ComponentName -import android.net.Uri -import androidx.browser.customtabs.CustomTabsClient -import androidx.browser.customtabs.CustomTabsServiceConnection -import androidx.browser.customtabs.CustomTabsSession -import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.withState -import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.hasSso -import im.vector.app.features.login.ssoIdentityProviders - -abstract class AbstractSSOLoginFragment2 : AbstractLoginFragment2() { - - // For sso - private var customTabsServiceConnection: CustomTabsServiceConnection? = null - private var customTabsClient: CustomTabsClient? = null - private var customTabsSession: CustomTabsSession? = null - - override fun onStart() { - super.onStart() - val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } - if (hasSSO) { - val packageName = CustomTabsClient.getPackageName(requireContext(), null) - - // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device - if (packageName != null) { - customTabsServiceConnection = object : CustomTabsServiceConnection() { - override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { - customTabsClient = client - .also { it.warmup(0L) } - prefetchIfNeeded() - } - - override fun onServiceDisconnected(name: ComponentName?) { - } - } - .also { - CustomTabsClient.bindCustomTabsService( - requireContext(), - // Despite the API, packageName cannot be null - packageName, - it - ) - } - } - } - } - - override fun onStop() { - super.onStop() - val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } - if (hasSSO) { - customTabsServiceConnection?.let { requireContext().unbindService(it) } - customTabsServiceConnection = null - } - } - - private fun prefetchUrl(url: String) { - if (customTabsSession == null) { - customTabsSession = customTabsClient?.newSession(null) - } - - customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null) - } - - protected fun openInCustomTab(ssoUrl: String) { - openUrlInChromeCustomTab(requireContext(), customTabsSession, ssoUrl) - } - - private fun prefetchIfNeeded() { - withState(loginViewModel) { state -> - if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { - // in this case we can prefetch (not other cases for privacy concerns) - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = null - ) - ?.let { prefetchUrl(it) } - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt deleted file mode 100644 index 37dafbfbe0..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt +++ /dev/null @@ -1,92 +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.login2 - -import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.features.login.LoginConfig -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.api.auth.registration.RegisterThreePid -import org.matrix.android.sdk.api.network.ssl.Fingerprint - -sealed class LoginAction2 : VectorViewModelAction { - // First action - data class UpdateSignMode(val signMode: SignMode2) : LoginAction2() - - // Signin, but user wants to choose a server - object ChooseAServerForSignin : LoginAction2() - - object EnterServerUrl : LoginAction2() - object ChooseDefaultHomeServer : LoginAction2() - data class UpdateHomeServer(val homeServerUrl: String) : LoginAction2() - data class LoginWithToken(val loginToken: String) : LoginAction2() - data class WebLoginSuccess(val credentials: Credentials) : LoginAction2() - data class InitWith(val loginConfig: LoginConfig?) : LoginAction2() - data class ResetPassword(val email: String, val newPassword: String) : LoginAction2() - object ResetPasswordMailConfirmed : LoginAction2() - - // Username to Login or Register, depending on the signMode - data class SetUserName(val username: String) : LoginAction2() - - // Password to Login or Register, depending on the signMode - data class SetUserPassword(val password: String) : LoginAction2() - - // When user has selected a homeserver - data class LoginWith(val login: String, val password: String) : LoginAction2() - - // Register actions - open class RegisterAction : LoginAction2() - - data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction() - object SendAgainThreePid : RegisterAction() - - // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app) - data class ValidateThreePid(val code: String) : RegisterAction() - - data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction() - object StopEmailValidationCheck : RegisterAction() - - data class CaptchaDone(val captchaResponse: String) : RegisterAction() - object AcceptTerms : RegisterAction() - object RegisterDummy : RegisterAction() - - // Reset actions - open class ResetAction : LoginAction2() - - object ResetHomeServerUrl : ResetAction() - object ResetSignMode : ResetAction() - object ResetSignin : ResetAction() - object ResetSignup : ResetAction() - object ResetResetPassword : ResetAction() - - // Homeserver history - object ClearHomeServerHistory : LoginAction2() - - // For the soft logout case - data class SetupSsoForSessionRecovery( - val homeServerUrl: String, - val deviceId: String, - val ssoIdentityProviders: List? - ) : LoginAction2() - - data class PostViewEvent(val viewEvent: LoginViewEvents2) : LoginAction2() - - data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction2() - - // Account customization is over - object Finish : LoginAction2() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt deleted file mode 100644 index 5fabe0ca32..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt +++ /dev/null @@ -1,196 +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.login2 - -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Bitmap -import android.net.http.SslError -import android.os.Build -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.ViewGroup -import android.webkit.SslErrorHandler -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.core.view.isVisible -import com.airbnb.mvrx.args -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.utils.AssetReader -import im.vector.app.databinding.FragmentLoginCaptchaBinding -import im.vector.app.features.login.JavascriptResponse -import im.vector.app.features.login.LoginCaptchaFragmentArgument -import org.matrix.android.sdk.api.util.MatrixJsonParser -import timber.log.Timber -import java.net.URLDecoder -import java.util.Formatter -import javax.inject.Inject - -/** - * In this screen, the user is asked to confirm he is not a robot. - */ -class LoginCaptchaFragment2 @Inject constructor( - private val assetReader: AssetReader -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding { - return FragmentLoginCaptchaBinding.inflate(inflater, container, false) - } - - private val params: LoginCaptchaFragmentArgument by args() - - private var isWebViewLoaded = false - - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebView(state: LoginViewState2) { - views.loginCaptchaWevView.settings.javaScriptEnabled = true - - val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html") - - val html = Formatter().format(reCaptchaPage, params.siteKey).toString() - val mime = "text/html" - val encoding = "utf-8" - - val homeServerUrl = state.homeServerUrl ?: error("missing url of homeserver") - views.loginCaptchaWevView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null) - views.loginCaptchaWevView.requestLayout() - - views.loginCaptchaWevView.webViewClient = object : WebViewClient() { - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - - if (!isAdded) { - return - } - - // Show loader - views.loginCaptchaProgress.isVisible = true - } - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - - if (!isAdded) { - return - } - - // Hide loader - views.loginCaptchaProgress.isVisible = false - } - - override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { - Timber.d("## onReceivedSslError() : ${error.certificate}") - - if (!isAdded) { - return - } - - MaterialAlertDialogBuilder(requireActivity()) - .setMessage(R.string.ssl_could_not_verify) - .setPositiveButton(R.string.ssl_trust) { _, _ -> - Timber.d("## onReceivedSslError() : the user trusted") - handler.proceed() - } - .setNegativeButton(R.string.ssl_do_not_trust) { _, _ -> - Timber.d("## onReceivedSslError() : the user did not trust") - handler.cancel() - } - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - handler.cancel() - Timber.d("## onReceivedSslError() : the user dismisses the trust dialog.") - dialog.dismiss() - return@OnKeyListener true - } - false - }) - .setCancelable(false) - .show() - } - - // common error message - private fun onError(errorMessage: String) { - Timber.e("## onError() : $errorMessage") - - // TODO - // Toast.makeText(this@AccountCreationCaptchaActivity, errorMessage, Toast.LENGTH_LONG).show() - - // on error case, close this activity - // runOnUiThread(Runnable { finish() }) - } - - @SuppressLint("NewApi") - override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { - super.onReceivedHttpError(view, request, errorResponse) - - if (request.url.toString().endsWith("favicon.ico")) { - // Ignore this error - return - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - onError(errorResponse.reasonPhrase) - } else { - onError(errorResponse.toString()) - } - } - - @Deprecated("Deprecated in Java") - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - @Suppress("DEPRECATION") - super.onReceivedError(view, errorCode, description, failingUrl) - onError(description) - } - - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - if (url?.startsWith("js:") == true) { - var json = url.substring(3) - var javascriptResponse: JavascriptResponse? = null - - try { - // URL decode - json = URLDecoder.decode(json, "UTF-8") - javascriptResponse = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json) - } catch (e: Exception) { - Timber.e(e, "## shouldOverrideUrlLoading(): failed") - } - - val response = javascriptResponse?.response - if (javascriptResponse?.action == "verifyCallback" && response != null) { - loginViewModel.handle(LoginAction2.CaptchaDone(response)) - } - } - return true - } - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun updateWithState(state: LoginViewState2) { - if (!isWebViewLoaded) { - setupWebView(state) - isWebViewLoaded = true - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt deleted file mode 100644 index 34bebd655a..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt +++ /dev/null @@ -1,163 +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.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.databinding.FragmentLoginSigninPassword2Binding -import im.vector.app.features.home.AvatarRenderer -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.login.LoginProfileInfo -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.isInvalidPassword -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -import javax.net.ssl.HttpsURLConnection - -/** - * In this screen: - * - the user is asked for password to sign in to a homeserver. - * - He also can reset his password - */ -class LoginFragmentSigninPassword2 @Inject constructor( - private val avatarRenderer: AvatarRenderer -) : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninPassword2Binding { - return FragmentLoginSigninPassword2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupForgottenPasswordButton() - setupAutoFill() - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupForgottenPasswordButton() { - views.forgetPasswordButton.setOnClickListener { forgetPasswordClicked() } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) - } - } - - private fun submit() { - cleanupUi() - - val password = views.passwordField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.error_empty_field_your_password) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserPassword(password)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.passwordFieldTil.error = null - } - - private fun setupUi(state: LoginViewState2) { - // Name and avatar - views.loginWelcomeBack.text = getString( - R.string.login_welcome_back, - state.loginProfileInfo()?.displayName?.takeIf { it.isNotBlank() } ?: state.userIdentifier() - ) - - avatarRenderer.render( - profileInfo = state.loginProfileInfo() ?: LoginProfileInfo(state.userIdentifier(), null, null), - imageView = views.loginUserIcon - ) - - views.loginWelcomeBackWarning.isVisible = ((state.loginProfileInfo as? Fail) - ?.error as? Failure.ServerError) - ?.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */ - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.passwordField - .textChanges() - .map { it.isNotEmpty() } - .onEach { - views.passwordFieldTil.error = null - views.loginSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun forgetPasswordClicked() { - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen)) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onError(throwable: Throwable) { - if (throwable.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - - if (state.isLoading) { - // Ensure password is hidden - views.passwordField.hidePassword() - } - } - - /** - * Detect if password ends or starts with spaces. - */ - private fun spaceInPassword() = views.passwordField.text.toString().let { it.trim() != it } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt deleted file mode 100644 index cb346451de..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt +++ /dev/null @@ -1,110 +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.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.autofill.HintConstants -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.databinding.FragmentLoginSigninUsername2Binding -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * - the user is asked for its matrix ID, and have the possibility to open the screen to select a server. - */ -class LoginFragmentSigninUsername2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninUsername2Binding { - return FragmentLoginSigninUsername2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - views.loginChooseAServer.setOnClickListener { - loginViewModel.handle(LoginAction2.ChooseAServerForSignin) - } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) - } - } - - private fun submit() { - cleanupUi() - - val login = views.loginField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (login.isEmpty()) { - views.loginFieldTil.error = getString(R.string.error_empty_field_enter_user_name) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserName(login)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.loginFieldTil.error = null - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.loginField.textChanges() - .map { it.trim().isNotEmpty() } - .onEach { - views.loginFieldTil.error = null - views.loginSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onError(throwable: Throwable) { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt deleted file mode 100644 index 806ff0524b..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.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.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.databinding.FragmentLoginSignupPassword2Binding -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * - the user is asked to choose a password to sign up to a homeserver. - */ -class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupPassword2Binding { - return FragmentLoginSignupPassword2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) - } - } - - private fun submit() { - cleanupUi() - - val password = views.passwordField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.error_empty_field_choose_password) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserPassword(password)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.passwordFieldTil.error = null - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.passwordField.textChanges() - .onEach { password -> - views.passwordFieldTil.error = null - views.loginSubmit.isEnabled = password.isNotEmpty() - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun onError(throwable: Throwable) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - - override fun updateWithState(state: LoginViewState2) { - views.loginMatrixIdentifier.text = state.userIdentifier() - - if (state.isLoading) { - // Ensure password is hidden - views.passwordField.hidePassword() - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt deleted file mode 100644 index a7c4b25344..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ /dev/null @@ -1,139 +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.login2 - -import android.annotation.SuppressLint -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginSignupUsername2Binding -import im.vector.app.features.login.LoginMode -import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SocialLoginButtonsView -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * - the user is asked for an identifier to sign up to a homeserver. - * - SSO option are displayed if available - */ -class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupUsername2Binding { - return FragmentLoginSignupUsername2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - setupSocialLoginButtons() - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) - } - } - - private fun setupSocialLoginButtons() { - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP - } - - private fun submit() { - cleanupUi() - - val login = views.loginField.text.toString().trim() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (login.isEmpty()) { - views.loginFieldTil.error = getString(R.string.error_empty_field_choose_user_name) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.SetUserName(login)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.loginFieldTil.error = null - } - - private fun setupUi(state: LoginViewState2) { - views.loginSubtitle.text = getString(R.string.login_signup_to, state.homeServerUrlFromUser.toReducedUrl()) - - if (state.loginMode is LoginMode.SsoAndPassword) { - views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() - views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = provider?.id - ) - ?.let { openInCustomTab(it) } - } - } - } else { - views.loginSocialLoginContainer.isVisible = false - views.loginSocialLoginButtons.ssoIdentityProviders = null - } - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - views.loginField.textChanges() - .map { it.trim() } - .onEach { text -> - val isNotEmpty = text.isNotEmpty() - views.loginFieldTil.error = null - views.loginSubmit.isEnabled = isNotEmpty - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun onError(throwable: Throwable) { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - - @SuppressLint("SetTextI18n") - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt deleted file mode 100644 index cc143b9255..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ /dev/null @@ -1,202 +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.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginSigninToAny2Binding -import im.vector.app.features.login.LoginMode -import im.vector.app.features.login.SSORedirectRouterActivity -import im.vector.app.features.login.SocialLoginButtonsView -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import org.matrix.android.sdk.api.failure.isInvalidPassword -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen: - * User want to sign in and has selected a server to do so - * - the user is asked for login (or email) and password to sign in to a homeserver. - * - He also can reset his password - * - It also possible to use SSO if server support it in this screen - */ -class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninToAny2Binding { - return FragmentLoginSigninToAny2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupForgottenPasswordButton() - setupAutoFill() - setupSocialLoginButtons() - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupForgottenPasswordButton() { - views.forgetPasswordButton.setOnClickListener { forgetPasswordClicked() } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) - } - } - - private fun setupSocialLoginButtons() { - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN - } - - private fun submit() { - cleanupUi() - - val login = views.loginField.text.toString() - val password = views.passwordField.text.toString() - - // This can be called by the IME action, so deal with empty cases - var error = 0 - if (login.isEmpty()) { - views.loginFieldTil.error = getString(R.string.error_empty_field_enter_user_name) - error++ - } - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.error_empty_field_your_password) - error++ - } - - if (error == 0) { - loginViewModel.handle(LoginAction2.LoginWith(login, password)) - } - } - - private fun cleanupUi() { - views.loginSubmit.hideKeyboard() - views.loginFieldTil.error = null - views.passwordFieldTil.error = null - } - - private fun setupUi(state: LoginViewState2) { - views.loginTitle.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl()) - - if (state.loginMode is LoginMode.SsoAndPassword) { - views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() - views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = provider?.id - ) - ?.let { openInCustomTab(it) } - } - } - } else { - views.loginSocialLoginContainer.isVisible = false - views.loginSocialLoginButtons.ssoIdentityProviders = null - } - } - - private fun setupSubmitButton() { - views.loginSubmit.setOnClickListener { submit() } - combine( - views.loginField.textChanges().map { it.trim().isNotEmpty() }, - views.passwordField.textChanges().map { it.isNotEmpty() } - ) { isLoginNotEmpty, isPasswordNotEmpty -> - isLoginNotEmpty && isPasswordNotEmpty - } - .onEach { - views.loginFieldTil.error = null - views.passwordFieldTil.error = null - views.loginSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun forgetPasswordClicked() { - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen)) - } - - override fun resetViewModel() { - // loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onError(throwable: Throwable) { - // Show M_WEAK_PASSWORD error in the password field - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_WEAK_PASSWORD) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } else { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (throwable.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } - } - } - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - - if (state.isLoading) { - // Ensure password is hidden - views.passwordField.hidePassword() - } - } - - /** - * Detect if password ends or starts with spaces. - */ - private fun spaceInPassword() = views.passwordField.text.toString().let { it.trim() != it } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt deleted file mode 100644 index 91954f29e4..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt +++ /dev/null @@ -1,268 +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.login2 - -import android.os.Build -import android.os.Bundle -import android.text.InputType -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.autofill.HintConstants -import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.args -import com.google.i18n.phonenumbers.NumberParseException -import com.google.i18n.phonenumbers.PhoneNumberUtil -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 im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginGenericTextInputForm2Binding -import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument -import im.vector.app.features.login.TextInputFormFragmentMode -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -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 reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen, the user is asked for a text input. - */ -class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFragment2() { - - private val params: LoginGenericTextInputFormFragmentArgument by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginGenericTextInputForm2Binding { - return FragmentLoginGenericTextInputForm2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - setupUi() - setupSubmitButton() - setupTil() - setupAutoFill() - } - - private fun setupViews() { - views.loginGenericTextInputFormOtherButton.setOnClickListener { onOtherButtonClicked() } - views.loginGenericTextInputFormSubmit.setOnClickListener { submit() } - views.loginGenericTextInputFormLater.setOnClickListener { submit() } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.loginGenericTextInputFormTextInput.setAutofillHints( - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS - TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER - TextInputFormFragmentMode.ConfirmMsisdn -> HintConstants.AUTOFILL_HINT_SMS_OTP - } - ) - } - } - - private fun setupTil() { - views.loginGenericTextInputFormTextInput.textChanges() - .onEach { - views.loginGenericTextInputFormTil.error = null - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun setupUi() { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> { - views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title_2) - views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice_2) - // Text will be updated with the state - views.loginGenericTextInputFormMandatoryNotice.isVisible = params.mandatory - views.loginGenericTextInputFormNotice2.isVisible = false - views.loginGenericTextInputFormTil.hint = - getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint) - views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - views.loginGenericTextInputFormOtherButton.isVisible = false - views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit) - } - TextInputFormFragmentMode.SetMsisdn -> { - views.loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title_2) - views.loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice_2) - // Text will be updated with the state - views.loginGenericTextInputFormMandatoryNotice.isVisible = params.mandatory - views.loginGenericTextInputFormNotice2.setTextOrHide(getString(R.string.login_set_msisdn_notice2)) - views.loginGenericTextInputFormTil.hint = - getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint) - views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_PHONE - views.loginGenericTextInputFormOtherButton.isVisible = false - views.loginGenericTextInputFormSubmit.text = getString(R.string.login_set_msisdn_submit) - } - TextInputFormFragmentMode.ConfirmMsisdn -> { - views.loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title) - views.loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice, params.extra) - views.loginGenericTextInputFormMandatoryNotice.isVisible = false - views.loginGenericTextInputFormNotice2.isVisible = false - views.loginGenericTextInputFormTil.hint = - getString(R.string.login_msisdn_confirm_hint) - views.loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_NUMBER - views.loginGenericTextInputFormOtherButton.isVisible = true - views.loginGenericTextInputFormOtherButton.text = getString(R.string.login_msisdn_confirm_send_again) - views.loginGenericTextInputFormSubmit.text = getString(R.string.login_msisdn_confirm_submit) - } - } - } - - private fun onOtherButtonClicked() { - when (params.mode) { - TextInputFormFragmentMode.ConfirmMsisdn -> { - loginViewModel.handle(LoginAction2.SendAgainThreePid) - } - else -> { - // Should not happen, button is not displayed - } - } - } - - private fun submit() { - cleanupUi() - val text = views.loginGenericTextInputFormTextInput.text.toString() - - if (text.isEmpty()) { - // Perform dummy action - loginViewModel.handle(LoginAction2.RegisterDummy) - } else { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> { - loginViewModel.handle(LoginAction2.AddThreePid(RegisterThreePid.Email(text))) - } - TextInputFormFragmentMode.SetMsisdn -> { - getCountryCodeOrShowError(text)?.let { countryCode -> - loginViewModel.handle(LoginAction2.AddThreePid(RegisterThreePid.Msisdn(text, countryCode))) - } - } - TextInputFormFragmentMode.ConfirmMsisdn -> { - loginViewModel.handle(LoginAction2.ValidateThreePid(text)) - } - } - } - } - - private fun cleanupUi() { - views.loginGenericTextInputFormSubmit.hideKeyboard() - views.loginGenericTextInputFormSubmit.error = null - } - - private fun getCountryCodeOrShowError(text: String): String? { - // We expect an international format for the moment (see https://github.com/vector-im/riotX-android/issues/693) - if (text.startsWith("+")) { - try { - val phoneNumber = PhoneNumberUtil.getInstance().parse(text, null) - return PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(phoneNumber.countryCode) - } catch (e: NumberParseException) { - views.loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_other) - } - } else { - views.loginGenericTextInputFormTil.error = getString(R.string.login_msisdn_error_not_international) - } - - // Error - return null - } - - private fun setupSubmitButton() { - views.loginGenericTextInputFormSubmit.isEnabled = false - views.loginGenericTextInputFormTextInput.textChanges() - .onEach { text -> - views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text) - updateSubmitButtons(text) - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun updateSubmitButtons(text: CharSequence) { - if (params.mandatory) { - views.loginGenericTextInputFormSubmit.isVisible = true - views.loginGenericTextInputFormLater.isVisible = false - } else { - views.loginGenericTextInputFormSubmit.isVisible = text.isNotEmpty() - views.loginGenericTextInputFormLater.isVisible = text.isEmpty() - } - } - - private fun isInputValid(input: CharSequence): Boolean { - return if (input.isEmpty() && !params.mandatory) { - true - } else { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> input.isEmail() - TextInputFormFragmentMode.SetMsisdn -> input.isNotBlank() - TextInputFormFragmentMode.ConfirmMsisdn -> input.isNotBlank() - } - } - } - - override fun onError(throwable: Throwable) { - when (params.mode) { - TextInputFormFragmentMode.SetEmail -> { - if (throwable.is401()) { - // This is normal use case, we go to the mail waiting screen - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnSendEmailSuccess(loginViewModel.currentThreePid ?: ""))) - } else { - views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) - } - } - TextInputFormFragmentMode.SetMsisdn -> { - if (throwable.is401()) { - // This is normal use case, we go to the enter code screen - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnSendMsisdnSuccess(loginViewModel.currentThreePid ?: ""))) - } else { - views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) - } - } - TextInputFormFragmentMode.ConfirmMsisdn -> { - when { - throwable is Failure.SuccessError -> - // The entered code is not correct - views.loginGenericTextInputFormTil.error = getString(R.string.login_validation_code_is_not_correct) - throwable.is401() -> - // It can happen if user request again the 3pid - Unit - else -> - views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable) - } - } - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } - - override fun updateWithState(state: LoginViewState2) { - views.loginGenericTextInputFormMandatoryNotice.text = when (params.mode) { - TextInputFormFragmentMode.SetEmail -> getString(R.string.login_set_email_mandatory_notice_2, state.homeServerUrlFromUser.toReducedUrl()) - TextInputFormFragmentMode.SetMsisdn -> getString(R.string.login_set_msisdn_mandatory_notice_2, state.homeServerUrlFromUser.toReducedUrl()) - TextInputFormFragmentMode.ConfirmMsisdn -> null - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt deleted file mode 100644 index 7916d9bbf2..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt +++ /dev/null @@ -1,163 +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.login2 - -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.hidePassword -import im.vector.app.core.extensions.isEmail -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.core.utils.autoResetTextInputLayoutErrors -import im.vector.app.databinding.FragmentLoginResetPassword2Binding -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject - -/** - * In this screen, the user is asked for email and new password to reset his password. - */ -class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2() { - - // Show warning only once - private var showWarning = true - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPassword2Binding { - return FragmentLoginResetPassword2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupSubmitButton() - setupAutoFill() - - autoResetTextInputLayoutErrors(listOf(views.resetPasswordEmailTil, views.passwordFieldTil)) - - views.passwordField.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - views.resetPasswordEmail.setAutofillHints(HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS) - views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) - } - } - - private fun setupUi(state: LoginViewState2) { - views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlFromUser.toReducedUrl()) - } - - private fun setupSubmitButton() { - views.resetPasswordSubmit.setOnClickListener { submit() } - combine( - views.resetPasswordEmail.textChanges().map { it.isEmail() }, - views.passwordField.textChanges().map { it.isNotEmpty() } - ) { isEmail, isPasswordNotEmpty -> - isEmail && isPasswordNotEmpty - } - .onEach { - views.resetPasswordSubmit.isEnabled = it - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun submit() { - cleanupUi() - - var error = 0 - - val email = views.resetPasswordEmail.text.toString() - val password = views.passwordField.text.toString() - - if (email.isEmpty()) { - views.resetPasswordEmailTil.error = getString(R.string.auth_reset_password_missing_email) - error++ - } - - if (password.isEmpty()) { - views.passwordFieldTil.error = getString(R.string.login_please_choose_a_new_password) - error++ - } - - if (error > 0) { - return - } - - if (showWarning) { - // Display a warning as Riot-Web does first - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.login_reset_password_warning_title) - .setMessage(R.string.login_reset_password_warning_content) - .setPositiveButton(R.string.login_reset_password_warning_submit) { _, _ -> - showWarning = false - doSubmit() - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } else { - doSubmit() - } - } - - private fun doSubmit() { - val email = views.resetPasswordEmail.text.toString() - val password = views.passwordField.text.toString() - - loginViewModel.handle(LoginAction2.ResetPassword(email, password)) - } - - private fun cleanupUi() { - views.resetPasswordSubmit.hideKeyboard() - views.resetPasswordEmailTil.error = null - views.passwordFieldTil.error = null - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetResetPassword) - } - - override fun onError(throwable: Throwable) { - views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable) - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - - if (state.isLoading) { - // Ensure new password is hidden - views.passwordField.hidePassword() - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt deleted file mode 100644 index de1bcb8eea..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordMailConfirmationFragment2.kt +++ /dev/null @@ -1,74 +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.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmation2Binding -import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject - -/** - * In this screen, the user is asked to check their email and to click on a button once it's done. - */ -class LoginResetPasswordMailConfirmationFragment2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordMailConfirmation2Binding { - return FragmentLoginResetPasswordMailConfirmation2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - views.resetPasswordMailConfirmationSubmit.setOnClickListener { submit() } - } - - private fun setupUi(state: LoginViewState2) { - views.resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetPasswordEmail) - } - - private fun submit() { - loginViewModel.handle(LoginAction2.ResetPasswordMailConfirmed) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetResetPassword) - } - - override fun onError(throwable: Throwable) { - // Link in email not yet clicked ? - val message = if (throwable.is401()) { - getString(R.string.auth_reset_password_error_unauthorized) - } else { - errorFormatter.toHumanReadable(throwable) - } - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt deleted file mode 100644 index 33ebd13f2a..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordSuccessFragment2.kt +++ /dev/null @@ -1,48 +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.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import im.vector.app.databinding.FragmentLoginResetPasswordSuccess2Binding -import javax.inject.Inject - -/** - * In this screen, we confirm to the user that his password has been reset. - */ -class LoginResetPasswordSuccessFragment2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordSuccess2Binding { - return FragmentLoginResetPasswordSuccess2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - views.resetPasswordSuccessSubmit.setOnClickListener { submit() } - } - - private fun submit() { - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnResetPasswordMailConfirmationSuccessDone)) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetResetPassword) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt deleted file mode 100644 index b338b96c5d..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerSelectionFragment2.kt +++ /dev/null @@ -1,94 +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.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import im.vector.app.R -import im.vector.app.core.extensions.setTextWithColoredPart -import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.databinding.FragmentLoginServerSelection2Binding -import im.vector.app.features.login.EMS_LINK -import javax.inject.Inject - -/** - * In this screen, the user will choose between matrix.org, or other type of homeserver. - */ -class LoginServerSelectionFragment2 @Inject constructor() : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerSelection2Binding { - return FragmentLoginServerSelection2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - initViews() - } - - private fun initViews() { - views.loginServerChoiceMatrixOrg.setOnClickListener { selectMatrixOrg() } - views.loginServerChoiceOther.setOnClickListener { selectOther() } - - views.loginServerChoiceEmsLearnMore.setTextWithColoredPart( - fullTextRes = R.string.login_server_modular_learn_more_about_ems, - coloredTextRes = R.string.login_server_modular_learn_more, - underline = true - ) - views.loginServerChoiceEmsLearnMore.setOnClickListener { - openUrlInChromeCustomTab(requireActivity(), null, EMS_LINK) - } - } - - private fun updateUi(state: LoginViewState2) { - when (state.signMode) { - SignMode2.Unknown -> Unit - SignMode2.SignUp -> { - views.loginServerTitle.setText(R.string.login_please_choose_a_server) - } - SignMode2.SignIn -> { - views.loginServerTitle.setText(R.string.login_please_select_your_server) - } - } - } - - private fun selectMatrixOrg() { - views.loginServerChoiceMatrixOrg.isChecked = true - loginViewModel.handle(LoginAction2.ChooseDefaultHomeServer) - } - - private fun selectOther() { - views.loginServerChoiceOther.isChecked = true - loginViewModel.handle(LoginAction2.EnterServerUrl) - } - - override fun onResume() { - super.onResume() - views.loginServerChoiceMatrixOrg.isChecked = false - views.loginServerChoiceOther.isChecked = false - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetHomeServerUrl) - } - - override fun updateWithState(state: LoginViewState2) { - updateUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt deleted file mode 100644 index 50f8f38634..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt +++ /dev/null @@ -1,150 +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.login2 - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.ArrayAdapter -import androidx.core.view.isInvisible -import androidx.lifecycle.lifecycleScope -import com.google.android.material.textfield.TextInputLayout -import im.vector.app.R -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.resources.BuildMeta -import im.vector.app.core.utils.ensureProtocol -import im.vector.app.databinding.FragmentLoginServerUrlForm2Binding -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import reactivecircus.flowbinding.android.widget.textChanges -import java.net.UnknownHostException -import javax.inject.Inject -import javax.net.ssl.HttpsURLConnection - -/** - * In this screen, the user is prompted to enter a homeserver url. - */ -class LoginServerUrlFormFragment2 @Inject constructor( - private val buildMeta: BuildMeta, -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlForm2Binding { - return FragmentLoginServerUrlForm2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - setupHomeServerField() - } - - private fun setupViews() { - views.loginServerUrlFormClearHistory.setOnClickListener { clearHistory() } - views.loginServerUrlFormSubmit.setOnClickListener { submit() } - } - - private fun setupHomeServerField() { - views.loginServerUrlFormHomeServerUrl.textChanges() - .onEach { - views.loginServerUrlFormHomeServerUrlTil.error = null - views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank() - } - .launchIn(viewLifecycleOwner.lifecycleScope) - - views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - views.loginServerUrlFormHomeServerUrl.dismissDropDown() - submit() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - } - - private fun setupUi(state: LoginViewState2) { - val completions = state.knownCustomHomeServersUrls + if (buildMeta.isDebug) listOf("http://10.0.2.2:8080") else emptyList() - views.loginServerUrlFormHomeServerUrl.setAdapter( - ArrayAdapter( - requireContext(), - R.layout.item_completion_homeserver, - completions - ) - ) - views.loginServerUrlFormHomeServerUrlTil.endIconMode = TextInputLayout.END_ICON_DROPDOWN_MENU - .takeIf { completions.isNotEmpty() } - ?: TextInputLayout.END_ICON_NONE - - views.loginServerUrlFormClearHistory.isInvisible = state.knownCustomHomeServersUrls.isEmpty() - } - - private fun clearHistory() { - loginViewModel.handle(LoginAction2.ClearHomeServerHistory) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetHomeServerUrl) - } - - @SuppressLint("SetTextI18n") - private fun submit() { - cleanupUi() - - // Static check of homeserver url, empty, malformed, etc. - val serverUrl = views.loginServerUrlFormHomeServerUrl.text.toString().trim().ensureProtocol() - - when { - serverUrl.isBlank() -> { - views.loginServerUrlFormHomeServerUrlTil.error = getString(R.string.login_error_invalid_home_server) - } - else -> { - views.loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/) - loginViewModel.handle(LoginAction2.UpdateHomeServer(serverUrl)) - } - } - } - - private fun cleanupUi() { - views.loginServerUrlFormSubmit.hideKeyboard() - views.loginServerUrlFormHomeServerUrlTil.error = null - } - - override fun onError(throwable: Throwable) { - views.loginServerUrlFormHomeServerUrlTil.error = if (throwable is Failure.NetworkConnection && - throwable.ioException is UnknownHostException) { - // Invalid homeserver? - getString(R.string.login_error_homeserver_not_found) - } else { - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { - getString(R.string.login_registration_disabled) - } else { - errorFormatter.toHumanReadable(throwable) - } - } - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt deleted file mode 100644 index 84af28f75e..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSplashSignUpSignInSelectionFragment2.kt +++ /dev/null @@ -1,74 +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.login2 - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import im.vector.app.core.resources.BuildMeta -import im.vector.app.databinding.FragmentLoginSplash2Binding -import im.vector.app.features.settings.VectorPreferences -import javax.inject.Inject - -/** - * In this screen, the user is asked to sign up or to sign in to the homeserver. - * This is the new splash screen. - */ -class LoginSplashSignUpSignInSelectionFragment2 @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val buildMeta: BuildMeta, -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplash2Binding { - return FragmentLoginSplash2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - } - - private fun setupViews() { - views.loginSignupSigninSignUp.setOnClickListener { signUp() } - views.loginSignupSigninSignIn.setOnClickListener { signIn() } - - if (buildMeta.isDebug || vectorPreferences.developerMode()) { - views.loginSplashVersion.isVisible = true - @SuppressLint("SetTextI18n") - views.loginSplashVersion.text = "Version : ${buildMeta.versionName}\n" + - "Branch: ${buildMeta.gitBranchName}\n" + - "Build: ${buildMeta.buildNumber}" - views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } - } - } - - private fun signUp() { - loginViewModel.handle(LoginAction2.UpdateSignMode(SignMode2.SignUp)) - } - - private fun signIn() { - loginViewModel.handle(LoginAction2.UpdateSignMode(SignMode2.SignIn)) - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignMode) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt deleted file mode 100644 index 7aa2150c98..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginSsoOnlyFragment2.kt +++ /dev/null @@ -1,69 +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.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.withState -import im.vector.app.R -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.databinding.FragmentLoginSsoOnly2Binding -import im.vector.app.features.login.SSORedirectRouterActivity -import javax.inject.Inject - -/** - * In this screen, the user is asked to sign up or to sign in to the homeserver. - */ -class LoginSsoOnlyFragment2 @Inject constructor() : AbstractSSOLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSsoOnly2Binding { - return FragmentLoginSsoOnly2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - } - - private fun setupViews() { - views.loginSignupSigninSubmit.setOnClickListener { submit() } - } - - private fun setupUi(state: LoginViewState2) { - views.loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl()) - } - - private fun submit() = withState(loginViewModel) { state -> - loginViewModel.getSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - providerId = null - ) - ?.let { openInCustomTab(it) } - } - - override fun resetViewModel() { - // No op - } - - override fun updateWithState(state: LoginViewState2) { - setupUi(state) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt deleted file mode 100644 index 11a441923e..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewEvents2.kt +++ /dev/null @@ -1,63 +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.login2 - -import im.vector.app.core.platform.VectorViewEvents -import org.matrix.android.sdk.api.auth.registration.FlowResult - -/** - * Transient events for Login. - */ -sealed class LoginViewEvents2 : VectorViewEvents { - data class Failure(val throwable: Throwable) : LoginViewEvents2() - - data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents2() - object OutdatedHomeserver : LoginViewEvents2() - - // Navigation event - object OpenSigninPasswordScreen : LoginViewEvents2() - object OpenSignupPasswordScreen : LoginViewEvents2() - - object OpenSignInEnterIdentifierScreen : LoginViewEvents2() - - object OpenSignUpChooseUsernameScreen : LoginViewEvents2() - object OpenSignInWithAnythingScreen : LoginViewEvents2() - - object OpenSsoOnlyScreen : LoginViewEvents2() - - object OpenServerSelection : LoginViewEvents2() - object OpenHomeServerUrlFormScreen : LoginViewEvents2() - - object OpenResetPasswordScreen : LoginViewEvents2() - object OnResetPasswordSendThreePidDone : LoginViewEvents2() - object OnResetPasswordMailConfirmationSuccess : LoginViewEvents2() - object OnResetPasswordMailConfirmationSuccessDone : LoginViewEvents2() - - object CancelRegistration : LoginViewEvents2() - - data class OnLoginModeNotSupported(val supportedTypes: List) : LoginViewEvents2() - - data class OnSendEmailSuccess(val email: String) : LoginViewEvents2() - data class OnSendMsisdnSuccess(val msisdn: String) : LoginViewEvents2() - - data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginViewEvents2() - - data class OnSessionCreated(val newAccount: Boolean) : LoginViewEvents2() - - object Finish : LoginViewEvents2() -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt deleted file mode 100644 index 834612c4f4..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ /dev/null @@ -1,829 +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.login2 - -import android.content.Context -import android.net.Uri -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksViewModelFactory -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.di.MavericksAssistedViewModelFactory -import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.configureAndStart -import im.vector.app.core.extensions.tryAsync -import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.ensureTrailingSlash -import im.vector.app.features.login.HomeServerConnectionConfigFactory -import im.vector.app.features.login.LoginConfig -import im.vector.app.features.login.LoginMode -import im.vector.app.features.login.ReAuthHelper -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getServerName -import org.matrix.android.sdk.api.auth.AuthenticationService -import org.matrix.android.sdk.api.auth.HomeServerHistoryService -import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.LoginFlowTypes -import org.matrix.android.sdk.api.auth.login.LoginWizard -import org.matrix.android.sdk.api.auth.registration.FlowResult -import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability -import org.matrix.android.sdk.api.auth.registration.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.RegistrationWizard -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.wellknown.WellknownResult -import org.matrix.android.sdk.api.session.Session -import timber.log.Timber -import java.util.concurrent.CancellationException - -/** - * - */ -class LoginViewModel2 @AssistedInject constructor( - @Assisted initialState: LoginViewState2, - private val applicationContext: Context, - private val authenticationService: AuthenticationService, - private val activeSessionHolder: ActiveSessionHolder, - private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, - private val reAuthHelper: ReAuthHelper, - private val stringProvider: StringProvider, - private val homeServerHistoryService: HomeServerHistoryService -) : VectorViewModel(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: LoginViewState2): LoginViewModel2 - } - - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - - init { - getKnownCustomHomeServersUrls() - } - - private fun getKnownCustomHomeServersUrls() { - setState { - copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls()) - } - } - - // Store the last action, to redo it after user has trusted the untrusted certificate - private var lastAction: LoginAction2? = null - private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null - - private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() - - val currentThreePid: String? - get() = registrationWizard?.getCurrentThreePid() - - // True when login and password has been sent with success to the homeserver - val isRegistrationStarted: Boolean - get() = authenticationService.isRegistrationStarted() - - private val registrationWizard: RegistrationWizard? - get() = authenticationService.getRegistrationWizard() - - private val loginWizard: LoginWizard? - get() = authenticationService.getLoginWizard() - - private var loginConfig: LoginConfig? = null - - private var currentJob: Job? = null - set(value) { - // Cancel any previous Job - field?.cancel() - field = value - } - - override fun handle(action: LoginAction2) { - when (action) { - is LoginAction2.EnterServerUrl -> handleEnterServerUrl() - is LoginAction2.ChooseAServerForSignin -> handleChooseAServerForSignin() - is LoginAction2.UpdateSignMode -> handleUpdateSignMode(action) - is LoginAction2.InitWith -> handleInitWith(action) - is LoginAction2.ChooseDefaultHomeServer -> handle(LoginAction2.UpdateHomeServer(matrixOrgUrl)) - is LoginAction2.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } - is LoginAction2.SetUserName -> handleSetUserName(action).also { lastAction = action } - is LoginAction2.SetUserPassword -> handleSetUserPassword(action).also { lastAction = action } - is LoginAction2.LoginWith -> handleLoginWith(action).also { lastAction = action } - is LoginAction2.LoginWithToken -> handleLoginWithToken(action) - is LoginAction2.WebLoginSuccess -> handleWebLoginSuccess(action) - is LoginAction2.ResetPassword -> handleResetPassword(action) - is LoginAction2.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() - is LoginAction2.RegisterAction -> handleRegisterAction(action) - is LoginAction2.ResetAction -> handleResetAction(action) - is LoginAction2.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) - is LoginAction2.UserAcceptCertificate -> handleUserAcceptCertificate(action) - LoginAction2.ClearHomeServerHistory -> handleClearHomeServerHistory() - is LoginAction2.PostViewEvent -> _viewEvents.post(action.viewEvent) - is LoginAction2.Finish -> handleFinish() - } - } - - private fun handleFinish() { - // Just post a view Event - _viewEvents.post(LoginViewEvents2.Finish) - } - - private fun handleChooseAServerForSignin() { - // Just post a view Event - _viewEvents.post(LoginViewEvents2.OpenServerSelection) - } - - private fun handleUserAcceptCertificate(action: LoginAction2.UserAcceptCertificate) { - // It happens when we get the login flow, or during direct authentication. - // So alter the homeserver config and retrieve again the login flow - when (val finalLastAction = lastAction) { - is LoginAction2.UpdateHomeServer -> { - currentHomeServerConnectionConfig - ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { getLoginFlow(it) } - } - is LoginAction2.SetUserName -> - handleSetUserNameForSignIn( - finalLastAction, - HomeServerConnectionConfig.Builder() - // Will be replaced by the task - .withHomeServerUri("https://dummy.org") - .withAllowedFingerPrints(listOf(action.fingerprint)) - .build() - ) - is LoginAction2.SetUserPassword -> - handleSetUserPassword(finalLastAction) - is LoginAction2.LoginWith -> - handleLoginWith(finalLastAction) - else -> Unit - } - } - - private fun rememberHomeServer(homeServerUrl: String) { - homeServerHistoryService.addHomeServerToHistory(homeServerUrl) - getKnownCustomHomeServersUrls() - } - - private fun handleClearHomeServerHistory() { - homeServerHistoryService.clearHistory() - getKnownCustomHomeServersUrls() - } - - private fun handleLoginWithToken(action: LoginAction2.LoginWithToken) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - safeLoginWizard.loginWithToken(action.loginToken) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } - ?.let { onSessionCreated(it) } - - setState { copy(isLoading = false) } - } - } - } - - private fun handleSetupSsoForSessionRecovery(action: LoginAction2.SetupSsoForSessionRecovery) { - setState { - copy( - signMode = SignMode2.SignIn, - loginMode = LoginMode.Sso(action.ssoIdentityProviders), - homeServerUrlFromUser = action.homeServerUrl, - homeServerUrl = action.homeServerUrl, - deviceId = action.deviceId - ) - } - } - - private fun handleRegisterAction(action: LoginAction2.RegisterAction) { - when (action) { - is LoginAction2.CaptchaDone -> handleCaptchaDone(action) - is LoginAction2.AcceptTerms -> handleAcceptTerms() - is LoginAction2.RegisterDummy -> handleRegisterDummy() - is LoginAction2.AddThreePid -> handleAddThreePid(action) - is LoginAction2.SendAgainThreePid -> handleSendAgainThreePid() - is LoginAction2.ValidateThreePid -> handleValidateThreePid(action) - is LoginAction2.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action) - is LoginAction2.StopEmailValidationCheck -> handleStopEmailValidationCheck() - } - } - - private fun handleCheckIfEmailHasBeenValidated(action: LoginAction2.CheckIfEmailHasBeenValidated) { - // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state - currentJob = executeRegistrationStep(withLoading = false) { - it.checkIfEmailHasBeenValidated(action.delayMillis) - } - } - - private fun handleStopEmailValidationCheck() { - currentJob = null - } - - private fun handleValidateThreePid(action: LoginAction2.ValidateThreePid) { - currentJob = executeRegistrationStep { - it.handleValidateThreePid(action.code) - } - } - - private fun executeRegistrationStep( - withLoading: Boolean = true, - block: suspend (RegistrationWizard) -> RegistrationResult - ): Job { - if (withLoading) { - setState { copy(isLoading = true) } - } - return viewModelScope.launch { - try { - registrationWizard?.let { block(it) } - } catch (failure: Throwable) { - if (failure !is CancellationException) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - } - null - } - ?.let { data -> - when (data) { - is RegistrationResult.Success -> onSessionCreated(data.session) - is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) - } - } - - setState { copy(isLoading = false) } - } - } - - private fun handleAddThreePid(action: LoginAction2.AddThreePid) { - setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - try { - registrationWizard?.addThreePid(action.threePid) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - } - setState { copy(isLoading = false) } - } - } - - private fun handleSendAgainThreePid() { - setState { copy(isLoading = true) } - currentJob = viewModelScope.launch { - try { - registrationWizard?.sendAgainThreePid() - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - } - setState { copy(isLoading = false) } - } - } - - private fun handleAcceptTerms() { - currentJob = executeRegistrationStep { - it.acceptTerms() - } - } - - private fun handleRegisterDummy() { - currentJob = executeRegistrationStep { - it.dummy() - } - } - - /** - * Check that the user name is available. - */ - private fun handleSetUserNameForSignUp(action: LoginAction2.SetUserName) { - setState { copy(isLoading = true) } - - val safeRegistrationWizard = registrationWizard ?: error("Invalid") - - viewModelScope.launch { - val available = safeRegistrationWizard.registrationAvailable(action.username) - - val event = when (available) { - RegistrationAvailability.Available -> { - // Ask for a password - LoginViewEvents2.OpenSignupPasswordScreen - } - is RegistrationAvailability.NotAvailable -> { - LoginViewEvents2.Failure(available.failure) - } - } - _viewEvents.post(event) - setState { copy(isLoading = false) } - } - } - - private fun handleCaptchaDone(action: LoginAction2.CaptchaDone) { - currentJob = executeRegistrationStep { - it.performReCaptcha(action.captchaResponse) - } - } - - // TODO Update this - private fun handleResetAction(action: LoginAction2.ResetAction) { - // Cancel any request - currentJob = null - - when (action) { - LoginAction2.ResetHomeServerUrl -> { - viewModelScope.launch { - authenticationService.reset() - setState { - copy( - homeServerUrlFromUser = null, - homeServerUrl = null, - loginMode = LoginMode.Unknown - ) - } - } - } - LoginAction2.ResetSignMode -> { - setState { - copy( - signMode = SignMode2.Unknown, - loginMode = LoginMode.Unknown - ) - } - } - LoginAction2.ResetSignin -> { - viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() - setState { - copy(isLoading = false) - } - } - _viewEvents.post(LoginViewEvents2.CancelRegistration) - } - LoginAction2.ResetSignup -> { - viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() - setState { - // Always create a new state, to ensure the state is correctly reset - LoginViewState2( - knownCustomHomeServersUrls = knownCustomHomeServersUrls - ) - } - } - _viewEvents.post(LoginViewEvents2.CancelRegistration) - } - LoginAction2.ResetResetPassword -> { - setState { - copy( - resetPasswordEmail = null, - resetPasswordNewPassword = null - ) - } - } - } - } - - private fun handleUpdateSignMode(action: LoginAction2.UpdateSignMode) { - setState { - copy( - signMode = action.signMode - ) - } - - when (action.signMode) { - SignMode2.SignUp -> _viewEvents.post(LoginViewEvents2.OpenServerSelection) - SignMode2.SignIn -> _viewEvents.post(LoginViewEvents2.OpenSignInEnterIdentifierScreen) - SignMode2.Unknown -> Unit - } - } - - private fun handleEnterServerUrl() { - _viewEvents.post(LoginViewEvents2.OpenHomeServerUrlFormScreen) - } - - private fun handleInitWith(action: LoginAction2.InitWith) { - loginConfig = action.loginConfig - - // If there is a pending email validation continue on this step - try { - if (registrationWizard?.isRegistrationStarted() == true) { - currentThreePid?.let { - handle(LoginAction2.PostViewEvent(LoginViewEvents2.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: LoginAction2.ResetPassword) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - safeLoginWizard.resetPassword(action.email) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - return@launch - } - - setState { - copy( - isLoading = false, - resetPasswordEmail = action.email, - resetPasswordNewPassword = action.newPassword - ) - } - - _viewEvents.post(LoginViewEvents2.OnResetPasswordSendThreePidDone) - } - } - } - - private fun handleResetPasswordMailConfirmed() { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - } else { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - try { - val state = awaitState() - safeLoginWizard.resetPasswordMailConfirmed(state.resetPasswordNewPassword!!) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - return@launch - } - setState { - copy( - isLoading = false, - resetPasswordEmail = null, - resetPasswordNewPassword = null - ) - } - - _viewEvents.post(LoginViewEvents2.OnResetPasswordMailConfirmationSuccess) - } - } - } - - private fun handleSetUserName(action: LoginAction2.SetUserName) = withState { state -> - setState { - copy( - userName = action.username - ) - } - - when (state.signMode) { - SignMode2.Unknown -> error("Developer error, invalid sign mode") - SignMode2.SignIn -> handleSetUserNameForSignIn(action, null) - SignMode2.SignUp -> handleSetUserNameForSignUp(action) - } - } - - private fun handleSetUserPassword(action: LoginAction2.SetUserPassword) = withState { state -> - when (state.signMode) { - SignMode2.Unknown -> error("Developer error, invalid sign mode") - SignMode2.SignIn -> handleSignInWithPassword(action) - SignMode2.SignUp -> handleRegisterWithPassword(action) - } - } - - private fun handleRegisterWithPassword(action: LoginAction2.SetUserPassword) = withState { state -> - val username = state.userName ?: error("Developer error, username not set") - - reAuthHelper.data = action.password - currentJob = executeRegistrationStep { - it.createAccount( - userName = username, - password = action.password, - initialDeviceDisplayName = stringProvider.getString(R.string.login_default_session_public_name) - ) - } - } - - private fun handleSignInWithPassword(action: LoginAction2.SetUserPassword) = withState { state -> - val username = state.userName ?: error("Developer error, username not set") - setState { copy(isLoading = true) } - loginWith(username, action.password) - } - - private fun handleLoginWith(action: LoginAction2.LoginWith) { - setState { copy(isLoading = true) } - loginWith(action.login, action.password) - } - - private fun loginWith(login: String, password: String) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard == null) { - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Bad configuration"))) - setState { copy(isLoading = false) } - } else { - currentJob = viewModelScope.launch { - try { - safeLoginWizard.login( - login = login, - password = password, - initialDeviceName = stringProvider.getString(R.string.login_default_session_public_name) - ) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } - ?.let { - reAuthHelper.data = password - onSessionCreated(it) - } - setState { copy(isLoading = false) } - } - } - } - - /** - * Perform wellknown request. - */ - private fun handleSetUserNameForSignIn(action: LoginAction2.SetUserName, homeServerConnectionConfig: HomeServerConnectionConfig?) { - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - val data = try { - authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return@launch - } - when (data) { - is WellknownResult.Prompt -> - onWellknownSuccess(action, data, homeServerConnectionConfig) - is WellknownResult.FailPrompt -> - // Relax on IS discovery if homeserver is valid - if (data.homeServerUrl != null && data.wellKnown != null) { - onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) - } else { - onWellKnownError() - } - else -> { - onWellKnownError() - } - } - } - } - - private fun onWellKnownError() { - _viewEvents.post(LoginViewEvents2.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) - setState { copy(isLoading = false) } - } - - private suspend fun onWellknownSuccess( - action: LoginAction2.SetUserName, - wellKnownPrompt: WellknownResult.Prompt, - homeServerConnectionConfig: HomeServerConnectionConfig? - ) { - val alteredHomeServerConnectionConfig = homeServerConnectionConfig - ?.copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - ?: HomeServerConnectionConfig( - homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - - // Ensure login flow is retrieved, and this is not a SSO only server - val data = try { - authenticationService.getLoginFlow(alteredHomeServerConnectionConfig) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } ?: return - - val loginMode = when { - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } - - val viewEvent = when (loginMode) { - LoginMode.Password, - is LoginMode.SsoAndPassword -> { - retrieveProfileInfo(action.username) - // We can navigate to the password screen - LoginViewEvents2.OpenSigninPasswordScreen - } - is LoginMode.Sso -> { - LoginViewEvents2.OpenSsoOnlyScreen - } - LoginMode.Unsupported -> LoginViewEvents2.OnLoginModeNotSupported(data.supportedLoginTypes.toList()) - LoginMode.Unknown -> null - } - viewEvent?.let { _viewEvents.post(it) } - - val urlFromUser = action.username.getServerName() - setState { - copy( - isLoading = false, - homeServerUrlFromUser = urlFromUser, - homeServerUrl = data.homeServerUrl, - loginMode = loginMode - ) - } - - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || - data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(LoginViewEvents2.OutdatedHomeserver) - } - } - - private suspend fun retrieveProfileInfo(username: String) { - val safeLoginWizard = loginWizard - - if (safeLoginWizard != null) { - setState { copy(loginProfileInfo = Loading()) } - val result = tryAsync { - safeLoginWizard.getProfileInfo(username) - } - setState { copy(loginProfileInfo = result) } - } - } - - private fun onDirectLoginError(failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - } - - private fun onFlowResponse(flowResult: FlowResult) { - // If dummy stage is mandatory, and password is already sent, do the dummy stage now - if (isRegistrationStarted && - flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) { - handleRegisterDummy() - } else { - // Notify the user - _viewEvents.post(LoginViewEvents2.RegistrationFlowResult(flowResult, isRegistrationStarted)) - } - } - - private suspend fun onSessionCreated(session: Session) { - activeSessionHolder.setActiveSession(session) - - authenticationService.reset() - session.configureAndStart(applicationContext) - withState { state -> - _viewEvents.post(LoginViewEvents2.OnSessionCreated(state.signMode == SignMode2.SignUp)) - } - } - - private fun handleWebLoginSuccess(action: LoginAction2.WebLoginSuccess) = withState { state -> - val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.homeServerUrl) - - if (homeServerConnectionConfigFinal == null) { - // Should not happen - Timber.w("homeServerConnectionConfig is null") - } else { - currentJob = viewModelScope.launch { - try { - authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - null - } - ?.let { onSessionCreated(it) } - } - } - } - - private fun handleUpdateHomeserver(action: LoginAction2.UpdateHomeServer) { - val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) - if (homeServerConnectionConfig == null) { - // This is invalid - _viewEvents.post(LoginViewEvents2.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) - } else { - getLoginFlow(homeServerConnectionConfig) - } - } - - private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) = withState { state -> - currentHomeServerConnectionConfig = homeServerConnectionConfig - - setState { copy(isLoading = true) } - - currentJob = viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() - - val data = try { - authenticationService.getLoginFlow(homeServerConnectionConfig) - } catch (failure: Throwable) { - _viewEvents.post(LoginViewEvents2.Failure(failure)) - setState { copy(isLoading = false) } - null - } ?: return@launch - - // Valid Homeserver, add it to the history. - // Note: we add what the user has input, data.homeServerUrlBase can be different - rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) - - val loginMode = when { - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } - - val viewEvent = when (loginMode) { - LoginMode.Password, - is LoginMode.SsoAndPassword -> { - when (state.signMode) { - SignMode2.Unknown -> null - SignMode2.SignUp -> { - // Check that registration is possible on this server - try { - registrationWizard?.getRegistrationFlow() - - /* - // Simulate registration disabled - throw Failure.ServerError( - error = MatrixError( - code = MatrixError.M_FORBIDDEN, - message = "Registration is disabled" - ), - httpCode = 403 - ) - */ - - LoginViewEvents2.OpenSignUpChooseUsernameScreen - } catch (throwable: Throwable) { - // Registration disabled? - LoginViewEvents2.Failure(throwable) - } - } - SignMode2.SignIn -> LoginViewEvents2.OpenSignInWithAnythingScreen - } - } - is LoginMode.Sso -> { - LoginViewEvents2.OpenSsoOnlyScreen - } - LoginMode.Unsupported -> LoginViewEvents2.OnLoginModeNotSupported(data.supportedLoginTypes.toList()) - LoginMode.Unknown -> null - } - viewEvent?.let { _viewEvents.post(it) } - - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || - data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(LoginViewEvents2.OutdatedHomeserver) - } - - setState { - copy( - isLoading = false, - homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(), - homeServerUrl = data.homeServerUrl, - loginMode = loginMode - ) - } - } - } - - fun getInitialHomeServerUrl(): String? { - return loginConfig?.homeServerUrl - } - - fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { - return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId) - } - - fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { - return authenticationService.getFallbackUrl(forSignIn, deviceId) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt deleted file mode 100644 index 8405381c4f..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt +++ /dev/null @@ -1,70 +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.login2 - -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Uninitialized -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.features.login.LoginMode -import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.auth.login.LoginProfileInfo - -data class LoginViewState2( - val isLoading: Boolean = false, - - // User choices - @PersistState - val signMode: SignMode2 = SignMode2.Unknown, - @PersistState - val userName: String? = null, - @PersistState - val resetPasswordEmail: String? = null, - @PersistState - val resetPasswordNewPassword: String? = null, - @PersistState - val homeServerUrlFromUser: String? = null, - - // Can be modified after a Wellknown request - @PersistState - val homeServerUrl: String? = null, - - // For SSO session recovery - @PersistState - val deviceId: String? = null, - - // Network result - val loginProfileInfo: Async = Uninitialized, - - // Network result - @PersistState - val loginMode: LoginMode = LoginMode.Unknown, - - // From database - val knownCustomHomeServersUrls: List = emptyList() -) : MavericksState { - - // Pending user identifier - fun userIdentifier(): String { - return if (userName != null && MatrixPatterns.isUserId(userName)) { - userName - } else { - "@$userName:${homeServerUrlFromUser.toReducedUrl()}" - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt deleted file mode 100644 index 772db7be5f..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWaitForEmailFragment2.kt +++ /dev/null @@ -1,75 +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.login2 - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.args -import im.vector.app.R -import im.vector.app.databinding.FragmentLoginWaitForEmail2Binding -import im.vector.app.features.login.LoginWaitForEmailFragmentArgument -import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject - -/** - * In this screen, the user is asked to check their emails. - */ -class LoginWaitForEmailFragment2 @Inject constructor() : AbstractLoginFragment2() { - - private val params: LoginWaitForEmailFragmentArgument by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWaitForEmail2Binding { - return FragmentLoginWaitForEmail2Binding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupUi() - } - - override fun onResume() { - super.onResume() - - loginViewModel.handle(LoginAction2.CheckIfEmailHasBeenValidated(0)) - } - - override fun onPause() { - super.onPause() - - loginViewModel.handle(LoginAction2.StopEmailValidationCheck) - } - - private fun setupUi() { - views.loginWaitForEmailNotice.text = getString(R.string.login_wait_for_email_notice_2, params.email) - } - - override fun onError(throwable: Throwable) { - if (throwable.is401()) { - // Try again, with a delay - loginViewModel.handle(LoginAction2.CheckIfEmailHasBeenValidated(10_000)) - } else { - super.onError(throwable) - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt deleted file mode 100644 index 708efd9d56..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt +++ /dev/null @@ -1,262 +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. - */ - -@file:Suppress("DEPRECATION") - -package im.vector.app.features.login2 - -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Bitmap -import android.net.http.SslError -import android.os.Bundle -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.SslErrorHandler -import android.webkit.WebView -import android.webkit.WebViewClient -import com.airbnb.mvrx.activityViewModel -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.utils.AssetReader -import im.vector.app.databinding.FragmentLoginWebBinding -import im.vector.app.features.login.JavascriptResponse -import im.vector.app.features.signout.soft.SoftLogoutAction -import im.vector.app.features.signout.soft.SoftLogoutViewModel -import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.util.MatrixJsonParser -import timber.log.Timber -import java.net.URLDecoder -import javax.inject.Inject - -/** - * This screen is displayed when the application does not support login flow or registration flow - * of the homeserver, as a fallback to login or to create an account. - */ -class LoginWebFragment2 @Inject constructor( - private val assetReader: AssetReader -) : AbstractLoginFragment2() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding { - return FragmentLoginWebBinding.inflate(inflater, container, false) - } - - private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() - - private var isWebViewLoaded = false - private var isForSessionRecovery = false - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupToolbar(views.loginWebToolbar) - .allowBack() - } - - override fun updateWithState(state: LoginViewState2) { - setupTitle(state) - - isForSessionRecovery = state.deviceId?.isNotBlank() == true - - if (!isWebViewLoaded) { - setupWebView(state) - isWebViewLoaded = true - } - } - - private fun setupTitle(state: LoginViewState2) { - toolbar?.title = when (state.signMode) { - SignMode2.SignIn -> getString(R.string.login_signin) - else -> getString(R.string.login_signup) - } - } - - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebView(state: LoginViewState2) { - views.loginWebWebView.settings.javaScriptEnabled = true - - // Enable local storage to support SSO with Firefox accounts - views.loginWebWebView.settings.domStorageEnabled = true - views.loginWebWebView.settings.databaseEnabled = true - - // Due to https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html, we hack - // the user agent to bypass the limitation of Google, as a quick fix (a proper solution will be to use the SSO SDK) - views.loginWebWebView.settings.userAgentString = "Mozilla/5.0 Google" - - // AppRTC requires third party cookies to work - val cookieManager = android.webkit.CookieManager.getInstance() - - // clear the cookies - if (cookieManager == null) { - launchWebView(state) - } else { - if (!cookieManager.hasCookies()) { - launchWebView(state) - } else { - try { - cookieManager.removeAllCookies { launchWebView(state) } - } catch (e: Exception) { - Timber.e(e, " cookieManager.removeAllCookie() fails") - launchWebView(state) - } - } - } - } - - private fun launchWebView(state: LoginViewState2) { - val url = loginViewModel.getFallbackUrl(state.signMode == SignMode2.SignIn, state.deviceId) ?: return - - views.loginWebWebView.loadUrl(url) - - views.loginWebWebView.webViewClient = object : WebViewClient() { - override fun onReceivedSslError( - view: WebView, - handler: SslErrorHandler, - error: SslError - ) { - MaterialAlertDialogBuilder(requireActivity()) - .setMessage(R.string.ssl_could_not_verify) - .setPositiveButton(R.string.ssl_trust) { _, _ -> handler.proceed() } - .setNegativeButton(R.string.ssl_do_not_trust) { _, _ -> handler.cancel() } - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - handler.cancel() - dialog.dismiss() - return@OnKeyListener true - } - false - }) - .setCancelable(false) - .show() - } - - @Deprecated("Deprecated in Java") - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - super.onReceivedError(view, errorCode, description, failingUrl) - - loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OnWebLoginError(errorCode, description, failingUrl))) - } - - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - - toolbar?.subtitle = url - } - - override fun onPageFinished(view: WebView, url: String) { - // avoid infinite onPageFinished call - if (url.startsWith("http")) { - // Generic method to make a bridge between JS and the UIWebView - assetReader.readAssetFile("sendObject.js")?.let { view.loadUrl(it) } - - if (state.signMode == SignMode2.SignIn) { - // The function the fallback page calls when the login is complete - assetReader.readAssetFile("onLogin.js")?.let { view.loadUrl(it) } - } else { - // MODE_REGISTER - // The function the fallback page calls when the registration is complete - assetReader.readAssetFile("onRegistered.js")?.let { view.loadUrl(it) } - } - } - } - - /** - * Example of (formatted) url for MODE_LOGIN: - * - *
-             * js:{
-             *     "action":"onLogin",
-             *     "credentials":{
-             *         "user_id":"@user:matrix.org",
-             *         "access_token":"[ACCESS_TOKEN]",
-             *         "home_server":"matrix.org",
-             *         "device_id":"[DEVICE_ID]",
-             *         "well_known":{
-             *             "m.homeserver":{
-             *                 "base_url":"https://matrix.org/"
-             *                 }
-             *             }
-             *         }
-             *    }
-             *    .
-             * 
- * @param view - * @param url - * @return - */ - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { - if (url == null) return super.shouldOverrideUrlLoading(view, url as String?) - - if (url.startsWith("js:")) { - var json = url.substring(3) - var javascriptResponse: JavascriptResponse? = null - - try { - // URL decode - json = URLDecoder.decode(json, "UTF-8") - val adapter = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java) - javascriptResponse = adapter.fromJson(json) - } catch (e: Exception) { - Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed") - } - - // succeeds to parse parameters - if (javascriptResponse != null) { - val action = javascriptResponse.action - - if (state.signMode == SignMode2.SignIn) { - if (action == "onLogin") { - javascriptResponse.credentials?.let { notifyViewModel(it) } - } - } else { - // MODE_REGISTER - // check the required parameters - if (action == "onRegistered") { - javascriptResponse.credentials?.let { notifyViewModel(it) } - } - } - } - return true - } - - return super.shouldOverrideUrlLoading(view, url) - } - } - } - - private fun notifyViewModel(credentials: Credentials) { - if (isForSessionRecovery) { - softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials)) - } else { - loginViewModel.handle(LoginAction2.WebLoginSuccess(credentials)) - } - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignin) - } - - override fun onBackPressed(toolbarButton: Boolean): Boolean { - return when { - toolbarButton -> super.onBackPressed(toolbarButton) - views.loginWebWebView.canGoBack() -> views.loginWebWebView.goBack().run { true } - else -> super.onBackPressed(toolbarButton) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt deleted file mode 100644 index d549c22028..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.created - -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.date.DateFormatKind -import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper -import im.vector.app.core.intent.getFilenameFromUri -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock -import im.vector.app.databinding.DialogBaseEditTextBinding -import im.vector.app.databinding.FragmentLoginAccountCreatedBinding -import im.vector.app.features.displayname.getBestName -import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider -import im.vector.app.features.login2.AbstractLoginFragment2 -import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginViewState2 -import im.vector.app.features.onboarding.OnboardingActivity -import org.matrix.android.sdk.api.util.MatrixItem -import java.util.UUID -import javax.inject.Inject - -/** - * In this screen: - * - the account has been created and we propose the user to set an avatar and a display name. - */ -class AccountCreatedFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val dateFormatter: VectorDateFormatter, - private val matrixItemColorProvider: MatrixItemColorProvider, - private val clock: Clock, - colorProvider: ColorProvider -) : AbstractLoginFragment2(), - GalleryOrCameraDialogHelper.Listener { - - private val viewModel: AccountCreatedViewModel by fragmentViewModel() - - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginAccountCreatedBinding { - return FragmentLoginAccountCreatedBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupClickListener() - setupSubmitButton() - observeViewEvents() - - viewModel.onEach { invalidateState(it) } - - views.loginAccountCreatedTime.text = dateFormatter.format(clock.epochMillis(), DateFormatKind.MESSAGE_SIMPLE) - } - - private fun observeViewEvents() { - viewModel.observeViewEvents { - when (it) { - is AccountCreatedViewEvents.Failure -> displayErrorDialog(it.throwable) - } - } - } - - private fun setupClickListener() { - views.loginAccountCreatedMessage.debouncedClicks { - // Update display name - displayDialog() - } - views.loginAccountCreatedAvatar.debouncedClicks { - galleryOrCameraDialogHelper.show() - } - } - - private fun displayDialog() = withState(viewModel) { state -> - val inflater = requireActivity().layoutInflater - val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) - val views = DialogBaseEditTextBinding.bind(layout) - views.editText.setText(state.currentUser()?.getBestName().orEmpty()) - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.settings_display_name) - .setView(layout) - .setPositiveButton(R.string.ok) { _, _ -> - val newName = views.editText.text.toString() - viewModel.handle(AccountCreatedAction.SetDisplayName(newName)) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - - override fun onImageReady(uri: Uri?) { - uri ?: return - viewModel.handle( - AccountCreatedAction.SetAvatar( - avatarUri = uri, - filename = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString() - ) - ) - } - - private fun setupSubmitButton() { - views.loginAccountCreatedLater.debouncedClicks { terminate() } - views.loginAccountCreatedDone.debouncedClicks { terminate() } - } - - private fun terminate() { - loginViewModel.handle(LoginAction2.Finish) - } - - private fun invalidateState(state: AccountCreatedViewState) { - // Ugly hack... - (activity as? OnboardingActivity)?.setIsLoading(state.isLoading) - - views.loginAccountCreatedSubtitle.text = getString(R.string.login_account_created_subtitle, state.userId) - - val user = state.currentUser() - if (user != null) { - avatarRenderer.render(user, views.loginAccountCreatedAvatar) - views.loginAccountCreatedMemberName.text = user.getBestName() - } else { - // Should not happen - views.loginAccountCreatedMemberName.text = state.userId - } - - // User color - views.loginAccountCreatedMemberName - .setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(state.userId))) - - views.loginAccountCreatedLater.isVisible = state.hasBeenModified.not() - views.loginAccountCreatedDone.isVisible = state.hasBeenModified - } - - override fun updateWithState(state: LoginViewState2) { - // No op - } - - override fun resetViewModel() { - // No op - } - - override fun onBackPressed(toolbarButton: Boolean): Boolean { - // Just start the next Activity - terminate() - return false - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt deleted file mode 100644 index c3b7f2d3d3..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login2.created - -import com.airbnb.mvrx.MavericksViewModelFactory -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import im.vector.app.core.di.MavericksAssistedViewModelFactory -import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap -import timber.log.Timber - -class AccountCreatedViewModel @AssistedInject constructor( - @Assisted initialState: AccountCreatedViewState, - private val session: Session -) : VectorViewModel(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel - } - - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - - init { - setState { - copy( - userId = session.myUserId - ) - } - observeUser() - } - - private fun observeUser() { - session.flow() - .liveUser(session.myUserId) - .unwrap() - .map { - if (MatrixPatterns.isUserId(it.userId)) { - it.toMatrixItem() - } else { - Timber.w("liveUser() has returned an invalid user: $it") - MatrixItem.UserItem(session.myUserId, null, null) - } - } - .execute { - copy(currentUser = it) - } - } - - override fun handle(action: AccountCreatedAction) { - when (action) { - is AccountCreatedAction.SetAvatar -> handleSetAvatar(action) - is AccountCreatedAction.SetDisplayName -> handleSetDisplayName(action) - } - } - - private fun handleSetAvatar(action: AccountCreatedAction.SetAvatar) { - setState { copy(isLoading = true) } - viewModelScope.launch { - val result = runCatching { session.profileService().updateAvatar(session.myUserId, action.avatarUri, action.filename) } - .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } - setState { - copy( - isLoading = false, - hasBeenModified = hasBeenModified || result.isSuccess - ) - } - } - } - - private fun handleSetDisplayName(action: AccountCreatedAction.SetDisplayName) { - setState { copy(isLoading = true) } - viewModelScope.launch { - val result = runCatching { session.profileService().setDisplayName(session.myUserId, action.displayName) } - .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } - setState { - copy( - isLoading = false, - hasBeenModified = hasBeenModified || result.isSuccess - ) - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt deleted file mode 100755 index a48996a16f..0000000000 --- a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2018 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.login2.terms - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.args -import im.vector.app.core.extensions.cleanup -import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.databinding.FragmentLoginTerms2Binding -import im.vector.app.features.login.terms.LocalizedFlowDataLoginTermsChecked -import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login.terms.LoginTermsViewState -import im.vector.app.features.login.terms.PolicyController -import im.vector.app.features.login2.AbstractLoginFragment2 -import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginViewState2 -import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms -import javax.inject.Inject - -/** - * LoginTermsFragment displays the list of policies the user has to accept. - */ -class LoginTermsFragment2 @Inject constructor( - private val policyController: PolicyController -) : AbstractLoginFragment2(), - PolicyController.PolicyControllerListener { - - private val params: LoginTermsFragmentArgument by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTerms2Binding { - return FragmentLoginTerms2Binding.inflate(inflater, container, false) - } - - private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList()) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupViews() - views.loginTermsPolicyList.configureWith(policyController) - policyController.listener = this - - val list = ArrayList() - - params.localizedFlowDataLoginTerms - .forEach { - list.add(LocalizedFlowDataLoginTermsChecked(it)) - } - - loginTermsViewState = LoginTermsViewState(list) - } - - private fun setupViews() { - views.loginTermsSubmit.setOnClickListener { submit() } - } - - override fun onDestroyView() { - views.loginTermsPolicyList.cleanup() - policyController.listener = null - super.onDestroyView() - } - - private fun renderState() { - policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked) - - // Button is enabled only if all checkboxes are checked - views.loginTermsSubmit.isEnabled = loginTermsViewState.allChecked() - } - - override fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean) { - if (isChecked) { - loginTermsViewState.check(localizedFlowDataLoginTerms) - } else { - loginTermsViewState.uncheck(localizedFlowDataLoginTerms) - } - - renderState() - } - - override fun openPolicy(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms) { - localizedFlowDataLoginTerms.localizedUrl - ?.takeIf { it.isNotBlank() } - ?.let { - openUrlInChromeCustomTab(requireContext(), null, it) - } - } - - private fun submit() { - loginViewModel.handle(LoginAction2.AcceptTerms) - } - - override fun updateWithState(state: LoginViewState2) { - policyController.homeServer = state.homeServerUrlFromUser.toReducedUrl() - renderState() - } - - override fun resetViewModel() { - loginViewModel.handle(LoginAction2.ResetSignup) - } -} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 8dd9fd030a..f564802b57 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -28,6 +28,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.ButtonStateView @@ -38,10 +39,12 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType import javax.inject.Inject -class MatrixToRoomSpaceFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val spaceCardRenderer: SpaceCardRenderer -) : VectorBaseFragment() { +@AndroidEntryPoint +class MatrixToRoomSpaceFragment : + VectorBaseFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var spaceCardRenderer: SpaceCardRenderer private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt index 3792183bca..c8b18e327b 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt @@ -28,15 +28,18 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentMatrixToUserCardBinding import im.vector.app.features.home.AvatarRenderer import javax.inject.Inject -class MatrixToUserFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment() { +@AndroidEntryPoint +class MatrixToUserFragment : + VectorBaseFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel() 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 e724084501..4eec5c75a1 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 @@ -130,7 +130,6 @@ class DefaultNavigator @Inject constructor( override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { val intent = when (features.onboardingVariant()) { OnboardingVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig) - OnboardingVariant.LOGIN_2, OnboardingVariant.FTUE_AUTH -> OnboardingActivity.newIntent(context, loginConfig) } intent.addFlags(flags) @@ -140,7 +139,6 @@ class DefaultNavigator @Inject constructor( override fun loginSSORedirect(context: Context, data: Uri?) { val intent = when (features.onboardingVariant()) { OnboardingVariant.LEGACY -> LoginActivity.redirectIntent(context, data) - OnboardingVariant.LOGIN_2, OnboardingVariant.FTUE_AUTH -> OnboardingActivity.redirectIntent(context, data) } context.startActivity(intent) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt deleted file mode 100644 index 7def6d62f0..0000000000 --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.onboarding - -import android.content.Intent -import android.view.View -import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.core.view.children -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE -import im.vector.app.core.extensions.addFragment -import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.resetBackstack -import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.databinding.ActivityLoginBinding -import im.vector.app.features.home.HomeActivity -import im.vector.app.features.login.LoginCaptchaFragmentArgument -import im.vector.app.features.login.LoginConfig -import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument -import im.vector.app.features.login.LoginWaitForEmailFragmentArgument -import im.vector.app.features.login.TextInputFormFragmentMode -import im.vector.app.features.login.isSupported -import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login2.LoginAction2 -import im.vector.app.features.login2.LoginCaptchaFragment2 -import im.vector.app.features.login2.LoginFragmentSigninPassword2 -import im.vector.app.features.login2.LoginFragmentSigninUsername2 -import im.vector.app.features.login2.LoginFragmentSignupPassword2 -import im.vector.app.features.login2.LoginFragmentSignupUsername2 -import im.vector.app.features.login2.LoginFragmentToAny2 -import im.vector.app.features.login2.LoginGenericTextInputFormFragment2 -import im.vector.app.features.login2.LoginResetPasswordFragment2 -import im.vector.app.features.login2.LoginResetPasswordMailConfirmationFragment2 -import im.vector.app.features.login2.LoginResetPasswordSuccessFragment2 -import im.vector.app.features.login2.LoginServerSelectionFragment2 -import im.vector.app.features.login2.LoginServerUrlFormFragment2 -import im.vector.app.features.login2.LoginSplashSignUpSignInSelectionFragment2 -import im.vector.app.features.login2.LoginSsoOnlyFragment2 -import im.vector.app.features.login2.LoginViewEvents2 -import im.vector.app.features.login2.LoginViewModel2 -import im.vector.app.features.login2.LoginViewState2 -import im.vector.app.features.login2.LoginWaitForEmailFragment2 -import im.vector.app.features.login2.LoginWebFragment2 -import im.vector.app.features.login2.created.AccountCreatedFragment -import im.vector.app.features.login2.terms.LoginTermsFragment2 -import org.matrix.android.sdk.api.auth.registration.FlowResult -import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms -import org.matrix.android.sdk.api.extensions.tryOrNull - -private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" -private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" - -class Login2Variant( - private val views: ActivityLoginBinding, - private val loginViewModel: LoginViewModel2, - private val activity: VectorBaseActivity, - private val supportFragmentManager: FragmentManager -) : OnboardingVariant { - - private val enterAnim = R.anim.enter_fade_in - private val exitAnim = R.anim.exit_fade_out - - private val popEnterAnim = R.anim.no_anim - private val popExitAnim = R.anim.exit_fade_out - - private val topFragment: Fragment? - get() = supportFragmentManager.findFragmentById(views.loginFragmentContainer.id) - - private val commonOption: (FragmentTransaction) -> Unit = { ft -> - // Find the loginLogo on the current Fragment, this should not return null - (topFragment?.view as? ViewGroup) - // Find activity.findViewById does not work, I do not know why - // activity.findViewById(views.loginLogo) - ?.children - ?.firstOrNull { it.id == R.id.loginLogo } - ?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - } - - override fun initUiAndData(isFirstCreation: Boolean) { - if (isFirstCreation) { - addFirstFragment() - } - - with(activity) { - loginViewModel.onEach { - updateWithState(it) - } - loginViewModel.observeViewEvents { handleLoginViewEvents(it) } - } - - // Get config extra - val loginConfig = activity.intent.getParcelableExtra(OnboardingActivity.EXTRA_CONFIG) - if (isFirstCreation) { - // TODO Check this - loginViewModel.handle(LoginAction2.InitWith(loginConfig)) - } - } - - private fun addFirstFragment() { - activity.addFragment(views.loginFragmentContainer, LoginSplashSignUpSignInSelectionFragment2::class.java) - } - - private fun handleLoginViewEvents(event: LoginViewEvents2) { - when (event) { - is LoginViewEvents2.RegistrationFlowResult -> { - // Check that all flows are supported by the application - if (event.flowResult.missingStages.any { !it.isSupported() }) { - // Display a popup to propose use web fallback - onRegistrationStageNotSupported() - } else { - if (event.isRegistrationStarted) { - // Go on with registration flow - handleRegistrationNavigation(event.flowResult) - } else { - /* - // First ask for login and password - // I add a tag to indicate that this fragment is a registration stage. - // This way it will be automatically popped in when starting the next registration stage - activity.addFragmentToBackstack(views.loginFragmentContainer, - LoginFragment2::class.java, - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - - */ - } - } - } - is LoginViewEvents2.OutdatedHomeserver -> { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.login_error_outdated_homeserver_title) - .setMessage(R.string.login_error_outdated_homeserver_warning_content) - .setPositiveButton(R.string.ok, null) - .show() - Unit - } - is LoginViewEvents2.OpenServerSelection -> - activity.addFragmentToBackstack(views.loginFragmentContainer, - LoginServerSelectionFragment2::class.java, - option = { ft -> - activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // TODO Disabled because it provokes a flickering - // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - }) - is LoginViewEvents2.OpenHomeServerUrlFormScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginServerUrlFormFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignInEnterIdentifierScreen -> { - activity.addFragmentToBackstack(views.loginFragmentContainer, - LoginFragmentSigninUsername2::class.java, - option = { ft -> - activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // TODO Disabled because it provokes a flickering - // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) - }) - } - is LoginViewEvents2.OpenSsoOnlyScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginSsoOnlyFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OnWebLoginError -> onWebLoginError(event) - is LoginViewEvents2.OpenResetPasswordScreen -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginResetPasswordFragment2::class.java, - option = commonOption - ) - is LoginViewEvents2.OnResetPasswordSendThreePidDone -> { - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginResetPasswordMailConfirmationFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OnResetPasswordMailConfirmationSuccess -> { - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginResetPasswordSuccessFragment2::class.java, - option = commonOption - ) - } - is LoginViewEvents2.OnResetPasswordMailConfirmationSuccessDone -> { - // Go back to the login fragment - supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) - } - is LoginViewEvents2.OnSendEmailSuccess -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginWaitForEmailFragment2::class.java, - LoginWaitForEmailFragmentArgument(event.email), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is LoginViewEvents2.OpenSigninPasswordScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentSigninPassword2::class.java, - tag = FRAGMENT_LOGIN_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignupPasswordScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentSignupPassword2::class.java, - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentSignupUsername2::class.java, - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OpenSignInWithAnythingScreen -> { - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginFragmentToAny2::class.java, - tag = FRAGMENT_LOGIN_TAG, - option = commonOption - ) - } - is LoginViewEvents2.OnSendMsisdnSuccess -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginGenericTextInputFormFragment2::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, event.msisdn), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is LoginViewEvents2.Failure -> - // This is handled by the Fragments - Unit - is LoginViewEvents2.OnLoginModeNotSupported -> - onLoginModeNotSupported(event.supportedTypes) - is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) - is LoginViewEvents2.Finish -> terminate() - is LoginViewEvents2.CancelRegistration -> handleCancelRegistration() - } - } - - private fun handleCancelRegistration() { - // Cleanup the back stack - activity.resetBackstack() - } - - private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) { - if (event.newAccount) { - // Propose to set avatar and display name - // Back on this Fragment will finish the Activity - activity.addFragmentToBackstack( - views.loginFragmentContainer, - AccountCreatedFragment::class.java, - option = commonOption - ) - } else { - terminate() - } - } - - private fun terminate() { - val intent = HomeActivity.newIntent( - activity, - firstStartMainActivity = false, - ) - activity.startActivity(intent) - activity.finish() - } - - private fun updateWithState(loginViewState2: LoginViewState2) { - // Loading - setIsLoading(loginViewState2.isLoading) - } - - // Hack for AccountCreatedFragment - override fun setIsLoading(isLoading: Boolean) { - views.loginLoading.isVisible = isLoading - } - - private fun onWebLoginError(onWebLoginError: LoginViewEvents2.OnWebLoginError) { - // Pop the backstack - supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) - - // And inform the user - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.dialog_title_error) - .setMessage(activity.getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode)) - .setPositiveButton(R.string.ok, null) - .show() - } - - /** - * Handle the SSO redirection here. - */ - override fun onNewIntent(intent: Intent?) { - intent?.data - ?.let { tryOrNull { it.getQueryParameter("loginToken") } } - ?.let { loginViewModel.handle(LoginAction2.LoginWithToken(it)) } - } - - private fun onRegistrationStageNotSupported() { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.app_name) - .setMessage(activity.getString(R.string.login_registration_not_supported)) - .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginWebFragment2::class.java, - option = commonOption - ) - } - .setNegativeButton(R.string.no, null) - .show() - } - - private fun onLoginModeNotSupported(supportedTypes: List) { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.app_name) - .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" })) - .setPositiveButton(R.string.yes) { _, _ -> - activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginWebFragment2::class.java, - option = commonOption - ) - } - .setNegativeButton(R.string.no, null) - .show() - } - - private fun handleRegistrationNavigation(flowResult: FlowResult) { - // Complete all mandatory stages first - val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory } - - if (mandatoryStage != null) { - doStage(mandatoryStage) - } else { - // Consider optional stages - val optionalStage = flowResult.missingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy } - if (optionalStage == null) { - // Should not happen... - } else { - doStage(optionalStage) - } - } - } - - private fun doStage(stage: Stage) { - // Ensure there is no fragment for registration stage in the backstack - supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) - - when (stage) { - is Stage.ReCaptcha -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginCaptchaFragment2::class.java, - LoginCaptchaFragmentArgument(stage.publicKey), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is Stage.Email -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginGenericTextInputFormFragment2::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is Stage.Msisdn -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginGenericTextInputFormFragment2::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - is Stage.Terms -> activity.addFragmentToBackstack( - views.loginFragmentContainer, - LoginTermsFragment2::class.java, - LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption - ) - else -> Unit // Should not happen - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index d07ac46b85..f1617b660b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -82,7 +82,7 @@ sealed interface OnboardingAction : VectorViewModelAction { data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction - data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction + data class UserAcceptCertificate(val fingerprint: Fingerprint, val retryAction: OnboardingAction) : OnboardingAction object PersonalizeProfile : OnboardingAction data class UpdateDisplayName(val displayName: String) : OnboardingAction diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt index 5a19732341..060472a2da 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt @@ -33,7 +33,7 @@ import javax.inject.Inject class OnboardingActivity : VectorBaseActivity(), UnlockedActivity { private val onboardingVariant by lifecycleAwareLazy { - onboardingVariantFactory.create(this, views = views, onboardingViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) + onboardingVariantFactory.create(this, views = views, onboardingViewModel = lazyViewModel()) } @Inject lateinit var onboardingVariantFactory: OnboardingVariantFactory diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt index 9837db8e91..fec0374afb 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt @@ -20,7 +20,6 @@ import im.vector.app.config.OnboardingVariant import im.vector.app.core.platform.ScreenOrientationLocker import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.VectorFeatures -import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.onboarding.ftueauth.FtueAuthVariant import javax.inject.Inject @@ -33,7 +32,6 @@ class OnboardingVariantFactory @Inject constructor( activity: OnboardingActivity, views: ActivityLoginBinding, onboardingViewModel: Lazy, - loginViewModel2: Lazy ) = when (vectorFeatures.onboardingVariant()) { OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE") OnboardingVariant.FTUE_AUTH -> FtueAuthVariant( @@ -44,11 +42,5 @@ class OnboardingVariantFactory @Inject constructor( vectorFeatures = vectorFeatures, orientationLocker = orientationLocker ) - OnboardingVariant.LOGIN_2 -> Login2Variant( - views = views, - loginViewModel = loginViewModel2.value, - activity = activity, - supportFragmentManager = activity.supportFragmentManager - ) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index bbbf13fba9..dcf6521499 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.failure.Failure as SdkFailure /** * Transient events for Login. @@ -28,7 +29,7 @@ import org.matrix.android.sdk.api.auth.registration.Stage sealed class OnboardingViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : OnboardingViewEvents() data class Failure(val throwable: Throwable) : OnboardingViewEvents() - data class DeeplinkAuthenticationFailure(val retryAction: OnboardingAction) : OnboardingViewEvents() + data class UnrecognisedCertificateFailure(val retryAction: OnboardingAction, val cause: SdkFailure.UnrecognizedCertificateFailure) : OnboardingViewEvents() object DisplayRegistrationFallback : OnboardingViewEvents() data class DisplayRegistrationStage(val stage: Stage) : OnboardingViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 73288bd6d5..9661feb002 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -60,7 +60,11 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability import org.matrix.android.sdk.api.auth.registration.RegistrationWizard +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.isHomeserverConnectionError import org.matrix.android.sdk.api.failure.isHomeserverUnavailable +import org.matrix.android.sdk.api.failure.isUnrecognisedCertificate +import org.matrix.android.sdk.api.network.ssl.Fingerprint import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import timber.log.Timber @@ -113,19 +117,12 @@ class OnboardingViewModel @AssistedInject constructor( } } - // Store the last action, to redo it after user has trusted the untrusted certificate - private var lastAction: OnboardingAction? = null - private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null - private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() private val defaultHomeserverUrl = matrixOrgUrl private val registrationWizard: RegistrationWizard get() = authenticationService.getRegistrationWizard() - val currentThreePid: String? - get() = registrationWizard.getCurrentThreePid() - // True when login and password has been sent with success to the homeserver val isRegistrationStarted: Boolean get() = authenticationService.isRegistrationStarted() @@ -146,9 +143,9 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) - is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) } + is OnboardingAction.HomeServerChange -> handleHomeserverChange(action) is OnboardingAction.UserNameEnteredAction -> handleUserNameEntered(action) - is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) } + is AuthenticateAction -> handleAuthenticateAction(action) is OnboardingAction.LoginWithToken -> handleLoginWithToken(action) is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) is OnboardingAction.ResetPassword -> handleResetPassword(action) @@ -221,11 +218,6 @@ class OnboardingViewModel @AssistedInject constructor( ) } - private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) { - lastAction = action - block(action) - } - private fun handleAuthenticateAction(action: AuthenticateAction) { when (action) { is AuthenticateAction.Register -> handleRegisterWith(action.username, action.password, action.initialDeviceName) @@ -276,20 +268,13 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) { // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow - when (val finalLastAction = lastAction) { - is OnboardingAction.HomeServerChange.SelectHomeServer -> { - currentHomeServerConnectionConfig - ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { startAuthenticationFlow(finalLastAction, it, serverTypeOverride = null) } - } + when (action.retryAction) { + is OnboardingAction.HomeServerChange -> handleHomeserverChange(action.retryAction, fingerprint = action.fingerprint) is AuthenticateAction.LoginDirect -> handleDirectLogin( - finalLastAction, - HomeServerConnectionConfig.Builder() - // Will be replaced by the task - .withHomeServerUri("https://dummy.org") - .withAllowedFingerPrints(listOf(action.fingerprint)) - .build() + action.retryAction, + // Will be replaced by the task + homeServerConnectionConfigFactory.create("https://dummy.org", action.fingerprint) ) else -> Unit } @@ -492,17 +477,6 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleInitWith(action: OnboardingAction.InitWith) { loginConfig = action.loginConfig - // If there is a pending email validation continue on this step - try { - if (registrationWizard.isRegistrationStarted()) { - currentThreePid?.let { - handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it, isRestoredSession = true))) - } - } - } 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: OnboardingAction.ResetPassword) { @@ -589,9 +563,19 @@ class OnboardingViewModel @AssistedInject constructor( currentJob = viewModelScope.launch { directLoginUseCase.execute(action, homeServerConnectionConfig).fold( onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) }, - onFailure = { + onFailure = { error -> setState { copy(isLoading = false) } - _viewEvents.post(OnboardingViewEvents.Failure(it)) + when { + error.isUnrecognisedCertificate() -> { + _viewEvents.post( + OnboardingViewEvents.UnrecognisedCertificateFailure( + retryAction = action, + cause = error as Failure.UnrecognizedCertificateFailure + ) + ) + } + else -> _viewEvents.post(OnboardingViewEvents.Failure(error)) + } } ) } @@ -682,8 +666,13 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange, serverTypeOverride: ServerType? = null, postAction: suspend () -> Unit = {}) { - val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) + private fun handleHomeserverChange( + action: OnboardingAction.HomeServerChange, + serverTypeOverride: ServerType? = null, + fingerprint: Fingerprint? = null, + postAction: suspend () -> Unit = {}, + ) { + val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl, fingerprint) if (homeServerConnectionConfig == null) { // This is invalid _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) @@ -698,8 +687,6 @@ class OnboardingViewModel @AssistedInject constructor( serverTypeOverride: ServerType?, postAction: suspend () -> Unit = {}, ) { - currentHomeServerConnectionConfig = homeServerConnectionConfig - currentJob = viewModelScope.launch { setState { copy(isLoading = true) } runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold( @@ -715,25 +702,28 @@ class OnboardingViewModel @AssistedInject constructor( private fun onAuthenticationStartError(error: Throwable, trigger: OnboardingAction.HomeServerChange) { when { - error.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(sdkIntProvider) -> _viewEvents.post( - OnboardingViewEvents.Failure(error) - ) - deeplinkUrlIsUnavailable(error, trigger) -> _viewEvents.post( - OnboardingViewEvents.DeeplinkAuthenticationFailure( - retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl() - ) - ) - else -> _viewEvents.post( - OnboardingViewEvents.Failure(error) - ) + error.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(sdkIntProvider) -> _viewEvents.post(OnboardingViewEvents.Failure(error)) + isUnableToSelectServer(error, trigger) -> { + withState { state -> + when { + canEditServerSelectionError(state) -> handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) + else -> _viewEvents.post(OnboardingViewEvents.Failure(error)) + } + } + } + error.isUnrecognisedCertificate() -> { + _viewEvents.post(OnboardingViewEvents.UnrecognisedCertificateFailure(trigger, error as Failure.UnrecognizedCertificateFailure)) + } + else -> _viewEvents.post(OnboardingViewEvents.Failure(error)) } } - private fun deeplinkUrlIsUnavailable(error: Throwable, trigger: OnboardingAction.HomeServerChange) = error.isHomeserverUnavailable() && - loginConfig != null && - trigger is OnboardingAction.HomeServerChange.SelectHomeServer + private fun canEditServerSelectionError(state: OnboardingViewState) = + (state.onboardingFlow == OnboardingFlow.SignIn && vectorFeatures.isOnboardingCombinedLoginEnabled()) || + (state.onboardingFlow == OnboardingFlow.SignUp && vectorFeatures.isOnboardingCombinedRegisterEnabled()) - private fun OnboardingAction.HomeServerChange.SelectHomeServer.resetToDefaultUrl() = copy(homeServerUrl = defaultHomeserverUrl) + private fun isUnableToSelectServer(error: Throwable, trigger: OnboardingAction.HomeServerChange) = + trigger is OnboardingAction.HomeServerChange.SelectHomeServer && error.isHomeserverConnectionError() private suspend fun onAuthenticationStartedSuccess( trigger: OnboardingAction.HomeServerChange, @@ -820,6 +810,8 @@ class OnboardingViewModel @AssistedInject constructor( return loginConfig?.homeServerUrl } + fun getDefaultHomeserverUrl() = defaultHomeserverUrl + fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? { setState { val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType()) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt index 2dc9a05154..db21a53854 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt @@ -18,6 +18,7 @@ package im.vector.app.features.onboarding import im.vector.app.core.extensions.containsAllItems import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.toSsoState import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.LoginFlowResult @@ -50,8 +51,8 @@ class StartAuthenticationFlowUseCase @Inject constructor( ) private fun LoginFlowResult.findPreferredLoginMode() = when { - supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders) - supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders) + supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders.toSsoState()) + supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders.toSsoState()) supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index 072e94bc30..f3cb326221 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -33,7 +33,6 @@ import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.CancellationException -import org.matrix.android.sdk.api.failure.Failure /** * Parent Fragment for all the login/registration screens. @@ -68,6 +67,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment showFailure(viewEvents.throwable) + is OnboardingViewEvents.UnrecognisedCertificateFailure -> showUnrecognizedCertificateFailure(viewEvents) else -> // This is handled by the Activity Unit @@ -84,20 +84,20 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment /* Ignore this error, user has cancelled the action */ Unit - is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable) else -> onError(throwable) } } - private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) { + private fun showUnrecognizedCertificateFailure(event: OnboardingViewEvents.UnrecognisedCertificateFailure) { // Ask the user to accept the certificate + val cause = event.cause unrecognizedCertificateDialog.show(requireActivity(), - failure.fingerprint, - failure.url, + cause.fingerprint, + cause.url, object : UnrecognizedCertificateDialog.Callback { override fun onAccept() { // User accept the certificate - viewModel.handle(OnboardingAction.UserAcceptCertificate(failure.fingerprint)) + viewModel.handle(OnboardingAction.UserAcceptCertificate(cause.fingerprint, event.retryAction)) } override fun onIgnore() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt index 1b764f4ce6..b1352db0cc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt @@ -26,7 +26,7 @@ import com.airbnb.mvrx.withState import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.hasSso -import im.vector.app.features.login.ssoIdentityProviders +import im.vector.app.features.login.ssoState abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthFragment() { @@ -88,7 +88,7 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF private fun prefetchIfNeeded() { withState(viewModel) { state -> - if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) { + if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoState().isFallback()) { // in this case we can prefetch (not other cases for privacy concerns) viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt index 9f2aadef5c..a53ca52e85 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.text.toSpannable import androidx.core.view.isVisible +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.animations.play import im.vector.app.core.di.ActiveSessionHolder @@ -34,9 +35,11 @@ import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import javax.inject.Inject -class FtueAuthAccountCreatedFragment @Inject constructor( - private val activeSessionHolder: ActiveSessionHolder -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthAccountCreatedFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var activeSessionHolder: ActiveSessionHolder private var hasPlayedConfetti = false diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt index 3720a41455..f170868307 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt @@ -16,15 +16,24 @@ package im.vector.app.features.onboarding.ftueauth +import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup +import android.view.ViewStub import com.airbnb.mvrx.args +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.extensions.crawlCausesFor import im.vector.app.databinding.FragmentFtueLoginCaptchaBinding +import im.vector.app.databinding.ViewStubWebviewBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.RegisterAction import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.extensions.orFalse import javax.inject.Inject @Parcelize @@ -35,15 +44,39 @@ data class FtueAuthCaptchaFragmentArgument( /** * In this screen, the user is asked to confirm they are not a robot. */ -class FtueAuthCaptchaFragment @Inject constructor( - private val captchaWebview: CaptchaWebview -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthCaptchaFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var captchaWebview: CaptchaWebview private val params: FtueAuthCaptchaFragmentArgument by args() + private var webViewBinding: ViewStubWebviewBinding? = null private var isWebViewLoaded = false override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueLoginCaptchaBinding { - return FragmentFtueLoginCaptchaBinding.inflate(inflater, container, false) + return FragmentFtueLoginCaptchaBinding.inflate(inflater, container, false).also { + it.loginCaptchaWebViewStub.setOnInflateListener { _, inflated -> + webViewBinding = ViewStubWebviewBinding.bind(inflated) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + inflateWebViewOrShowError() + } + + private fun inflateWebViewOrShowError() { + views.loginCaptchaWebViewStub.inflateWebView(onError = { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(it.localizedMessage) + .setPositiveButton(R.string.ok) { _, _ -> + requireActivity().recreate() + } + .show() + }) } override fun resetViewModel() { @@ -51,11 +84,26 @@ class FtueAuthCaptchaFragment @Inject constructor( } override fun updateWithState(state: OnboardingViewState) { - if (!isWebViewLoaded) { - captchaWebview.setupWebView(this, views.loginCaptchaWevView, views.loginCaptchaProgress, params.siteKey, state) { + if (!isWebViewLoaded && webViewBinding != null) { + captchaWebview.setupWebView(this, webViewBinding!!.root, views.loginCaptchaProgress, params.siteKey, state) { viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(it))) } isWebViewLoaded = true } } } + +private fun ViewStub.inflateWebView(onError: (Throwable) -> Unit) { + try { + inflate() + } catch (e: Exception) { + val isMissingWebView = e.crawlCausesFor { it.message?.contains("MissingWebViewPackageException").orFalse() } + if (isMissingWebView) { + onError(MissingWebViewException(e)) + } else { + onError(e) + } + } +} + +private class MissingWebViewException(cause: Throwable) : IllegalStateException("Failed to load WebView provider: No WebView installed", cause) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt index c4b4e807ac..234a5789c1 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt @@ -22,15 +22,17 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.hasContent import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.databinding.FragmentFtueDisplayNameBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState -import javax.inject.Inject -class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthChooseDisplayNameFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueDisplayNameBinding { return FragmentFtueDisplayNameBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt index 1a673b156a..92d0aa2a0f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt @@ -24,12 +24,11 @@ import android.view.ViewGroup import android.widget.Toast import androidx.core.view.isInvisible import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper -import im.vector.app.core.extensions.singletonEntryPoint -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.databinding.FragmentFtueProfilePictureBinding import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.onboarding.OnboardingAction @@ -38,19 +37,26 @@ import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject -class FtueAuthChooseProfilePictureFragment @Inject constructor( - private val activeSessionHolder: ActiveSessionHolder, - colorProvider: ColorProvider, - clock: Clock, -) : AbstractFtueAuthFragment(), GalleryOrCameraDialogHelper.Listener { +@AndroidEntryPoint +class FtueAuthChooseProfilePictureFragment : + AbstractFtueAuthFragment(), + GalleryOrCameraDialogHelper.Listener { - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) - private val avatarRenderer: AvatarRenderer by lazy { requireContext().singletonEntryPoint().avatarRenderer() } + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + @Inject lateinit var avatarRenderer: AvatarRenderer + + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueProfilePictureBinding { return FragmentFtueProfilePictureBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupViews() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index c2d2346765..6877810f0a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -24,6 +24,7 @@ import android.view.ViewGroup import androidx.autofill.HintConstants import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.clearErrorOnChange import im.vector.app.core.extensions.content @@ -38,20 +39,22 @@ import im.vector.app.databinding.FragmentFtueCombinedLoginBinding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.login.SsoState import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject -class FtueAuthCombinedLoginFragment @Inject constructor( - private val loginFieldsValidation: LoginFieldsValidation, - private val loginErrorParser: LoginErrorParser -) : AbstractSSOFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthCombinedLoginFragment : + AbstractSSOFtueAuthFragment() { + + @Inject lateinit var loginFieldsValidation: LoginFieldsValidation + @Inject lateinit var loginErrorParser: LoginErrorParser override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedLoginBinding { return FragmentFtueCombinedLoginBinding.inflate(inflater, container, false) @@ -125,11 +128,11 @@ class FtueAuthCombinedLoginFragment @Inject constructor( when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { showUsernamePassword() - renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) } is LoginMode.Sso -> { hideUsernamePassword() - renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) } else -> { showUsernamePassword() @@ -138,10 +141,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor( } } - private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { - views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true - views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible - views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> + private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) { + views.ssoGroup.isVisible = true + views.ssoButtonsHeader.isVisible = isUsernameAndPasswordVisible() + views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, @@ -163,6 +166,8 @@ class FtueAuthCombinedLoginFragment @Inject constructor( views.loginEntryGroup.isVisible = true } + private fun isUsernameAndPasswordVisible() = views.loginEntryGroup.isVisible + private fun setupAutoFill() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { views.loginInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt index 8340fb903a..66668f5303 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -27,6 +27,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.clearErrorOnChange import im.vector.app.core.extensions.content @@ -44,6 +45,7 @@ import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.login.SsoState import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction @@ -51,7 +53,6 @@ import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidUsername @@ -60,11 +61,12 @@ import org.matrix.android.sdk.api.failure.isRegistrationDisabled import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject private const val MINIMUM_PASSWORD_LENGTH = 8 -class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthCombinedRegisterFragment : + AbstractSSOFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding { return FragmentFtueCombinedRegisterBinding.inflate(inflater, container, false) @@ -205,14 +207,14 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu } when (state.selectedHomeserver.preferredLoginMode) { - is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoState) else -> hideSsoProviders() } } - private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { - views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true - views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> + private fun renderSsoProviders(deviceId: String?, ssoState: SsoState) { + views.ssoGroup.isVisible = true + views.ssoButtons.render(ssoState, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = deviceId, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt index 749aac2898..f39946a1d0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.clearErrorOnChange @@ -27,6 +28,7 @@ import im.vector.app.core.extensions.content import im.vector.app.core.extensions.editText import im.vector.app.core.extensions.realignPercentagesToParent import im.vector.app.core.extensions.setOnImeDoneListener +import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureTrailingSlash @@ -37,9 +39,10 @@ import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.failure.isHomeserverUnavailable -import javax.inject.Inject -class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthCombinedServerSelectionFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueServerSelectionCombinedBinding { return FragmentFtueServerSelectionCombinedBinding.inflate(inflater, container, false) @@ -86,9 +89,12 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt ) if (views.chooseServerInput.content().isEmpty()) { - val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() + val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() ?: viewModel.getDefaultHomeserverUrl() views.chooseServerInput.editText().setText(userUrlInput) } + + views.chooseServerInput.editText().selectAll() + views.chooseServerInput.editText().showKeyboard(true) } override fun onError(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt index 5de8fce82f..4cd35c8a66 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.autofillEmail @@ -35,9 +36,10 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.RegisterAction import org.matrix.android.sdk.api.auth.registration.RegisterThreePid -import javax.inject.Inject -class FtueAuthEmailEntryFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthEmailEntryFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueEmailInputBinding { return FragmentFtueEmailInputBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt index edfbcd89b6..02d0c25cfd 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt @@ -29,6 +29,7 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.isEmail @@ -44,7 +45,6 @@ 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 reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject @Parcelize data class FtueAuthGenericTextInputFormFragmentArgument( @@ -56,7 +56,9 @@ data class FtueAuthGenericTextInputFormFragmentArgument( /** * In this screen, the user is asked for a text input. */ -class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthGenericTextInputFormFragment : + AbstractFtueAuthFragment() { private val params: FtueAuthGenericTextInputFormFragmentArgument by args() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt index b8b30529a6..0efd8390ba 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt @@ -20,6 +20,7 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.ViewGroup import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.databinding.FragmentLoginCaptchaBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState @@ -35,9 +36,11 @@ data class FtueAuthLegacyStyleCaptchaFragmentArgument( /** * In this screen, the user is asked to confirm they are not a robot. */ -class FtueAuthLegacyStyleCaptchaFragment @Inject constructor( - private val captchaWebview: CaptchaWebview -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthLegacyStyleCaptchaFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var captchaWebview: CaptchaWebview private val params: FtueAuthLegacyStyleCaptchaFragmentArgument by args() private var isWebViewLoaded = false diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt index c815f354f0..fb468ddeb2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt @@ -21,16 +21,18 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.databinding.FragmentLoginWaitForEmailBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.RegisterAction -import javax.inject.Inject /** * In this screen, the user is asked to check their emails. */ -class FtueAuthLegacyWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthLegacyWaitForEmailFragment : + AbstractFtueAuthFragment() { private val params: FtueAuthWaitForEmailFragmentArgument by args() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index 17ceb5314c..3fd8df6bb9 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -28,6 +28,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -37,7 +38,8 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode -import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.login.SocialLoginButtonsView.Mode +import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState @@ -45,7 +47,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidUsername import org.matrix.android.sdk.api.failure.isLoginEmailUnknown @@ -53,7 +54,6 @@ import org.matrix.android.sdk.api.failure.isRegistrationDisabled import org.matrix.android.sdk.api.failure.isUsernameInUse import org.matrix.android.sdk.api.failure.isWeakPassword import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject /** * In this screen: @@ -63,7 +63,9 @@ import javax.inject.Inject * In signup mode: * - the user is asked for login and password */ -class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthLoginFragment : + AbstractSSOFtueAuthFragment() { private var isSignupMode = false @@ -111,13 +113,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } } - private fun setupSocialLoginButtons(state: OnboardingViewState) { - views.loginSocialLoginButtons.mode = when (state.signMode) { - SignMode.Unknown -> error("developer error") - SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP - SignMode.SignIn, - SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN - } + private fun ssoMode(state: OnboardingViewState) = when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> Mode.MODE_SIGN_UP + SignMode.SignIn, + SignMode.SignInWithMatrixId -> Mode.MODE_SIGN_IN } private fun submit() { @@ -215,16 +215,13 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) { views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted() - views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - viewModel.fetchSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - provider = provider - ) - ?.let { openInCustomTab(it) } - } + views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, ssoMode(state)) { provider -> + viewModel.fetchSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + provider = provider + ) + ?.let { openInCustomTab(it) } } } else { views.loginSocialLoginContainer.isVisible = false @@ -305,7 +302,6 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< setupUi(state) setupAutoFill(state) - setupSocialLoginButtons(state) setupButtons(state) if (state.isLoading) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt index 8e88a6ed46..96cc1c3b45 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt @@ -20,12 +20,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.databinding.FragmentFtuePersonalizationCompleteBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents -import javax.inject.Inject -class FtueAuthPersonalizationCompleteFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthPersonalizationCompleteFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtuePersonalizationCompleteBinding { return FragmentFtuePersonalizationCompleteBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneConfirmationFragment.kt index 39577efa19..af6c33c028 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneConfirmationFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.clearErrorOnChange @@ -32,14 +33,15 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.RegisterAction import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.Failure -import javax.inject.Inject @Parcelize data class FtueAuthPhoneConfirmationFragmentArgument( val msisdn: String ) : Parcelable -class FtueAuthPhoneConfirmationFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthPhoneConfirmationFragment : + AbstractFtueAuthFragment() { private val params: FtueAuthPhoneConfirmationFragmentArgument by args() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneEntryFragment.kt index 32291ecb6e..620dd1293c 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneEntryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPhoneEntryFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.autofillPhoneNumber @@ -38,9 +39,11 @@ import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject -class FtueAuthPhoneEntryFragment @Inject constructor( - private val phoneNumberParser: PhoneNumberParser -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthPhoneEntryFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var phoneNumberParser: PhoneNumberParser override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtuePhoneInputBinding { return FragmentFtuePhoneInputBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordBreakerFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordBreakerFragment.kt index 721423ecdf..0daf1b3c6f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordBreakerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordBreakerFragment.kt @@ -39,7 +39,8 @@ data class FtueAuthResetPasswordBreakerArgument( ) : Parcelable @AndroidEntryPoint -class FtueAuthResetPasswordBreakerFragment : AbstractFtueAuthFragment() { +class FtueAuthResetPasswordBreakerFragment : + AbstractFtueAuthFragment() { @Inject lateinit var themeProvider: ThemeProvider private val params: FtueAuthResetPasswordBreakerArgument by args() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEmailEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEmailEntryFragment.kt index 5fa1a8ed82..51c73a40e3 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEmailEntryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEmailEntryFragment.kt @@ -33,7 +33,8 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState @AndroidEntryPoint -class FtueAuthResetPasswordEmailEntryFragment : AbstractFtueAuthFragment() { +class FtueAuthResetPasswordEmailEntryFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueResetPasswordEmailInputBinding { return FragmentFtueResetPasswordEmailInputBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt index 61826352bf..0b0e06a0b0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt @@ -34,7 +34,8 @@ import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.failure.isMissingEmailVerification @AndroidEntryPoint -class FtueAuthResetPasswordEntryFragment : AbstractFtueAuthFragment() { +class FtueAuthResetPasswordEntryFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueResetPasswordInputBinding { return FragmentFtueResetPasswordInputBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt index 9bef084750..376218d474 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword @@ -35,12 +36,13 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject /** * In this screen, the user is asked for email and new password to reset his password. */ -class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthResetPasswordFragment : + AbstractFtueAuthFragment() { // Show warning only once private var showWarning = true diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt index 76fbae6f40..301ed4fd8b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -21,17 +21,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.failure.is401 -import javax.inject.Inject /** * In this screen, the user is asked to check their email and to click on a button once it's done. */ -class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthResetPasswordMailConfirmationFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordMailConfirmationBinding { return FragmentLoginResetPasswordMailConfirmationBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt index 956566a587..3e9881ec56 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordSuccessFragment.kt @@ -20,15 +20,17 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.databinding.FragmentLoginResetPasswordSuccessBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents -import javax.inject.Inject /** * In this screen, we confirm to the user that his password has been reset. */ -class FtueAuthResetPasswordSuccessFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthResetPasswordSuccessFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordSuccessBinding { return FragmentLoginResetPasswordSuccessBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt index d4396d81d2..9977152b5a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerSelectionFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.FragmentLoginServerSelectionBinding @@ -29,12 +30,13 @@ import im.vector.app.features.login.SignMode import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState import me.gujun.android.span.span -import javax.inject.Inject /** * In this screen, the user will choose between matrix.org, modular or other type of homeserver. */ -class FtueAuthServerSelectionFragment @Inject constructor() : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthServerSelectionFragment : + AbstractFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerSelectionBinding { return FragmentLoginServerSelectionBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt index b16ad3ee93..91f176edf5 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt @@ -27,6 +27,7 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.resources.BuildMeta @@ -47,9 +48,11 @@ import javax.inject.Inject /** * In this screen, the user is prompted to enter a homeserver url. */ -class FtueAuthServerUrlFormFragment @Inject constructor( - private val buildMeta: BuildMeta, -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthServerUrlFormFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var buildMeta: BuildMeta override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlFormBinding { return FragmentLoginServerUrlFormBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt index 6723e48bcc..b2f2eeb167 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.annotation.DrawableRes import androidx.core.view.isVisible import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding @@ -30,17 +31,17 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode -import im.vector.app.features.login.SocialLoginButtonsView -import im.vector.app.features.login.ssoIdentityProviders +import im.vector.app.features.login.SocialLoginButtonsView.Mode +import im.vector.app.features.login.render import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState -import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -import javax.inject.Inject /** * In this screen, the user is asked to sign up or to sign in to the homeserver. */ -class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthSignUpSignInSelectionFragment : + AbstractSSOFtueAuthFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupSigninSelectionBinding { return FragmentLoginSignupSigninSelectionBinding.inflate(inflater, container, false) @@ -80,16 +81,13 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted() - views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { - override fun onProviderSelected(provider: SsoIdentityProvider?) { - viewModel.fetchSsoUrl( - redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, - deviceId = state.deviceId, - provider = provider - ) - ?.let { openInCustomTab(it) } - } + views.loginSignupSigninSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode.ssoState, Mode.MODE_CONTINUE) { provider -> + viewModel.fetchSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + provider = provider + ) + ?.let { openInCustomTab(it) } } } else -> { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt index 0333f6047b..f41bb4f547 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.incrementByOneAndWrap import im.vector.app.core.extensions.setCurrentItem @@ -44,13 +45,15 @@ import javax.inject.Inject private const val CAROUSEL_ROTATION_DELAY_MS = 5000L private const val CAROUSEL_TRANSITION_TIME_MS = 500L -class FtueAuthSplashCarouselFragment @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val vectorFeatures: VectorFeatures, - private val carouselController: SplashCarouselController, - private val carouselStateFactory: SplashCarouselStateFactory, - private val buildMeta: BuildMeta, -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthSplashCarouselFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var vectorFeatures: VectorFeatures + @Inject lateinit var carouselController: SplashCarouselController + @Inject lateinit var carouselStateFactory: SplashCarouselStateFactory + @Inject lateinit var buildMeta: BuildMeta private var tabLayoutMediator: TabLayoutMediator? = null diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index a04e8a5c01..b62e72daee 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.resources.BuildMeta import im.vector.app.databinding.FragmentFtueAuthSplashBinding @@ -34,11 +35,13 @@ import javax.inject.Inject /** * In this screen, the user is viewing an introduction to what he can do with this application. */ -class FtueAuthSplashFragment @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val vectorFeatures: VectorFeatures, - private val buildMeta: BuildMeta, -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthSplashFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var vectorFeatures: VectorFeatures + @Inject lateinit var buildMeta: BuildMeta override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthSplashBinding { return FragmentFtueAuthSplashBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 289aa811d6..da8aac1d54 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -29,6 +29,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.view.isGone +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.getResTintedDrawable import im.vector.app.core.extensions.getTintedDrawable @@ -45,10 +46,12 @@ import javax.inject.Inject private const val DARK_MODE_ICON_BACKGROUND_ALPHA = 0.30f private const val LIGHT_MODE_ICON_BACKGROUND_ALPHA = 0.15f -class FtueAuthUseCaseFragment @Inject constructor( - private val themeProvider: ThemeProvider, - private val vectorFeatures: VectorFeatures, -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthUseCaseFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var themeProvider: ThemeProvider + @Inject lateinit var vectorFeatures: VectorFeatures override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthUseCaseBinding { return FragmentFtueAuthUseCaseBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 150ab74ec2..f3767aa546 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -46,6 +46,7 @@ import im.vector.app.features.login.SignMode import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingActivity +import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingVariant import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewModel @@ -202,6 +203,7 @@ class FtueAuthVariant( openMsisdnConfirmation(viewEvents.msisdn) } is OnboardingViewEvents.Failure, + is OnboardingViewEvents.UnrecognisedCertificateFailure, is OnboardingViewEvents.Loading -> // This is handled by the Fragments Unit @@ -212,7 +214,7 @@ class FtueAuthVariant( option = commonOption ) } - OnboardingViewEvents.OpenCombinedRegister -> openStartCombinedRegister() + OnboardingViewEvents.OpenCombinedRegister -> onStartCombinedRegister() is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() @@ -228,43 +230,48 @@ class FtueAuthVariant( tag = FRAGMENT_EDIT_HOMESERVER_TAG ) } - OnboardingViewEvents.OnHomeserverEdited -> supportFragmentManager.popBackStack( - FRAGMENT_EDIT_HOMESERVER_TAG, - FragmentManager.POP_BACK_STACK_INCLUSIVE - ) + OnboardingViewEvents.OnHomeserverEdited -> { + supportFragmentManager.popBackStack( + FRAGMENT_EDIT_HOMESERVER_TAG, + FragmentManager.POP_BACK_STACK_INCLUSIVE + ) + ensureEditServerBackstack() + } OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin() - is OnboardingViewEvents.DeeplinkAuthenticationFailure -> onDeeplinkedHomeserverUnavailable(viewEvents) OnboardingViewEvents.DisplayRegistrationFallback -> displayFallbackWebDialog() is OnboardingViewEvents.DisplayRegistrationStage -> doStage(viewEvents.stage) OnboardingViewEvents.DisplayStartRegistration -> when { - vectorFeatures.isOnboardingCombinedRegisterEnabled() -> openStartCombinedRegister() + vectorFeatures.isOnboardingCombinedRegisterEnabled() -> onStartCombinedRegister() else -> openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG) } } } - private fun onDeeplinkedHomeserverUnavailable(viewEvents: OnboardingViewEvents.DeeplinkAuthenticationFailure) { - showHomeserverUnavailableDialog(onboardingViewModel.getInitialHomeServerUrl().orEmpty()) { - onboardingViewModel.handle(OnboardingAction.ResetDeeplinkConfig) - onboardingViewModel.handle(viewEvents.retryAction) + private fun ensureEditServerBackstack() { + when (activity.supportFragmentManager.findFragmentById(views.loginFragmentContainer.id)) { + is FtueAuthCombinedLoginFragment, + is FtueAuthCombinedRegisterFragment -> { + // do nothing + } + else -> { + withState(onboardingViewModel) { state -> + when (state.onboardingFlow) { + OnboardingFlow.SignIn -> onStartCombinedLogin() + OnboardingFlow.SignUp -> onStartCombinedRegister() + OnboardingFlow.SignInSignUp, + null -> error("${state.onboardingFlow} does not support editing server url") + } + } + } } } - private fun showHomeserverUnavailableDialog(url: String, action: () -> Unit) { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.dialog_title_error) - .setMessage(activity.getString(R.string.login_error_homeserver_from_url_not_found, url)) - .setPositiveButton(R.string.login_error_homeserver_from_url_not_found_enter_manual) { _, _ -> action() } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - private fun onStartCombinedLogin() { - addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java) + addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java, allowStateLoss = true) } - private fun openStartCombinedRegister() { - addRegistrationStageFragmentToBackstack(FtueAuthCombinedRegisterFragment::class.java) + private fun onStartCombinedRegister() { + addRegistrationStageFragmentToBackstack(FtueAuthCombinedRegisterFragment::class.java, allowStateLoss = true) } private fun displayFallbackWebDialog() { @@ -519,13 +526,14 @@ class FtueAuthVariant( ) } - private fun addRegistrationStageFragmentToBackstack(fragmentClass: Class, params: Parcelable? = null) { + private fun addRegistrationStageFragmentToBackstack(fragmentClass: Class, params: Parcelable? = null, allowStateLoss: Boolean = false) { activity.addFragmentToBackstack( views.loginFragmentContainer, fragmentClass, params, tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption + option = commonOption, + allowStateLoss = allowStateLoss, ) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt index eb00dc3e21..ddd662be86 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isInvisible import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.utils.colorTerminatingFullStop import im.vector.app.databinding.FragmentFtueWaitForEmailVerificationBinding @@ -42,9 +43,11 @@ data class FtueAuthWaitForEmailFragmentArgument( /** * In this screen, the user is asked to check their emails. */ -class FtueAuthWaitForEmailFragment @Inject constructor( - private val themeProvider: ThemeProvider -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthWaitForEmailFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var themeProvider: ThemeProvider private val params: FtueAuthWaitForEmailFragmentArgument by args() private var inferHasLeftAndReturnedToScreen = false diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt index 9e32ec263c..62a89e437b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt @@ -31,6 +31,7 @@ import android.webkit.SslErrorHandler import android.webkit.WebView import android.webkit.WebViewClient import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.utils.AssetReader import im.vector.app.databinding.FragmentLoginWebBinding @@ -49,9 +50,11 @@ import javax.inject.Inject * This screen is displayed when the application does not support login flow or registration flow * of the homeserver, as a fallback to login or to create an account. */ -class FtueAuthWebFragment @Inject constructor( - private val assetReader: AssetReader -) : AbstractFtueAuthFragment() { +@AndroidEntryPoint +class FtueAuthWebFragment : + AbstractFtueAuthFragment() { + + @Inject lateinit var assetReader: AssetReader override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding { return FragmentLoginWebBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt index af38062663..1b5c1adc74 100755 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.toReducedUrl @@ -46,11 +47,13 @@ data class FtueAuthTermsLegacyStyleFragmentArgument( /** * LoginTermsFragment displays the list of policies the user has to accept. */ -class FtueAuthLegacyStyleTermsFragment @Inject constructor( - private val policyController: PolicyController -) : AbstractFtueAuthFragment(), +@AndroidEntryPoint +class FtueAuthLegacyStyleTermsFragment : + AbstractFtueAuthFragment(), PolicyController.PolicyControllerListener { + @Inject lateinit var policyController: PolicyController + private val params: FtueAuthTermsLegacyStyleFragmentArgument by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTermsBinding { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt index 371c618d54..e9b32a1c7f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.doOnLayout import com.airbnb.mvrx.args +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -43,11 +44,13 @@ import kotlin.math.roundToInt /** * LoginTermsFragment displays the list of policies the user has to accept. */ -class FtueAuthTermsFragment @Inject constructor( - private val policyController: PolicyController -) : AbstractFtueAuthFragment(), +@AndroidEntryPoint +class FtueAuthTermsFragment : + AbstractFtueAuthFragment(), PolicyController.PolicyControllerListener { + @Inject lateinit var policyController: PolicyController + private val params: FtueAuthTermsLegacyStyleFragmentArgument by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueLoginTermsBinding { diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index 1688452167..3a3a0e66fd 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -26,6 +26,7 @@ import android.widget.Toast import com.airbnb.mvrx.args import com.airbnb.mvrx.asMavericksArgs import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseFragment @@ -49,11 +50,13 @@ data class PinArgs( val pinMode: PinMode ) : Parcelable -class PinFragment @Inject constructor( - private val pinCodeStore: PinCodeStore, - private val vectorPreferences: VectorPreferences, - private val defaultConfiguration: LockScreenConfiguration, -) : VectorBaseFragment() { +@AndroidEntryPoint +class PinFragment : + VectorBaseFragment() { + + @Inject lateinit var pinCodeStore: PinCodeStore + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var defaultConfiguration: LockScreenConfiguration private val fragmentArgs: PinArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt index a7a228a105..8e9fdf4fae 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt @@ -35,7 +35,8 @@ import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.views.LockScreenCodeView @AndroidEntryPoint -class LockScreenFragment : VectorBaseFragment() { +class LockScreenFragment : + VectorBaseFragment() { var lockScreenListener: LockScreenListener? = null var onLeftButtonClickedListener: View.OnClickListener? = null diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt index 0feef3b5e5..848b27009b 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -42,9 +43,12 @@ data class CreatePollArgs( val mode: PollMode ) : Parcelable -class CreatePollFragment @Inject constructor( - private val controller: CreatePollController -) : VectorBaseFragment(), CreatePollController.Callback { +@AndroidEntryPoint +class CreatePollFragment : + VectorBaseFragment(), + CreatePollController.Callback { + + @Inject lateinit var controller: CreatePollController private val viewModel: CreatePollViewModel by activityViewModel() private val args: CreatePollArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt index 9dc7fa6548..c2b81abf12 100644 --- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.args import com.google.zxing.BarcodeFormat import com.google.zxing.Result import com.google.zxing.ResultMetadataType +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult @@ -45,7 +46,6 @@ import im.vector.lib.multipicker.utils.ImageUtils import kotlinx.parcelize.Parcelize import me.dm7.barcodescanner.zxing.ZXingScannerView import org.matrix.android.sdk.api.extensions.tryOrNull -import javax.inject.Inject @Parcelize data class QrScannerArgs( @@ -53,7 +53,10 @@ data class QrScannerArgs( @StringRes val titleRes: Int ) : Parcelable -class QrCodeScannerFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler { +@AndroidEntryPoint +class QrCodeScannerFragment : + VectorBaseFragment(), + ZXingScannerView.ResultHandler { private val qrViewModel: QrCodeScannerViewModel by activityViewModel() private val scannerArgs: QrScannerArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index ad09593ebd..eefbf63a12 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -78,6 +78,7 @@ class BugReporter @Inject constructor( private val systemLocaleProvider: SystemLocaleProvider, private val matrix: Matrix, private val buildMeta: BuildMeta, + private val processInfo: ProcessInfo, private val sdkIntProvider: BuildVersionSdkIntProvider, ) { var inMultiWindowMode = false @@ -499,10 +500,26 @@ class BugReporter @Inject constructor( */ fun openBugReportScreen(activity: FragmentActivity, reportType: ReportType = ReportType.BUG_REPORT) { screenshot = takeScreenshot(activity) - matrix.debugService().logDbUsageInfo() + logDbInfo() + logProcessInfo() + logOtherInfo() activity.startActivity(BugReportActivity.intent(activity, reportType)) } + private fun logOtherInfo() { + Timber.i("SyncThread state: " + activeSessionHolder.getSafeActiveSession()?.syncService()?.getSyncState()) + } + + private fun logDbInfo() { + val dbInfo = matrix.debugService().getDbUsageInfo() + Timber.i(dbInfo) + } + + private fun logProcessInfo() { + val pInfo = processInfo.getInfo() + Timber.i(pInfo) + } + private fun rageShakeAppNameForReport(reportType: ReportType): String { // As per https://github.com/matrix-org/rageshake // app: Identifier for the application (eg 'riot-web'). diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt b/vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt new file mode 100644 index 0000000000..78e49a2e65 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/rageshake/ProcessInfo.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 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.rageshake + +import android.annotation.SuppressLint +import android.app.Application +import android.os.Build +import android.os.Process +import java.lang.reflect.Method +import javax.inject.Inject + +class ProcessInfo @Inject constructor() { + fun getInfo() = buildString { + append("===========================================\n") + append("* PROCESS INFO *\n") + append("===========================================\n") + val processId = Process.myPid() + append("ProcessId: $processId\n") + append("ProcessName: ${getProcessName()}\n") + append(getThreadInfo()) + append("===========================================\n") + } + + @SuppressLint("PrivateApi") + private fun getProcessName(): String? { + return if (Build.VERSION.SDK_INT >= 28) { + Application.getProcessName() + } else { + try { + val activityThread = Class.forName("android.app.ActivityThread") + val getProcessName: Method = activityThread.getDeclaredMethod("currentProcessName") + getProcessName.invoke(null) as? String + } catch (t: Throwable) { + null + } + } + } + + private fun getThreadInfo() = buildString { + append("Thread activeCount: ${Thread.activeCount()}\n") + Thread.getAllStackTraces().keys + .sortedBy { it.name } + .forEach { thread -> append(thread.getInfo()) } + } +} + +private fun Thread.getInfo() = buildString { + append("Thread '$name':") + append(" id: $id") + append(" priority: $priority") + append(" group name: ${threadGroup?.name ?: "null"}") + append(" state: $state") + append(" isAlive: $isAlive") + append(" isDaemon: $isDaemon") + append(" isInterrupted: $isInterrupted") + append("\n") +} diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt index f16db4a66d..a8023d2313 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorFileLogger.kt @@ -74,7 +74,7 @@ class VectorFileLogger @Inject constructor( } for (i in 0..15) { - val file = File(cacheDirectory, "elementLogs.$i.txt") + val file = File(cacheDirectory, "elementLogs.${i}.txt") tryOrNull { file.delete() } } @@ -121,7 +121,7 @@ class VectorFileLogger @Inject constructor( ?.flush() ?.let { 0 until logRotationCount } ?.mapNotNull { index -> - File(cacheDirectory, "$fileNamePrefix.$index.txt") + File(cacheDirectory, "$fileNamePrefix.${index}.txt") .takeIf { it.exists() } } } diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiChooserFragment.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiChooserFragment.kt index bcc18a995a..24065645ea 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiChooserFragment.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiChooserFragment.kt @@ -20,17 +20,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.EmojiChooserFragmentBinding import javax.inject.Inject -class EmojiChooserFragment @Inject constructor( - private val emojiRecyclerAdapter: EmojiRecyclerAdapter -) : VectorBaseFragment(), +@AndroidEntryPoint +class EmojiChooserFragment : + VectorBaseFragment(), EmojiRecyclerAdapter.InteractionListener, ReactionClickListener { + @Inject lateinit var emojiRecyclerAdapter: EmojiRecyclerAdapter + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): EmojiChooserFragmentBinding { return EmojiChooserFragmentBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt index 9292ad8fc6..3a448185ac 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt @@ -21,6 +21,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -29,9 +30,12 @@ import im.vector.app.core.utils.LiveEvent import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject -class EmojiSearchResultFragment @Inject constructor( - private val epoxyController: EmojiSearchResultController -) : VectorBaseFragment(), ReactionClickListener { +@AndroidEntryPoint +class EmojiSearchResultFragment : + VectorBaseFragment(), + ReactionClickListener { + + @Inject lateinit var epoxyController: EmojiSearchResultController override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index e16e1ec313..847c675c5e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -25,6 +25,7 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -51,14 +52,16 @@ import javax.inject.Inject * What can be improved: * - When filtering more (when entering new chars), we could filter on result we already have, during the new server request, to avoid empty screen effect. */ -class PublicRoomsFragment @Inject constructor( - private val publicRoomsController: PublicRoomsController, - private val permalinkHandler: PermalinkHandler, - private val session: Session -) : VectorBaseFragment(), +@AndroidEntryPoint +class PublicRoomsFragment : + VectorBaseFragment(), PublicRoomsController.Callback, VectorMenuProvider { + @Inject lateinit var publicRoomsController: PublicRoomsController + @Inject lateinit var permalinkHandler: PermalinkHandler + @Inject lateinit var session: Session + private val viewModel: RoomDirectoryViewModel by activityViewModel() private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 649ba8fd13..f4c3e515c5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -30,14 +30,14 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.databinding.FragmentCreateRoomBinding import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.navigation.Navigator @@ -61,28 +61,34 @@ data class CreateRoomArgs( val openAfterCreate: Boolean = true ) : Parcelable -class CreateRoomFragment @Inject constructor( - private val createRoomController: CreateRoomController, - private val createSpaceController: CreateSubSpaceController, - colorProvider: ColorProvider, - clock: Clock, -) : VectorBaseFragment(), +@AndroidEntryPoint +class CreateRoomFragment : + VectorBaseFragment(), CreateRoomController.Listener, GalleryOrCameraDialogHelper.Listener, OnBackPressed { + @Inject lateinit var createRoomController: CreateRoomController + @Inject lateinit var createSpaceController: CreateSubSpaceController + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel private val viewModel: CreateRoomViewModel by fragmentViewModel() private val args: CreateRoomArgs by args() private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreateRoomBinding { return FragmentCreateRoomBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index 66e09bb2d4..32be4e076f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -39,12 +40,14 @@ import im.vector.app.features.roomdirectory.RoomDirectoryViewModel import timber.log.Timber import javax.inject.Inject -class RoomDirectoryPickerFragment @Inject constructor( - private val roomDirectoryPickerController: RoomDirectoryPickerController -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomDirectoryPickerFragment : + VectorBaseFragment(), OnBackPressed, RoomDirectoryPickerController.Callback { + @Inject lateinit var roomDirectoryPickerController: RoomDirectoryPickerController + private val viewModel: RoomDirectoryViewModel by activityViewModel() private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel private val pickerViewModel: RoomDirectoryPickerViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 826dea0c3b..7c639dde99 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.ButtonStateView @@ -52,9 +53,11 @@ import javax.inject.Inject /** * Note: this Fragment is also used for world readable room for the moment. */ -class RoomPreviewNoPreviewFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment() { +@AndroidEntryPoint +class RoomPreviewNoPreviewFragment : + VectorBaseFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer private val roomPreviewViewModel: RoomPreviewViewModel by fragmentViewModel() private val roomPreviewData: RoomPreviewData by args() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 686c87a18c..2894cd4621 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.animations.AppBarStateChangeListener import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener @@ -69,15 +70,17 @@ data class RoomMemberProfileArgs( val roomId: String? = null ) : Parcelable -class RoomMemberProfileFragment @Inject constructor( - private val roomMemberProfileController: RoomMemberProfileController, - private val avatarRenderer: AvatarRenderer, - private val roomDetailPendingActionStore: RoomDetailPendingActionStore, - private val matrixItemColorProvider: MatrixItemColorProvider -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomMemberProfileFragment : + VectorBaseFragment(), RoomMemberProfileController.Callback, VectorMenuProvider { + @Inject lateinit var roomMemberProfileController: RoomMemberProfileController + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore + @Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider + private lateinit var headerViews: ViewStubRoomMemberProfileHeaderBinding private val fragmentArgs: RoomMemberProfileArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt index 48a8a819bc..7ca32bb49b 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -30,12 +31,14 @@ import im.vector.app.databinding.BottomSheetGenericListBinding import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject -class DeviceListFragment @Inject constructor( - val dimensionConverter: DimensionConverter, - val epoxyController: DeviceListEpoxyController -) : VectorBaseFragment(), +@AndroidEntryPoint +class DeviceListFragment : + VectorBaseFragment(), DeviceListEpoxyController.InteractionListener { + @Inject lateinit var dimensionConverter: DimensionConverter + @Inject lateinit var epoxyController: DeviceListEpoxyController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetGenericListBinding { return BottomSheetGenericListBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt index a733197372..d8abd91091 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -30,12 +31,14 @@ import im.vector.app.databinding.BottomSheetGenericListBinding import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject -class DeviceTrustInfoActionFragment @Inject constructor( - val dimensionConverter: DimensionConverter, - val epoxyController: DeviceTrustInfoEpoxyController -) : VectorBaseFragment(), +@AndroidEntryPoint +class DeviceTrustInfoActionFragment : + VectorBaseFragment(), DeviceTrustInfoEpoxyController.InteractionListener { + @Inject lateinit var dimensionConverter: DimensionConverter + @Inject lateinit var epoxyController: DeviceTrustInfoEpoxyController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetGenericListBinding { return BottomSheetGenericListBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 1830cc04e8..4135ab3d1c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -31,6 +31,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.animations.AppBarStateChangeListener import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener @@ -65,15 +66,16 @@ data class RoomProfileArgs( val roomId: String ) : Parcelable -class RoomProfileFragment @Inject constructor( - private val roomProfileController: RoomProfileController, - private val avatarRenderer: AvatarRenderer, - private val roomDetailPendingActionStore: RoomDetailPendingActionStore, -) : +@AndroidEntryPoint +class RoomProfileFragment : VectorBaseFragment(), RoomProfileController.Callback, VectorMenuProvider { + @Inject lateinit var roomProfileController: RoomProfileController + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore + private lateinit var headerViews: ViewStubRoomProfileHeaderBinding private val roomProfileArgs: RoomProfileArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 49f658861b..b9b0f604c7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -26,6 +26,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -46,13 +47,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class RoomAliasFragment @Inject constructor( - private val controller: RoomAliasController, - private val avatarRenderer: AvatarRenderer -) : +@AndroidEntryPoint +class RoomAliasFragment : VectorBaseFragment(), RoomAliasController.Callback { + @Inject lateinit var controller: RoomAliasController + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomAliasViewModel by fragmentViewModel() private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt index 8063212ba1..a8e34d0117 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt @@ -26,6 +26,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -38,12 +39,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class RoomBannedMemberListFragment @Inject constructor( - private val roomMemberListController: RoomBannedMemberListController, - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomBannedMemberListFragment : + VectorBaseFragment(), RoomBannedMemberListController.Callback { + @Inject lateinit var roomMemberListController: RoomBannedMemberListController + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomBannedMemberListViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index 52a2339f13..259fde1635 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -28,6 +28,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -43,12 +44,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class RoomMemberListFragment @Inject constructor( - private val roomMemberListController: RoomMemberListController, - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomMemberListFragment : + VectorBaseFragment(), RoomMemberListController.Callback { + @Inject lateinit var roomMemberListController: RoomMemberListController + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomMemberListViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt index 1bf392d9f8..7afd696332 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.core.view.isVisible import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -35,13 +36,15 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class RoomNotificationSettingsFragment @Inject constructor( - val viewModelFactory: RoomNotificationSettingsViewModel.Factory, - private val roomNotificationSettingsController: RoomNotificationSettingsController, - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomNotificationSettingsFragment : + VectorBaseFragment(), RoomNotificationSettingsController.Callback { + @Inject lateinit var viewModelFactory: RoomNotificationSettingsViewModel.Factory + @Inject lateinit var roomNotificationSettingsController: RoomNotificationSettingsController + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomNotificationSettingsViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomSettingGenericBinding { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt index dc42310c16..06b9343dbf 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -24,6 +24,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -38,13 +39,14 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class RoomPermissionsFragment @Inject constructor( - private val controller: RoomPermissionsController, - private val avatarRenderer: AvatarRenderer -) : +@AndroidEntryPoint +class RoomPermissionsFragment : VectorBaseFragment(), RoomPermissionsController.Callback { + @Inject lateinit var controller: RoomPermissionsController + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomPermissionsViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 45c8461fa7..ba50890db3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -29,16 +29,16 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding import im.vector.app.features.analytics.plan.MobileScreen @@ -56,25 +56,25 @@ import org.matrix.android.sdk.api.util.toMatrixItem import java.util.UUID import javax.inject.Inject -class RoomSettingsFragment @Inject constructor( - private val controller: RoomSettingsController, - colorProvider: ColorProvider, - private val avatarRenderer: AvatarRenderer, - clock: Clock, -) : +@AndroidEntryPoint +class RoomSettingsFragment : VectorBaseFragment(), RoomSettingsController.Callback, OnBackPressed, GalleryOrCameraDialogHelper.Listener, VectorMenuProvider { + @Inject lateinit var controller: RoomSettingsController + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + private val viewModel: RoomSettingsViewModel by fragmentViewModel() private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel private lateinit var roomHistoryVisibilitySharedActionViewModel: RoomHistoryVisibilitySharedActionViewModel private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel private val roomProfileArgs: RoomProfileArgs by args() - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomSettingGenericBinding { return FragmentRoomSettingGenericBinding.inflate(inflater, container, false) @@ -85,6 +85,7 @@ class RoomSettingsFragment @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) analyticsScreenName = MobileScreen.ScreenName.RoomSettings + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt index 4e42cce3ee..1c4f93f49a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleFragment.kt @@ -25,6 +25,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -37,11 +38,14 @@ import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRul import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import javax.inject.Inject -class RoomJoinRuleFragment @Inject constructor( - val controller: RoomJoinRuleAdvancedController, - val avatarRenderer: AvatarRenderer -) : VectorBaseFragment(), - OnBackPressed, RoomJoinRuleAdvancedController.InteractionListener { +@AndroidEntryPoint +class RoomJoinRuleFragment : + VectorBaseFragment(), + OnBackPressed, + RoomJoinRuleAdvancedController.InteractionListener { + + @Inject lateinit var controller: RoomJoinRuleAdvancedController + @Inject lateinit var avatarRenderer: AvatarRenderer private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt index b65e90aeed..462f3be1c3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.OnBackPressed @@ -36,13 +37,15 @@ import org.matrix.android.sdk.api.util.MatrixItem import reactivecircus.flowbinding.appcompat.queryTextChanges import javax.inject.Inject -class RoomJoinRuleChooseRestrictedFragment @Inject constructor( - val controller: ChooseRestrictedController, - val avatarRenderer: AvatarRenderer -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomJoinRuleChooseRestrictedFragment : + VectorBaseFragment(), ChooseRestrictedController.Listener, OnBackPressed { + @Inject lateinit var controller: ChooseRestrictedController + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel(RoomJoinRuleChooseRestrictedViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt index 3ecbcb5e00..d982ab3e32 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -27,6 +27,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment @@ -42,11 +43,13 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class RoomUploadsFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val notificationUtils: NotificationUtils, - private val clock: Clock, -) : VectorBaseFragment() { +@AndroidEntryPoint +class RoomUploadsFragment : + VectorBaseFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var notificationUtils: NotificationUtils + @Inject lateinit var clock: Clock private val roomProfileArgs: RoomProfileArgs by args() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 5bb81424cf..e7ee47020a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -26,6 +26,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -38,12 +39,14 @@ import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel import org.matrix.android.sdk.api.session.room.uploads.UploadEvent import javax.inject.Inject -class RoomUploadsFilesFragment @Inject constructor( - private val controller: UploadsFileController -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomUploadsFilesFragment : + VectorBaseFragment(), UploadsFileController.Listener, StateView.EventCallback { + @Inject lateinit var controller: UploadsFileController + private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericStateViewRecyclerBinding { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index c6dd3c63c1..f53f572e38 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.appbar.AppBarLayout +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.trackItemsVisibilityChange @@ -53,13 +54,15 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import javax.inject.Inject -class RoomUploadsMediaFragment @Inject constructor( - private val controller: UploadsMediaController, - private val dimensionConverter: DimensionConverter -) : VectorBaseFragment(), +@AndroidEntryPoint +class RoomUploadsMediaFragment : + VectorBaseFragment(), UploadsMediaController.Listener, StateView.EventCallback { + @Inject lateinit var controller: UploadsMediaController + @Inject lateinit var dimensionConverter: DimensionConverter + private val uploadsViewModel by parentFragmentViewModel(RoomUploadsViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericStateViewRecyclerBinding { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index e54bc4e624..9c08d446f4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -19,15 +19,17 @@ package im.vector.app.features.settings import android.os.Bundle import androidx.preference.Preference import androidx.preference.SeekBarPreference +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.rageshake.RageShake -import javax.inject.Inject -class VectorSettingsAdvancedSettingsFragment @Inject constructor() : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsAdvancedSettingsFragment : + VectorSettingsBaseFragment() { override var titleRes = R.string.settings_advanced_settings override val preferenceXmlRes = R.xml.vector_settings_advanced_settings diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 7906de3796..548a7be180 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -34,8 +34,10 @@ import androidx.preference.SwitchPreference import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.cache.DiskCache import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.toMvRxBundle @@ -44,8 +46,6 @@ import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.preference.UserAvatarPreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorSwitchPreference -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.getSizeOfFiles import im.vector.app.core.utils.toast @@ -74,17 +74,17 @@ import java.io.File import java.util.UUID import javax.inject.Inject -class VectorSettingsGeneralFragment @Inject constructor( - colorProvider: ColorProvider, - clock: Clock, -) : +@AndroidEntryPoint +class VectorSettingsGeneralFragment : VectorSettingsBaseFragment(), GalleryOrCameraDialogHelper.Listener { + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + override var titleRes = R.string.settings_general_title override val preferenceXmlRes = R.xml.vector_settings_general - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper private val mUserSettingsCategory by lazy { findPreference(VectorPreferences.SETTINGS_USER_SETTINGS_PREFERENCE_KEY)!! @@ -124,6 +124,7 @@ class VectorSettingsGeneralFragment @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) analyticsScreenName = MobileScreen.ScreenName.SettingsGeneral + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt index df7baa7397..8c7afaabc0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt @@ -18,6 +18,7 @@ package im.vector.app.features.settings import android.os.Bundle import androidx.preference.Preference +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.orEmpty import im.vector.app.core.preference.VectorPreference @@ -31,10 +32,12 @@ import im.vector.app.features.version.VersionProvider import org.matrix.android.sdk.api.Matrix import javax.inject.Inject -class VectorSettingsHelpAboutFragment @Inject constructor( - private val versionProvider: VersionProvider, - private val buildMeta: BuildMeta, -) : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsHelpAboutFragment : + VectorSettingsBaseFragment() { + + @Inject lateinit var versionProvider: VersionProvider + @Inject lateinit var buildMeta: BuildMeta override var titleRes = R.string.preference_root_help_about override val preferenceXmlRes = R.xml.vector_settings_help_about diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 70908d7560..eb7864a89d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -22,6 +22,7 @@ import android.widget.TextView import androidx.preference.Preference import androidx.preference.SwitchPreference import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.features.MainActivity @@ -31,11 +32,13 @@ import im.vector.app.features.home.room.threads.ThreadsManager import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import javax.inject.Inject -class VectorSettingsLabsFragment @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val lightweightSettingsStorage: LightweightSettingsStorage, - private val threadsManager: ThreadsManager -) : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsLabsFragment : + VectorSettingsBaseFragment() { + + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var lightweightSettingsStorage: LightweightSettingsStorage + @Inject lateinit var threadsManager: ThreadsManager override var titleRes = R.string.room_settings_labs_pref_title override val preferenceXmlRes = R.xml.vector_settings_labs diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index db402758f1..f3f013f2c7 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -19,6 +19,7 @@ package im.vector.app.features.settings import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.SwitchPreference +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.preference.VectorPreference @@ -36,13 +37,15 @@ import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber import javax.inject.Inject -class VectorSettingsPinFragment @Inject constructor( - private val pinCodeStore: PinCodeStore, - private val navigator: Navigator, - private val notificationDrawerManager: NotificationDrawerManager, - biometricHelperFactory: BiometricHelper.BiometricHelperFactory, - defaultLockScreenConfiguration: LockScreenConfiguration, -) : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsPinFragment : + VectorSettingsBaseFragment() { + + @Inject lateinit var pinCodeStore: PinCodeStore + @Inject lateinit var navigator: Navigator + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + @Inject lateinit var biometricHelperFactory: BiometricHelper.BiometricHelperFactory + @Inject lateinit var defaultLockScreenConfiguration: LockScreenConfiguration override var titleRes = R.string.settings_security_application_protection_screen_title override val preferenceXmlRes = R.xml.vector_settings_pin diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index ac7d29ab7a..0bd5316b8f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -22,6 +22,7 @@ import android.os.Bundle import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.dialogs.PhotoOrVideoDialog import im.vector.app.core.extensions.restart @@ -37,10 +38,12 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.presence.model.PresenceEnum import javax.inject.Inject -class VectorSettingsPreferencesFragment @Inject constructor( - private val vectorPreferences: VectorPreferences, - private val fontScalePreferences: FontScalePreferences, -) : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsPreferencesFragment : + VectorSettingsBaseFragment() { + + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var fontScalePreferences: FontScalePreferences override var titleRes = R.string.settings_preferences override val preferenceXmlRes = R.xml.vector_settings_preferences diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt index 51011e29a2..0b3dcfa2ac 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt @@ -17,12 +17,14 @@ package im.vector.app.features.settings import android.os.Bundle +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.preference.VectorPreference import im.vector.app.features.analytics.plan.MobileScreen -import javax.inject.Inject -class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsRootFragment : + VectorSettingsBaseFragment() { override var titleRes: Int = R.string.title_activity_settings override val preferenceXmlRes = R.xml.vector_settings_root diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index b6fbddd3ce..2b4d376f55 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -34,6 +34,7 @@ import androidx.preference.SwitchPreference import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.dialogs.ExportKeysDialog @@ -79,16 +80,18 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse import javax.inject.Inject -class VectorSettingsSecurityPrivacyFragment @Inject constructor( - private val activeSessionHolder: ActiveSessionHolder, - private val pinCodeStore: PinCodeStore, - private val keysExporter: KeysExporter, - private val keysImporter: KeysImporter, - private val rawService: RawService, - private val navigator: Navigator, - private val analyticsConfig: AnalyticsConfig, - private val vectorFeatures: VectorFeatures, -) : VectorSettingsBaseFragment() { +@AndroidEntryPoint +class VectorSettingsSecurityPrivacyFragment : + VectorSettingsBaseFragment() { + + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var pinCodeStore: PinCodeStore + @Inject lateinit var keysExporter: KeysExporter + @Inject lateinit var keysImporter: KeysImporter + @Inject lateinit var rawService: RawService + @Inject lateinit var navigator: Navigator + @Inject lateinit var analyticsConfig: AnalyticsConfig + @Inject lateinit var vectorFeatures: VectorFeatures override var titleRes = R.string.settings_security_and_privacy override val preferenceXmlRes = R.xml.vector_settings_security_privacy diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt index 7ccae3665d..c9a43c5e31 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt @@ -24,6 +24,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment @@ -35,9 +36,10 @@ import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.settings.VectorSettingsActivity import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException -import javax.inject.Inject -class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class DeactivateAccountFragment : + VectorBaseFragment() { private val viewModel: DeactivateAccountViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt index 8fcda3219e..c0cefc9bcf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -39,11 +40,13 @@ import javax.inject.Inject /** * This Fragment is only used when user activates developer mode from the settings. */ -class CrossSigningSettingsFragment @Inject constructor( - private val controller: CrossSigningSettingsController, -) : VectorBaseFragment(), +@AndroidEntryPoint +class CrossSigningSettingsFragment : + VectorBaseFragment(), CrossSigningSettingsController.InteractionListener { + @Inject lateinit var controller: CrossSigningSettingsController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index a132dc1f49..2d82e48aac 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -28,6 +28,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.dialogs.ManuallyVerifyDialog import im.vector.app.core.extensions.cleanup @@ -46,11 +47,13 @@ import javax.inject.Inject /** * Display the list of the user's device. */ -class VectorSettingsDevicesFragment @Inject constructor( - private val devicesController: DevicesController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VectorSettingsDevicesFragment : + VectorBaseFragment(), DevicesController.Callback { + @Inject lateinit var devicesController: DevicesController + // used to avoid requesting to enter the password for each deletion // Note: Sonar does not like to use password for member name. // private var mAccountPass: String = "" diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 28a7fd513b..41d93cb957 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -40,13 +40,13 @@ import im.vector.app.features.settings.devices.DevicesAction import im.vector.app.features.settings.devices.DevicesViewEvents import im.vector.app.features.settings.devices.DevicesViewModel import im.vector.app.features.settings.devices.DevicesViewState -import javax.inject.Inject /** * Display the list of the user's devices and sessions. */ @AndroidEntryPoint -class VectorSettingsDevicesFragment @Inject constructor() : VectorBaseFragment() { +class VectorSettingsDevicesFragment : + VectorBaseFragment() { private val viewModel: DevicesViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt index 740ef3996a..2f8dd84ddd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt @@ -24,6 +24,7 @@ import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -36,12 +37,14 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.util.MatrixJsonParser import javax.inject.Inject -class AccountDataFragment @Inject constructor( - private val epoxyController: AccountDataEpoxyController, - private val colorProvider: ColorProvider -) : VectorBaseFragment(), +@AndroidEntryPoint +class AccountDataFragment : + VectorBaseFragment(), AccountDataEpoxyController.InteractionListener { + @Inject lateinit var epoxyController: AccountDataEpoxyController + @Inject lateinit var colorProvider: ColorProvider + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index ec4ef26001..9dfcd1b15c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -22,21 +22,22 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentGenericRecyclerBinding import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import javax.inject.Inject -class GossipingEventsPaperTrailFragment @Inject constructor( - private val epoxyController: GossipingTrailPagedEpoxyController, - private val colorProvider: ColorProvider -) : VectorBaseFragment(), +@AndroidEntryPoint +class GossipingEventsPaperTrailFragment : + VectorBaseFragment(), GossipingTrailPagedEpoxyController.InteractionListener { + @Inject lateinit var epoxyController: GossipingTrailPagedEpoxyController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt index ac4bef9c94..b276acb1d6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -29,9 +30,11 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject -class IncomingKeyRequestListFragment @Inject constructor( - private val epoxyController: IncomingKeyRequestPagedController -) : VectorBaseFragment() { +@AndroidEntryPoint +class IncomingKeyRequestListFragment : + VectorBaseFragment() { + + @Inject lateinit var epoxyController: IncomingKeyRequestPagedController override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index 5684e941f1..f7e4a12793 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.safeOpenOutputStream @@ -41,11 +42,13 @@ import im.vector.app.databinding.FragmentDevtoolKeyrequestsBinding import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject -class KeyRequestsFragment @Inject constructor( - private val clock: Clock, -) : VectorBaseFragment(), +@AndroidEntryPoint +class KeyRequestsFragment : + VectorBaseFragment(), VectorMenuProvider { + @Inject lateinit var clock: Clock + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDevtoolKeyrequestsBinding { return FragmentDevtoolKeyrequestsBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt index ca1f36dbb2..1963045de3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -29,9 +30,11 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject -class OutgoingKeyRequestListFragment @Inject constructor( - private val epoxyController: OutgoingKeyRequestPagedController -) : VectorBaseFragment() { +@AndroidEntryPoint +class OutgoingKeyRequestListFragment : + VectorBaseFragment() { + + @Inject lateinit var epoxyController: OutgoingKeyRequestPagedController override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt index 78c06d5969..005797b5c1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.restart import im.vector.app.core.platform.VectorBaseFragment @@ -29,9 +30,12 @@ import im.vector.app.databinding.FragmentSettingsFontScalingBinding import im.vector.app.features.settings.FontScaleValue import javax.inject.Inject -class FontScaleSettingFragment @Inject constructor( - private val fontListController: FontScaleSettingController -) : VectorBaseFragment(), FontScaleSettingController.Callback { +@AndroidEntryPoint +class FontScaleSettingFragment : + VectorBaseFragment(), + FontScaleSettingController.Callback { + + @Inject lateinit var fontListController: FontScaleSettingController private val viewModel: FontScaleSettingViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt index 09fd848c99..eb342209bf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -33,11 +34,13 @@ import javax.inject.Inject /** * Display some information about the homeserver. */ -class HomeserverSettingsFragment @Inject constructor( - private val homeserverSettingsController: HomeserverSettingsController -) : VectorBaseFragment(), +@AndroidEntryPoint +class HomeserverSettingsFragment : + VectorBaseFragment(), HomeserverSettingsController.Callback { + @Inject lateinit var homeserverSettingsController: HomeserverSettingsController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 6ab3d365eb..4cf8982085 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -33,11 +34,13 @@ import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.features.analytics.plan.MobileScreen import javax.inject.Inject -class VectorSettingsIgnoredUsersFragment @Inject constructor( - private val ignoredUsersController: IgnoredUsersController -) : VectorBaseFragment(), +@AndroidEntryPoint +class VectorSettingsIgnoredUsersFragment : + VectorBaseFragment(), IgnoredUsersController.Callback { + @Inject lateinit var ignoredUsersController: IgnoredUsersController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt index aef1c69baa..6ed3cd9156 100644 --- a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt @@ -22,6 +22,7 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -35,12 +36,14 @@ import im.vector.app.features.discovery.ServerPolicy import im.vector.app.features.settings.VectorSettingsUrls import javax.inject.Inject -class LegalsFragment @Inject constructor( - private val controller: LegalsController, - private val flavorLegals: FlavorLegals, -) : VectorBaseFragment(), +@AndroidEntryPoint +class LegalsFragment : + VectorBaseFragment(), LegalsController.Listener { + @Inject lateinit var controller: LegalsController + @Inject lateinit var flavorLegals: FlavorLegals + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt index d46b66dd87..39d41f9cfc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -32,11 +33,13 @@ import im.vector.app.databinding.FragmentLocalePickerBinding import java.util.Locale import javax.inject.Inject -class LocalePickerFragment @Inject constructor( - private val controller: LocalePickerController -) : VectorBaseFragment(), +@AndroidEntryPoint +class LocalePickerFragment : + VectorBaseFragment(), LocalePickerController.Listener { + @Inject lateinit var controller: LocalePickerController + private val viewModel: LocalePickerViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocalePickerBinding { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt index 8eccc8c593..183d997ffb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -17,6 +17,7 @@ package im.vector.app.features.settings.notifications import androidx.lifecycle.lifecycleScope import androidx.preference.Preference +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.preference.PushRulePreference import im.vector.app.core.preference.VectorPreference @@ -25,9 +26,9 @@ import im.vector.app.features.settings.VectorSettingsBaseFragment import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind -import javax.inject.Inject -class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() : +@AndroidEntryPoint +class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() { override var titleRes: Int = R.string.settings_notification_advanced diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 62f5823b65..a09bb1e6a4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -30,6 +30,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.map import androidx.preference.Preference import androidx.preference.SwitchPreference +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.registerStartForActivityResult @@ -62,16 +63,18 @@ import org.matrix.android.sdk.api.session.pushrules.RuleKind import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml -class VectorSettingsNotificationPreferenceFragment @Inject constructor( - private val unifiedPushHelper: UnifiedPushHelper, - private val pushersManager: PushersManager, - private val activeSessionHolder: ActiveSessionHolder, - private val vectorPreferences: VectorPreferences, - private val guardServiceStarter: GuardServiceStarter, - private val vectorFeatures: VectorFeatures, -) : VectorSettingsBaseFragment(), +@AndroidEntryPoint +class VectorSettingsNotificationPreferenceFragment : + VectorSettingsBaseFragment(), BackgroundSyncModeChooserDialog.InteractionListener { + @Inject lateinit var unifiedPushHelper: UnifiedPushHelper + @Inject lateinit var pushersManager: PushersManager + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var guardServiceStarter: GuardServiceStarter + @Inject lateinit var vectorFeatures: VectorFeatures + override var titleRes: Int = R.string.settings_notifications override val preferenceXmlRes = R.xml.vector_settings_notifications diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt index e75824195e..137f1c8722 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt @@ -29,6 +29,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionManager +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.registerStartForActivityResult @@ -44,11 +45,13 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject -class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( - private val bugReporter: BugReporter, - private val testManagerFactory: NotificationTroubleshootTestManagerFactory, - private val actionIds: NotificationActionIds, -) : VectorBaseFragment() { +@AndroidEntryPoint +class VectorSettingsNotificationsTroubleshootFragment : + VectorBaseFragment() { + + @Inject lateinit var bugReporter: BugReporter + @Inject lateinit var testManagerFactory: NotificationTroubleshootTestManagerFactory + @Inject lateinit var actionIds: NotificationActionIds private var testManager: NotificationTroubleshootTestManager? = null // members diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt index da06f067c6..2bbb93e63c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt @@ -25,6 +25,7 @@ import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -35,11 +36,13 @@ import org.matrix.android.sdk.api.session.pushers.Pusher import javax.inject.Inject // Referenced in vector_settings_notifications.xml -class PushGatewaysFragment @Inject constructor( - private val epoxyController: PushGateWayController -) : VectorBaseFragment(), +@AndroidEntryPoint +class PushGatewaysFragment : + VectorBaseFragment(), VectorMenuProvider { + @Inject lateinit var epoxyController: PushGateWayController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt index 666f27272b..6e4c049202 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt @@ -22,6 +22,7 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -30,9 +31,11 @@ import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject // Referenced in vector_settings_notifications.xml -class PushRulesFragment @Inject constructor( - private val epoxyController: PushRulesController -) : VectorBaseFragment() { +@AndroidEntryPoint +class PushRulesFragment : + VectorBaseFragment() { + + @Inject lateinit var epoxyController: PushRulesController override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { return FragmentGenericRecyclerBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index 0d6e639168..ae3cbcc9ca 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -25,6 +25,7 @@ import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -41,13 +42,14 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.identity.ThreePid import javax.inject.Inject -class ThreePidsSettingsFragment @Inject constructor( - private val epoxyController: ThreePidsSettingsController -) : +@AndroidEntryPoint +class ThreePidsSettingsFragment : VectorBaseFragment(), OnBackPressed, ThreePidsSettingsController.InteractionListener { + @Inject lateinit var epoxyController: ThreePidsSettingsController + private val viewModel: ThreePidsSettingsViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt index 3e2ddc469c..a80d058197 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt @@ -29,6 +29,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -46,13 +47,14 @@ import javax.inject.Inject * Display the list of rooms. * The user can select multiple rooms to send the data to. */ -class IncomingShareFragment @Inject constructor( - private val incomingShareController: IncomingShareController, - private val shareIntentHandler: ShareIntentHandler, -) : +@AndroidEntryPoint +class IncomingShareFragment : VectorBaseFragment(), IncomingShareController.Callback { + @Inject lateinit var incomingShareController: IncomingShareController + @Inject lateinit var shareIntentHandler: ShareIntentHandler + private val viewModel: IncomingShareViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentIncomingShareBinding { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 5b369d4b49..47670b486a 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -39,11 +40,13 @@ import javax.inject.Inject * - the user is asked to enter a password to sign in again to a homeserver. * - or to cleanup all the data */ -class SoftLogoutFragment @Inject constructor( - private val softLogoutController: SoftLogoutController -) : AbstractLoginFragment(), +@AndroidEntryPoint +class SoftLogoutFragment : + AbstractLoginFragment(), SoftLogoutController.Listener { + @Inject lateinit var softLogoutController: SoftLogoutController + private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGenericRecyclerBinding { @@ -63,7 +66,7 @@ class SoftLogoutFragment @Inject constructor( LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - mode.ssoIdentityProviders + mode.ssoState.providersOrNull() ) ) } @@ -72,7 +75,7 @@ class SoftLogoutFragment @Inject constructor( LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, - mode.ssoIdentityProviders + mode.ssoState.providersOrNull() ) ) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 9d0580638b..f3e2f82edc 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -33,6 +33,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.hasUnsavedKeys import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.toSsoState import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.LoginType @@ -115,8 +116,8 @@ class SoftLogoutViewModel @AssistedInject constructor( val loginMode = when { // SSO login is taken first data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders.toSsoState()) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders.toSsoState()) data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password else -> LoginMode.Unsupported } diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceAddItem.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceAddItem.kt new file mode 100644 index 0000000000..60816df9c0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceAddItem.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.spaces + +import android.content.res.ColorStateList +import android.widget.ImageView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +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.features.themes.ThemeUtils + +@EpoxyModelClass +abstract class NewSpaceAddItem : VectorEpoxyModel(R.layout.item_new_space_add) { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.onClick(listener) + + holder.plus.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary)) + } + + class Holder : VectorEpoxyHolder() { + val plus by bind(R.id.plus) + } +} diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceListHeaderItem.kt similarity index 57% rename from vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt rename to vector/src/main/java/im/vector/app/features/spaces/NewSpaceListHeaderItem.kt index 0ae60e910c..8fc53f07d4 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceListHeaderItem.kt @@ -14,16 +14,14 @@ * limitations under the License. */ -package im.vector.app.features.login2.created +package im.vector.app.features.spaces -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.Uninitialized -import org.matrix.android.sdk.api.util.MatrixItem +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel -data class AccountCreatedViewState( - val userId: String = "", - val isLoading: Boolean = false, - val currentUser: Async = Uninitialized, - val hasBeenModified: Boolean = false -) : MavericksState +@EpoxyModelClass +abstract class NewSpaceListHeaderItem : VectorEpoxyModel(R.layout.item_new_space_list_header) { + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt new file mode 100644 index 0000000000..7c4435bf59 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.spaces + +import com.airbnb.epoxy.EpoxyController +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.grouplist.newHomeSpaceSummaryItem +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +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.SpaceChildInfo +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class NewSpaceSummaryController @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider, +) : EpoxyController() { + + var callback: Callback? = null + private var viewState: SpaceListViewState? = null + + private val subSpaceComparator: Comparator = compareBy { it.order }.thenBy { it.childRoomId } + + fun update(viewState: SpaceListViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val nonNullViewState = viewState ?: return + buildGroupModels( + nonNullViewState.spaces, + nonNullViewState.selectedSpace, + nonNullViewState.rootSpacesOrdered, + nonNullViewState.homeAggregateCount + ) + } + + private fun buildGroupModels( + spaceSummaries: List?, + selectedSpace: RoomSummary?, + rootSpaces: List?, + homeCount: RoomAggregateNotificationCount + ) { + val host = this + newSpaceListHeaderItem { + id("space_list_header") + } + + if (selectedSpace != null) { + addSubSpaces(selectedSpace, spaceSummaries, homeCount) + } else { + addHomeItem(true, homeCount) + addRootSpaces(rootSpaces) + } + + newSpaceAddItem { + id("create") + listener { host.callback?.onAddSpaceSelected() } + } + } + + private fun addHomeItem(selected: Boolean, homeCount: RoomAggregateNotificationCount) { + val host = this + newHomeSpaceSummaryItem { + id("space_home") + text(host.stringProvider.getString(R.string.all_chats)) + selected(selected) + countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight)) + listener { host.callback?.onSpaceSelected(null) } + } + } + + private fun addSubSpaces( + selectedSpace: RoomSummary, + spaceSummaries: List?, + homeCount: RoomAggregateNotificationCount, + ) { + val host = this + val spaceChildren = selectedSpace.spaceChildren + var subSpacesAdded = false + + spaceChildren?.sortedWith(subSpaceComparator)?.forEach { spaceChild -> + val subSpaceSummary = spaceSummaries?.firstOrNull { it.roomId == spaceChild.childRoomId } ?: return@forEach + + if (subSpaceSummary.membership != Membership.INVITE) { + subSpacesAdded = true + newSpaceSummaryItem { + avatarRenderer(host.avatarRenderer) + id(subSpaceSummary.roomId) + matrixItem(subSpaceSummary.toMatrixItem()) + selected(false) + listener { host.callback?.onSpaceSelected(subSpaceSummary) } + countState( + UnreadCounterBadgeView.State( + subSpaceSummary.notificationCount, + subSpaceSummary.highlightCount > 0 + ) + ) + } + } + } + + if (!subSpacesAdded) { + addHomeItem(false, homeCount) + } + } + + private fun addRootSpaces(rootSpaces: List?) { + val host = this + rootSpaces + ?.filter { it.membership != Membership.INVITE } + ?.forEach { roomSummary -> + newSpaceSummaryItem { + avatarRenderer(host.avatarRenderer) + id(roomSummary.roomId) + matrixItem(roomSummary.toMatrixItem()) + listener { host.callback?.onSpaceSelected(roomSummary) } + countState(UnreadCounterBadgeView.State(roomSummary.notificationCount, roomSummary.highlightCount > 0)) + } + } + } + + interface Callback { + fun onSpaceSelected(spaceSummary: RoomSummary?) + fun onSpaceInviteSelected(spaceSummary: RoomSummary) + fun onSpaceSettings(spaceSummary: RoomSummary) + fun onAddSpaceSelected() + fun sendFeedBack() + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt new file mode 100644 index 0000000000..778b9c933e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryItem.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.spaces + +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.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.platform.CheckableConstraintLayout +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class NewSpaceSummaryItem : VectorEpoxyModel(R.layout.item_new_space) { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var selected: Boolean = false + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + @EpoxyAttribute var countState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + + override fun bind(holder: Holder) { + super.bind(holder) + holder.rootView.onClick(listener) + holder.name.text = matrixItem.displayName + holder.rootView.isChecked = selected + + avatarRenderer.render(matrixItem, holder.avatar) + holder.unreadCounter.render(countState) + } + + override fun unbind(holder: Holder) { + avatarRenderer.clear(holder.avatar) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val rootView by bind(R.id.root) + val avatar by bind(R.id.avatar) + val name by bind(R.id.name) + val unreadCounter by bind(R.id.unread_counter) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index 9fa4a53efc..04412185f6 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -120,7 +120,7 @@ class SpaceCreationActivity : SimpleFragmentActivity() { } private fun navigateToFragment(fragmentClass: Class) { - val frag = supportFragmentManager.findFragmentByTag(fragmentClass.name) ?: createFragment(fragmentClass) + val frag = supportFragmentManager.findFragmentByTag(fragmentClass.name) ?: fragmentClass.newInstance() supportFragmentManager.beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) .replace( diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt new file mode 100644 index 0000000000..910f8c5379 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 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.spaces + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import im.vector.app.R +import im.vector.app.core.extensions.replaceChildFragment +import im.vector.app.databinding.FragmentSpacesBottomSheetBinding + +class SpaceListBottomSheet : BottomSheetDialogFragment() { + + private lateinit var binding: FragmentSpacesBottomSheetBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentSpacesBottomSheetBinding.inflate(inflater, container, false) + if (savedInstanceState == null) { + replaceChildFragment(R.id.space_list, SpaceListFragment::class.java) + } + return binding.root + } + + companion object { + const val TAG = "SpacesBottomSheet" + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index b358a8c1a6..ca22ac30a1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -27,25 +27,39 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceListBinding +import im.vector.app.features.VectorFeatures import im.vector.app.features.home.HomeActivitySharedAction import im.vector.app.features.home.HomeSharedActionViewModel +import im.vector.app.features.home.room.list.actions.RoomListSharedAction +import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject /** * This Fragment is displayed in the navigation drawer [im.vector.app.features.home.HomeDrawerFragment] and * is displaying the space hierarchy, with some actions on Spaces. + * + * In the New App Layout this fragment will instead be displayed in a Bottom Sheet [SpaceListBottomSheet] + * and will only display spaces that are direct children of the currently selected space (or root spaces if none) */ -class SpaceListFragment @Inject constructor( - private val spaceController: SpaceSummaryController -) : VectorBaseFragment(), SpaceSummaryController.Callback { +@AndroidEntryPoint +class SpaceListFragment : + VectorBaseFragment(), + SpaceSummaryController.Callback, + NewSpaceSummaryController.Callback { - private lateinit var sharedActionViewModel: HomeSharedActionViewModel + @Inject lateinit var spaceController: SpaceSummaryController + @Inject lateinit var newSpaceController: NewSpaceSummaryController + @Inject lateinit var vectorFeatures: VectorFeatures + + private lateinit var homeActivitySharedActionViewModel: HomeSharedActionViewModel + private lateinit var roomListSharedActionViewModel: RoomListSharedActionViewModel private val viewModel: SpaceListViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSpaceListBinding { @@ -54,10 +68,69 @@ class SpaceListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) - spaceController.callback = this + homeActivitySharedActionViewModel = activityViewModelProvider[HomeSharedActionViewModel::class.java] + roomListSharedActionViewModel = activityViewModelProvider[RoomListSharedActionViewModel::class.java] views.stateView.contentView = views.groupListView - views.groupListView.configureWith(spaceController) + setupSpaceController() + observeViewEvents() + } + + private fun setupSpaceController() { + if (vectorFeatures.isNewAppLayoutEnabled()) { + enableDragAndDropForNewSpaceController() + newSpaceController.callback = this + views.groupListView.configureWith(newSpaceController) + } else { + enableDragAndDropForSpaceController() + spaceController.callback = this + views.groupListView.configureWith(spaceController) + } + } + + private fun enableDragAndDropForNewSpaceController() { + EpoxyTouchHelper.initDragging(newSpaceController) + .withRecyclerView(views.groupListView) + .forVerticalList() + .withTarget(NewSpaceSummaryItem::class.java) + .andCallbacks(object : EpoxyTouchHelper.DragCallbacks() { + var toPositionM: Int? = null + var fromPositionM: Int? = null + var initialElevation: Float? = null + + override fun onDragStarted(model: NewSpaceSummaryItem?, itemView: View?, adapterPosition: Int) { + toPositionM = null + fromPositionM = null + model?.matrixItem?.id?.let { + viewModel.handle(SpaceListAction.OnStartDragging(it, false)) + } + itemView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + initialElevation = itemView?.elevation + itemView?.elevation = 6f + } + + override fun onDragReleased(model: NewSpaceSummaryItem?, itemView: View?) { + if (toPositionM == null || fromPositionM == null) return + val movedSpaceId = model?.matrixItem?.id ?: return + viewModel.handle(SpaceListAction.MoveSpace(movedSpaceId, toPositionM!! - fromPositionM!!)) + } + + override fun clearView(model: NewSpaceSummaryItem?, itemView: View?) { + itemView?.elevation = initialElevation ?: 0f + } + + override fun onModelMoved(fromPosition: Int, toPosition: Int, modelBeingMoved: NewSpaceSummaryItem?, itemView: View?) { + if (fromPositionM == null) { + fromPositionM = fromPosition + } + if (toPositionM != toPosition) { + toPositionM = toPosition + itemView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + } + }) + } + + private fun enableDragAndDropForSpaceController() { EpoxyTouchHelper.initDragging(spaceController) .withRecyclerView(views.groupListView) .forVerticalList() @@ -100,14 +173,14 @@ class SpaceListFragment @Inject constructor( return model?.canDrag == true } }) + } - viewModel.observeViewEvents { - when (it) { - is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id)) - is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace) - is SpaceListViewEvents.OpenSpaceInvite -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id)) - SpaceListViewEvents.CloseDrawer -> sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) - } + private fun observeViewEvents() = viewModel.observeViewEvents { + when (it) { + is SpaceListViewEvents.OpenSpaceSummary -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id)) + is SpaceListViewEvents.AddSpace -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.AddSpace) + is SpaceListViewEvents.OpenSpaceInvite -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id)) + SpaceListViewEvents.CloseDrawer -> homeActivitySharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) } } @@ -120,15 +193,24 @@ class SpaceListFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> when (state.asyncSpaces) { Uninitialized, - is Loading -> views.stateView.state = StateView.State.Loading + is Loading -> { + views.stateView.state = StateView.State.Loading + return@withState + } is Success -> views.stateView.state = StateView.State.Content else -> Unit } - spaceController.update(state) + + if (vectorFeatures.isNewAppLayoutEnabled()) { + newSpaceController.update(state) + } else { + spaceController.update(state) + } } override fun onSpaceSelected(spaceSummary: RoomSummary?) { viewModel.handle(SpaceListAction.SelectSpace(spaceSummary)) + roomListSharedActionViewModel.post(RoomListSharedAction.CloseBottomSheet) } override fun onSpaceInviteSelected(spaceSummary: RoomSummary) { @@ -136,7 +218,7 @@ class SpaceListFragment @Inject constructor( } override fun onSpaceSettings(spaceSummary: RoomSummary) { - sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId)) + homeActivitySharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId)) } override fun onToggleExpand(spaceSummary: RoomSummary) { @@ -148,6 +230,6 @@ class SpaceListFragment @Inject constructor( } override fun sendFeedBack() { - sharedActionViewModel.post(HomeActivitySharedAction.SendSpaceFeedBack) + homeActivitySharedActionViewModel.post(HomeActivitySharedAction.SendSpaceFeedBack) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index eea11f9b1b..9048026771 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -194,7 +194,7 @@ class SpaceListViewModel @AssistedInject constructor( val moved = removeAt(index) add(index + action.delta, moved) }, - spaceOrderLocalEchos = updatedLocalEchos + spaceOrderLocalEchos = updatedLocalEchos, ) } session.coroutineScope.launch { @@ -257,29 +257,29 @@ class SpaceListViewModel @AssistedInject constructor( } combine( - session.flow() - .liveSpaceSummaries(params), + session.flow().liveSpaceSummaries(params), session.accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) .asFlow() ) { spaces, _ -> spaces + }.execute { asyncSpaces -> + val spaces = asyncSpaces.invoke().orEmpty() + val rootSpaces = asyncSpaces.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() } + val orders = rootSpaces.associate { + it.roomId to session.getRoom(it.roomId) + ?.roomAccountDataService() + ?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER) + ?.content.toModel() + ?.safeOrder() + } + copy( + asyncSpaces = asyncSpaces, + spaces = spaces, + rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)), + spaceOrderInfo = orders + ) } - .execute { async -> - val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() } - val orders = rootSpaces.associate { - it.roomId to session.getRoom(it.roomId) - ?.roomAccountDataService() - ?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER) - ?.content.toModel() - ?.safeOrder() - } - copy( - asyncSpaces = async, - rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)), - spaceOrderInfo = orders - ) - } // clear local echos on update session.accountDataService() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt index 794f1dbd69..f75c336b5d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.util.MatrixItem data class SpaceListViewState( val myMxItem: Async = Uninitialized, val asyncSpaces: Async> = Uninitialized, + val spaces: List = emptyList(), val selectedSpace: RoomSummary? = null, val rootSpacesOrdered: List? = null, val spaceOrderInfo: Map? = null, diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt index 6d3003dfcf..02ffb7ff6a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.epoxy.onClick import im.vector.app.core.platform.OnBackPressed @@ -29,9 +30,12 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentSpaceCreateChoosePrivateModelBinding import javax.inject.Inject -class ChoosePrivateSpaceTypeFragment @Inject constructor( - private val stringProvider: StringProvider -) : VectorBaseFragment(), OnBackPressed { +@AndroidEntryPoint +class ChoosePrivateSpaceTypeFragment : + VectorBaseFragment(), + OnBackPressed { + + @Inject lateinit var stringProvider: StringProvider private val sharedViewModel: CreateSpaceViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt index b4c5e63687..4c44bfc7a8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt @@ -21,12 +21,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.epoxy.onClick import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceCreateChooseTypeBinding -import javax.inject.Inject -class ChooseSpaceTypeFragment @Inject constructor() : VectorBaseFragment() { +@AndroidEntryPoint +class ChooseSpaceTypeFragment : + VectorBaseFragment() { private val sharedViewModel: CreateSpaceViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt index 6260047d16..a46991caa3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -31,12 +32,14 @@ import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding import im.vector.app.features.settings.VectorSettingsActivity import javax.inject.Inject -class CreateSpaceAdd3pidInvitesFragment @Inject constructor( - private val epoxyController: SpaceAdd3pidEpoxyController -) : VectorBaseFragment(), +@AndroidEntryPoint +class CreateSpaceAdd3pidInvitesFragment : + VectorBaseFragment(), SpaceAdd3pidEpoxyController.Listener, OnBackPressed { + @Inject lateinit var epoxyController: SpaceAdd3pidEpoxyController + private val sharedViewModel: CreateSpaceViewModel by activityViewModel() override fun onBackPressed(toolbarButton: Boolean): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt index 4ed7e91417..68e9522282 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.hideKeyboard @@ -29,12 +30,14 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding import javax.inject.Inject -class CreateSpaceDefaultRoomsFragment @Inject constructor( - private val epoxyController: SpaceDefaultRoomEpoxyController -) : VectorBaseFragment(), +@AndroidEntryPoint +class CreateSpaceDefaultRoomsFragment : + VectorBaseFragment(), SpaceDefaultRoomEpoxyController.Listener, OnBackPressed { + @Inject lateinit var epoxyController: SpaceDefaultRoomEpoxyController + private val sharedViewModel: CreateSpaceViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt index 847ee5f865..2baac269e0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt @@ -22,29 +22,37 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding import javax.inject.Inject -class CreateSpaceDetailsFragment @Inject constructor( - private val epoxyController: SpaceDetailEpoxyController, - colorProvider: ColorProvider, - clock: Clock, -) : VectorBaseFragment(), SpaceDetailEpoxyController.Listener, - GalleryOrCameraDialogHelper.Listener, OnBackPressed { +@AndroidEntryPoint +class CreateSpaceDetailsFragment : + VectorBaseFragment(), + SpaceDetailEpoxyController.Listener, + GalleryOrCameraDialogHelper.Listener, + OnBackPressed { + + @Inject lateinit var epoxyController: SpaceDetailEpoxyController + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory private val sharedViewModel: CreateSpaceViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false) - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 3818f4278a..b6c49e5d41 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -32,6 +32,7 @@ import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -61,16 +62,18 @@ data class SpaceDirectoryArgs( val spaceId: String ) : Parcelable -class SpaceDirectoryFragment @Inject constructor( - private val epoxyController: SpaceDirectoryController, - private val permalinkHandler: PermalinkHandler, - private val colorProvider: ColorProvider -) : VectorBaseFragment(), +@AndroidEntryPoint +class SpaceDirectoryFragment : + VectorBaseFragment(), SpaceDirectoryController.InteractionListener, TimelineEventController.UrlClickCallback, OnBackPressed, VectorMenuProvider { + @Inject lateinit var epoxyController: SpaceDirectoryController + @Inject lateinit var permalinkHandler: PermalinkHandler + @Inject lateinit var colorProvider: ColorProvider + override fun getMenuRes() = R.menu.menu_space_directory override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt index de1273b8d5..ea06a12f08 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt @@ -28,6 +28,7 @@ import androidx.core.view.isVisible import com.airbnb.mvrx.Success import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -38,12 +39,14 @@ import im.vector.app.databinding.FragmentSpaceLeaveAdvancedBinding import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject -class SpaceLeaveAdvancedFragment @Inject constructor( - val controller: SelectChildrenController -) : VectorBaseFragment(), +@AndroidEntryPoint +class SpaceLeaveAdvancedFragment : + VectorBaseFragment(), SelectChildrenController.Listener, VectorMenuProvider { + @Inject lateinit var controller: SelectChildrenController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceLeaveAdvancedBinding.inflate(layoutInflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index 848c17deb6..d0115d561a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.OnBackPressed @@ -47,15 +48,17 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import reactivecircus.flowbinding.appcompat.queryTextChanges import javax.inject.Inject -class SpaceAddRoomFragment @Inject constructor( - private val spaceEpoxyController: AddRoomListController, - private val roomEpoxyController: AddRoomListController, - private val dmEpoxyController: AddRoomListController, -) : VectorBaseFragment(), +@AndroidEntryPoint +class SpaceAddRoomFragment : + VectorBaseFragment(), OnBackPressed, AddRoomListController.Listener, VectorMenuProvider { + @Inject lateinit var spaceEpoxyController: AddRoomListController + @Inject lateinit var roomEpoxyController: AddRoomListController + @Inject lateinit var dmEpoxyController: AddRoomListController + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceAddRoomsBinding.inflate(layoutInflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt index d0e78bff5b..4580119070 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -46,13 +47,15 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import reactivecircus.flowbinding.appcompat.queryTextChanges import javax.inject.Inject -class SpaceManageRoomsFragment @Inject constructor( - private val epoxyController: SpaceManageRoomsController -) : VectorBaseFragment(), +@AndroidEntryPoint +class SpaceManageRoomsFragment : + VectorBaseFragment(), OnBackPressed, SpaceManageRoomsController.Listener, Callback { + @Inject lateinit var epoxyController: SpaceManageRoomsController + private val viewModel by fragmentViewModel(SpaceManageRoomsViewModel::class) private val sharedViewModel: SpaceManageSharedViewModel by activityViewModel() private val epoxyVisibilityTracker = EpoxyVisibilityTracker() diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index eb1de4fe60..ac3e5e4d05 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -30,16 +30,16 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding import im.vector.app.features.home.AvatarRenderer @@ -59,23 +59,24 @@ import org.matrix.android.sdk.api.util.toMatrixItem import java.util.UUID import javax.inject.Inject -class SpaceSettingsFragment @Inject constructor( - private val epoxyController: SpaceSettingsController, - colorProvider: ColorProvider, - clock: Clock, - private val avatarRenderer: AvatarRenderer, -) : VectorBaseFragment(), +@AndroidEntryPoint +class SpaceSettingsFragment : + VectorBaseFragment(), SpaceSettingsController.Callback, GalleryOrCameraDialogHelper.Listener, OnBackPressed, VectorMenuProvider { + @Inject lateinit var epoxyController: SpaceSettingsController + @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory + @Inject lateinit var avatarRenderer: AvatarRenderer + private val viewModel: RoomSettingsViewModel by fragmentViewModel() private val sharedViewModel: SpaceManageSharedViewModel by activityViewModel() private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel - private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider, clock) + private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentRoomSettingGenericBinding.inflate(inflater) @@ -83,6 +84,11 @@ class SpaceSettingsFragment @Inject constructor( override fun getMenuRes() = R.menu.vector_room_settings + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + galleryOrCameraDialogHelper = galleryOrCameraDialogHelperFactory.create(this) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar(views.roomSettingsToolbar) diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt index 1181ccfa52..239b0ccb53 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt @@ -28,6 +28,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -43,10 +44,13 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import reactivecircus.flowbinding.appcompat.queryTextChanges import javax.inject.Inject -class SpacePeopleFragment @Inject constructor( - private val epoxyController: SpacePeopleListController -) : VectorBaseFragment(), - OnBackPressed, SpacePeopleListController.InteractionListener { +@AndroidEntryPoint +class SpacePeopleFragment : + VectorBaseFragment(), + OnBackPressed, + SpacePeopleListController.InteractionListener { + + @Inject lateinit var epoxyController: SpacePeopleListController private val viewModel by fragmentViewModel(SpacePeopleViewModel::class) private val membersViewModel by fragmentViewModel(RoomMemberListViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index 5722a0ff2c..df15d3001c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -29,6 +29,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -50,10 +51,12 @@ data class SpacePreviewArgs( val idOrAlias: String ) : Parcelable -class SpacePreviewFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer, - private val epoxyController: SpacePreviewController -) : VectorBaseFragment() { +@AndroidEntryPoint +class SpacePreviewFragment : + VectorBaseFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var epoxyController: SpacePreviewController private val viewModel by fragmentViewModel(SpacePreviewViewModel::class) lateinit var sharedActionViewModel: SpacePreviewSharedActionViewModel diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt index f7012b93c4..2e8e2c3c14 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt @@ -25,6 +25,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.cleanup @@ -35,11 +36,13 @@ import im.vector.app.databinding.FragmentReviewTermsBinding import org.matrix.android.sdk.api.session.terms.TermsService import javax.inject.Inject -class ReviewTermsFragment @Inject constructor( - private val termsController: TermsController -) : VectorBaseFragment(), +@AndroidEntryPoint +class ReviewTermsFragment : + VectorBaseFragment(), TermsController.Listener { + @Inject lateinit var termsController: TermsController + private val reviewTermsViewModel: ReviewTermsViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentReviewTermsBinding { diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt index 259a220874..4232b94ead 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO @@ -32,9 +33,11 @@ import im.vector.app.databinding.FragmentUserCodeShowBinding import im.vector.app.features.home.AvatarRenderer import javax.inject.Inject -class ShowUserCodeFragment @Inject constructor( - private val avatarRenderer: AvatarRenderer -) : VectorBaseFragment() { +@AndroidEntryPoint +class ShowUserCodeFragment : + VectorBaseFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentUserCodeShowBinding { return FragmentUserCodeShowBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index 3fe95cfb7c..fbb6a8ee14 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -30,6 +30,7 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.chip.Chip +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -51,13 +52,15 @@ import org.matrix.android.sdk.api.session.user.model.User import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject -class UserListFragment @Inject constructor( - private val userListController: UserListController, - private val dimensionConverter: DimensionConverter, -) : VectorBaseFragment(), +@AndroidEntryPoint +class UserListFragment : + VectorBaseFragment(), UserListController.Callback, VectorMenuProvider { + @Inject lateinit var userListController: UserListController + @Inject lateinit var dimensionConverter: DimensionConverter + private val args: UserListFragmentArgs by args() private val viewModel: UserListViewModel by activityViewModel() private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel() diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index ed1bace70c..4d94493d17 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -38,6 +38,7 @@ import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.OnBackPressed @@ -66,16 +67,17 @@ data class WidgetArgs( val urlParams: Map = emptyMap() ) : Parcelable -class WidgetFragment @Inject constructor( - private val permissionUtils: WebviewPermissionUtils, - private val checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase, - private val vectorPreferences: VectorPreferences, -) : +@AndroidEntryPoint +class WidgetFragment : VectorBaseFragment(), WebEventListener, OnBackPressed, VectorMenuProvider { + @Inject lateinit var permissionUtils: WebviewPermissionUtils + @Inject lateinit var checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase + @Inject lateinit var vectorPreferences: VectorPreferences + private val fragmentArgs: WidgetArgs by args() private val viewModel: WidgetViewModel by activityViewModel() diff --git a/vector/src/main/res/drawable/ic_chat.xml b/vector/src/main/res/drawable/ic_chat.xml new file mode 100644 index 0000000000..fb10eae9c9 --- /dev/null +++ b/vector/src/main/res/drawable/ic_chat.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_plus.xml b/vector/src/main/res/drawable/ic_plus.xml new file mode 100644 index 0000000000..25a611472b --- /dev/null +++ b/vector/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,11 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_room_add.xml b/vector/src/main/res/drawable/ic_room_add.xml new file mode 100644 index 0000000000..8404ff2181 --- /dev/null +++ b/vector/src/main/res/drawable/ic_room_add.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_room_explore.xml b/vector/src/main/res/drawable/ic_room_explore.xml new file mode 100644 index 0000000000..9811a09054 --- /dev/null +++ b/vector/src/main/res/drawable/ic_room_explore.xml @@ -0,0 +1,14 @@ + + + + diff --git a/vector/src/main/res/drawable/new_space_home_background.xml b/vector/src/main/res/drawable/new_space_home_background.xml new file mode 100644 index 0000000000..47fdeb0226 --- /dev/null +++ b/vector/src/main/res/drawable/new_space_home_background.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/vector/src/main/res/drawable/space_home_background.xml b/vector/src/main/res/drawable/space_home_background.xml index 2efd6d407c..c7fc727267 100644 --- a/vector/src/main/res/drawable/space_home_background.xml +++ b/vector/src/main/res/drawable/space_home_background.xml @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml new file mode 100644 index 0000000000..1766695354 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_ftue_login_captcha.xml b/vector/src/main/res/layout/fragment_ftue_login_captcha.xml index 2f6970c785..fc93d0f990 100644 --- a/vector/src/main/res/layout/fragment_ftue_login_captcha.xml +++ b/vector/src/main/res/layout/fragment_ftue_login_captcha.xml @@ -64,15 +64,27 @@ android:id="@+id/titleContentSpacing" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/loginCaptchaWevView" + app:layout_constraintBottom_toTopOf="@id/loginWebViewBarrier" app:layout_constraintHeight_percent="0.03" app:layout_constraintTop_toBottomOf="@id/captchaHeaderTitle" /> - + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_login_account_created.xml b/vector/src/main/res/layout/fragment_login_account_created.xml deleted file mode 100644 index 2f02da71d7..0000000000 --- a/vector/src/main/res/layout/fragment_login_account_created.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -