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. -<b>Fitur Element termasuk</b> -- Alat komunikasi online yang canggih +<b>Fitur Element termasuk:</b> +- 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. <b>Perpesanan dengan privasi dan enkripsi</b> -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. -<b>Element dapat dihost sendiri</b> -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. +<b>Element dapat di-host sendiri</b> +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. <b>Miliki data Anda</b> 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 <b>Perpesanan dan kolaborasi terbuka</b> -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. <b>Sangat aman</b> -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. <b>Komunikasi dan integrasi lengkap</b> -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. <b>Ambil di mana Anda tinggalkan</b> -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 <b>Sumber terbuka</b> -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<RealmConfiguration> /** - * 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<String>() + var invitedUserIds = mutableListOf<String>() /** * A list of objects representing third party IDs to invite into the room. */ - val invite3pids = mutableListOf<ThreePid>() + var invite3pids = mutableListOf<ThreePid>() /** * 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<String, Any>() + var creationContent = mutableMapOf<String, Any>() /** * 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<CreateRoomStateEvent>() + var initialStates = mutableListOf<CreateRoomStateEvent>() /** * 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<Unit> { 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<Unit> { 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<Unit> { 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<T : RealmObject>(protected val r LiveEntityObserver, RealmChangeListener<RealmResults<T>> { 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<LocalRoomSummaryEntity> { + val query = realm.where<LocalRoomSummaryEntity>() + 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<RealmConfiguration> { 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<Params, List<Event>> { + 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<Event> { + 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<Event>.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<Event>.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<Event>.createRoomMemberEvents(userIds: List<String>) { + 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<Event>.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<Event>.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<Event>.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<Event>.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<Event>.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<Event>.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<CreateRoomParams, String> 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<RoomSummaryEntity>(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<LocalRoomSummaryEntity>(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<String, RoomMemberContent?>() 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<Event> { - 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<CreateRoomFromLocalRoomTask.Params, String> { + 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<RoomTombstoneContent>() + ?.replacementRoomId + + if (replacementRoomId != null) { + return replacementRoomId + } + + var createRoomParams: CreateRoomParams? = null + var isEncrypted = false + monarchy.doWithRealm { realm -> + realm.where<LocalRoomSummaryEntity>() + .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<SendStateTask.Params, String> { 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<PARAM : SessionWorkerParams>( // 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<PARAM : SessionWorkerParams>( * 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<UserService>() + + 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>() + + 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<RoomNameContent>()?.name shouldBeEqualTo aRoomName + roomTopicEvent?.content.toModel<RoomTopicContent>()?.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<RoomMember> = 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<RoomMemberContent>() + 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<PowerLevelsContent>() 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<RoomCanonicalAliasContent>()?.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<RoomJoinRulesContent>() + ?.joinRules shouldBeEqualTo case.expectedResult.joinRules + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel<RoomHistoryVisibilityContent>() + ?.historyVisibility shouldBeEqualTo case.expectedResult.historyVisibility + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel<RoomGuestAccessContent>() + ?.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<RoomThirdPartyInviteContent>() } + val localThirdPartyInviteEvents = result.filter { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + val localThirdPartyInviteContents = localThirdPartyInviteEvents.map { it.content.toModel<LocalRoomThirdPartyInviteContent>() } + + 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<String>() == 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>() + 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<RoomGuestAccessContent>()?.guestAccess shouldBeEqualTo GuestAccess.Forbidden + // History visibility + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel<RoomHistoryVisibilityContent>()?.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<RoomHistoryVisibilityContent>()?.historyVisibility shouldBeEqualTo expectedHistoryVisibility + result.lastOrNull { it.type == EventType.STATE_ROOM_MEMBER } + ?.content.toModel<RoomMemberContent>()?.isDirect shouldBeEqualTo expectedIsDirect + result.lastOrNull { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + ?.content.toModel<LocalRoomThirdPartyInviteContent>()?.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<CreateRoomTask>() + 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<Any>(realmConfiguration = any(), timeoutMillis = any(), builder = any()) } + + mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") + coEvery { any<EventEntity>().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<CreateRoomParams>() + val aLocalRoomSummaryEntity = mockk<LocalRoomSummaryEntity> { + 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<LocalRoomSummaryEntity>() + .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<RoomTombstoneContent>() + ?.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<Monarchy>() - 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<suspend (Realm) -> Any>().invoke(fakeRealm.instance) } + coEvery { + instance.doWithRealm(any()) + } coAnswers { + firstArg<Monarchy.RealmBlock>().doWithRealm(fakeRealm.instance) + } + every { instance.realmConfiguration } returns mockk() } inline fun <reified T : RealmModel> givenWhere(): RealmQuery<T> { 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 </#if> -//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<FragmentDebugMemoryLeaksBinding>() { +class DebugMemoryLeaksFragment : + VectorBaseFragment<FragmentDebugMemoryLeaksBinding>() { 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 @@ <activity android:name=".features.location.LocationSharingActivity" /> <activity android:name=".features.location.live.map.LiveLocationMapViewActivity" /> <activity android:name=".features.settings.font.FontScaleSettingActivity"/> + <activity android:name=".features.call.dialpad.PstnDialActivity" /> + <activity android:name=".features.home.room.list.home.invites.InvitesActivity"/> <!-- Services --> 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<out Fragment>) 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<Class<out Fragment>, Provider<Fragment>> -) : FragmentFactory() { - - override fun instantiate(classLoader: ClassLoader, className: String): Fragment { - val fragmentClass = loadFragmentClass(classLoader, className) - val creator: Provider<out Fragment>? = 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<VB : ViewBinding> : 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<VB : ViewBinding> : 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<VB : ViewBinding> : AppCompatActivity(), Maver bugReporter.inMultiWindowMode = it.isInMultiWindowMode } - protected fun createFragment(fragmentClass: Class<out Fragment>, 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<VB : ViewBinding> : 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<ActivityMainBinding>(), 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<String, Any>? { + return mutableMapOf<String, Any>().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<FragmentAnalyticsOptinBinding>(), OnBackPressed { +@AndroidEntryPoint +class AnalyticsOptInFragment : + VectorBaseFragment<FragmentAnalyticsOptinBinding>(), + 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<ContentAttachmentData> ) : Parcelable -class AttachmentsPreviewFragment @Inject constructor( - private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController, - private val attachmentBigPreviewController: AttachmentBigPreviewController, - private val colorProvider: ColorProvider, - private val clock: Clock, -) : VectorBaseFragment<FragmentAttachmentsPreviewBinding>(), +@AndroidEntryPoint +class AttachmentsPreviewFragment : + VectorBaseFragment<FragmentAttachmentsPreviewBinding>(), 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<FragmentContactsBookBinding>(), ContactsBookController.Callback { +@AndroidEntryPoint +class ContactsBookFragment : + VectorBaseFragment<FragmentContactsBookBinding>(), + 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<FragmentKeysBackupRestoreFromKeyBinding>() { 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<FragmentKeysBackupRestoreFromPassphraseBinding>() { +@AndroidEntryPoint +class KeysBackupRestoreFromPassphraseFragment : + VectorBaseFragment<FragmentKeysBackupRestoreFromPassphraseBinding>() { 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<FragmentKeysBackupRestoreSuccessBinding>() { +@AndroidEntryPoint +class KeysBackupRestoreSuccessFragment : + VectorBaseFragment<FragmentKeysBackupRestoreSuccessBinding>() { 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<FragmentKeysBackupSettingsBinding>(), 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<FragmentKeysBackupSetupStep1Binding>() { +@AndroidEntryPoint +class KeysBackupSetupStep1Fragment : + VectorBaseFragment<FragmentKeysBackupSetupStep1Binding>() { 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<FragmentKeysBackupSetupStep2Binding>() { +@AndroidEntryPoint +class KeysBackupSetupStep2Fragment : + VectorBaseFragment<FragmentKeysBackupSetupStep2Binding>() { 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<FragmentKeysBackupSetupStep3Binding>() { +@AndroidEntryPoint +class KeysBackupSetupStep3Fragment : + VectorBaseFragment<FragmentKeysBackupSetupStep3Binding>() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSetupStep3Binding { return FragmentKeysBackupSetupStep3Binding.inflate(inflater, container, false) @@ -134,7 +136,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment<Fr selectTxtFileToWrite( activity = requireActivity(), activityResultLauncher = saveRecoveryActivityResultLauncher, - defaultFileName = "recovery-key-$userId-$timestamp.txt", + defaultFileName = "recovery-key-$userId-${timestamp}.txt", chooserHint = getString(R.string.save_recovery_key_chooser_hint) ) dialog.dismiss() diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt index 5a7aba65a5..efb24ebea7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt @@ -24,6 +24,7 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel +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 kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges -import javax.inject.Inject -class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment<FragmentSsssAccessFromKeyBinding>() { +@AndroidEntryPoint +class SharedSecuredStorageKeyFragment : + VectorBaseFragment<FragmentSsssAccessFromKeyBinding>() { 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<FragmentSsssAccessFromPassphraseBinding>() { +@AndroidEntryPoint +class SharedSecuredStoragePassphraseFragment : + VectorBaseFragment<FragmentSsssAccessFromPassphraseBinding>() { 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<FragmentSsssResetAllBinding>() { 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<FragmentBootstrapConclusionBinding>() { +@AndroidEntryPoint +class BootstrapConclusionFragment : + VectorBaseFragment<FragmentBootstrapConclusionBinding>() { + + @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<FragmentBootstrapEnterPassphraseBinding>() { 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<FragmentBootstrapEnterPassphraseBinding>() { 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<FragmentBootstrapMigrateBackupBinding>() { +@AndroidEntryPoint +class BootstrapMigrateBackupFragment : + VectorBaseFragment<FragmentBootstrapMigrateBackupBinding>() { + + @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<FragmentBootstrapReauthBinding>() { +@AndroidEntryPoint +class BootstrapReAuthFragment : + VectorBaseFragment<FragmentBootstrapReauthBinding>() { 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<FragmentBootstrapSaveKeyBinding>() { +@AndroidEntryPoint +class BootstrapSaveRecoveryKeyFragment : + VectorBaseFragment<FragmentBootstrapSaveKeyBinding>() { 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<FragmentBootstrapSetupRecoveryBinding>() { 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<FragmentBootstrapWaitingBinding>() { 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<FragmentProgressBinding>() { +@AndroidEntryPoint +class QuadSLoadingFragment : + VectorBaseFragment<FragmentProgressBinding>() { 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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationCancelFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationNotMeFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationChooseMethodFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationConclusionFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationEmojiCodeFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<BottomSheetVerificationChildFragmentBinding>() { +@AndroidEntryPoint +class VerificationQRWaitingFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>() { + + @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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationQrScannedByOtherFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<BottomSheetVerificationChildFragmentBinding>(), +@AndroidEntryPoint +class VerificationRequestFragment : + VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(), 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<FragmentDevtoolsEditorBinding>() { 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class RoomDevToolFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener { +@AndroidEntryPoint +class RoomDevToolSendFormFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), + 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<FragmentGenericRecyclerBinding>(), DevToolsInteractionListener { +@AndroidEntryPoint +class RoomDevToolStateEventListFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), + 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class DiscoverySettingsFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentSetIdentityServerBinding>() { +@AndroidEntryPoint +class SetIdentityServerFragment : + VectorBaseFragment<FragmentSetIdentityServerBinding>() { + + @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<HomeSpaceSummaryItem.Holder>(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<NewHomeSpaceSummaryItem.Holder>(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<CheckableConstraintLayout>(R.id.root) + val avatar by bind<ImageView>(R.id.avatar) + val name by bind<TextView>(R.id.name) + val unreadCounter by bind<UnreadCounterBadgeView>(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<FragmentHomeDetailBinding>(), +@AndroidEntryPoint +class HomeDetailFragment : + VectorBaseFragment<FragmentHomeDetailBinding>(), 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<FragmentHomeDrawerBinding>() { +@AndroidEntryPoint +class HomeDrawerFragment : + VectorBaseFragment<FragmentHomeDrawerBinding>() { + + @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<FragmentLoadingBinding>() { +@AndroidEntryPoint +class LoadingFragment : VectorBaseFragment<FragmentLoadingBinding>() { 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<FragmentNewHomeDetailBinding>(), +@AndroidEntryPoint +class NewHomeDetailFragment : + VectorBaseFragment<FragmentNewHomeDetailBinding>(), 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<FragmentBreadcrumbsBinding>(), +@AndroidEntryPoint +class BreadcrumbsFragment : + VectorBaseFragment<FragmentBreadcrumbsBinding>(), 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<FragmentTimelineBinding>(), 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<RoomTombstoneContent>() + ?.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<FragmentSearchBinding>(), +@AndroidEntryPoint +class SearchFragment : + VectorBaseFragment<FragmentSearchBinding>(), 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<FragmentRoomListBinding>(), +@AndroidEntryPoint +class RoomListFragment : + VectorBaseFragment<FragmentRoomListBinding>(), 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<RoomListViewModel, RoomListViewState> 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<RoomsSection> 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<RoomListSharedAction>() 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<Preferences> 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<Boolean> = context.dataStore.data + .map { preferences -> preferences[areRecentsEnbabled].orFalse() } + .distinctUntilChanged() + + val areFiltersEnabledFlow: Flow<Boolean> = context.dataStore.data + .map { preferences -> preferences[areFiltersEnabled].orFalse() } + .distinctUntilChanged() + + val isAZOrderingEnabledFlow: Flow<Boolean> = 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<FragmentRoomListBinding>(), +@AndroidEntryPoint +class HomeRoomListFragment : + VectorBaseFragment<FragmentRoomListBinding>(), 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<HomeRoomSection>) { - 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<HomeRoomSection>) { + 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<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) { @AssistedFactory @@ -82,17 +86,30 @@ class HomeRoomListViewModel @AssistedInject constructor( init { configureSections() + observePreferences() } - private fun configureSections() { - val newSections = mutableSetOf<HomeRoomSection>() + 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<HomeRoomSection>() + 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<List<HomeRoomFilter>> { - val flow = MutableSharedFlow<List<HomeRoomFilter>>(replay = 1) + private fun getFiltersDataFlow(): SharedFlow<Optional<List<HomeRoomFilter>>> { + val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(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<PagedList<RoomSummary>>, - val showFilters: Boolean, - val filtersData: SharedFlow<List<HomeRoomFilter>> + val filtersData: SharedFlow<Optional<List<HomeRoomFilter>>>, ) : HomeRoomSection() data class RecentRoomsData( val list: LiveData<List<RoomSummary>> ) : HomeRoomSection() + + data class InvitesCountData( + val count: LiveData<Int> + ) : 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<RoomSummary>( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -48,7 +47,7 @@ class HomeFilteredRoomsController( override fun addModels(models: List<EpoxyModel<*>>) { 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<HomeRoomFilter>) { + fun submitFiltersData(data: List<HomeRoomFilter>?) { 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<InviteCounterItem.Holder>(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<UnreadCounterBadgeView>(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<ActivitySimpleBinding>() { + + 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<RoomSummary>( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = 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<FragmentInvitesBinding>(), 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<InvitesViewState, InvitesAction, InvitesViewEvents>(initialState) { + + private val pagedListConfig = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(true) + .setPrefetchDistance(10) + .build() + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory<InvitesViewModel, InvitesViewState> { + override fun create(initialState: InvitesViewState): InvitesViewModel + } + + companion object : MavericksViewModelFactory<InvitesViewModel, InvitesViewState> 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<PagedList<RoomSummary>>? = null, + val roomMembershipChanges: Map<String, ChangeMembershipState> = 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<BottomSheetHomeLayoutSettingsBinding>() { + + @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<FragmentThreadListBinding>(), +@AndroidEntryPoint +class ThreadListFragment : + VectorBaseFragment<FragmentThreadListBinding>(), 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<FragmentLocationSharingBinding>(), +@AndroidEntryPoint +class LocationSharingFragment : + VectorBaseFragment<FragmentLocationSharingBinding>(), 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<FragmentLiveLocationMapViewBinding>() { +class LiveLocationMapViewFragment : + VectorBaseFragment<FragmentLiveLocationMapViewBinding>() { @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<FragmentLocationPreviewBinding>(), +@AndroidEntryPoint +class LocationPreviewFragment : + VectorBaseFragment<FragmentLocationPreviewBinding>(), 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<VB : ViewBinding> : 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<FragmentLoginCaptchaBinding>() { +@AndroidEntryPoint +class LoginCaptchaFragment : + AbstractLoginFragment<FragmentLoginCaptchaBinding>() { + + @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<FragmentLoginBinding>() { +@AndroidEntryPoint +class LoginFragment : + AbstractSSOLoginFragment<FragmentLoginBinding>() { private var isSignupMode = false @@ -100,13 +101,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog } } - private fun setupSocialLoginButtons(state: LoginViewState) { - 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: 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<FragmentLog 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) } - } + views.loginSocialLoginButtons.render(state.loginMode.ssoState, ssoMode(state)) { provider -> + 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<FragmentLog setupUi(state) setupAutoFill(state) - setupSocialLoginButtons(state) setupButtons(state) when (state.asyncLoginAction) { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt index 07253d80ec..2bc8419989 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt @@ -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 @@ -41,7 +42,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 enum class TextInputFormFragmentMode { SetEmail, @@ -59,7 +59,9 @@ data class LoginGenericTextInputFormFragmentArgument( /** * In this screen, the user is asked for a text input. */ -class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFragment<FragmentLoginGenericTextInputFormBinding>() { +@AndroidEntryPoint +class LoginGenericTextInputFormFragment : + AbstractLoginFragment<FragmentLoginGenericTextInputFormBinding>() { 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<SsoIdentityProvider>?) : LoginMode() - @Parcelize data class SsoAndPassword(val ssoIdentityProviders: List<SsoIdentityProvider>?) : 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<SsoIdentityProvider>? { +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<FragmentLoginResetPasswordBinding>() { +@AndroidEntryPoint +class LoginResetPasswordFragment : + AbstractLoginFragment<FragmentLoginResetPasswordBinding>() { // 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<FragmentLoginResetPasswordMailConfirmationBinding>() { +@AndroidEntryPoint +class LoginResetPasswordMailConfirmationFragment : + AbstractLoginFragment<FragmentLoginResetPasswordMailConfirmationBinding>() { 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<FragmentLoginResetPasswordSuccessBinding>() { +@AndroidEntryPoint +class LoginResetPasswordSuccessFragment : + AbstractLoginFragment<FragmentLoginResetPasswordSuccessBinding>() { 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<FragmentLoginServerSelectionBinding>() { +@AndroidEntryPoint +class LoginServerSelectionFragment : + AbstractLoginFragment<FragmentLoginServerSelectionBinding>() { 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<FragmentLoginServerUrlFormBinding>() { +@AndroidEntryPoint +class LoginServerUrlFormFragment : + AbstractLoginFragment<FragmentLoginServerUrlFormBinding>() { + + @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<FragmentLoginSignupSigninSelectionBinding>() { +@AndroidEntryPoint +class LoginSignUpSignInSelectionFragment : + AbstractSSOLoginFragment<FragmentLoginSignupSigninSelectionBinding>() { 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<FragmentLoginSplashBinding>() { +@AndroidEntryPoint +class LoginSplashFragment : + AbstractLoginFragment<FragmentLoginSplashBinding>() { + + @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<FragmentLoginWaitForEmailBinding>() { +@AndroidEntryPoint +class LoginWaitForEmailFragment : + AbstractLoginFragment<FragmentLoginWaitForEmailBinding>() { 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<FragmentLoginWebBinding>() { +@AndroidEntryPoint +class LoginWebFragment : + AbstractLoginFragment<FragmentLoginWebBinding>() { + + @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<SsoIdentityProvider>?, 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<SsoIdentityProvider>) : SsoState + + @Parcelize + object Fallback : SsoState + + fun isFallback() = this == Fallback + + fun providersOrNull() = when (this) { + Fallback -> null + is IdentityProviders -> providers.takeIf { it.isNotEmpty() } + } +} + +fun List<SsoIdentityProvider>?.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<FragmentLoginTermsBinding>(), +class LoginTermsFragment : + AbstractLoginFragment<FragmentLoginTermsBinding>(), 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<VB : ViewBinding> : VectorBaseFragment<VB>(), 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<VB : ViewBinding> : AbstractLoginFragment2<VB>() { - - // 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<SsoIdentityProvider>? - ) : 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<FragmentLoginCaptchaBinding>() { - - 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<FragmentLoginSigninPassword2Binding>() { - - 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<FragmentLoginSigninUsername2Binding>() { - - 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<FragmentLoginSignupPassword2Binding>() { - - 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<FragmentLoginSignupUsername2Binding>() { - - 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<FragmentLoginSigninToAny2Binding>() { - - 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<FragmentLoginGenericTextInputForm2Binding>() { - - 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<FragmentLoginResetPassword2Binding>() { - - // 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<FragmentLoginResetPasswordMailConfirmation2Binding>() { - - 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<FragmentLoginResetPasswordSuccess2Binding>() { - - 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<FragmentLoginServerSelection2Binding>() { - - 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<FragmentLoginServerUrlForm2Binding>() { - - 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<FragmentLoginSplash2Binding>() { - - 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<FragmentLoginSsoOnly2Binding>() { - - 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<String>) : 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<LoginViewState2, LoginAction2, LoginViewEvents2>(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory<LoginViewModel2, LoginViewState2> { - override fun create(initialState: LoginViewState2): LoginViewModel2 - } - - companion object : MavericksViewModelFactory<LoginViewModel2, LoginViewState2> 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<LoginProfileInfo> = Uninitialized, - - // Network result - @PersistState - val loginMode: LoginMode = LoginMode.Unknown, - - // From database - val knownCustomHomeServersUrls: List<String> = 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<FragmentLoginWaitForEmail2Binding>() { - - 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<FragmentLoginWebBinding>() { - - 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: - * - * <pre> - * 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/" - * } - * } - * } - * } - * . - * </pre> - * @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<FragmentLoginAccountCreatedBinding>(), - 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<AccountCreatedViewState, AccountCreatedAction, AccountCreatedViewEvents>(initialState) { - - @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory<AccountCreatedViewModel, AccountCreatedViewState> { - override fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel - } - - companion object : MavericksViewModelFactory<AccountCreatedViewModel, AccountCreatedViewState> 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<FragmentLoginTerms2Binding>(), - 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<LocalizedFlowDataLoginTermsChecked>() - - 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<FragmentMatrixToRoomSpaceCardBinding>() { +@AndroidEntryPoint +class MatrixToRoomSpaceFragment : + VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() { + + @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<FragmentMatrixToUserCardBinding>() { +@AndroidEntryPoint +class MatrixToUserFragment : + VectorBaseFragment<FragmentMatrixToUserCardBinding>() { + + @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<ActivityLoginBinding>, - 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<View?>(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<LoginConfig?>(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<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // activity.findViewById<View?>(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // activity.findViewById<View?>(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<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // activity.findViewById<View?>(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // activity.findViewById<View?>(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<String>) { - 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<ActivityLoginBinding>(), 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<OnboardingViewModel>, - loginViewModel2: Lazy<LoginViewModel2> ) = 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<VB : ViewBinding> : VectorBaseFragment<V private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) { when (viewEvents) { is OnboardingViewEvents.Failure -> showFailure(viewEvents.throwable) + is OnboardingViewEvents.UnrecognisedCertificateFailure -> showUnrecognizedCertificateFailure(viewEvents) else -> // This is handled by the Activity Unit @@ -84,20 +84,20 @@ abstract class AbstractFtueAuthFragment<VB : ViewBinding> : VectorBaseFragment<V 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) { + 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<VB : ViewBinding> : AbstractFtueAuthFragment<VB>() { @@ -88,7 +88,7 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : 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<FragmentFtueAccountCreatedBinding>() { +@AndroidEntryPoint +class FtueAuthAccountCreatedFragment : + AbstractFtueAuthFragment<FragmentFtueAccountCreatedBinding>() { + + @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<FragmentFtueLoginCaptchaBinding>() { +@AndroidEntryPoint +class FtueAuthCaptchaFragment : + AbstractFtueAuthFragment<FragmentFtueLoginCaptchaBinding>() { + + @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<FragmentFtueDisplayNameBinding>() { +@AndroidEntryPoint +class FtueAuthChooseDisplayNameFragment : + AbstractFtueAuthFragment<FragmentFtueDisplayNameBinding>() { 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<FragmentFtueProfilePictureBinding>(), GalleryOrCameraDialogHelper.Listener { +@AndroidEntryPoint +class FtueAuthChooseProfilePictureFragment : + AbstractFtueAuthFragment<FragmentFtueProfilePictureBinding>(), + 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<FragmentFtueCombinedLoginBinding>() { +@AndroidEntryPoint +class FtueAuthCombinedLoginFragment : + AbstractSSOFtueAuthFragment<FragmentFtueCombinedLoginBinding>() { + + @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<SsoIdentityProvider>?) { - 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<FragmentFtueCombinedRegisterBinding>() { +@AndroidEntryPoint +class FtueAuthCombinedRegisterFragment : + AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() { 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<SsoIdentityProvider>?) { - 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<FragmentFtueServerSelectionCombinedBinding>() { +@AndroidEntryPoint +class FtueAuthCombinedServerSelectionFragment : + AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() { 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<FragmentFtueEmailInputBinding>() { +@AndroidEntryPoint +class FtueAuthEmailEntryFragment : + AbstractFtueAuthFragment<FragmentFtueEmailInputBinding>() { 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<FragmentLoginGenericTextInputFormBinding>() { +@AndroidEntryPoint +class FtueAuthGenericTextInputFormFragment : + AbstractFtueAuthFragment<FragmentLoginGenericTextInputFormBinding>() { 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<FragmentLoginCaptchaBinding>() { +@AndroidEntryPoint +class FtueAuthLegacyStyleCaptchaFragment : + AbstractFtueAuthFragment<FragmentLoginCaptchaBinding>() { + + @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<FragmentLoginWaitForEmailBinding>() { +@AndroidEntryPoint +class FtueAuthLegacyWaitForEmailFragment : + AbstractFtueAuthFragment<FragmentLoginWaitForEmailBinding>() { 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<FragmentLoginBinding>() { +@AndroidEntryPoint +class FtueAuthLoginFragment : + AbstractSSOFtueAuthFragment<FragmentLoginBinding>() { 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<FragmentFtuePersonalizationCompleteBinding>() { +@AndroidEntryPoint +class FtueAuthPersonalizationCompleteFragment : + AbstractFtueAuthFragment<FragmentFtuePersonalizationCompleteBinding>() { 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<FragmentFtuePhoneConfirmationBinding>() { +@AndroidEntryPoint +class FtueAuthPhoneConfirmationFragment : + AbstractFtueAuthFragment<FragmentFtuePhoneConfirmationBinding>() { 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<FragmentFtuePhoneInputBinding>() { +@AndroidEntryPoint +class FtueAuthPhoneEntryFragment : + AbstractFtueAuthFragment<FragmentFtuePhoneInputBinding>() { + + @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<FragmentFtueResetPasswordBreakerBinding>() { +class FtueAuthResetPasswordBreakerFragment : + AbstractFtueAuthFragment<FragmentFtueResetPasswordBreakerBinding>() { @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<FragmentFtueResetPasswordEmailInputBinding>() { +class FtueAuthResetPasswordEmailEntryFragment : + AbstractFtueAuthFragment<FragmentFtueResetPasswordEmailInputBinding>() { 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<FragmentFtueResetPasswordInputBinding>() { +class FtueAuthResetPasswordEntryFragment : + AbstractFtueAuthFragment<FragmentFtueResetPasswordInputBinding>() { 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<FragmentLoginResetPasswordBinding>() { +@AndroidEntryPoint +class FtueAuthResetPasswordFragment : + AbstractFtueAuthFragment<FragmentLoginResetPasswordBinding>() { // 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<FragmentLoginResetPasswordMailConfirmationBinding>() { +@AndroidEntryPoint +class FtueAuthResetPasswordMailConfirmationFragment : + AbstractFtueAuthFragment<FragmentLoginResetPasswordMailConfirmationBinding>() { 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<FragmentLoginResetPasswordSuccessBinding>() { +@AndroidEntryPoint +class FtueAuthResetPasswordSuccessFragment : + AbstractFtueAuthFragment<FragmentLoginResetPasswordSuccessBinding>() { 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<FragmentLoginServerSelectionBinding>() { +@AndroidEntryPoint +class FtueAuthServerSelectionFragment : + AbstractFtueAuthFragment<FragmentLoginServerSelectionBinding>() { 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<FragmentLoginServerUrlFormBinding>() { +@AndroidEntryPoint +class FtueAuthServerUrlFormFragment : + AbstractFtueAuthFragment<FragmentLoginServerUrlFormBinding>() { + + @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<FragmentLoginSignupSigninSelectionBinding>() { +@AndroidEntryPoint +class FtueAuthSignUpSignInSelectionFragment : + AbstractSSOFtueAuthFragment<FragmentLoginSignupSigninSelectionBinding>() { 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<FragmentFtueSplashCarouselBinding>() { +@AndroidEntryPoint +class FtueAuthSplashCarouselFragment : + AbstractFtueAuthFragment<FragmentFtueSplashCarouselBinding>() { + + @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<FragmentFtueAuthSplashBinding>() { +@AndroidEntryPoint +class FtueAuthSplashFragment : + AbstractFtueAuthFragment<FragmentFtueAuthSplashBinding>() { + + @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<FragmentFtueAuthUseCaseBinding>() { +@AndroidEntryPoint +class FtueAuthUseCaseFragment : + AbstractFtueAuthFragment<FragmentFtueAuthUseCaseBinding>() { + + @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<out Fragment>, params: Parcelable? = null) { + private fun addRegistrationStageFragmentToBackstack(fragmentClass: Class<out Fragment>, 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<FragmentFtueWaitForEmailVerificationBinding>() { +@AndroidEntryPoint +class FtueAuthWaitForEmailFragment : + AbstractFtueAuthFragment<FragmentFtueWaitForEmailVerificationBinding>() { + + @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<FragmentLoginWebBinding>() { +@AndroidEntryPoint +class FtueAuthWebFragment : + AbstractFtueAuthFragment<FragmentLoginWebBinding>() { + + @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<FragmentLoginTermsBinding>(), +@AndroidEntryPoint +class FtueAuthLegacyStyleTermsFragment : + AbstractFtueAuthFragment<FragmentLoginTermsBinding>(), 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<FragmentFtueLoginTermsBinding>(), +@AndroidEntryPoint +class FtueAuthTermsFragment : + AbstractFtueAuthFragment<FragmentFtueLoginTermsBinding>(), 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<FragmentPinBinding>() { +@AndroidEntryPoint +class PinFragment : + VectorBaseFragment<FragmentPinBinding>() { + + @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<FragmentLockScreenBinding>() { +class LockScreenFragment : + VectorBaseFragment<FragmentLockScreenBinding>() { 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<FragmentCreatePollBinding>(), CreatePollController.Callback { +@AndroidEntryPoint +class CreatePollFragment : + VectorBaseFragment<FragmentCreatePollBinding>(), + 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<FragmentQrCodeScannerBinding>(), ZXingScannerView.ResultHandler { +@AndroidEntryPoint +class QrCodeScannerFragment : + VectorBaseFragment<FragmentQrCodeScannerBinding>(), + 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<EmojiChooserFragmentBinding>(), +@AndroidEntryPoint +class EmojiChooserFragment : + VectorBaseFragment<EmojiChooserFragmentBinding>(), 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<FragmentGenericRecyclerBinding>(), ReactionClickListener { +@AndroidEntryPoint +class EmojiSearchResultFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), + 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<FragmentPublicRoomsBinding>(), +@AndroidEntryPoint +class PublicRoomsFragment : + VectorBaseFragment<FragmentPublicRoomsBinding>(), 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<FragmentCreateRoomBinding>(), +@AndroidEntryPoint +class CreateRoomFragment : + VectorBaseFragment<FragmentCreateRoomBinding>(), 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<FragmentRoomDirectoryPickerBinding>(), +@AndroidEntryPoint +class RoomDirectoryPickerFragment : + VectorBaseFragment<FragmentRoomDirectoryPickerBinding>(), 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<FragmentRoomPreviewNoPreviewBinding>() { +@AndroidEntryPoint +class RoomPreviewNoPreviewFragment : + VectorBaseFragment<FragmentRoomPreviewNoPreviewBinding>() { + + @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<FragmentMatrixProfileBinding>(), +@AndroidEntryPoint +class RoomMemberProfileFragment : + VectorBaseFragment<FragmentMatrixProfileBinding>(), 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<BottomSheetGenericListBinding>(), +@AndroidEntryPoint +class DeviceListFragment : + VectorBaseFragment<BottomSheetGenericListBinding>(), 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<BottomSheetGenericListBinding>(), +@AndroidEntryPoint +class DeviceTrustInfoActionFragment : + VectorBaseFragment<BottomSheetGenericListBinding>(), 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<FragmentMatrixProfileBinding>(), 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<FragmentRoomSettingGenericBinding>(), 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<FragmentRoomSettingGenericBinding>(), +@AndroidEntryPoint +class RoomBannedMemberListFragment : + VectorBaseFragment<FragmentRoomSettingGenericBinding>(), 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<FragmentRoomMemberListBinding>(), +@AndroidEntryPoint +class RoomMemberListFragment : + VectorBaseFragment<FragmentRoomMemberListBinding>(), 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<FragmentRoomSettingGenericBinding>(), +@AndroidEntryPoint +class RoomNotificationSettingsFragment : + VectorBaseFragment<FragmentRoomSettingGenericBinding>(), 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<FragmentRoomSettingGenericBinding>(), 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<FragmentRoomSettingGenericBinding>(), 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<FragmentJoinRulesRecyclerBinding>(), - OnBackPressed, RoomJoinRuleAdvancedController.InteractionListener { +@AndroidEntryPoint +class RoomJoinRuleFragment : + VectorBaseFragment<FragmentJoinRulesRecyclerBinding>(), + 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<FragmentSpaceRestrictedSelectBinding>(), +@AndroidEntryPoint +class RoomJoinRuleChooseRestrictedFragment : + VectorBaseFragment<FragmentSpaceRestrictedSelectBinding>(), 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<FragmentRoomUploadsBinding>() { +@AndroidEntryPoint +class RoomUploadsFragment : + VectorBaseFragment<FragmentRoomUploadsBinding>() { + + @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<FragmentGenericStateViewRecyclerBinding>(), +@AndroidEntryPoint +class RoomUploadsFilesFragment : + VectorBaseFragment<FragmentGenericStateViewRecyclerBinding>(), 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<FragmentGenericStateViewRecyclerBinding>(), +@AndroidEntryPoint +class RoomUploadsMediaFragment : + VectorBaseFragment<FragmentGenericStateViewRecyclerBinding>(), 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<PreferenceCategory>(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<FragmentDeactivateAccountBinding>() { +@AndroidEntryPoint +class DeactivateAccountFragment : + VectorBaseFragment<FragmentDeactivateAccountBinding>() { 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class CrossSigningSettingsFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class VectorSettingsDevicesFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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 82efcc5db9..ca98be625c 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.DeviceFullInfo 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 javax.inject.Inject /** * Display the list of the user's devices and sessions. */ @AndroidEntryPoint -class VectorSettingsDevicesFragment @Inject constructor() : VectorBaseFragment<FragmentSettingsDevicesBinding>() { +class VectorSettingsDevicesFragment : + VectorBaseFragment<FragmentSettingsDevicesBinding>() { 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class AccountDataFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class GossipingEventsPaperTrailFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>() { +@AndroidEntryPoint +class IncomingKeyRequestListFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>() { + + @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<FragmentDevtoolKeyrequestsBinding>(), +@AndroidEntryPoint +class KeyRequestsFragment : + VectorBaseFragment<FragmentDevtoolKeyrequestsBinding>(), 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<FragmentGenericRecyclerBinding>() { +@AndroidEntryPoint +class OutgoingKeyRequestListFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>() { + + @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<FragmentSettingsFontScalingBinding>(), FontScaleSettingController.Callback { +@AndroidEntryPoint +class FontScaleSettingFragment : + VectorBaseFragment<FragmentSettingsFontScalingBinding>(), + 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class HomeserverSettingsFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class VectorSettingsIgnoredUsersFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class LegalsFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentLocalePickerBinding>(), +@AndroidEntryPoint +class LocalePickerFragment : + VectorBaseFragment<FragmentLocalePickerBinding>(), 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<FragmentSettingsNotificationsTroubleshootBinding>() { +@AndroidEntryPoint +class VectorSettingsNotificationsTroubleshootFragment : + VectorBaseFragment<FragmentSettingsNotificationsTroubleshootBinding>() { + + @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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class PushGatewaysFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>(), 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<FragmentGenericRecyclerBinding>() { +@AndroidEntryPoint +class PushRulesFragment : + VectorBaseFragment<FragmentGenericRecyclerBinding>() { + + @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<FragmentGenericRecyclerBinding>(), 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<FragmentIncomingShareBinding>(), 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<FragmentGenericRecyclerBinding>(), +@AndroidEntryPoint +class SoftLogoutFragment : + AbstractLoginFragment<FragmentGenericRecyclerBinding>(), 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<NewSpaceAddItem.Holder>(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<ImageView>(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<MatrixItem.UserItem> = Uninitialized, - val hasBeenModified: Boolean = false -) : MavericksState +@EpoxyModelClass +abstract class NewSpaceListHeaderItem : VectorEpoxyModel<NewSpaceListHeaderItem.Holder>(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<SpaceChildInfo> = compareBy<SpaceChildInfo> { 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<RoomSummary>?, + selectedSpace: RoomSummary?, + rootSpaces: List<RoomSummary>?, + 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<RoomSummary>?, + 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<RoomSummary>?) { + 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<NewSpaceSummaryItem.Holder>(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<CheckableConstraintLayout>(R.id.root) + val avatar by bind<ImageView>(R.id.avatar) + val name by bind<TextView>(R.id.name) + val unreadCounter by bind<UnreadCounterBadgeView>(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<out Fragment>) { - 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<FragmentSpaceListBinding>(), SpaceSummaryController.Callback { +@AndroidEntryPoint +class SpaceListFragment : + VectorBaseFragment<FragmentSpaceListBinding>(), + 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<NewSpaceSummaryItem>() { + 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<SpaceOrderContent>() + ?.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<SpaceOrderContent>() - ?.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<MatrixItem.UserItem> = Uninitialized, val asyncSpaces: Async<List<RoomSummary>> = Uninitialized, + val spaces: List<RoomSummary> = emptyList(), val selectedSpace: RoomSummary? = null, val rootSpacesOrdered: List<RoomSummary>? = null, val spaceOrderInfo: Map<String, String?>? = 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<FragmentSpaceCreateChoosePrivateModelBinding>(), OnBackPressed { +@AndroidEntryPoint +class ChoosePrivateSpaceTypeFragment : + VectorBaseFragment<FragmentSpaceCreateChoosePrivateModelBinding>(), + 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<FragmentSpaceCreateChooseTypeBinding>() { +@AndroidEntryPoint +class ChooseSpaceTypeFragment : + VectorBaseFragment<FragmentSpaceCreateChooseTypeBinding>() { 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<FragmentSpaceCreateGenericEpoxyFormBinding>(), +@AndroidEntryPoint +class CreateSpaceAdd3pidInvitesFragment : + VectorBaseFragment<FragmentSpaceCreateGenericEpoxyFormBinding>(), 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<FragmentSpaceCreateGenericEpoxyFormBinding>(), +@AndroidEntryPoint +class CreateSpaceDefaultRoomsFragment : + VectorBaseFragment<FragmentSpaceCreateGenericEpoxyFormBinding>(), 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<FragmentSpaceCreateGenericEpoxyFormBinding>(), SpaceDetailEpoxyController.Listener, - GalleryOrCameraDialogHelper.Listener, OnBackPressed { +@AndroidEntryPoint +class CreateSpaceDetailsFragment : + VectorBaseFragment<FragmentSpaceCreateGenericEpoxyFormBinding>(), + 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<FragmentSpaceDirectoryBinding>(), +@AndroidEntryPoint +class SpaceDirectoryFragment : + VectorBaseFragment<FragmentSpaceDirectoryBinding>(), 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<FragmentSpaceLeaveAdvancedBinding>(), +@AndroidEntryPoint +class SpaceLeaveAdvancedFragment : + VectorBaseFragment<FragmentSpaceLeaveAdvancedBinding>(), 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<FragmentSpaceAddRoomsBinding>(), +@AndroidEntryPoint +class SpaceAddRoomFragment : + VectorBaseFragment<FragmentSpaceAddRoomsBinding>(), 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<FragmentSpaceAddRoomsBinding>(), +@AndroidEntryPoint +class SpaceManageRoomsFragment : + VectorBaseFragment<FragmentSpaceAddRoomsBinding>(), 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<FragmentRoomSettingGenericBinding>(), +@AndroidEntryPoint +class SpaceSettingsFragment : + VectorBaseFragment<FragmentRoomSettingGenericBinding>(), 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<FragmentRecyclerviewWithSearchBinding>(), - OnBackPressed, SpacePeopleListController.InteractionListener { +@AndroidEntryPoint +class SpacePeopleFragment : + VectorBaseFragment<FragmentRecyclerviewWithSearchBinding>(), + 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<FragmentSpacePreviewBinding>() { +@AndroidEntryPoint +class SpacePreviewFragment : + VectorBaseFragment<FragmentSpacePreviewBinding>() { + + @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<FragmentReviewTermsBinding>(), +@AndroidEntryPoint +class ReviewTermsFragment : + VectorBaseFragment<FragmentReviewTermsBinding>(), 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<FragmentUserCodeShowBinding>() { +@AndroidEntryPoint +class ShowUserCodeFragment : + VectorBaseFragment<FragmentUserCodeShowBinding>() { + + @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<FragmentUserListBinding>(), +@AndroidEntryPoint +class UserListFragment : + VectorBaseFragment<FragmentUserListBinding>(), 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<String, String> = emptyMap() ) : Parcelable -class WidgetFragment @Inject constructor( - private val permissionUtils: WebviewPermissionUtils, - private val checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase, - private val vectorPreferences: VectorPreferences, -) : +@AndroidEntryPoint +class WidgetFragment : VectorBaseFragment<FragmentRoomWidgetBinding>(), 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12.283,21.44C17.649,21.44 22,17.088 22,11.719C22,6.351 17.649,1.999 12.283,1.999C6.916,1.999 2.566,6.351 2.566,11.719C2.566,13.223 2.907,14.648 3.517,15.918L2.045,20.705C1.808,21.474 2.531,22.194 3.299,21.953L8.046,20.47C9.326,21.091 10.764,21.44 12.283,21.44Z" + android:fillColor="#737D8C" + android:fillType="evenOdd"/> +</vector> 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12.7822,4.2963C12.7822,3.8641 12.4318,3.5137 11.9996,3.5137C11.5673,3.5137 11.217,3.8641 11.217,4.2963V11.2173L4.2963,11.2173C3.8641,11.2173 3.5137,11.5676 3.5137,11.9999C3.5137,12.4321 3.8641,12.7825 4.2963,12.7825H11.217V19.7038C11.217,20.136 11.5673,20.4864 11.9996,20.4864C12.4318,20.4864 12.7822,20.136 12.7822,19.7038V12.7825H19.7038C20.136,12.7825 20.4864,12.4321 20.4864,11.9999C20.4864,11.5676 20.136,11.2173 19.7038,11.2173L12.7822,11.2173V4.2963Z" + android:fillColor="#17191C" + android:fillType="evenOdd"/> +</vector> + 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17.048,4.105C17.107,3.556 16.708,3.064 16.159,3.006C15.61,2.948 15.118,3.346 15.059,3.895L14.668,7.602H9.613L9.983,4.105C10.041,3.556 9.642,3.064 9.093,3.006C8.544,2.948 8.052,3.346 7.994,3.895L7.602,7.602H4.834C4.281,7.602 3.834,8.05 3.834,8.602C3.834,9.154 4.281,9.602 4.834,9.602H7.39L6.872,14.505H4C3.448,14.505 3,14.953 3,15.505C3,16.058 3.448,16.506 4,16.506H6.661L6.331,19.622C6.273,20.171 6.671,20.663 7.221,20.721C7.77,20.779 8.262,20.381 8.32,19.832L8.589,17.288C8.319,16.818 8.165,16.274 8.165,15.693C8.165,14.871 8.474,14.122 8.984,13.555L9.401,9.602H12.959C13.538,8.776 14.497,8.235 15.583,8.235C16.669,8.235 17.628,8.776 18.207,9.602H19.379C19.931,9.602 20.379,9.154 20.379,8.602C20.379,8.05 19.931,7.602 19.379,7.602H16.679L17.048,4.105ZM15.583,10.382C16.135,10.382 16.583,10.83 16.583,11.382V14.693H19.852C20.404,14.693 20.852,15.141 20.852,15.693C20.852,16.246 20.404,16.694 19.852,16.694H16.583V20.004C16.583,20.557 16.135,21.004 15.583,21.004C15.031,21.004 14.583,20.557 14.583,20.004V16.694H11.313C10.76,16.694 10.313,16.246 10.313,15.693C10.313,15.141 10.76,14.693 11.313,14.693H14.583V11.382C14.583,10.83 15.031,10.382 15.583,10.382Z" + android:fillColor="#737D8C" + android:fillType="evenOdd"/> +</vector> 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16.647,3.006C17.196,3.065 17.593,3.558 17.533,4.108L17.147,7.684H19.998C20.55,7.684 20.998,8.132 20.998,8.684C20.998,9.236 20.55,9.684 19.998,9.684H16.931L16.8,10.891C16.058,10.967 15.356,11.181 14.722,11.508L14.919,9.684H9.582L9.039,14.708H11.898C11.67,15.332 11.545,16.006 11.544,16.708H8.822L8.455,20.111C8.395,20.66 7.902,21.057 7.353,20.997C6.804,20.938 6.407,20.445 6.466,19.896L6.811,16.708H3.999C3.447,16.708 2.999,16.26 2.999,15.708C2.999,15.156 3.447,14.708 3.999,14.708H7.027L7.57,9.684H4.864C4.312,9.684 3.864,9.236 3.864,8.684C3.864,8.132 4.312,7.684 4.864,7.684H7.786L8.196,3.893C8.255,3.344 8.748,2.947 9.298,3.006C9.847,3.065 10.244,3.558 10.184,4.108L9.798,7.684H15.135L15.545,3.893C15.604,3.344 16.097,2.947 16.647,3.006Z" + android:fillColor="#737D8C" + android:fillType="evenOdd"/> + <path + android:pathData="M19.003,16.765C19.003,17.817 18.151,18.669 17.1,18.669C16.048,18.669 15.196,17.817 15.196,16.765C15.196,15.714 16.048,14.862 17.1,14.862C18.151,14.862 19.003,15.714 19.003,16.765ZM20.332,18.698C20.67,18.133 20.865,17.472 20.865,16.765C20.865,14.686 19.179,13 17.1,13C15.02,13 13.334,14.686 13.334,16.765C13.334,18.845 15.02,20.531 17.1,20.531C17.806,20.531 18.467,20.336 19.032,19.998C19.062,20.038 19.094,20.077 19.131,20.114L20.745,21.727C21.108,22.091 21.698,22.091 22.061,21.727C22.425,21.364 22.425,20.774 22.061,20.411L20.448,18.797C20.411,18.76 20.372,18.728 20.332,18.698Z" + android:fillColor="#737D8C" + android:fillType="evenOdd"/> +</vector> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <size android:width="40dp" android:height="40dp"/> + + <solid android:color="?android:colorBackground" /> + + <corners android:radius="8dp" /> + +</shape> 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 @@ <corners android:radius="8dp" /> -</shape> \ No newline at end of file +</shape> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/rootLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?colorSurface" + android:orientation="vertical"> + + <TextView + style="@style/Widget.Vector.TextView.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="8dp" + android:text="@string/home_layout_preferences" + android:textAllCaps="true" /> + + <com.google.android.material.switchmaterial.SwitchMaterial + android:id="@+id/home_layout_settings_recents" + android:layout_width="match_parent" + android:layout_height="64dp" + android:layout_marginHorizontal="16dp" + android:checked="true" + android:text="@string/home_layout_preferences_recents" /> + + <com.google.android.material.switchmaterial.SwitchMaterial + android:id="@+id/home_layout_settings_filters" + android:layout_width="match_parent" + android:layout_height="64dp" + android:layout_marginHorizontal="16dp" + android:checked="true" + android:text="@string/home_layout_preferences_filters" /> + + <TextView + style="@style/Widget.Vector.TextView.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginVertical="8dp" + android:text="@string/home_layout_preferences_sort_by" + android:textAllCaps="true" /> + + <RadioGroup + android:id="@+id/home_layout_settings_sort_group" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:orientation="vertical"> + + <RadioButton + android:id="@+id/home_layout_settings_sort_activity" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:checked="true" + android:text="@string/home_layout_preferences_sort_activity" + android:textColor="?vctr_content_primary" /> + + <RadioButton + android:id="@+id/home_layout_settings_sort_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/home_layout_preferences_sort_name" + android:textColor="?vctr_content_primary" /> + </RadioGroup> + +</LinearLayout> 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" /> - <WebView - android:id="@+id/loginCaptchaWevView" + <androidx.constraintlayout.widget.Barrier + android:id="@+id/loginWebViewBarrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:constraint_referenced_ids="loginCaptchaWebViewStub,loginCaptchaWebView" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd" + app:layout_constraintStart_toStartOf="@id/captchaGutterStart" + app:layout_constraintTop_toBottomOf="@id/titleContentSpacing"/> + + <ViewStub + android:id="@+id/loginCaptchaWebViewStub" android:layout_width="0dp" android:layout_height="0dp" android:contentDescription="@string/login_a11y_captcha_container" + android:layout="@layout/view_stub_webview" + android:inflatedId="@+id/loginCaptchaWebView" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd" app:layout_constraintStart_toStartOf="@id/captchaGutterStart" diff --git a/vector/src/main/res/layout/fragment_invites.xml b/vector/src/main/res/layout/fragment_invites.xml new file mode 100644 index 0000000000..74226357c9 --- /dev/null +++ b/vector/src/main/res/layout/fragment_invites.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/appBarLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/invites_toolbar" + android:layout_width="match_parent" + android:layout_height="?actionBarSize" + app:title="@string/invites_title" /> + + </com.google.android.material.appbar.AppBarLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/invites_recycler" + android:layout_width="0dp" + android:layout_height="0dp" + android:fastScrollEnabled="true" + android:overScrollMode="always" + android:scrollbars="vertical" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/appBarLayout" /> + +</androidx.constraintlayout.widget.ConstraintLayout> 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 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_account_created_title" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/loginAccountCreatedSubtitle" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_primary" - tools:text="@string/login_account_created_subtitle" /> - - <TextView - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:text="@string/login_account_created_notice" - android:textColor="?vctr_content_secondary" /> - - <TextView - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_account_created_notice_2" - android:textColor="?vctr_content_secondary" /> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/loginAccountCreatedMessage" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:background="@drawable/bg_login_server_selector" - android:padding="4dp"> - - <ImageView - android:id="@+id/loginAccountCreatedAvatar" - android:layout_width="44dp" - android:layout_height="44dp" - android:importantForAccessibility="no" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:src="@tools:sample/avatars" /> - - <TextView - android:id="@+id/loginAccountCreatedMemberName" - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginTop="2dp" - android:layout_marginEnd="4dp" - android:ellipsize="end" - android:maxLines="1" - android:textColor="?vctr_content_primary" - android:textStyle="bold" - app:layout_constraintEnd_toStartOf="@id/loginAccountCreatedTime" - app:layout_constraintStart_toEndOf="@id/loginAccountCreatedAvatar" - app:layout_constraintTop_toTopOf="parent" - tools:text="\@user:domain.org" /> - - <TextView - android:id="@+id/loginAccountCreatedTime" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" - android:textColor="?vctr_content_secondary" - app:layout_constraintBaseline_toBaselineOf="@id/loginAccountCreatedMemberName" - app:layout_constraintEnd_toEndOf="parent" - tools:text="@tools:sample/date/hhmm" /> - - <TextView - style="@style/Widget.Vector.TextView.Body" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:text="@string/login_account_created_message" - android:textColor="?vctr_content_primary" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/loginAccountCreatedMemberName" - app:layout_constraintTop_toBottomOf="@id/loginAccountCreatedMemberName" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <TextView - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_account_created_instruction" - android:textColor="?vctr_content_secondary" /> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp"> - - <Button - android:id="@+id/loginAccountCreatedLater" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/later" /> - - <Button - android:id="@+id/loginAccountCreatedDone" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/done" - android:visibility="gone" - tools:layout_marginEnd="120dp" - tools:visibility="visible" /> - - </FrameLayout> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_generic_text_input_form_2.xml b/vector/src/main/res/layout/fragment_login_generic_text_input_form_2.xml deleted file mode 100644 index a5b65d62f5..0000000000 --- a/vector/src/main/res/layout/fragment_login_generic_text_input_form_2.xml +++ /dev/null @@ -1,121 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginGenericTextInputFormTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_primary" - tools:text="@string/login_set_email_title_2" /> - - <TextView - android:id="@+id/loginGenericTextInputFormNotice" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:layout_marginBottom="@dimen/layout_vertical_margin" - android:gravity="start" - android:textColor="?vctr_content_secondary" - tools:text="@string/login_set_email_notice_2" /> - - <TextView - android:id="@+id/loginGenericTextInputFormMandatoryNotice" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="26dp" - android:gravity="start" - android:textColor="?vctr_content_secondary" - android:visibility="gone" - tools:text="@string/login_set_email_mandatory_notice_2" - tools:visibility="visible" /> - - <TextView - android:id="@+id/loginGenericTextInputFormNotice2" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="start" - android:textColor="?vctr_content_secondary" - android:visibility="gone" - tools:text="@string/login_set_msisdn_notice2" - tools:visibility="visible" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/loginGenericTextInputFormTil" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:errorEnabled="true" - tools:hint="@string/login_set_email_optional_hint"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/loginGenericTextInputFormTextInput" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:imeOptions="actionDone" - android:maxLines="1" - tools:inputType="textEmailAddress" /> - - </com.google.android.material.textfield.TextInputLayout> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp"> - - <Button - android:id="@+id/loginGenericTextInputFormOtherButton" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:visibility="gone" - tools:text="@string/login_msisdn_confirm_send_again" - tools:visibility="visible" /> - - <Button - android:id="@+id/loginGenericTextInputFormLater" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/later" - android:visibility="gone" - tools:layout_marginEnd="100dp" - tools:visibility="visible" /> - - <Button - android:id="@+id/loginGenericTextInputFormSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:enabled="false" - tools:ignore="RelativeOverlap" - tools:text="@string/login_set_email_submit" /> - - </FrameLayout> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_reset_password_2.xml b/vector/src/main/res/layout/fragment_login_reset_password_2.xml deleted file mode 100644 index 6a2dad569c..0000000000 --- a/vector/src/main/res/layout/fragment_login_reset_password_2.xml +++ /dev/null @@ -1,108 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/resetPasswordTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_primary" - tools:text="@string/login_reset_password_on" /> - - <TextView - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginTop="32dp" - android:text="@string/login_enter_your_email" - android:textColor="?vctr_content_primary" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/resetPasswordEmailTil" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:hint="@string/login_reset_password_email_hint" - app:errorEnabled="true"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/resetPasswordEmail" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textEmailAddress" - android:maxLines="1" /> - - </com.google.android.material.textfield.TextInputLayout> - - <TextView - android:id="@+id/loginNotice" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:gravity="start" - android:text="@string/login_reset_password_notice" - android:textColor="?vctr_content_secondary" /> - - <TextView - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginTop="16dp" - android:text="@string/login_choose_a_new_password" - android:textColor="?vctr_content_primary" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/passwordFieldTil" - style="@style/Widget.Vector.TextInputLayout.Password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:hint="@string/login_reset_password_password_hint" - app:errorEnabled="true" - app:errorIconDrawable="@null"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/passwordField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ems="10" - android:imeOptions="actionDone" - android:inputType="textPassword" - android:maxLines="1" - android:paddingEnd="48dp" - tools:ignore="RtlSymmetry" /> - - </com.google.android.material.textfield.TextInputLayout> - - <Button - android:id="@+id/resetPasswordSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/login_reset_password_submit" - tools:ignore="RelativeOverlap" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_reset_password_mail_confirmation_2.xml b/vector/src/main/res/layout/fragment_login_reset_password_mail_confirmation_2.xml deleted file mode 100644 index fa3aa74285..0000000000 --- a/vector/src/main/res/layout/fragment_login_reset_password_mail_confirmation_2.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_reset_password_mail_confirmation_title" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/resetPasswordMailConfirmationNotice" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_secondary" - tools:text="@string/login_reset_password_mail_confirmation_notice" /> - - <TextView - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_reset_password_mail_confirmation_notice_2" - android:textColor="?vctr_content_secondary" /> - - <Button - android:id="@+id/resetPasswordMailConfirmationSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="32dp" - android:text="@string/login_reset_password_mail_confirmation_submit" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_reset_password_success_2.xml b/vector/src/main/res/layout/fragment_login_reset_password_success_2.xml deleted file mode 100644 index d0e7a447cc..0000000000 --- a/vector/src/main/res/layout/fragment_login_reset_password_success_2.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_reset_password_success_title" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/resetPasswordSuccessNotice" - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_reset_password_success_notice" - android:textColor="?vctr_content_primary" /> - - <TextView - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_reset_password_success_notice_2" - android:textColor="?vctr_content_secondary" /> - - <Button - android:id="@+id/resetPasswordSuccessSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="32dp" - android:text="@string/login_reset_password_success_submit" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_server_selection_2.xml b/vector/src/main/res/layout/fragment_login_server_selection_2.xml deleted file mode 100644 index 9113e5e84b..0000000000 --- a/vector/src/main/res/layout/fragment_login_server_selection_2.xml +++ /dev/null @@ -1,147 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginServerTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_please_choose_a_server" - android:textColor="?vctr_content_primary" - tools:ignore="UnknownId" /> - - <TextView - android:id="@+id/loginServerText" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:gravity="start" - android:text="@string/login_server_text" - android:textColor="?vctr_content_secondary" /> - - <!-- Use a CheckableConstraintLayout to keep the pressed state when retrieving login flow --> - <im.vector.app.core.platform.CheckableConstraintLayout - android:id="@+id/loginServerChoiceMatrixOrg" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:background="@drawable/bg_login_server_selector" - android:contentDescription="@string/login_a11y_choose_matrix_org" - android:minHeight="80dp" - android:paddingStart="@dimen/layout_horizontal_margin" - android:paddingEnd="@dimen/layout_horizontal_margin"> - - <ImageView - android:id="@+id/loginServerChoiceMatrixOrgIcon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:importantForAccessibility="no" - android:src="@drawable/ic_logo_matrix_org" - app:layout_constraintBottom_toTopOf="@id/loginServerChoiceMatrixOrgText" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_chainStyle="packed" - app:tint="?vctr_content_primary" - tools:ignore="MissingPrefix" /> - - <TextView - android:id="@+id/loginServerChoiceMatrixOrgText" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="5dp" - android:gravity="start" - android:text="@string/login_server_matrix_org_text" - android:textColor="?vctr_content_secondary" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginServerChoiceMatrixOrgIcon" /> - - </im.vector.app.core.platform.CheckableConstraintLayout> - - <TextView - android:id="@+id/loginServerText2" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="5dp" - android:text="@string/login_if_you_re_not_sure_select_this_option" - android:textColor="?vctr_content_secondary" - android:textStyle="italic" /> - - <im.vector.app.core.platform.CheckableConstraintLayout - android:id="@+id/loginServerChoiceOther" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:background="@drawable/bg_login_server_selector" - android:contentDescription="@string/login_a11y_choose_other" - android:minHeight="80dp" - android:paddingStart="@dimen/layout_horizontal_margin" - android:paddingEnd="@dimen/layout_horizontal_margin"> - - <TextView - android:id="@+id/loginServerChoiceOtherTitle" - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="start" - android:text="@string/login_element_matrix_server_and_others" - android:textColor="?vctr_content_primary" - app:layout_constraintBottom_toTopOf="@id/loginServerChoiceOtherText" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_chainStyle="packed" /> - - <TextView - android:id="@+id/loginServerChoiceOtherText" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="4dp" - android:gravity="start" - android:text="@string/login_server_other_text" - android:textColor="?vctr_content_secondary" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginServerChoiceOtherTitle" /> - - </im.vector.app.core.platform.CheckableConstraintLayout> - - <TextView - android:id="@+id/loginServerChoiceEmsLearnMore" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="32dp" - android:paddingTop="16dp" - android:paddingBottom="16dp" - android:text="@string/login_server_modular_learn_more_about_ems" - android:textColor="?vctr_content_secondary" - app:layout_constraintBottom_toBottomOf="@id/loginServerChoiceEmsText" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="@id/loginServerChoiceEmsText" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_server_url_form_2.xml b/vector/src/main/res/layout/fragment_login_server_url_form_2.xml deleted file mode 100644 index 6038cfaedd..0000000000 --- a/vector/src/main/res/layout/fragment_login_server_url_form_2.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginServerUrlFormTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_server_url_form_common_notice" - android:textColor="?vctr_content_primary" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/loginServerUrlFormHomeServerUrlTil" - style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="26dp" - app:errorEnabled="true" - tools:hint="@string/login_server_url_form_modular_hint"> - - <AutoCompleteTextView - android:id="@+id/loginServerUrlFormHomeServerUrl" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/hs_url" - android:imeOptions="actionDone" - android:inputType="textUri" - android:maxLines="1" /> - - </com.google.android.material.textfield.TextInputLayout> - - <TextView - android:id="@+id/loginServerUrlFormClearHistory" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:paddingStart="16dp" - android:paddingEnd="0dp" - android:paddingBottom="16dp" - android:text="@string/login_clear_homeserver_history" - android:textColor="?colorPrimary" - android:visibility="invisible" - tools:visibility="visible" /> - - <Button - android:id="@+id/loginServerUrlFormSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="4dp" - android:text="@string/login_continue" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_signin_password_2.xml b/vector/src/main/res/layout/fragment_login_signin_password_2.xml deleted file mode 100644 index c70982f209..0000000000 --- a/vector/src/main/res/layout/fragment_login_signin_password_2.xml +++ /dev/null @@ -1,110 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_please_enter_your_password" - android:textColor="?vctr_content_primary" /> - - <ImageView - android:id="@+id/loginUserIcon" - android:layout_width="92dp" - android:layout_height="92dp" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:importantForAccessibility="no" - tools:ignore="MissingPrefix" - tools:src="@tools:sample/avatars" /> - - <TextView - android:id="@+id/loginWelcomeBack" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_secondary" - tools:text="Welcome back user!" /> - - <TextView - android:id="@+id/loginWelcomeBackWarning" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_unknown_user_warning" - android:textColor="@color/vector_warning_color_2" - android:visibility="gone" - tools:visibility="visible" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/passwordFieldTil" - style="@style/Widget.Vector.TextInputLayout.Password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:hint="@string/login_signup_password_hint" - app:errorEnabled="true" - app:errorIconDrawable="@null"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/passwordField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ems="10" - android:imeOptions="actionDone" - android:inputType="textPassword" - android:maxLines="1" - android:paddingEnd="48dp" - tools:ignore="RtlSymmetry" /> - - </com.google.android.material.textfield.TextInputLayout> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin"> - - <Button - android:id="@+id/forgetPasswordButton" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:paddingStart="0dp" - android:paddingEnd="0dp" - android:text="@string/auth_forgot_password" /> - - <Button - android:id="@+id/loginSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/auth_login" - tools:enabled="false" - tools:ignore="RelativeOverlap" /> - - </FrameLayout> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_signin_to_any_2.xml b/vector/src/main/res/layout/fragment_login_signin_to_any_2.xml deleted file mode 100644 index 61f565e093..0000000000 --- a/vector/src/main/res/layout/fragment_login_signin_to_any_2.xml +++ /dev/null @@ -1,127 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_primary" - tools:text="@string/login_signin_to" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/loginFieldTil" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:hint="@string/login_signin_username_hint" - app:errorEnabled="true"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/loginField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textEmailAddress" - android:maxLines="1" /> - - </com.google.android.material.textfield.TextInputLayout> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/passwordFieldTil" - style="@style/Widget.Vector.TextInputLayout.Password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:hint="@string/login_signup_password_hint" - app:errorEnabled="true" - app:errorIconDrawable="@null"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/passwordField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ems="10" - android:imeOptions="actionDone" - android:inputType="textPassword" - android:maxLines="1" - android:paddingEnd="48dp" - tools:ignore="RtlSymmetry" /> - - </com.google.android.material.textfield.TextInputLayout> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin"> - - <Button - android:id="@+id/forgetPasswordButton" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:paddingStart="0dp" - android:paddingEnd="0dp" - android:text="@string/auth_forgot_password" /> - - <Button - android:id="@+id/loginSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/auth_login" - tools:enabled="false" - tools:ignore="RelativeOverlap" /> - - </FrameLayout> - - <!-- Social Logins buttons --> - <LinearLayout - android:id="@+id/loginSocialLoginContainer" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="vertical" - android:visibility="gone" - tools:visibility="visible"> - - <TextView - android:id="@+id/loginSocialLoginHeader" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:gravity="center" - android:text="@string/login_social_continue" - android:textColor="?vctr_content_secondary" /> - - <im.vector.app.features.login.SocialLoginButtonsView - android:id="@+id/loginSocialLoginButtons" - android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:signMode="signin" /> - - </LinearLayout> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_signin_username_2.xml b/vector/src/main/res/layout/fragment_login_signin_username_2.xml deleted file mode 100644 index acba8dd7e1..0000000000 --- a/vector/src/main/res/layout/fragment_login_signin_username_2.xml +++ /dev/null @@ -1,84 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_please_enter_your_matrix_identifier" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/loginSubtitle" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_please_enter_your_matrix_identifier_help" - android:textColor="?vctr_content_secondary" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/loginFieldTil" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:hint="@string/login_signin_matrix_id_hint" - app:errorEnabled="true"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/loginField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textEmailAddress" - android:maxLines="1" /> - - </com.google.android.material.textfield.TextInputLayout> - - <Button - android:id="@+id/loginSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/_continue" - tools:enabled="false" /> - - <TextView - android:id="@+id/loginServerText3" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:gravity="start" - android:text="@string/login_enter_identifier_help" - android:textColor="?vctr_content_secondary" /> - - <Button - android:id="@+id/loginChooseAServer" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/login_choose_a_server" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> diff --git a/vector/src/main/res/layout/fragment_login_signup_password_2.xml b/vector/src/main/res/layout/fragment_login_signup_password_2.xml deleted file mode 100644 index 33730e92cb..0000000000 --- a/vector/src/main/res/layout/fragment_login_signup_password_2.xml +++ /dev/null @@ -1,99 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_please_choose_a_password" - android:textColor="?vctr_content_primary" /> - - <TextView - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:text="@string/login_your_matrix_identifier" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/loginMatrixIdentifier" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:textColor="?vctr_content_secondary" - tools:text="\@user:domain.org" /> - - <TextView - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/login_press_back_to_change" - android:textColor="?vctr_content_secondary" - android:textStyle="italic" /> - - <TextView - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_choose_a_password" - android:textColor="?vctr_content_primary" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/passwordFieldTil" - style="@style/Widget.Vector.TextInputLayout.Password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:hint="@string/login_signup_password_hint" - app:errorEnabled="true" - app:errorIconDrawable="@null"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/passwordField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ems="10" - android:imeOptions="actionDone" - android:inputType="textPassword" - android:maxLines="1" - android:paddingEnd="48dp" - tools:ignore="RtlSymmetry" /> - - </com.google.android.material.textfield.TextInputLayout> - - <Button - android:id="@+id/loginSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:text="@string/_continue" - tools:enabled="false" - tools:ignore="RelativeOverlap" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_signup_username_2.xml b/vector/src/main/res/layout/fragment_login_signup_username_2.xml deleted file mode 100644 index 6eeff13d86..0000000000 --- a/vector/src/main/res/layout/fragment_login_signup_username_2.xml +++ /dev/null @@ -1,105 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/login_fragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_please_choose_a_user_name" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/loginSubtitle" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:textColor="?vctr_content_secondary" - tools:text="@string/login_signup_to" /> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/loginFieldTil" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:hint="@string/login_signup_username_hint" - app:errorEnabled="true"> - - <com.google.android.material.textfield.TextInputEditText - android:id="@+id/loginField" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:inputType="textEmailAddress" - android:maxLines="1" /> - - </com.google.android.material.textfield.TextInputLayout> - - <TextView - android:id="@+id/loginChooseHelp" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/login_please_choose_a_user_name_help_2" - android:textColor="?vctr_content_secondary" - android:textStyle="italic" /> - - <Button - android:id="@+id/loginSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="8dp" - android:text="@string/_continue" - tools:enabled="false" /> - - <!-- SSO Option --> - <!-- Social Logins buttons --> - <LinearLayout - android:id="@+id/loginSocialLoginContainer" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="vertical" - android:visibility="gone" - tools:visibility="visible"> - - <TextView - android:id="@+id/loginSocialLoginHeader" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:gravity="center" - android:text="@string/login_social_continue" - android:textColor="?vctr_content_secondary" /> - - <im.vector.app.features.login.SocialLoginButtonsView - android:id="@+id/loginSocialLoginButtons" - android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:signMode="signin" /> - - </LinearLayout> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> diff --git a/vector/src/main/res/layout/fragment_login_splash_2.xml b/vector/src/main/res/layout/fragment_login_splash_2.xml deleted file mode 100644 index b7010ba4da..0000000000 --- a/vector/src/main/res/layout/fragment_login_splash_2.xml +++ /dev/null @@ -1,232 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground" - android:paddingStart="36dp" - android:paddingTop="@dimen/layout_vertical_margin" - android:paddingEnd="36dp" - android:paddingBottom="@dimen/layout_vertical_margin"> - - <!-- Strategy: Spaces are used to spread the remaining space, using weight --> - - <Space - android:id="@+id/loginSplashSpace1" - android:layout_width="match_parent" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/loginSplashLogoContainer" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_chainStyle="spread_inside" - app:layout_constraintVertical_weight="4" /> - - <LinearLayout - android:id="@+id/loginSplashLogoContainer" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:gravity="center_horizontal" - android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@id/loginSplashSpace2" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginSplashSpace1"> - - <ImageView - android:id="@+id/loginSplashLogo" - android:layout_width="64dp" - android:layout_height="64dp" - android:importantForAccessibility="no" - android:src="@drawable/element_logo_green" - android:transitionName="loginLogoTransition" /> - - <ImageView - android:id="@+id/logoType" - android:layout_width="wrap_content" - android:layout_height="44dp" - android:layout_marginTop="8dp" - android:contentDescription="@string/app_name" - android:src="@drawable/element_logotype" - app:tint="?colorSecondary" - tools:ignore="MissingPrefix" /> - - </LinearLayout> - - <Space - android:id="@+id/loginSplashSpace2" - android:layout_width="match_parent" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/loginSplashTitle" - app:layout_constraintTop_toBottomOf="@id/loginSplashLogoContainer" - app:layout_constraintVertical_weight="1" /> - - <TextView - android:id="@+id/loginSplashTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:gravity="center" - android:text="@string/login_splash_title" - android:textColor="?vctr_content_primary" - android:transitionName="loginTitleTransition" - app:layout_constraintBottom_toTopOf="@id/loginSplashSpace25" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginSplashSpace2" /> - - <Space - android:id="@+id/loginSplashSpace25" - android:layout_width="match_parent" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/loginSplashContent" - app:layout_constraintTop_toBottomOf="@id/loginSplashTitle" - app:layout_constraintVertical_weight="3" /> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/loginSplashContent" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintBottom_toTopOf="@id/loginSplashSpace3" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginSplashSpace25"> - - <ImageView - android:id="@+id/loginSplashPicto1" - android:layout_width="32dp" - android:layout_height="wrap_content" - android:layout_marginStart="2dp" - android:importantForAccessibility="no" - android:src="@drawable/ic_login_splash_message_circle" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@id/loginSplashText1" - app:tint="?vctr_content_secondary" - tools:ignore="MissingPrefix" /> - - <TextView - android:id="@+id/loginSplashText1" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:gravity="start" - android:text="@string/login_splash_text1" - android:textColor="?vctr_content_secondary" - app:layout_constraintBottom_toTopOf="@id/loginSplashText2" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/loginSplashPicto1" - app:layout_constraintTop_toTopOf="parent" /> - - <ImageView - android:id="@+id/loginSplashPicto2" - android:layout_width="32dp" - android:layout_height="wrap_content" - android:importantForAccessibility="no" - android:src="@drawable/ic_login_splash_lock" - app:layout_constraintStart_toStartOf="@id/loginSplashPicto1" - app:layout_constraintTop_toTopOf="@id/loginSplashText2" - app:tint="?vctr_content_secondary" - tools:ignore="MissingPrefix" /> - - <TextView - android:id="@+id/loginSplashText2" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:gravity="start" - android:text="@string/login_splash_text2" - android:textColor="?vctr_content_secondary" - app:layout_constraintBottom_toTopOf="@id/loginSplashText3" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/loginSplashText1" - app:layout_constraintTop_toBottomOf="@id/loginSplashText1" /> - - <ImageView - android:id="@+id/loginSplashPicto3" - android:layout_width="32dp" - android:layout_height="wrap_content" - android:importantForAccessibility="no" - android:src="@drawable/ic_login_splash_sliders" - app:layout_constraintStart_toStartOf="@id/loginSplashPicto1" - app:layout_constraintTop_toTopOf="@id/loginSplashText3" - app:tint="?vctr_content_secondary" - tools:ignore="MissingPrefix" /> - - <TextView - android:id="@+id/loginSplashText3" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:gravity="start" - android:text="@string/login_splash_text3" - android:textColor="?vctr_content_secondary" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/loginSplashText1" - app:layout_constraintTop_toBottomOf="@id/loginSplashText2" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <Space - android:id="@+id/loginSplashSpace3" - android:layout_width="match_parent" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/loginFormContent" - app:layout_constraintTop_toBottomOf="@id/loginSplashContent" - app:layout_constraintVertical_weight="3" /> - - <LinearLayout - android:id="@+id/loginFormContent" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@id/loginSplashSpace5" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginSplashSpace3"> - - <Button - android:id="@+id/loginSignupSigninSignUp" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:text="@string/login_create_a_new_account" /> - - <Button - android:id="@+id/loginSignupSigninSignIn" - style="@style/Widget.Vector.Button.Text.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="14dp" - android:text="@string/login_i_already_have_an_account" /> - - </LinearLayout> - - <Space - android:id="@+id/loginSplashSpace5" - android:layout_width="match_parent" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginFormContent" - app:layout_constraintVertical_weight="6" /> - - <TextView - android:id="@+id/loginSplashVersion" - style="@style/Widget.Vector.TextView.Caption" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:drawablePadding="12dp" - android:textColor="?vctr_content_secondary" - android:visibility="gone" - app:drawableStartCompat="@drawable/ic_debug_icon" - app:drawableTint="?colorPrimary" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" - tools:text="@string/settings_version" - tools:visibility="visible" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/fragment_login_sso_only_2.xml b/vector/src/main/res/layout/fragment_login_sso_only_2.xml deleted file mode 100644 index baa05ee30f..0000000000 --- a/vector/src/main/res/layout/fragment_login_sso_only_2.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginSignupSigninTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:textColor="?vctr_content_primary" - tools:ignore="UnknownId" - tools:text="@string/login_connect_to" /> - - <Button - android:id="@+id/loginSignupSigninSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="38dp" - android:text="@string/login_signin_sso" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_terms_2.xml b/vector/src/main/res/layout/fragment_login_terms_2.xml deleted file mode 100644 index 18b7df3880..0000000000 --- a/vector/src/main/res/layout/fragment_login_terms_2.xml +++ /dev/null @@ -1,74 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.constraintlayout.widget.ConstraintLayout - style="@style/LoginFormScrollView" - android:layout_height="match_parent" - tools:ignore="MissingConstraints"> - - <ImageView - style="@style/LoginLogo" - android:layout_marginTop="36dp" - android:importantForAccessibility="no" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/loginTermsTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="36dp" - android:layout_marginTop="52dp" - android:layout_marginEnd="36dp" - android:text="@string/login_terms_title" - android:textColor="?vctr_content_primary" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/loginLogo" - tools:ignore="UnknownId" /> - - <TextView - android:id="@+id/loginTermsNotice" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="12dp" - android:paddingStart="36dp" - android:paddingEnd="36dp" - android:text="@string/auth_accept_policies" - android:textColor="?vctr_content_secondary" - app:layout_constraintTop_toBottomOf="@id/loginTermsTitle" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/loginTermsPolicyList" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_marginTop="10dp" - android:layout_marginBottom="16dp" - android:paddingStart="16dp" - android:paddingEnd="16dp" - app:layout_constraintBottom_toTopOf="@id/loginTermsSubmit" - app:layout_constraintTop_toBottomOf="@id/loginTermsNotice" - tools:listitem="@layout/item_policy" /> - - <Button - android:id="@+id/loginTermsSubmit" - style="@style/Widget.Vector.Button.Login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="36dp" - android:text="@string/action_accept" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_login_wait_for_email_2.xml b/vector/src/main/res/layout/fragment_login_wait_for_email_2.xml deleted file mode 100644 index 15046afb61..0000000000 --- a/vector/src/main/res/layout/fragment_login_wait_for_email_2.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> - - <androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView"> - - <LinearLayout style="@style/LoginFormContainer"> - - <ImageView - style="@style/LoginLogo" - tools:ignore="ContentDescription" /> - - <TextView - android:id="@+id/loginWaitForEmailTitle" - style="@style/Widget.Vector.TextView.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:text="@string/login_wait_for_email_title" - android:textColor="?vctr_content_primary" /> - - <TextView - android:id="@+id/loginWaitForEmailNotice" - style="@style/Widget.Vector.TextView.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/layout_vertical_margin" - android:gravity="start" - android:textColor="?vctr_content_primary" - tools:text="@string/login_wait_for_email_notice_2" /> - - <TextView - android:id="@+id/loginWaitForEmailHelp" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="32dp" - android:gravity="start" - android:text="@string/login_wait_for_email_help" - android:textColor="?vctr_content_secondary" /> - - <ProgressBar - style="@style/Widget.Vector.ProgressBar.Horizontal" - android:layout_width="240dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginTop="32dp" - android:indeterminate="true" /> - - </LinearLayout> - - </androidx.core.widget.NestedScrollView> - -</FrameLayout> - diff --git a/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml new file mode 100644 index 0000000000..07c19b43eb --- /dev/null +++ b/vector/src/main/res/layout/fragment_new_chat_bottom_sheet.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/start_chat" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:drawablePadding="16dp" + android:paddingHorizontal="16dp" + android:paddingVertical="16dp" + android:text="@string/start_chat" + android:textColor="?vctr_content_primary" + android:textSize="16sp" + app:drawableStartCompat="@drawable/ic_chat" /> + + <TextView + android:id="@+id/create_room" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:drawablePadding="16dp" + android:paddingHorizontal="16dp" + android:paddingVertical="16dp" + android:text="@string/create_room" + android:textColor="?vctr_content_primary" + android:textSize="16sp" + app:drawableStartCompat="@drawable/ic_room_add" /> + + <TextView + android:id="@+id/explore_rooms" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:drawablePadding="16dp" + android:paddingHorizontal="16dp" + android:paddingVertical="16dp" + android:text="@string/explore_rooms" + android:textColor="?vctr_content_primary" + android:textSize="16sp" + app:drawableStartCompat="@drawable/ic_room_explore" /> + +</LinearLayout> diff --git a/vector/src/main/res/layout/fragment_new_home_detail.xml b/vector/src/main/res/layout/fragment_new_home_detail.xml index b0e7bf7634..85ff076364 100644 --- a/vector/src/main/res/layout/fragment_new_home_detail.xml +++ b/vector/src/main/res/layout/fragment_new_home_detail.xml @@ -49,6 +49,7 @@ app:layout_constraintTop_toBottomOf="@id/syncStateView"> <com.google.android.material.appbar.CollapsingToolbarLayout + android:id="@+id/collapsing_toolbar" style="@style/Widget.Vector.Material3.CollapsingToolbar.Medium" android:layout_width="match_parent" android:layout_height="?attr/collapsingToolbarLayoutMediumSize" @@ -61,14 +62,14 @@ android:layout_height="?attr/actionBarSize" android:elevation="0dp" app:layout_collapseMode="pin" - app:title="@string/all_chats"> + tools:title="@string/all_chats"> <ImageView android:id="@+id/avatar" android:layout_width="36dp" android:layout_height="36dp" - android:padding="6dp" android:contentDescription="@string/a11y_open_settings" + android:padding="6dp" tools:src="@sample/user_round_avatars" /> </com.google.android.material.appbar.MaterialToolbar> @@ -77,26 +78,13 @@ </com.google.android.material.appbar.AppBarLayout> - <androidx.constraintlayout.widget.ConstraintLayout + <androidx.fragment.app.FragmentContainerView + android:id="@+id/roomListContainer" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - - <androidx.fragment.app.FragmentContainerView - android:id="@+id/roomListContainer" - android:layout_width="match_parent" - android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/bottomNavigationView" - app:layout_constraintTop_toTopOf="parent" /> - - <com.google.android.material.bottomnavigation.BottomNavigationView - android:id="@+id/bottomNavigationView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" - app:menu="@menu/home_bottom_navigation" /> - - </androidx.constraintlayout.widget.ConstraintLayout> + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index 17d93eb98a..4b6f8f5895 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -1,101 +1,107 @@ <?xml version="1.0" encoding="utf-8"?> -<im.vector.app.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/stateView" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:colorBackground"> + android:layout_height="match_parent"> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/roomListView" + <im.vector.app.core.platform.StateView + android:id="@+id/stateView" android:layout_width="match_parent" android:layout_height="match_parent" - android:overScrollMode="always" - tools:itemCount="5" - tools:listitem="@layout/item_room" /> + android:background="?android:colorBackground"> - <im.vector.app.features.home.room.list.widget.NotifsFabMenuView - android:id="@+id/createChatFabMenu" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - app:layoutDescription="@xml/motion_scene_notifs_fab_menu" - tools:showPaths="true" - tools:visibility="visible" /> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/roomListView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:overScrollMode="always" + tools:itemCount="5" + tools:listitem="@layout/item_room" /> - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/createChatRoomButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom|end" - android:layout_marginEnd="16dp" - android:layout_marginBottom="16dp" - android:accessibilityTraversalBefore="@id/roomListView" - android:contentDescription="@string/a11y_create_direct_message" - android:scaleType="center" - android:src="@drawable/ic_fab_add_chat" - android:visibility="gone" - app:maxImageSize="34dp" - tools:layout_marginEnd="80dp" - tools:visibility="visible" /> - - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/createGroupRoomButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom|end" - android:layout_marginEnd="16dp" - android:layout_marginBottom="16dp" - android:accessibilityTraversalBefore="@id/roomListView" - android:contentDescription="@string/a11y_create_room" - android:src="@drawable/ic_fab_add_room" - android:visibility="gone" - app:maxImageSize="32dp" - tools:layout_marginEnd="144dp" - tools:visibility="visible" /> - - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="bottom|end"> - - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/newLayoutOpenSpacesButton" - style="@style/Widget.Vector.FloatingActionButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="20dp" - android:accessibilityTraversalAfter="@id/newLayoutCreateChatButton" - android:contentDescription="@string/a11y_open_spaces" - android:src="@drawable/ic_open_spaces" + <im.vector.app.features.home.room.list.widget.NotifsFabMenuView + android:id="@+id/createChatFabMenu" + android:layout_width="match_parent" + android:layout_height="match_parent" android:visibility="gone" - app:backgroundTint="?attr/vctr_toolbar_background" - app:fabSize="mini" - app:layout_constraintBottom_toTopOf="@id/newLayoutCreateChatButton" - app:layout_constraintEnd_toEndOf="@id/newLayoutCreateChatButton" - app:layout_constraintStart_toStartOf="@id/newLayoutCreateChatButton" - app:tint="?attr/colorPrimary" - tools:visibility="visible" - tools:targetApi="lollipop_mr1" /> - + app:layoutDescription="@xml/motion_scene_notifs_fab_menu" + tools:showPaths="true" + tools:visibility="visible" /> <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/newLayoutCreateChatButton" - style="@style/Widget.Vector.FloatingActionButton" + android:id="@+id/createChatRoomButton" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="bottom|end" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:accessibilityTraversalBefore="@id/roomListView" - android:contentDescription="@string/a11y_create_message" - android:src="@drawable/ic_new_chat" + android:contentDescription="@string/a11y_create_direct_message" + android:scaleType="center" + android:src="@drawable/ic_fab_add_chat" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - tools:visibility="visible" - tools:targetApi="lollipop_mr1" /> + app:maxImageSize="34dp" + tools:layout_marginEnd="80dp" + tools:visibility="visible" /> - </androidx.constraintlayout.widget.ConstraintLayout> + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/createGroupRoomButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp" + android:accessibilityTraversalBefore="@id/roomListView" + android:contentDescription="@string/a11y_create_room" + android:src="@drawable/ic_fab_add_room" + android:visibility="gone" + app:maxImageSize="32dp" + tools:layout_marginEnd="144dp" + tools:visibility="visible" /> -</im.vector.app.core.platform.StateView> + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="bottom|end"> + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/newLayoutOpenSpacesButton" + style="@style/Widget.Vector.FloatingActionButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="20dp" + android:accessibilityTraversalAfter="@id/newLayoutCreateChatButton" + android:contentDescription="@string/a11y_open_spaces" + android:src="@drawable/ic_open_spaces" + android:visibility="gone" + app:backgroundTint="?attr/vctr_toolbar_background" + app:fabSize="mini" + app:layout_constraintBottom_toTopOf="@id/newLayoutCreateChatButton" + app:layout_constraintEnd_toEndOf="@id/newLayoutCreateChatButton" + app:layout_constraintStart_toStartOf="@id/newLayoutCreateChatButton" + app:tint="?attr/colorPrimary" + tools:targetApi="lollipop_mr1" + tools:visibility="visible" /> + + + <com.google.android.material.floatingactionbutton.FloatingActionButton + android:id="@+id/newLayoutCreateChatButton" + style="@style/Widget.Vector.FloatingActionButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp" + android:accessibilityTraversalBefore="@id/roomListView" + android:contentDescription="@string/a11y_create_message" + android:src="@drawable/ic_new_chat" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:targetApi="lollipop_mr1" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </im.vector.app.core.platform.StateView> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/vector/src/main/res/layout/fragment_spaces_bottom_sheet.xml b/vector/src/main/res/layout/fragment_spaces_bottom_sheet.xml new file mode 100644 index 0000000000..27324c852f --- /dev/null +++ b/vector/src/main/res/layout/fragment_spaces_bottom_sheet.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <androidx.fragment.app.FragmentContainerView + android:id="@+id/space_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</FrameLayout> diff --git a/vector/src/main/res/layout/item_invites_count.xml b/vector/src/main/res/layout/item_invites_count.xml new file mode 100644 index 0000000000..6408749941 --- /dev/null +++ b/vector/src/main/res/layout/item_invites_count.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?vctr_toolbar_background" + android:clickable="true" + android:focusable="true" + + tools:viewBindingIgnore="true"> + + <im.vector.app.features.home.room.list.UnreadCounterBadgeView + android:id="@+id/invites_count_badge" + style="@style/Widget.Vector.TextView.Micro" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:gravity="center" + android:minWidth="16dp" + android:minHeight="16dp" + android:paddingStart="4dp" + android:paddingEnd="4dp" + android:textColor="?colorOnError" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:background="@drawable/bg_unread_highlight" + tools:text="4" + tools:visibility="visible" /> + + + <TextView + android:id="@+id/invites_count_title" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + android:layout_marginEnd="8dp" + android:text="@string/invites_title" + android:textAllCaps="true" + android:textColor="?colorSecondary" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/invites_count_badge" + app:layout_constraintTop_toTopOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/item_new_space.xml b/vector/src/main/res/layout/item_new_space.xml new file mode 100644 index 0000000000..367d69ce69 --- /dev/null +++ b/vector/src/main/res/layout/item_new_space.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<im.vector.app.core.platform.CheckableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root" + android:layout_width="match_parent" + android:layout_height="65dp" + android:background="@drawable/bg_space_item" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground" + tools:viewBindingIgnore="true"> + + <ImageView + android:id="@+id/avatar" + android:layout_width="42dp" + android:layout_height="42dp" + android:layout_gravity="center" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:duplicateParentState="true" + android:importantForAccessibility="no" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@sample/space_avatars" /> + + <TextView + android:id="@+id/name" + style="@style/Widget.Vector.TextView.Subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:ellipsize="end" + android:maxLines="1" + android:textColor="?vctr_content_primary" + android:textStyle="bold" + app:layout_constraintStart_toEndOf="@id/avatar" + app:layout_constraintEnd_toStartOf="@id/unread_counter" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="Element Corp" /> + + <im.vector.app.features.home.room.list.UnreadCounterBadgeView + android:id="@+id/unread_counter" + style="@style/Widget.Vector.TextView.Micro" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:gravity="center" + android:minWidth="16dp" + android:minHeight="16dp" + android:paddingStart="4dp" + android:paddingEnd="4dp" + android:textColor="?colorOnError" + android:visibility="gone" + app:layout_constraintEnd_toStartOf="@id/chevron" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + tools:background="@drawable/bg_unread_highlight" + tools:text="147" + tools:visibility="visible" /> + + <ImageView + android:id="@+id/chevron" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="21dp" + android:importantForAccessibility="no" + android:src="@drawable/ic_arrow_right" + android:visibility="visible" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:tint="?vctr_content_secondary" + tools:ignore="MissingPrefix" /> + +</im.vector.app.core.platform.CheckableConstraintLayout> diff --git a/vector/src/main/res/layout/item_new_space_add.xml b/vector/src/main/res/layout/item_new_space_add.xml new file mode 100644 index 0000000000..5a62abd740 --- /dev/null +++ b/vector/src/main/res/layout/item_new_space_add.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<im.vector.app.core.platform.CheckableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/itemGroupLayout" + android:layout_width="match_parent" + android:layout_height="65dp" + android:background="@drawable/bg_space_item" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground" + tools:viewBindingIgnore="true"> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="26dp" + android:background="?attr/vctr_system" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/plus" + android:layout_width="42dp" + android:layout_height="42dp" + android:layout_gravity="center" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:background="@drawable/rounded_rect_shape_8" + android:backgroundTint="#4D8D97A5" + android:duplicateParentState="true" + android:importantForAccessibility="no" + android:padding="10dp" + android:src="@drawable/ic_plus" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/groupNameView" + style="@style/Widget.Vector.TextView.Subtitle.Medium" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:layout_marginEnd="@dimen/layout_horizontal_margin" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/create_space" + android:textColor="?vctr_message_text_color" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/plus" + app:layout_constraintTop_toTopOf="parent" /> + +</im.vector.app.core.platform.CheckableConstraintLayout> diff --git a/vector/src/main/res/layout/item_new_space_list_header.xml b/vector/src/main/res/layout/item_new_space_list_header.xml new file mode 100644 index 0000000000..2c52304249 --- /dev/null +++ b/vector/src/main/res/layout/item_new_space_list_header.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + style="@style/TextAppearance.Vector.Body.Medium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/bg_space_item" + android:ellipsize="middle" + android:orientation="vertical" + android:padding="16dp" + android:singleLine="true" + android:text="@string/change_space" + android:textAllCaps="true" + android:textColor="?vctr_content_tertiary" + android:textSize="14sp" + tools:viewBindingIgnore="true" /> diff --git a/vector/src/main/res/layout/view_stub_webview.xml b/vector/src/main/res/layout/view_stub_webview.xml new file mode 100644 index 0000000000..b4b4dc5f55 --- /dev/null +++ b/vector/src/main/res/layout/view_stub_webview.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<WebView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/vector/src/main/res/menu/menu_new_home.xml b/vector/src/main/res/menu/menu_new_home.xml index e893624d54..6cd52e5608 100644 --- a/vector/src/main/res/menu/menu_new_home.xml +++ b/vector/src/main/res/menu/menu_new_home.xml @@ -3,6 +3,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + <item + android:id="@+id/menu_home_layout_settings" + android:title="@string/home_layout_preferences"/> + <item android:id="@+id/menu_home_invite_friends" android:title="@string/invite_friends" @@ -37,6 +41,6 @@ android:icon="@drawable/ic_home_search" android:title="@string/home_filter_placeholder_home" app:iconTint="?vctr_content_secondary" - app:showAsAction="always" /> + app:showAsAction="ifRoom" /> </menu> diff --git a/vector/src/main/res/menu/room_list.xml b/vector/src/main/res/menu/room_list.xml index 60ffdcd87b..ad375d241b 100644 --- a/vector/src/main/res/menu/room_list.xml +++ b/vector/src/main/res/menu/room_list.xml @@ -1,9 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> <item android:id="@+id/menu_home_mark_all_as_read" android:icon="@drawable/ic_material_done" android:title="@string/action_mark_all_as_read" /> -</menu> \ No newline at end of file + <item + android:id="@+id/menu_home_dialpad" + android:title="@string/call_dial_pad_title" + android:visible="false" + app:showAsAction="never" + tools:visible="true" /> +</menu> diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json index ed8c479711..b233a455d2 100644 --- a/vector/src/main/res/raw/emoji_picker_datasource.json +++ b/vector/src/main/res/raw/emoji_picker_datasource.json @@ -1 +1 @@ -{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","melting-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","smiling-face-with-open-hands","face-with-hand-over-mouth","face-with-open-eyes-and-hand-over-mouth","face-with-peeking-eye","shushing-face","thinking-face","saluting-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","dotted-line-face","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","face-with-crossedout-eyes","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","face-with-diagonal-mouth","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","face-holding-back-tears","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","rightwards-hand","leftwards-hand","palm-down-hand","palm-up-hand","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","hand-with-index-finger-and-thumb-crossed","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","index-pointing-at-the-viewer","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","heart-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","biting-lip","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","person-with-crown","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","pregnant-man","pregnant-person","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","troll","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","coral","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","lotus","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind","empty-nest","nest-with-eggs"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","beans","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","pouring-liquid","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","jar","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","playground-slide","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","wheel","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","ring-buoy","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","hamsa","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","mirror-ball","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","low-battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","crutch","stethoscope","xray","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","bubbles","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard","identification-card"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","heavy-equals-sign","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["face","grin","smile","happy","joy",":D"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["face","mouth","open","smile","happy","joy","haha",":D",":)","funny"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["eye","face","mouth","open","smile","happy","joy","funny","haha","laugh","like",":D",":)"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["eye","face","grin","smile","happy","joy","kawaii"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["face","laugh","mouth","satisfied","smile","happy","joy","lol","haha","glad","XD"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["cold","face","open","smile","sweat","hot","happy","laugh","relief"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["face","floor","laugh","rofl","rolling","rotfl","laughing","lol","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","joy","laugh","tear","cry","tears","weep","happy","happytears","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["face","upside-down","upside_down_face","flipped","silly","smile"]},"melting-face":{"a":"⊛ Melting Face","b":"1FAE0","j":["disappear","dissolve","liquid","melt","hot","heat"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["face","wink","happy","mischievous","secret",";)","smile","eye"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","face","smile","happy","flushed","crush","embarrassed","shy","joy"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["angel","face","fantasy","halo","innocent","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["adore","crush","hearts","in love","face","love","like","affection","valentines","infatuation"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["eye","face","love","smile","smiling face with heart-eyes","smiling_face_with_heart_eyes","like","affection","valentines","infatuation","crush","heart"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["eyes","face","grinning","star","star-struck","starry-eyed","star_struck","smile","starry"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["face","kiss","love","like","3","valentines","infatuation"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["face","outlined","relaxed","smile","blush","massage","happiness"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","eye","face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["eye","face","kiss","smile","affection","valentines","infatuation"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["grateful","proud","relieved","smiling","tear","touched","sad","cry","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["delicious","face","savouring","smile","yum","happy","joy","tongue","silly","yummy","nom"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["face","tongue","prank","childish","playful","mischievous","smile"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["eye","face","joke","tongue","wink","prank","childish","playful","mischievous","smile"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","goofy","large","small","face","crazy"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["eye","face","horrible","taste","tongue","prank","playful","mischievous","smile"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["face","money","money-mouth face","mouth","money_mouth_face","rich","dollar"]},"smiling-face-with-open-hands":{"a":"Smiling Face with Open Hands","b":"1F917","j":["face","hug","hugging","open hands","smiling face","hugging_face","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"face-with-open-eyes-and-hand-over-mouth":{"a":"⊛ Face with Open Eyes and Hand over Mouth","b":"1FAE2","j":["amazement","awe","disbelief","embarrass","scared","surprise","silence","secret","shock"]},"face-with-peeking-eye":{"a":"⊛ Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare","scared","frightening","embarrassing"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["quiet","shush","face","shhh"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["face","thinking","hmmm","think","consider"]},"saluting-face":{"a":"⊛ Saluting Face","b":"1FAE1","j":["ok","salute","sunny","troops","yes","respect"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","mouth","zipper","zipper-mouth face","zipper_mouth_face","sealed","secret"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["distrust","skeptic","disapproval","disbelief","mild surprise","scepticism","face","surprise"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan","face","meh","neutral","indifference",":|"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["expressionless","face","inexpressive","meh","unexpressive","indifferent","-_-","deadpan"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","mouth","quiet","silent","hellokitty"]},"dotted-line-face":{"a":"⊛ Dotted Line Face","b":"1FAE5","j":["depressed","disappear","hide","introvert","invisible","lonely","isolation","depression"]},"face-in-clouds":{"a":"Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in the fog","head in clouds","shower","steam","dream"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","gasp","groan","relief","whisper","whistle","relieve","tired","sigh"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"face-with-crossedout-eyes":{"a":"Face with Crossed-out Eyes","b":"1F635","j":["crossed-out eyes","dead","face","face with crossed-out eyes","knocked out","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","hypnotized","spiral","trouble","whoa","sick","ill","confused","nauseous","nausea"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"face-with-diagonal-mouth":{"a":"⊛ Face with Diagonal Mouth","b":"1FAE4","j":["disappointed","meh","skeptical","unsure","skeptic","confuse","frustrated","indifferent"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":["face","worried","concern","nervous",":("]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frown","frowning","disappointed","sad","upset"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["face","frown","sad","upset"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","mouth","open","sympathy","surprise","impressed","wow","whoa",":O"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["face","hushed","stunned","surprised","woo","shh"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["astonished","face","shocked","totally","xox","surprised","poisoned"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["dazed","face","flushed","blush","shy","flattered"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","puppy eyes","face"]},"face-holding-back-tears":{"a":"⊛ Face Holding Back Tears","b":"1F979","j":["angry","cry","proud","resist","sad","touched","gratitude"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","love","lust","sacred heart","passionate","enthusiastic"]},"mending-heart":{"a":"Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","recovering","recuperating","well","broken heart","bandage","wounded"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"rightwards-hand":{"a":"⊛ Rightwards Hand","b":"1FAF1","j":["hand","right","rightward","palm","offer"]},"leftwards-hand":{"a":"⊛ Leftwards Hand","b":"1FAF2","j":["hand","left","leftward","palm","offer"]},"palm-down-hand":{"a":"⊛ Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo","palm"]},"palm-up-hand":{"a":"⊛ Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer","lift","demand"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["hand","OK","fingers","limbs","perfect","ok","okay"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["fingers","hand gesture","interrogation","pinched","sarcastic","size","tiny","small"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small amount","tiny","small","size"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["hand","v","victory","fingers","ohyeah","peace","two"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["cross","finger","hand","luck","good","lucky"]},"hand-with-index-finger-and-thumb-crossed":{"a":"⊛ Hand with Index Finger and Thumb Crossed","b":"1FAF0","j":["expensive","heart","love","money","snap"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["hand","ILY","love-you gesture","love_you_gesture","fingers","gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["finger","hand","horns","rock-on","fingers","evil_eye","sign_of_horns","rock_on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["call","hand","hands","gesture","shaka"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["backhand","finger","hand","index","point","direction","fingers","left"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["backhand","finger","hand","index","point","fingers","direction","right"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["backhand","finger","hand","point","up","fingers","direction"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["finger","hand","fingers","rude","middle","flipping"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["backhand","down","finger","hand","point","fingers","direction"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["finger","hand","index","point","up","fingers","direction"]},"index-pointing-at-the-viewer":{"a":"⊛ Index Pointing at the Viewer","b":"1FAF5","j":["point","you","recruit"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["+1","hand","thumb","up","thumbsup","yes","awesome","good","agree","accept","cool","like"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","down","hand","thumb","thumbsdown","no","dislike"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["clenched","fist","hand","punch","fingers","grasp"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["clenched","fist","hand","punch","angry","violence","hit","attack"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["fist","right-facing fist","rightwards","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["clap","hand","hands","praise","applause","congrats","yay"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["celebration","gesture","hand","hooray","raised","yea","hands"]},"heart-hands":{"a":"⊛ Heart Hands","b":"1FAF6","j":["love","appreciation","support"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"biting-lip":{"a":"⊛ Biting Lip","b":"1FAE6","j":["anxious","fear","flirting","nervous","uncomfortable","worried","flirt","sexy","pain","worry"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard","facial hair"]},"woman-beard":{"a":"Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard","facial hair"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"person-with-crown":{"a":"⊛ Person with Crown","b":"1FAC5","j":["monarch","noble","regal","royalty","power"]},"prince":{"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"]},"princess":{"a":"Princess","b":"1F478","j":["fairy tale","fantasy","girl","woman","female","blond","crown","royal","queen"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["turban","headdress"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["man","turban","male","indian","hinduism","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","woman","female","indian","hinduism","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["cap","gua pi mao","hat","person","skullcap","man_with_skullcap","male","boy","chinese"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["headscarf","hijab","mantilla","tichel","bandana","head kerchief","female"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["groom","person","tuxedo","man_in_tuxedo","couple","marriage","wedding"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","formal","fashion"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["tuxedo","woman","formal","fashion"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","person","veil","wedding","bride_with_veil","couple","marriage","woman"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","veil","wedding","marriage"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["veil","woman","wedding","marriage"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["pregnant","woman","baby"]},"pregnant-man":{"a":"⊛ Pregnant Man","b":"1FAC3","j":["belly","bloated","full","pregnant","baby"]},"pregnant-person":{"a":"⊛ Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant","baby"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["baby","breast","breast-feeding","nursing","breast_feeding"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["baby","feeding","nursing","woman","birth","food"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["baby","feeding","man","nursing","birth","food"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["baby","feeding","nursing","person","birth","food"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["angel","baby","face","fairy tale","fantasy","heaven","wings","halo"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["celebration","Christmas","claus","father","santa","festival","man","male","xmas","father christmas"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["celebration","Christmas","claus","mother","Mrs.","woman","female","xmas","mother christmas"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["good","hero","heroine","superpower","marvel"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["good","hero","man","superpower","male","superpowers"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["good","hero","heroine","superpower","woman","female","superpowers"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["criminal","evil","superpower","villain","marvel"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["criminal","evil","man","superpower","villain","male","bad","hero","superpowers"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["criminal","evil","superpower","villain","woman","female","bad","heroine","superpowers"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","sorceress","witch","wizard","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","wizard","man","male","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["sorceress","witch","woman","female","mage"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Oberon","Puck","Titania","wings","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["Oberon","Puck","man","male"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","woman","female"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["Dracula","undead","blood","twilight"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["Dracula","undead","man","male","dracula"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["undead","woman","female"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","merman","merwoman","sea"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["Triton","man","male","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["merwoman","woman","female","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["magical","LOTR style"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["magical","man","male"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["magical","woman","female"]},"genie":{"a":"Genie","b":"1F9DE","j":["djinn","(non-human color)","magical","wishes"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["djinn","man","male"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["djinn","woman","female"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["undead","walking dead","(non-human color)","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["undead","walking dead","man","male","dracula"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["undead","walking dead","woman","female"]},"troll":{"a":"⊛ Troll","b":"1F9CC","j":["fairy tale","fantasy","monster","mystical"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["face","massage","salon","relax"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["face","man","massage","male","boy","head"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["face","massage","woman","female","girl","head"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["barber","beauty","haircut","parlor","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["haircut","man","male","boy"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["haircut","woman","female","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["hike","walk","walking","move"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["hike","man","walk","human","feet","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["hike","walk","woman","human","feet","steps","female"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["stand","standing","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["kneeling","man","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["kneeling","woman","respectful","pray"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["accessibility","blind","man","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["accessibility","blind","woman","woman_with_probing_cane"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","wheelchair","disability"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["accessibility","man","wheelchair","disability"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["accessibility","wheelchair","woman","disability"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","wheelchair","disability"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["accessibility","man","wheelchair","disability"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["accessibility","wheelchair","woman","disability"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["marathon","running","move"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["man","marathon","racing","running","walking","exercise","race"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["marathon","racing","running","woman","walking","exercise","race","female"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","dancing","woman","female","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dance","dancing","man","male","boy","fun","dancer"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","person","suit","man_in_suit_levitating","levitate","hover","jump"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["bunny ear","dancer","partying","perform","costume"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["bunny ear","dancer","men","partying","male","bunny","boys"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["bunny ear","dancer","partying","women","female","bunny","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["sauna","steam room","hamam","steambath","relax","spa"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","steam room","male","man","spa","steamroom"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","steam room","female","woman","spa","steamroom"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["climber","sport"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["climber","sports","hobby","man","male","rock"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["climber","sports","hobby","woman","female","rock"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["fencer","fencing","sword","sports"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["horse","jockey","racehorse","racing","animal","betting","competition","gambling","luck"]},"skier":{"a":"Skier","b":"26F7","j":["ski","snow","sports","winter"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["ski","snow","snowboard","sports","winter"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["ball","golf","sports","business"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","man","sport"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["golf","woman","sports","business","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["surfing","sport","sea"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["man","surfing","sports","ocean","sea","summer","beach"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["surfing","woman","sports","ocean","sea","summer","beach","female"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["boat","rowboat","sport","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["boat","man","rowboat","sports","hobby","water","ship"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["boat","rowboat","woman","sports","hobby","water","ship","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["man","swim","sports","exercise","human","athlete","water","summer"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","woman","sports","exercise","human","athlete","water","summer","female"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["ball","sports","human"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["ball","man","sport"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["ball","woman","sports","human","female"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["lifter","weight","sports","training","exercise"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","weight lifter","sport"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["weight lifter","woman","sports","training","exercise","female"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["bicycle","biking","cyclist","sport","move"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["bicycle","biking","cyclist","man","sports","bike","exercise","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["bicycle","biking","cyclist","woman","sports","bike","exercise","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["bicycle","bicyclist","bike","cyclist","mountain","sport","move"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["bicycle","bike","cyclist","man","mountain","transportation","sports","human","race"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["bicycle","bike","biking","cyclist","mountain","woman","transportation","sports","human","race","female"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastics","sport","gymnastic"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","gymnastics","woman"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["wrestle","wrestler","sport"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["men","wrestle","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["women","wrestle","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["polo","water","sport"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["water polo","woman","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["ball","handball","sport"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["handball","man","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["handball","woman","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","multitask","skill","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["juggling","man","multitask","juggle","balance","skill"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["juggling","multitask","woman","juggle","balance","skill"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","man","male","serenity","zen","mindfulness"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","serenity","zen","mindfulness"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","clean","shower","bathroom"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["hotel","sleep","bed","rest"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["couple","hand","hold","holding hands","person","friendship"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["couple","hand","holding hands","women","pair","friendship","love","like","female","people","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["couple","hand","hold","holding hands","man","woman","pair","people","human","love","date","dating","like","affection","valentines","marriage"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["couple","Gemini","holding hands","man","men","twins","zodiac","pair","love","like","bromance","friendship","people","human"]},"kiss":{"a":"Kiss","b":"1F48F","j":["couple","pair","valentines","love","like","dating","marriage"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","woman","love"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","pair","valentines","love","like","dating","marriage"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["couple","kiss","woman","pair","valentines","love","like","dating","marriage"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["couple","love","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","woman"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["couple","couple with heart","love","woman","pair","like","affection","human","dating","valentines","marriage"]},"family":{"a":"Family","b":"1F46A","j":["home","parents","child","mom","dad","father","mother","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["boy","family","man","woman","love"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","child"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","man","woman","home","parents","people","human","children"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","man","woman","home","parents","people","human","children"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","children"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parents","people","human","children"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["boy","family","man","home","parent","people","human","child"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parent","people","human","children"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["family","girl","man","home","parent","people","human","child"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parent","people","human","children"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parent","people","human","children"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["boy","family","woman","home","parent","people","human","child"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parent","people","human","children"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["family","girl","woman","home","parent","people","human","child"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parent","people","human","children"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parent","people","human","children"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["face","head","silhouette","speak","speaking","user","person","human","sing","say","talk"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","silhouette","user","person","human"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["bust","silhouette","user","person","human","group","team"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["goodbye","hello","hug","thanks","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","footprint","print","feet","tracking","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["face","monkey","animal","nature","circus"]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["dog","face","pet","animal","friend","nature","woof","puppy","faithful"]},"dog":{"a":"Dog","b":"1F415","j":["pet","animal","nature","friend","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","blind","guide","animal"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["accessibility","assistance","dog","service","blind","animal"]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"]},"wolf":{"a":"Wolf","b":"1F43A","j":["face","animal","nature","wild"]},"fox":{"a":"Fox","b":"1F98A","j":["face","animal","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","sly","animal","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["cat","face","pet","animal","meow","nature","kitten"]},"cat":{"a":"Cat","b":"1F408","j":["pet","animal","meow","cats"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","cat","unlucky","superstition","luck"]},"lion":{"a":"Lion","b":"1F981","j":["face","Leo","zodiac","animal","nature"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["face","tiger","animal","cat","danger","wild","nature","roar"]},"tiger":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["face","horse","animal","brown","nature"]},"horse":{"a":"Horse","b":"1F40E","j":["equestrian","racehorse","racing","animal","gamble","luck"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["face","animal","nature","mystical"]},"zebra":{"a":"Zebra","b":"1F993","j":["stripe","animal","nature","stripes","safari"]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"]},"bison":{"a":"Bison","b":"1F9AC","j":["buffalo","herd","wisent","ox"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["cow","face","beef","ox","animal","nature","moo","milk"]},"ox":{"a":"Ox","b":"1F402","j":["bull","Taurus","zodiac","animal","cow","beef"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["buffalo","water","animal","nature","ox","cow"]},"cow":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["face","pig","animal","oink","nature"]},"pig":{"a":"Pig","b":"1F416","j":["sow","animal","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["face","nose","pig","animal","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","male","sheep","zodiac","animal","nature"]},"ewe":{"a":"Ewe","b":"1F411","j":["female","sheep","animal","nature","wool","shipit"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","zodiac","animal","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hump","animal","hot","desert"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["bactrian","camel","hump","two-hump camel","two_hump_camel","animal","nature","hot","desert"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","guanaco","vicuña","wool","animal","nature"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["spots","animal","nature","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["extinction","large","tusk","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["face","mouse","animal","nature","cheese_wedge","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["face","pet","animal","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","face","pet","rabbit","animal","nature","spring","magic"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","pet","animal","nature","magic","spring"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["squirrel","animal","nature","rodent"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["dam","animal","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["spiny","animal","nature"]},"bat":{"a":"Bat","b":"1F987","j":["vampire","animal","nature","blind"]},"bear":{"a":"Bear","b":"1F43B","j":["face","animal","nature","wild"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["arctic","bear","white","animal"]},"koala":{"a":"Koala","b":"1F428","j":["face","marsupial","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["face","animal","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["lazy","slow","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","playful","animal"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["Australia","joey","jump","marsupial","animal","nature","australia","hop"]},"badger":{"a":"Badger","b":"1F9A1","j":["honey badger","pester","animal","nature","honey"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["feet","paw","print","animal","tracking","footprints","dog","cat","pet"]},"turkey":{"a":"Turkey","b":"1F983","j":["bird","animal"]},"chicken":{"a":"Chicken","b":"1F414","j":["bird","animal","cluck","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["bird","animal","nature","chicken"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["baby","bird","chick","hatching","animal","chicken","egg","born"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["baby","bird","chick","animal","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["baby","bird","chick","front-facing baby chick","front_facing_baby_chick","animal","chicken"]},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"]},"penguin":{"a":"Penguin","b":"1F427","j":["bird","animal","nature"]},"dove":{"a":"Dove","b":"1F54A","j":["bird","fly","peace","animal"]},"eagle":{"a":"Eagle","b":"1F985","j":["bird","animal","nature"]},"duck":{"a":"Duck","b":"1F986","j":["bird","animal","nature","mallard"]},"swan":{"a":"Swan","b":"1F9A2","j":["bird","cygnet","ugly duckling","animal","nature"]},"owl":{"a":"Owl","b":"1F989","j":["bird","wise","animal","nature","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","large","Mauritius","animal","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["bird","flight","light","plumage","fly"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["flamboyant","tropical","animal"]},"peacock":{"a":"Peacock","b":"1F99A","j":["bird","ostentatious","peahen","proud","animal","nature"]},"parrot":{"a":"Parrot","b":"1F99C","j":["bird","pirate","talk","animal","nature"]},"frog":{"a":"Frog","b":"1F438","j":["face","animal","nature","croak","toad"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"]},"turtle":{"a":"Turtle","b":"1F422","j":["terrapin","tortoise","animal","slow","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["bearer","Ophiuchus","serpent","zodiac","animal","evil","nature","hiss","python"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","face","fairy tale","animal","myth","nature","chinese","green"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","animal","myth","nature","chinese","green"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["brachiosaurus","brontosaurus","diplodocus","animal","nature","dinosaur","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["Tyrannosaurus Rex","t_rex","animal","nature","dinosaur","tyrannosaurus","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["face","spouting","whale","animal","nature","sea","ocean"]},"whale":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["flipper","animal","nature","fish","sea","ocean","fins","beach"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea lion","animal","creature","sea"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","zodiac","animal","food","nature"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["fish","tropical","animal","swim","ocean","beach","nemo"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","animal","nature","food","sea","ocean"]},"shark":{"a":"Shark","b":"1F988","j":["fish","animal","nature","sea","ocean","jaws","fins","beach"]},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["shell","spiral","nature","sea","beach"]},"coral":{"a":"⊛ Coral","b":"1FAB8","j":["ocean","reef","sea"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"lotus":{"a":"⊛ Lotus","b":"1FAB7","j":["Buddhism","flower","Hinduism","India","purity","Vietnam","calm","meditation"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"empty-nest":{"a":"⊛ Empty Nest","b":"1FAB9","j":["nesting","bird"]},"nest-with-eggs":{"a":"⊛ Nest with Eggs","b":"1FABA","j":["nesting","bird"]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","grape","food","wine"]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["fruit","orange","food","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["citrus","fruit","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"]},"mango":{"a":"Mango","b":"1F96D","j":["fruit","tropical","food"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["apple","fruit","red","mac","school"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["apple","fruit","green","nature"]},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"]},"cherries":{"a":"Cherries","b":"1F352","j":["berries","cherry","fruit","red","food"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["berry","fruit","food","nature"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["berry","bilberry","blue","blueberry","fruit"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","fruit","kiwi"]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["palm","piña colada","fruit","nature","food"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["aubergine","vegetable","nature","food"]},"potato":{"a":"Potato","b":"1F954","j":["food","vegetable","tuber","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["corn","ear","maize","maze","food","vegetable","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["hot","pepper","food","spicy","chilli","chili"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["capsicum","pepper","vegetable","fruit","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["bok choy","cabbage","kale","lettuce","food","vegetable","plant"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["wild cabbage","fruit","food","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["flavoring","food","spice","cook"]},"onion":{"a":"Onion","b":"1F9C5","j":["flavoring","cook","food","spice"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut","peanut","vegetable"]},"beans":{"a":"⊛ Beans","b":"1FAD8","j":["food","kidney","legume"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","china","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"pouring-liquid":{"a":"⊛ Pouring Liquid","b":"1FAD7","j":["drink","empty","glass","spill","cup","water"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["juice","soda","malt","soft drink","water","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["bubble","milk","pearl","tea","taiwan","boba","milk tea","straw"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["beverage","box","juice","straw","sweet","drink"]},"mate":{"a":"Mate","b":"1F9C9","j":["drink","tea","beverage"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","ice cube","iceberg","water"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["hashi","jeotgarak","kuaizi","food"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["cooking","fork","knife","plate","food","eat","meal","lunch","dinner","restaurant"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","cutlery","fork","knife","kitchen"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","cutlery","kitchen"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["cooking","hocho","knife","tool","weapon","blade","cutlery","kitchen"]},"jar":{"a":"⊛ Jar","b":"1FAD9","j":["condiment","container","empty","sauce","store"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["Aquarius","cooking","drink","jug","zodiac","vase","jar"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Africa","earth","Europe","globe","globe showing Europe-Africa","world","globe_showing_europe_africa","international"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["Americas","earth","globe","globe showing Americas","world","USA","international"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["Asia","Australia","earth","globe","globe showing Asia-Australia","world","globe_showing_asia_australia","east","international"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","globe","meridians","world","international","internet","interweb","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["map","world","location","direction"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["Japan","map","map of Japan","nation","country","japanese","asia"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","navigation","orienteering"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["cold","mountain","snow","snow-capped mountain","snow_capped_mountain","photo","nature","environment","winter"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","nature","environment"]},"volcano":{"a":"Volcano","b":"1F30B","j":["eruption","mountain","photo","nature","disaster"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","mountain","photo","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["beach","umbrella","weather","summer","sunny","sand","mojito"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","warm","saharah"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["desert","island","photo","tropical","mojito"]},"national-park":{"a":"National Park","b":"1F3DE","j":["park","photo","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["photo","place","sports","concert","venue"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["classical","art","culture","history"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["construction","wip","working","progress"]},"brick":{"a":"Brick","b":"1F9F1","j":["bricks","clay","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["boulder","heavy","solid","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","lumber","timber","nature","trunk"]},"hut":{"a":"Hut","b":"1F6D6","j":["house","roundhouse","yurt","structure"]},"houses":{"a":"Houses","b":"1F3D8","j":["buildings","photo"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["derelict","house","abandon","evict","broken","building"]},"house":{"a":"House","b":"1F3E0","j":["home","building"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["garden","home","house","plant","nature"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["Japanese","Japanese post office","post","building","envelope","communication"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["European","post","building","email"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["doctor","medicine","building","health","surgery"]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["hotel","love","like","affection","dating"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["convenience","store","building","shopping","groceries"]},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["department","store","building","shopping","mall"]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","Japanese","photo","building"]},"castle":{"a":"Castle","b":"1F3F0","j":["European","building","royalty","history"]},"wedding":{"a":"Wedding","b":"1F492","j":["chapel","romance","love","like","affection","couple","marriage","bride","groom"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["Tokyo","tower","photo","japanese"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["liberty","statue","american","newyork"]},"church":{"a":"Church","b":"26EA","j":["Christian","cross","religion","building","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","Muslim","religion","worship","minaret"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["hindu","temple","religion"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","Jewish","religion","temple","judaism","worship","jewish"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["religion","shinto","shrine","temple","japan","kyoto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","Muslim","religion","mecca","mosque"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"]},"tent":{"a":"Tent","b":"26FA","j":["camping","photo","outdoors"]},"foggy":{"a":"Foggy","b":"1F301","j":["fog","photo","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","star","evening","city","downtown"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["city","photo","night life","urban"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","mountain","sun","sunrise","view","vacation","photo"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","sun","view","vacation","photo"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["city","dusk","evening","landscape","sunset","photo","sky","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","sun","photo","good morning","dawn"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["bridge","night","photo","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["hot","hotsprings","springs","steaming","bath","warm","relax"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["carousel","horse","photo","carnival"]},"playground-slide":{"a":"⊛ Playground Slide","b":"1F6DD","j":["amusement park","play","fun","park"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["amusement park","ferris","wheel","photo","carnival","londoneye"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["amusement park","coaster","roller","carnival","playground","photo","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["barber","haircut","pole","hair","salon","style"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["circus","tent","festival","carnival","party"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["engine","railway","steam","train","transportation","vehicle"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","railway","train","tram","trolleybus","transportation","vehicle"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["high-speed train","railway","shinkansen","speed","train","high_speed_train","transportation","vehicle"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["bullet","railway","shinkansen","speed","train","transportation","vehicle","fast","public","travel"]},"train":{"a":"Train","b":"1F686","j":["railway","transportation","vehicle"]},"metro":{"a":"Metro","b":"1F687","j":["subway","transportation","blue-square","mrt","underground","tube"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["railway","transportation","vehicle"]},"station":{"a":"Station","b":"1F689","j":["railway","train","transportation","vehicle","public"]},"tram":{"a":"Tram","b":"1F68A","j":["trolleybus","transportation","vehicle"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","mountain","railway","transportation","vehicle"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","tram","trolleybus","transportation","vehicle","carriage","public","travel"]},"bus":{"a":"Bus","b":"1F68C","j":["vehicle","car","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","oncoming","vehicle","transportation"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","tram","trolley","bart","transportation","vehicle"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","car","transportation"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["vehicle","health","911","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["engine","fire","truck","transportation","cars","vehicle"]},"police-car":{"a":"Police Car","b":"1F693","j":["car","patrol","police","vehicle","cars","transportation","law","legal","enforcement"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","oncoming","police","vehicle","law","legal","enforcement","911"]},"taxi":{"a":"Taxi","b":"1F695","j":["vehicle","uber","cars","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["oncoming","taxi","vehicle","cars","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","red","transportation","vehicle"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["automobile","car","oncoming","vehicle","transportation"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["recreational","sport utility","transportation","vehicle"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["pick-up","pickup","truck","car","transportation"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["delivery","truck","cars","transportation"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["lorry","semi","truck","vehicle","cars","transportation","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","racing","sports","race","fast","formula","f1"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","race","sports","fast"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["motor","scooter","vehicle","vespa","sasha"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["tuk tuk","move","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["bike","sports","exercise","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["kick","scooter","vehicle","razor"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","footwear","sports"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","stop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["drum","oil","barrell"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["diesel","fuel","fuelpump","gas","pump","station","gas station","petroleum"]},"wheel":{"a":"⊛ Wheel","b":"1F6DE","j":["circle","tire","turn","car","transport"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["beacon","car","light","police","revolving","ambulance","911","emergency","alert","error","pinged","law","legal"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","signal","traffic","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["light","signal","traffic","transportation","driving"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","sign","stop"]},"construction":{"a":"Construction","b":"1F6A7","j":["barrier","wip","progress","caution","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["ship","tool","ferry","sea","boat"]},"ring-buoy":{"a":"⊛ Ring Buoy","b":"1F6DF","j":["float","life preserver","life saver","rescue","safety"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","resort","sea","yacht","ship","summer","transportation","water","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["boat","ship","transportation","vehicle","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["passenger","ship","yacht","cruise","ferry"]},"ferry":{"a":"Ferry","b":"26F4","j":["boat","passenger","ship","yacht"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["boat","motorboat","ship"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","passenger","transportation","titanic","deploy"]},"airplane":{"a":"Airplane","b":"2708","j":["aeroplane","vehicle","transportation","flight","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["aeroplane","airplane","flight","transportation","fly","vehicle"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["aeroplane","airplane","check-in","departure","departures","airport","flight","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["aeroplane","airplane","arrivals","arriving","landing","airport","flight","boarding"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["chair","sit","airplane","transport","bus","flight","fly"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["railway","suspension","vehicle","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["cable","gondola","mountain","transportation","vehicle","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["aerial","cable","car","gondola","tramway","transportation","vehicle","ski"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["space","communication","gps","orbit","spaceflight","NASA","ISS"]},"rocket":{"a":"Rocket","b":"1F680","j":["space","launch","ship","staffmode","NASA","outer space","outer_space","fly"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["UFO","transportation","vehicle","ufo"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["packing","travel"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["sand","timer","time","clock","oldschool","limit","exam","quiz","test"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["hourglass","sand","timer","oldschool","time","countdown"]},"watch":{"a":"Watch","b":"231A","j":["clock","time","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["alarm","clock","time","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["clock","time","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["clock","time"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["00","12","12:00","clock","o’clock","twelve","twelve_o_clock","time","noon","midnight","midday","late","early","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["12","12:30","clock","thirty","twelve","twelve-thirty","twelve_thirty","time","late","early","schedule"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["00","1","1:00","clock","o’clock","one","one_o_clock","time","late","early","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["1","1:30","clock","one","one-thirty","thirty","one_thirty","time","late","early","schedule"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["00","2","2:00","clock","o’clock","two","two_o_clock","time","late","early","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2","2:30","clock","thirty","two","two-thirty","two_thirty","time","late","early","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["00","3","3:00","clock","o’clock","three","three_o_clock","time","late","early","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["3","3:30","clock","thirty","three","three-thirty","three_thirty","time","late","early","schedule"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["00","4","4:00","clock","four","o’clock","four_o_clock","time","late","early","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["4","4:30","clock","four","four-thirty","thirty","four_thirty","time","late","early","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["00","5","5:00","clock","five","o’clock","five_o_clock","time","late","early","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["5","5:30","clock","five","five-thirty","thirty","five_thirty","time","late","early","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["00","6","6:00","clock","o’clock","six","six_o_clock","time","late","early","schedule","dawn","dusk"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["6","6:30","clock","six","six-thirty","thirty","six_thirty","time","late","early","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["00","7","7:00","clock","o’clock","seven","seven_o_clock","time","late","early","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["7","7:30","clock","seven","seven-thirty","thirty","seven_thirty","time","late","early","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["00","8","8:00","clock","eight","o’clock","eight_o_clock","time","late","early","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["8","8:30","clock","eight","eight-thirty","thirty","eight_thirty","time","late","early","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["00","9","9:00","clock","nine","o’clock","nine_o_clock","time","late","early","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["9","9:30","clock","nine","nine-thirty","thirty","nine_thirty","time","late","early","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["00","10","10:00","clock","o’clock","ten","ten_o_clock","time","late","early","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["10","10:30","clock","ten","ten-thirty","thirty","ten_thirty","time","late","early","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["00","11","11:00","clock","eleven","o’clock","eleven_o_clock","time","late","early","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["11","11:30","clock","eleven","eleven-thirty","thirty","eleven_thirty","time","late","early","schedule"]},"new-moon":{"a":"New Moon","b":"1F311","j":["dark","moon","nature","twilight","planet","space","night","evening","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["crescent","moon","waxing","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["gibbous","moon","waxing","nature","night","sky","gray","twilight","planet","space","evening","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["full","moon","nature","yellow","twilight","planet","space","night","evening","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["gibbous","moon","waning","nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["crescent","moon","waning","nature","twilight","planet","space","night","evening","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["crescent","moon","night","sleep","sky","evening","magic"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["face","moon","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["weather","temperature","hot","cold"]},"sun":{"a":"Sun","b":"2600","j":["bright","rays","sunny","weather","nature","brightness","summer","beach","spring"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["bright","face","full","moon","nature","twilight","planet","space","night","evening","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["bright","face","sun","nature","morning","sky"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","saturnine","outerspace"]},"star":{"a":"Star","b":"2B50","j":["night","yellow"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["glittery","glow","shining","sparkle","star","night","awesome","good","magic"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["falling","shooting","star","night","photo"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["space","photo","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["weather","sky"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["cloud","sun","weather","nature","cloudy","morning","fall","spring"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["cloud","rain","thunder","weather","lightning"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["cloud","sun","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["cloud","sun","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["cloud","rain","sun","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["cloud","rain","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cloud","cold","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["cloud","lightning","weather","thunder"]},"tornado":{"a":"Tornado","b":"1F32A","j":["cloud","whirlwind","weather","cyclone","twister"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["blow","cloud","face","wind","gust","air"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["dizzy","hurricane","twister","typhoon","weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","nature","happy","unicorn_face","photo","sky","spring"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","rain","umbrella","weather","drizzle"]},"umbrella":{"a":"Umbrella","b":"2602","j":["clothing","rain","weather","spring"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","drop","rain","umbrella","rainy","weather","spring"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["rain","sun","umbrella","weather","summer"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["danger","electric","lightning","voltage","zap","thunder","weather","lightning bolt","fast"]},"snowflake":{"a":"Snowflake","b":"2744","j":["cold","snow","winter","season","weather","christmas","xmas"]},"snowman":{"a":"Snowman","b":"2603","j":["cold","snow","winter","season","weather","christmas","xmas","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["cold","snow","snowman","winter","season","weather","christmas","xmas","frozen","without_snow"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["flame","tool","hot","cook"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["cold","comic","drop","sweat","water","drip","faucet","spring"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["ocean","water","wave","sea","nature","tsunami","disaster"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["celebration","halloween","jack","jack-o-lantern","lantern","jack_o_lantern","light","pumpkin","creepy","fall"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["celebration","Christmas","tree","festival","vacation","december","xmas"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["celebration","photo","festival","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["celebration","fireworks","sparkle","stars","night","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["dynamite","explosive","fireworks","boom","explode","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","sparkle","star","stars","shine","shiny","cool","awesome","good","magic"]},"balloon":{"a":"Balloon","b":"1F388","j":["celebration","party","birthday","circus"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["celebration","party","popper","tada","congratulations","birthday","magic","circus"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["ball","celebration","confetti","festival","party","birthday","circus"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["banner","celebration","Japanese","tree","plant","nature","branch","summer"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["bamboo","celebration","Japanese","pine","plant","nature","vegetable","panda"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["celebration","doll","festival","Japanese","Japanese dolls","japanese","toy","kimono"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["carp","celebration","streamer","fish","japanese","koinobori","banner"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["bell","celebration","chime","wind","nature","ding","spring"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["celebration","ceremony","moon","photo","japan","asia","tsukimi"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["gift","good luck","hóngbāo","lai see","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["celebration","decoration","pink","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["box","celebration","gift","present","wrapped","birthday","christmas","xmas"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["celebration","reminder","ribbon","sports","cause","support","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["admission","ticket","sports","concert","entrance"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["admission","event","concert","pass"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["celebration","medal","military","award","winning","army"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["prize","win","award","contest","place","ftw","ceremony"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","award","winning"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["first","gold","medal","award","winning"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["bronze","medal","third","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["ball","football","soccer","sports"]},"baseball":{"a":"Baseball","b":"26BE","j":["ball","sports","balls"]},"softball":{"a":"Softball","b":"1F94E","j":["ball","glove","underarm","sports","balls"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["ball","hoop","sports","balls","NBA"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["ball","game","sports","balls"]},"american-football":{"a":"American Football","b":"1F3C8","j":["american","ball","football","sports","balls","NFL"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["ball","football","rugby","sports","team"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["ball","racquet","sports","balls","green"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["ball","game","sports","fun","play"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["ball","bat","game","sports"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["ball","field","game","hockey","stick","sports"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["game","hockey","ice","puck","stick","sports"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["ball","goal","stick","sports"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["ball","bat","game","paddle","table tennis","sports","pingpong"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","game","racquet","shuttlecock","sports"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","martial arts","taekwondo","uniform"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["goal","net","sports"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["golf","hole","sports","business","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["ice","skate","sports"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["fish","pole","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["diving","scuba","snorkeling","sport","ocean"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["athletics","running","sash","shirt","play","pageant"]},"skis":{"a":"Skis","b":"1F3BF","j":["ski","snow","sports","winter","cold"]},"sled":{"a":"Sled","b":"1F6F7","j":["sledge","sleigh","luge","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","rock","sports"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","game","hit","target","direct_hit","play","bar"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["fluctuate","toy","yo-yo","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["fly","soar","wind"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","ball","billiard","eight","game","pool","hobby","luck","magic"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["ball","crystal","fairy tale","fantasy","fortune","tool","disco","party","magic","circus","fortune_teller"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["magic","witch","wizard","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["bead","charm","evil-eye","nazar","talisman"]},"hamsa":{"a":"⊛ Hamsa","b":"1FAAC","j":["amulet","Fatima","hand","Mary","Miriam","protection","religion"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["controller","game","play","console","PS4"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["game","slot","bet","gamble","vegas","fruit machine","luck","casino"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["dice","die","game","random","tabletop","play","luck"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["clue","interlocking","jigsaw","piece","puzzle"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["plaything","plush","stuffed","toy"]},"piata":{"a":"Piñata","b":"1FA85","j":["celebration","party","piñata","pinata","mexico","candy"]},"mirror-ball":{"a":"⊛ Mirror Ball","b":"1FAA9","j":["dance","disco","glitter","party"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["doll","nesting","russia","matryoshka","toy"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["card","game","poker","cards","suits","magic"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["card","game","poker","cards","magic","suits"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["card","game","poker","cards","magic","suits"]},"club-suit":{"a":"Club Suit","b":"2663","j":["card","game","poker","cards","magic","suits"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["card","game","wildcard","poker","cards","play","magic"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["game","mahjong","red","play","chinese","kanji"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","flower","game","Japanese","playing","sunset","red"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["art","mask","performing","theater","theatre","acting","drama"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["art","frame","museum","painting","picture","photography"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["art","museum","painting","palette","design","paint","draw","colors"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","sewing","spool","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["embroidery","needle","sewing","stitches","sutures","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["ball","crochet","knit"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","tangled","tie","twine","twist","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["clothing","eye","eyeglasses","eyewear","fashion","accessories","eyesight","nerdy","dork","geek"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["dark","eye","eyewear","glasses","face","cool","accessories"]},"goggles":{"a":"Goggles","b":"1F97D","j":["eye protection","swimming","welding","eyes","protection","safety"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["doctor","experiment","scientist","chemist"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["emergency","safety","vest","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["clothing","tie","shirt","suitup","formal","fashion","cloth","business"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["clothing","shirt","t-shirt","t_shirt","fashion","cloth","casual","tee"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","pants","trousers","fashion","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["neck","winter","clothes"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["hand","hands","winter","clothes"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["stocking","stockings","clothes"]},"dress":{"a":"Dress","b":"1F457","j":["clothing","clothes","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","fashion","women","female","japanese"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","one-piece swimsuit","one_piece_swimsuit","fashion"]},"briefs":{"a":"Briefs","b":"1FA72","j":["bathing suit","one-piece","swimsuit","underwear","clothing"]},"shorts":{"a":"Shorts","b":"1FA73","j":["bathing suit","pants","underwear","clothing"]},"bikini":{"a":"Bikini","b":"1F459","j":["clothing","swim","swimming","female","woman","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","woman","woman’s clothes","woman_s_clothes","fashion","shopping_bags","female"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","coin","fashion","accessories","money","sales","shopping"]},"handbag":{"a":"Handbag","b":"1F45C","j":["bag","clothing","purse","fashion","accessory","accessories","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["bag","clothing","pouch","accessories","shopping"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["bag","hotel","shopping","mall","buy","purchase"]},"backpack":{"a":"Backpack","b":"1F392","j":["bag","rucksack","satchel","school","student","education"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["beach sandals","sandals","thong sandals","thongs","zōri","footwear","summer"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["clothing","man","man’s shoe","shoe","man_s_shoe","fashion","male"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["athletic","clothing","shoe","sneaker","shoes","sports","sneakers"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["backpacking","boot","camping","hiking"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["ballet flat","slip-on","slipper","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["clothing","heel","high-heeled shoe","shoe","woman","high_heeled_shoe","fashion","shoes","female","pumps","stiletto"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["clothing","sandal","shoe","woman","woman’s sandal","woman_s_sandal","shoes","fashion","flip flops"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["boot","clothing","shoe","woman","woman’s boot","woman_s_boot","shoes","fashion"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","king","queen","kod","leader","royalty","lord"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman","woman’s hat","woman_s_hat","fashion","accessories","female","lady","spring"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","top","tophat","magic","gentleman","classy","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["cap","celebration","clothing","graduation","hat","school","college","degree","university","legal","learn","education"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["baseball cap","cap","baseball"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["army","helmet","military","soldier","warrior","protection"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["aid","cross","face","hat","helmet","rescue worker’s helmet","rescue_worker_s_helmet","construction","build"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","necklace","prayer","religion","dhikr","religious"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["cosmetics","makeup","female","girl","fashion","woman"]},"ring":{"a":"Ring","b":"1F48D","j":["diamond","wedding","propose","marriage","valentines","fashion","jewelry","gem","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["diamond","gem","jewel","blue","ruby","jewelry"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["mute","quiet","silent","speaker","sound","volume","silence"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["soft","sound","volume","silence","broadcast"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["medium","volume","speaker","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["loud","volume","noise","noisy","speaker","broadcast"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["loud","public address","volume","sound"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["cheering","sound","speaker","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["horn","post","postal","instrument","music"]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["bell","forbidden","mute","quiet","silent","sound","volume"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["music","score","treble","clef","compose"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["music","note","score","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["music","note","notes","score"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","microphone","music","studio","sing","recording","artist","talkshow"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["level","music","slider","scale"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","knobs","music","dial"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["karaoke","mic","sound","music","PA","sing","talkshow"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["earbud","music","score","gadgets"]},"radio":{"a":"Radio","b":"1F4FB","j":["video","communication","music","podcast","program"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["instrument","music","sax","jazz","blues"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","squeeze box","music"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["instrument","keyboard","music","piano","compose"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["instrument","music","brass"]},"violin":{"a":"Violin","b":"1F3BB","j":["instrument","music","orchestra","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["music","stringed","instructment"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","music","instrument","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","conga","drum","rhythm","music"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["cell","mobile","phone","telephone","technology","apple","gadgets","dial"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["arrow","cell","mobile","phone","receive","iphone","incoming"]},"telephone":{"a":"Telephone","b":"260E","j":["phone","technology","communication","dial"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["phone","receiver","telephone","technology","communication","dial"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["fax","communication","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"]},"low-battery":{"a":"⊛ Low Battery","b":"1FAAB","j":["electronic","low energy","drained","dead"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["computer","pc","personal","technology","screen","display","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["computer","desktop","technology","computing","screen"]},"printer":{"a":"Printer","b":"1F5A8","j":["computer","paper","ink"]},"keyboard":{"a":"Keyboard","b":"2328","j":["computer","technology","type","input","text"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["computer","click"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["computer","technology","trackpad"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["computer","disk","minidisk","optical","technology","record","data","90s"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["computer","disk","floppy","oldschool","technology","save","90s","80s"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["cd","computer","disk","optical","technology","dvd","disc","90s"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","computer","disk","optical","cd","disc"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["camera","cinema","movie","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","film","frames","movie"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["cinema","film","movie","projector","video","tape","record"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","film","record"]},"television":{"a":"Television","b":"1F4FA","j":["tv","video","technology","program","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["video","gadgets","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["camera","flash","video","photography","gadgets"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["camera","video","film","record"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["tape","vhs","video","record","oldschool","90s","80s"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["glass","magnifying","search","tool","zoom","find","detective"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["glass","magnifying","search","tool","zoom","find","detective"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["bulb","comic","electric","idea","light","electricity"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","light","tool","torch","dark","camping","sight","night"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["bar","lantern","light","red","paper","halloween","spooky"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["diya","lamp","oil","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["book","cover","decorated","notebook","classroom","notes","record","paper","study"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["book","closed","read","library","knowledge","textbook","learn"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["book","open","read","library","knowledge","literature","learn","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["book","green","read","library","knowledge","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["blue","book","read","library","knowledge","learn","study"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["book","orange","read","library","knowledge","textbook","study"]},"books":{"a":"Books","b":"1F4DA","j":["book","literature","library","study"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notebook","notes","paper"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["curl","document","page","documents","office","paper"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","page","documents","office","paper","information"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["news","paper","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["news","newspaper","paper","rolled","rolled-up newspaper","rolled_up_newspaper","press","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["bookmark","mark","marker","tabs","favorite","save","order","tidy"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["mark","favorite","label","save"]},"label":{"a":"Label","b":"1F3F7","j":["sale","tag"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["bag","dollar","money","moneybag","payment","coins","sale"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","metal","money","silver","treasure","currency"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","bill","currency","money","note","yen","sales","japanese","dollar"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","bill","currency","dollar","money","note","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","bill","currency","euro","money","note","sales","dollar"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","bill","currency","money","note","pound","british","sterling","sales","bills","uk","england"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","fly","money","wings","dollar","bills","payment","sale"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["card","credit","money","sales","dollar","bill","payment","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["accounting","bookkeeping","evidence","proof","expenses"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["chart","graph","growth","money","yen","green-square","presentation","stats"]},"envelope":{"a":"Envelope","b":"2709","j":["email","letter","postal","inbox","communication"]},"email":{"a":"E-Mail","b":"1F4E7","j":["e-mail","letter","mail","e_mail","communication","inbox"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["e-mail","email","envelope","incoming","letter","receive","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["arrow","e-mail","email","envelope","outgoing","communication"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["box","letter","mail","outbox","sent","tray","inbox","email"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["box","inbox","letter","mail","receive","tray","email","documents"]},"package":{"a":"Package","b":"1F4E6","j":["box","parcel","mail","gift","cardboard","moving"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["closed","mail","mailbox","postbox","email","inbox","communication"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["closed","lowered","mail","mailbox","postbox","email","communication","inbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["mail","mailbox","open","postbox","email","inbox","communication"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["lowered","mail","mailbox","open","postbox","email","inbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["mail","mailbox","email","letter","envelope"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["ballot","box","election","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["stationery","write","paper","writing","school","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["nib","pen","stationery","writing","write"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["fountain","pen","stationery","writing","write"]},"pen":{"a":"Pen","b":"1F58A","j":["ballpoint","stationery","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["painting","drawing","creativity","art"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["file","folder","documents","business","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["file","folder","open","documents","load"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["card","dividers","index","organizing","business","stationery"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["calendar","tear-off calendar","tear_off_calendar","schedule","date","planning"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["note","pad","spiral","memo","stationery"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["calendar","pad","spiral","date","schedule","planning"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["card","index","rolodex","business","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["chart","graph","growth","trend","upward","presentation","stats","recovery","business","economics","money","sales","good","success"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["chart","down","graph","trend","presentation","stats","recession","business","economics","money","sales","bad","failure"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["bar","chart","graph","presentation","stats"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["pin","stationery","mark","here"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pin","pushpin","stationery","location","map","here"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["link","paperclip","documents","stationery"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["ruler","straight edge","stationery","calculate","length","math","school","drawing","architect","sketch"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["ruler","set","triangle","stationery","math","architect","sketch"]},"scissors":{"a":"Scissors","b":"2702","j":["cutting","tool","stationery","cut"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["box","card","file","business","stationery"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["bin","trash","rubbish","garbage","toss"]},"locked":{"a":"Locked","b":"1F512","j":["closed","security","password","padlock"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["lock","open","unlock","privacy","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["ink","lock","nib","pen","privacy","security","secret"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["closed","key","lock","secure","security","privacy"]},"key":{"a":"Key","b":"1F511","j":["lock","password","door"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["clue","key","lock","old","door","password"]},"hammer":{"a":"Hammer","b":"1F528","j":["tool","tools","build","create"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","split","wood","tool","cut"]},"pick":{"a":"Pick","b":"26CF","j":["mining","tool","tools","dig"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","pick","tool","tools","build","create"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["hammer","spanner","tool","wrench","tools","build","create"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","swords","weapon"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["australia","rebound","repercussion","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","arrow","bow","Sagittarius","zodiac","sports"]},"shield":{"a":"Shield","b":"1F6E1","j":["weapon","protection","security"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["carpenter","lumber","saw","tool","cut","chop"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","tool","tools","diy","ikea","fix","maintainer"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tool","tools"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["bolt","nut","tool","handy","tools","fix"]},"gear":{"a":"Gear","b":"2699","j":["cog","cogwheel","tool"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","tool","vice"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","justice","Libra","scale","zodiac","law","fairness","weight"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["chain","lock","arrest"]},"hook":{"a":"Hook","b":"1FA9D","j":["catch","crook","curve","ensnare","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["chest","mechanic","tool","tools","diy","fix","maintainer"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["attraction","horseshoe","magnetic"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["climb","rung","step","tools"]},"alembic":{"a":"Alembic","b":"2697","j":["chemistry","tool","distilling","science","experiment"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","chemistry","experiment","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","biologist","biology","culture","lab"]},"dna":{"a":"Dna","b":"1F9EC","j":["biologist","evolution","gene","genetics","life"]},"microscope":{"a":"Microscope","b":"1F52C","j":["science","tool","laboratory","experiment","zoomin","study"]},"telescope":{"a":"Telescope","b":"1F52D","j":["science","tool","stars","space","zoom","astronomy"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["antenna","dish","satellite","communication","future","radio","space"]},"syringe":{"a":"Syringe","b":"1F489","j":["medicine","needle","shot","sick","health","hospital","drugs","blood","doctor","nurse"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["bleed","blood donation","injury","medicine","menstruation","period","hurt","harm","wound"]},"pill":{"a":"Pill","b":"1F48A","j":["doctor","medicine","sick","health","pharmacy","drug"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"crutch":{"a":"⊛ Crutch","b":"1FA7C","j":["cane","disability","hurt","mobility aid","stick","accessibility","assist"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"⊛ X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton","x-ray","medicine"]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["reflection","reflector","speculum"]},"window":{"a":"Window","b":"1FA9F","j":["frame","fresh air","opening","transparent","view","scenery"]},"bed":{"a":"Bed","b":"1F6CF","j":["hotel","sleep","rest"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["couch","hotel","lamp","read","chill"]},"chair":{"a":"Chair","b":"1FA91","j":["seat","sit","furniture"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["force cup","plumber","suction","toilet"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","clean","bathroom"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["bath","clean","shower","bathroom"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["bait","mousetrap","snare","trap","cheese"]},"razor":{"a":"Razor","b":"1FA92","j":["sharp","shave","cut"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["lotion","moisturizer","shampoo","sunscreen"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["farming","laundry","picnic"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["paper towels","toilet paper","roll"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["cask","pail","vat","water","container"]},"soap":{"a":"Soap","b":"1F9FC","j":["bar","bathing","cleaning","lather","soapdish"]},"bubbles":{"a":"⊛ Bubbles","b":"1FAE7","j":["burp","clean","soap","underwater","fun","carbonation","sparkling"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["bathroom","brush","clean","dental","hygiene","teeth"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["absorbing","cleaning","porous"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["extinguish","fire","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["cart","shopping","trolley"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["smoking","kills","tobacco","joint","smoke"]},"coffin":{"a":"Coffin","b":"26B0","j":["death","vampire","dead","die","rip","graveyard","cemetery","casket","funeral","box"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","grave","graveyard","tombstone","death","rip"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["ashes","death","funeral","urn","dead","die","rip"]},"moai":{"a":"Moai","b":"1F5FF","j":["face","moyai","statue","rock","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","protest","sign","announcement"]},"identification-card":{"a":"⊛ Identification Card","b":"1FAAA","j":["credentials","ID","license","security","document"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["atm","ATM sign","automated","bank","teller","money","sales","cash","blue-square","payment"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["litter","litter bin","blue-square","sign","human","info"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["drinking","potable","water","blue-square","liquid","restroom","cleaning","faucet"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["access","blue-square","disabled","accessibility"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","man","men’s room","restroom","wc","men_s_room","toilet","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","restroom","wc","woman","women’s room","women_s_room","purple-square","female","toilet","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","WC","blue-square","toilet","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["closet","lavatory","restroom","water","wc","toilet","blue-square"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","custom","blue-square"]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","blue-square","airport","transport"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","locker","luggage","blue-square","travel"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","alert","error","problem","issue"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["child","crossing","pedestrian","traffic","school","warning","danger","sign","driving","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["entry","forbidden","no","not","prohibited","traffic","limit","security","privacy","bad","denied","stop","circle"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["entry","forbidden","no","not","forbid","stop","limit","denied","disallow","circle"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["bicycle","bike","forbidden","no","prohibited","cyclist","circle"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["forbidden","no","not","prohibited","smoking","cigarette","blue-square","smell","smoke"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["forbidden","litter","no","not","prohibited","trash","bin","garbage","circle"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non-drinking","non-potable","water","non_potable_water","drink","faucet","tap","circle"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["forbidden","no","not","pedestrian","prohibited","rules","crossing","walking","circle"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["cell","forbidden","mobile","no","phone","iphone","mute","circle"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["18","age restriction","eighteen","prohibited","underage","drink","pub","night","minor","circle"]},"radioactive":{"a":"Radioactive","b":"2622","j":["sign","nuclear","danger"]},"biohazard":{"a":"Biohazard","b":"2623","j":["sign","danger"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["arrow","cardinal","direction","north","blue-square","continue","top"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["arrow","direction","intercardinal","northeast","up-right arrow","up_right_arrow","blue-square","point","diagonal"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","cardinal","direction","east","blue-square","next"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["arrow","direction","down-right arrow","intercardinal","southeast","down_right_arrow","blue-square","diagonal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["arrow","cardinal","direction","down","south","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down-left arrow","intercardinal","southwest","down_left_arrow","blue-square","diagonal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["arrow","cardinal","direction","west","blue-square","previous","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["arrow","direction","intercardinal","northwest","up-left arrow","up_left_arrow","blue-square","point","diagonal"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","up-down arrow","up_down_arrow","blue-square","direction","way","vertical"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["arrow","left-right arrow","left_right_arrow","shape","direction","horizontal","sideways"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["arrow","back","return","blue-square","undo","enter"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","blue-square","return","rotate","direction"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["arrow","blue-square","direction","top"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","down","blue-square","direction","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["arrow","clockwise","reload","sync","cycle","round","repeat"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["anticlockwise","arrow","counterclockwise","withershins","blue-square","sync","cycle"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","back","BACK arrow","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","end","END arrow","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","on","ON! arrow","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","soon","SOON arrow","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","top","TOP arrow","up","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","worship","church","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","science","physics","chemistry"]},"om":{"a":"Om","b":"1F549","j":["Hindu","religion","hinduism","buddhism","sikhism","jainism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["David","Jew","Jewish","religion","star","star of David","judaism"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["Buddhist","dharma","religion","wheel","hinduism","buddhism","sikhism","jainism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["religion","tao","taoist","yang","yin","balance"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["Christian","cross","religion","christianity"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["Christian","cross","religion","suppedaneum"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["islam","Muslim","religion"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["candelabrum","candlestick","religion","hanukkah","candles","jewish"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["dotted six-pointed star","fortune","star","dotted_six_pointed_star","purple-square","religion","jewish","hexagram"]},"aries":{"a":"Aries","b":"2648","j":["ram","zodiac","sign","purple-square","astrology"]},"taurus":{"a":"Taurus","b":"2649","j":["bull","ox","zodiac","purple-square","sign","astrology"]},"gemini":{"a":"Gemini","b":"264A","j":["twins","zodiac","sign","purple-square","astrology"]},"cancer":{"a":"Cancer","b":"264B","j":["crab","zodiac","sign","purple-square","astrology"]},"leo":{"a":"Leo","b":"264C","j":["lion","zodiac","sign","purple-square","astrology"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","sign","purple-square","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","justice","scales","zodiac","sign","purple-square","astrology"]},"scorpio":{"a":"Scorpio","b":"264F","j":["scorpion","scorpius","zodiac","sign","purple-square","astrology"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["archer","zodiac","sign","purple-square","astrology"]},"capricorn":{"a":"Capricorn","b":"2651","j":["goat","zodiac","sign","purple-square","astrology"]},"aquarius":{"a":"Aquarius","b":"2652","j":["bearer","water","zodiac","sign","purple-square","astrology"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","zodiac","purple-square","sign","astrology"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["bearer","serpent","snake","zodiac","sign","purple-square","constellation","astrology"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["arrow","crossed","blue-square","shuffle","music","random"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["arrow","clockwise","repeat","loop","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["arrow","clockwise","once","blue-square","loop"]},"play-button":{"a":"Play Button","b":"25B6","j":["arrow","play","right","triangle","blue-square","direction"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["arrow","double","fast","fast-forward button","forward","fast_forward_button","blue-square","play","speed","continue"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["arrow","next scene","next track","triangle","forward","next","blue-square"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["arrow","pause","play","right","triangle","blue-square"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["arrow","left","reverse","triangle","blue-square","direction"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","rewind","play","blue-square"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["arrow","previous scene","previous track","triangle","backward"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["arrow","button","red","blue-square","triangle","direction","point","forward","top"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["arrow","double","blue-square","direction","top"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["arrow","button","down","red","blue-square","direction","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","double","down","blue-square","direction","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["bar","double","pause","vertical","blue-square"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["circle","record","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["camera","film","movie","blue-square","record","curtain","stage","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["brightness","dim","low","sun","afternoon","warm","summer"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["bright","brightness","sun","light"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["antenna","bar","cell","mobile","phone","blue-square","reception","internet","connection","wifi","bluetooth","bars"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["cell","mobile","mode","phone","telephone","vibration","orange-square"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["cell","mobile","off","phone","telephone","mute","orange-square","silence","quiet"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","lady","girl"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["transgender","lgbtq"]},"multiply":{"a":"Multiply","b":"2716","j":["×","cancel","multiplication","sign","x","multiplication_sign","math","calculation"]},"plus":{"a":"Plus","b":"2795","j":["+","math","sign","plus_sign","calculation","addition","more","increase"]},"minus":{"a":"Minus","b":"2796","j":["-","−","math","sign","minus_sign","calculation","subtract","less"]},"divide":{"a":"Divide","b":"2797","j":["÷","division","math","sign","division_sign","calculation"]},"heavy-equals-sign":{"a":"⊛ Heavy Equals Sign","b":"1F7F0","j":["equality","math"]},"infinity":{"a":"Infinity","b":"267E","j":["forever","unbounded","universal"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["!","!!","bangbang","exclamation","mark","surprise"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["!","!?","?","exclamation","interrobang","mark","punctuation","question","wat","surprise"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["?","mark","punctuation","question","question_mark","doubt","confused"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["?","mark","outlined","punctuation","question","doubts","gray","huh","confused"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["!","exclamation","mark","outlined","punctuation","surprise","gray","wow","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["!","exclamation","mark","punctuation","exclamation_mark","heavy_exclamation_mark","danger","surprise","wow","warning"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["dash","punctuation","wavy","draw","line","moustache","mustache","squiggle","scribble"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["bank","currency","exchange","money","sales","dollar","travel"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","dollar","money","sales","payment","buck"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["aesculapius","medicine","staff","health","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","arrow","environment","garbage","trash"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["fleur-de-lis","fleur_de_lis","decorative","scout"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["anchor","emblem","ship","tool","trident","weapon","spear"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["badge","name","fire","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf","badge","shield"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["circle","large","o","red","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["✓","button","check","mark","green-square","ok","agree","vote","election","answer","tick"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["✓","box","check","ok","agree","confirm","black-square","vote","election","yes","tick"]},"check-mark":{"a":"Check Mark","b":"2714","j":["✓","check","mark","ok","nike","answer","yes","tick"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["×","cancel","cross","mark","multiplication","multiply","x","no","delete","remove","red"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["×","mark","square","x","green-square","no","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["curl","loop","scribble","draw","shape","squiggle"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["curl","double","loop","tape","cassette"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["mark","part","graph","presentation","stats","business","economics","bad"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","asterisk","eight-spoked asterisk","eight_spoked_asterisk","star","sparkle","green-square"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","eight-pointed star","star","eight_pointed_star","orange-square","shape","polygon"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","green-square","awesome","good","fireworks"]},"copyright":{"a":"Copyright","b":"00A9","j":["c","ip","license","circle","law","legal"]},"registered":{"a":"Registered","b":"00AE","j":["r","alphabet","circle"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["mark","tm","trademark","brand","law","legal"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["keycap_","star"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["keycap","0","numbers","blue-square","null"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["keycap","blue-square","numbers","1"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["keycap","numbers","2","prime","blue-square"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["keycap","3","numbers","prime","blue-square"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["keycap","4","numbers","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["keycap","5","numbers","blue-square","prime"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["keycap","6","numbers","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["keycap","7","numbers","blue-square","prime"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["keycap","8","blue-square","numbers"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["keycap","blue-square","numbers","9"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["keycap","numbers","10","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["ABCD","input","latin","letters","uppercase","alphabet","words","blue-square"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["abcd","input","latin","letters","lowercase","blue-square","alphabet"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["1234","input","numbers","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","input","blue-square","music","note","ampersand","percent","glyphs","characters"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["abc","alphabet","input","latin","letters","blue-square"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","A button (blood type)","blood type","a_button","red-square","alphabet","letter"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","AB button (blood type)","blood type","ab_button","red-square","alphabet"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["b","B button (blood type)","blood type","b_button","red-square","alphabet","letter"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","CL button","alphabet","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["cool","COOL button","words","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["free","FREE button","blue-square","words"]},"information":{"a":"Information","b":"2139","j":["i","blue-square","alphabet","letter"]},"id-button":{"a":"Id Button","b":"1F194","j":["id","ID button","identity","purple-square","words"]},"circled-m":{"a":"Circled M","b":"24C2","j":["circle","circled M","m","alphabet","blue-circle","letter"]},"new-button":{"a":"New Button","b":"1F195","j":["new","NEW button","blue-square","words","start"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["ng","NG button","blue-square","words","shape","icon"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["blood type","o","O button (blood type)","o_button","alphabet","red-square","letter"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK","OK button","good","agree","yes","blue-square"]},"p-button":{"a":"P Button","b":"1F17F","j":["P button","parking","cars","blue-square","alphabet","letter"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["help","sos","SOS button","red-square","words","emergency","911"]},"up-button":{"a":"Up! Button","b":"1F199","j":["mark","up","UP! button","blue-square","above","high"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["versus","vs","VS button","words","orange-square"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["“here”","Japanese","Japanese “here” button","katakana","ココ","blue-square","here","japanese","destination"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ","japanese","blue-square"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月","chinese","month","moon","japanese","orange-square","kanji"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有","orange-square","chinese","have","kanji"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指","chinese","point","green-square","kanji"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得","chinese","kanji","obtain","get","circle"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","ideograph","Japanese","Japanese “discount” button","割","cut","divide","chinese","kanji","pink-square"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無","nothing","chinese","kanji","japanese","orange-square"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁","kanji","japanese","chinese","forbidden","limit","restricted","red-square"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可","ok","good","chinese","kanji","agree","yes","orange-circle"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["“application”","ideograph","Japanese","Japanese “application” button","申","chinese","japanese","kanji","orange-square"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合","japanese","chinese","join","kanji","red-square"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空","kanji","japanese","chinese","empty","sky","blue-square"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝","chinese","kanji","japanese","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["“secret”","ideograph","Japanese","Japanese “secret” button","秘","privacy","chinese","sshh","kanji","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営","japanese","opening hours","orange-square"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満","full","chinese","japanese","red-square","kanji"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["circle","geometric","red","shape","error","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","orange","round"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["circle","yellow","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["circle","green","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["blue","circle","geometric","shape","icon","button"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","purple","round"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["circle","geometric","shape","button","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["circle","geometric","shape","round"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["red","square"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["orange","square"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["square","yellow"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["blue","square"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["purple","square"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","square","shape","icon","button"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","square","shape","icon","stone","button"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","square","shape","button","icon"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","square","shape","stone","icon"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","square","black_medium_small_square","icon","shape","button"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","square","white medium-small square","white_medium_small_square","shape","stone","icon","button"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["geometric","square","shape","icon"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["geometric","square","shape","icon"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["diamond","geometric","orange","shape","jewel","gem"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["blue","diamond","geometric","shape","jewel","gem"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["diamond","geometric","orange","shape","jewel","gem"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["blue","diamond","geometric","shape","jewel","gem"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["geometric","red","shape","direction","up","top"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["down","geometric","red","shape","direction","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["comic","diamond","geometric","inside","jewel","blue","gem","crystal","fancy"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["button","geometric","radio","input","old","music","circle"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["button","geometric","outlined","square","shape","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["button","geometric","square","shape","input","frame"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["checkered","chequered","racing","contest","finishline","race","gokart"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["post","mark","milestone","place"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["celebration","cross","crossed","Japanese","japanese","nation","country","border"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["waving","losing","loser","lost","surrender","give up","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["pride","rainbow","flag","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["flag","light blue","pink","transgender","white","lgbtq"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["Jolly Roger","pirate","plunder","treasure","skull","crossbones","flag","banner"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","nation","country","banner","andorra"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner","united_arab_emirates"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner","afghanistan"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner","antigua_barbuda"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner","anguilla"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner","albania"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner","armenia"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner","angola"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner","antarctica"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner","argentina"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner","american_samoa"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner","austria"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner","australia"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner","aruba"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner","aland_islands"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner","azerbaijan"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner","bosnia_herzegovina"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner","barbados"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner","bangladesh"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner","belgium"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner","burkina_faso"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner","bulgaria"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner","bahrain"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner","burundi"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner","benin"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner","st_barthelemy"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner","bermuda"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner","brunei"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner","bolivia"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner","caribbean_netherlands"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner","brazil"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner","bahamas"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner","bhutan"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","nation","country","banner","botswana"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner","belarus"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner","belize"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner","canada"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner","cocos_islands"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner","congo_kinshasa"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner","central_african_republic"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner","congo_brazzaville"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner","switzerland"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner","cote_d_ivoire"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner","cook_islands"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner","chile"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner","cameroon"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["flag","china","chinese","prc","country","nation","banner"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["flag","co","nation","country","banner","colombia"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["flag","costa","rica","nation","country","banner","costa_rica"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner","cuba"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner","cape_verde"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner","curacao"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner","christmas_island"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner","cyprus"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner","czechia"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner","germany"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["flag","dj","nation","country","banner","djibouti"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner","denmark"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner","dominica"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner","dominican_republic"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner","algeria"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag","flag_ceuta_melilla"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["flag","ec","nation","country","banner","ecuador"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner","estonia"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner","egypt"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner","western_sahara"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner","eritrea"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["flag","spain","nation","country","banner"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["flag","et","nation","country","banner","ethiopia"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["flag","european","union","banner"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","fi","nation","country","banner","finland"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner","fiji"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner","falkland_islands"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["flag","micronesia","federated","states","nation","country","banner"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["flag","faroe","islands","nation","country","banner","faroe_islands"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","nation","france","french","country"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","nation","country","banner","gabon"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["flag","united","kingdom","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union jack","united_kingdom"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner","grenada"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner","georgia"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner","french_guiana"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner","guernsey"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner","ghana"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner","gibraltar"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner","greenland"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner","gambia"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner","guinea"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner","guadeloupe"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner","equatorial_guinea"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner","greece"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["flag","flag_south_georgia_south_sandwich_islands","south","georgia","sandwich","islands","nation","country","banner","south_georgia_south_sandwich_islands"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner","guatemala"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner","guam"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner","guinea_bissau"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner","guyana"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner","hong_kong_sar_china"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["flag","hn","nation","country","banner","honduras"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner","croatia"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner","haiti"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner","hungary"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner","canary_islands"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner","indonesia"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner","ireland"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner","israel"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner","isle_of_man"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner","india"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner","british_indian_ocean_territory"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner","iraq"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["flag","iran","islamic","republic","nation","country","banner"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","is","nation","country","banner","iceland"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["flag","italy","nation","country","banner"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["flag","je","nation","country","banner","jersey"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner","jamaica"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner","jordan"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner","japan"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner","kenya"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner","kyrgyzstan"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner","cambodia"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner","kiribati"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner","comoros"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner","st_kitts_nevis"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner","north_korea"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner","south_korea"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner","kuwait"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner","cayman_islands"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner","kazakhstan"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner","laos"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner","lebanon"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner","st_lucia"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner","liechtenstein"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner","sri_lanka"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner","liberia"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner","lesotho"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner","lithuania"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner","luxembourg"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner","latvia"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner","libya"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner","morocco"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner","monaco"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","moldova","republic","nation","country","banner"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["flag","me","nation","country","banner","montenegro"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["flag","mg","nation","country","banner","madagascar"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner","marshall_islands"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner","north_macedonia"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner","mali"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner","myanmar"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner","mongolia"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner","macao_sar_china"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner","northern_mariana_islands"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner","martinique"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner","mauritania"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner","montserrat"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner","malta"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner","mauritius"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner","maldives"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner","malawi"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner","mexico"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner","malaysia"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner","mozambique"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner","namibia"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner","new_caledonia"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner","niger"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner","norfolk_island"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner","nigeria"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner","nicaragua"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner","netherlands"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner","norway"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner","nepal"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner","nauru"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner","niue"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner","new_zealand"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner","oman"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner","panama"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner","peru"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner","french_polynesia"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner","papua_new_guinea"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner","philippines"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner","pakistan"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner","poland"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner","st_pierre_miquelon"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner","pitcairn_islands"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner","puerto_rico"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner","palestinian_territories"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner","portugal"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner","palau"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner","paraguay"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner","qatar"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner","reunion"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner","romania"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner","serbia"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner","russia"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner","rwanda"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner","saudi_arabia"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner","solomon_islands"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner","seychelles"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner","sudan"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner","sweden"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner","singapore"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner","st_helena"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner","slovenia"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag","flag_svalbard_jan_mayen"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","nation","country","banner","slovakia"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner","sierra_leone"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner","san_marino"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner","senegal"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner","somalia"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner","suriname"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner","south_sudan"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["flag","flag_sao_tome_principe","sao","tome","principe","nation","country","banner","sao_tome_principe"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner","el_salvador"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner","sint_maarten"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner","syria"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner","eswatini"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["flag","flag_turks_caicos_islands","turks","caicos","islands","nation","country","banner","turks_caicos_islands"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner","chad"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner","french_southern_territories"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner","togo"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner","thailand"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner","tajikistan"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner","tokelau"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner","timor_leste"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner","turkmenistan"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner","tunisia"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner","tonga"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["flag","turkey","nation","country","banner"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag","flag_trinidad_tobago","trinidad","tobago","nation","country","banner","trinidad_tobago"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner","tuvalu"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner","taiwan"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["flag","tanzania","united","republic","nation","country","banner"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","ua","nation","country","banner","ukraine"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner","uganda"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["flag","united","states","america","nation","country","banner","united_states"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner","uruguay"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner","uzbekistan"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner","vatican_city"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner","st_vincent_grenadines"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner","venezuela"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner","british_virgin_islands"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["flag","flag_u_s_virgin_islands","virgin","islands","us","nation","country","banner","u_s_virgin_islands"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner","vietnam"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner","vanuatu"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner","wallis_futuna"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner","samoa"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner","kosovo"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner","yemen"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner","mayotte"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner","south_africa"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner","zambia"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner","zimbabwe"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}} \ No newline at end of file +{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","melting-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","smiling-face-with-open-hands","face-with-hand-over-mouth","face-with-open-eyes-and-hand-over-mouth","face-with-peeking-eye","shushing-face","thinking-face","saluting-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","dotted-line-face","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","face-with-crossedout-eyes","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","face-with-diagonal-mouth","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","face-holding-back-tears","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","rightwards-hand","leftwards-hand","palm-down-hand","palm-up-hand","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","hand-with-index-finger-and-thumb-crossed","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","index-pointing-at-the-viewer","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","heart-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","biting-lip","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","person-with-crown","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","pregnant-man","pregnant-person","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","troll","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","coral","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","lotus","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind","empty-nest","nest-with-eggs"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","beans","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","pouring-liquid","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","jar","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","playground-slide","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","wheel","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","ring-buoy","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","hamsa","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","mirror-ball","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","low-battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","crutch","stethoscope","xray","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","bubbles","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard","identification-card"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","heavy-equals-sign","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["face","grin","smile","happy","joy",":D"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["face","mouth","open","smile","happy","joy","haha",":D",":)","funny"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["eye","face","mouth","open","smile","happy","joy","funny","haha","laugh","like",":D",":)"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["eye","face","grin","smile","happy","joy","kawaii"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["face","laugh","mouth","satisfied","smile","happy","joy","lol","haha","glad","XD"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["cold","face","open","smile","sweat","hot","happy","laugh","relief"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["face","floor","laugh","rofl","rolling","rotfl","laughing","lol","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","joy","laugh","tear","cry","tears","weep","happy","happytears","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["face","upside-down","upside_down_face","flipped","silly","smile"]},"melting-face":{"a":"⊛ Melting Face","b":"1FAE0","j":["disappear","dissolve","liquid","melt","hot","heat"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["face","wink","happy","mischievous","secret",";)","smile","eye"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","face","smile","happy","flushed","crush","embarrassed","shy","joy"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["angel","face","fantasy","halo","innocent","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["adore","crush","hearts","in love","face","love","like","affection","valentines","infatuation"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["eye","face","love","smile","smiling face with heart-eyes","smiling_face_with_heart_eyes","like","affection","valentines","infatuation","crush","heart"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["eyes","face","grinning","star","star-struck","starry-eyed","star_struck","smile","starry"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["face","kiss","love","like","3","valentines","infatuation"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["face","outlined","relaxed","smile","blush","massage","happiness"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","eye","face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["eye","face","kiss","smile","affection","valentines","infatuation"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["grateful","proud","relieved","smiling","tear","touched","sad","cry","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["delicious","face","savouring","smile","yum","happy","joy","tongue","silly","yummy","nom"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["face","tongue","prank","childish","playful","mischievous","smile"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["eye","face","joke","tongue","wink","prank","childish","playful","mischievous","smile"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","goofy","large","small","face","crazy"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["eye","face","horrible","taste","tongue","prank","playful","mischievous","smile"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["face","money","money-mouth face","mouth","money_mouth_face","rich","dollar"]},"smiling-face-with-open-hands":{"a":"Smiling Face with Open Hands","b":"1F917","j":["face","hug","hugging","open hands","smiling face","hugging_face","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"face-with-open-eyes-and-hand-over-mouth":{"a":"⊛ Face with Open Eyes and Hand over Mouth","b":"1FAE2","j":["amazement","awe","disbelief","embarrass","scared","surprise","silence","secret","shock"]},"face-with-peeking-eye":{"a":"⊛ Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare","scared","frightening","embarrassing"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["quiet","shush","face","shhh"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["face","thinking","hmmm","think","consider"]},"saluting-face":{"a":"⊛ Saluting Face","b":"1FAE1","j":["ok","salute","sunny","troops","yes","respect"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","mouth","zipper","zipper-mouth face","zipper_mouth_face","sealed","secret"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["distrust","skeptic","disapproval","disbelief","mild surprise","scepticism","face","surprise"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan","face","meh","neutral","indifference",":|"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["expressionless","face","inexpressive","meh","unexpressive","indifferent","-_-","deadpan"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","mouth","quiet","silent","hellokitty"]},"dotted-line-face":{"a":"⊛ Dotted Line Face","b":"1FAE5","j":["depressed","disappear","hide","introvert","invisible","lonely","isolation","depression"]},"face-in-clouds":{"a":"Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in the fog","head in clouds","shower","steam","dream"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","gasp","groan","relief","whisper","whistle","relieve","tired","sigh"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease","covid"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever","covid"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"face-with-crossedout-eyes":{"a":"Face with Crossed-out Eyes","b":"1F635","j":["crossed-out eyes","dead","face","face with crossed-out eyes","knocked out","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","hypnotized","spiral","trouble","whoa","sick","ill","confused","nauseous","nausea"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"face-with-diagonal-mouth":{"a":"⊛ Face with Diagonal Mouth","b":"1FAE4","j":["disappointed","meh","skeptical","unsure","skeptic","confuse","frustrated","indifferent"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":["face","worried","concern","nervous",":("]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frown","frowning","disappointed","sad","upset"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["face","frown","sad","upset"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","mouth","open","sympathy","surprise","impressed","wow","whoa",":O"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["face","hushed","stunned","surprised","woo","shh"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["astonished","face","shocked","totally","xox","surprised","poisoned"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["dazed","face","flushed","blush","shy","flattered"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","puppy eyes","face"]},"face-holding-back-tears":{"a":"⊛ Face Holding Back Tears","b":"1F979","j":["angry","cry","proud","resist","sad","touched","gratitude"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","love","lust","sacred heart","passionate","enthusiastic"]},"mending-heart":{"a":"Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","recovering","recuperating","well","broken heart","bandage","wounded"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"rightwards-hand":{"a":"⊛ Rightwards Hand","b":"1FAF1","j":["hand","right","rightward","palm","offer"]},"leftwards-hand":{"a":"⊛ Leftwards Hand","b":"1FAF2","j":["hand","left","leftward","palm","offer"]},"palm-down-hand":{"a":"⊛ Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo","palm"]},"palm-up-hand":{"a":"⊛ Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer","lift","demand"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["hand","OK","fingers","limbs","perfect","ok","okay"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["fingers","hand gesture","interrogation","pinched","sarcastic","size","tiny","small"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small amount","tiny","small","size"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["hand","v","victory","fingers","ohyeah","peace","two"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["cross","finger","hand","luck","good","lucky"]},"hand-with-index-finger-and-thumb-crossed":{"a":"⊛ Hand with Index Finger and Thumb Crossed","b":"1FAF0","j":["expensive","heart","love","money","snap"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["hand","ILY","love-you gesture","love_you_gesture","fingers","gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["finger","hand","horns","rock-on","fingers","evil_eye","sign_of_horns","rock_on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["call","hand","hands","gesture","shaka"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["backhand","finger","hand","index","point","direction","fingers","left"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["backhand","finger","hand","index","point","fingers","direction","right"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["backhand","finger","hand","point","up","fingers","direction"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["finger","hand","fingers","rude","middle","flipping"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["backhand","down","finger","hand","point","fingers","direction"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["finger","hand","index","point","up","fingers","direction"]},"index-pointing-at-the-viewer":{"a":"⊛ Index Pointing at the Viewer","b":"1FAF5","j":["point","you","recruit"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["+1","hand","thumb","up","thumbsup","yes","awesome","good","agree","accept","cool","like"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","down","hand","thumb","thumbsdown","no","dislike"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["clenched","fist","hand","punch","fingers","grasp"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["clenched","fist","hand","punch","angry","violence","hit","attack"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["fist","right-facing fist","rightwards","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["clap","hand","hands","praise","applause","congrats","yay"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["celebration","gesture","hand","hooray","raised","yea","hands"]},"heart-hands":{"a":"⊛ Heart Hands","b":"1FAF6","j":["love","appreciation","support"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive","thank you","appreciate"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"biting-lip":{"a":"⊛ Biting Lip","b":"1FAE6","j":["anxious","fear","flirting","nervous","uncomfortable","worried","flirt","sexy","pain","worry"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard","facial hair"]},"woman-beard":{"a":"Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard","facial hair"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"person-with-crown":{"a":"⊛ Person with Crown","b":"1FAC5","j":["monarch","noble","regal","royalty","power"]},"prince":{"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"]},"princess":{"a":"Princess","b":"1F478","j":["fairy tale","fantasy","girl","woman","female","blond","crown","royal","queen"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["turban","headdress"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["man","turban","male","indian","hinduism","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","woman","female","indian","hinduism","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["cap","gua pi mao","hat","person","skullcap","man_with_skullcap","male","boy","chinese"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["headscarf","hijab","mantilla","tichel","bandana","head kerchief","female"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["groom","person","tuxedo","man_in_tuxedo","couple","marriage","wedding"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","formal","fashion"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["tuxedo","woman","formal","fashion"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","person","veil","wedding","bride_with_veil","couple","marriage","woman"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","veil","wedding","marriage"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["veil","woman","wedding","marriage"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["pregnant","woman","baby"]},"pregnant-man":{"a":"⊛ Pregnant Man","b":"1FAC3","j":["belly","bloated","full","pregnant","baby"]},"pregnant-person":{"a":"⊛ Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant","baby"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["baby","breast","breast-feeding","nursing","breast_feeding"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["baby","feeding","nursing","woman","birth","food"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["baby","feeding","man","nursing","birth","food"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["baby","feeding","nursing","person","birth","food"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["angel","baby","face","fairy tale","fantasy","heaven","wings","halo"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["celebration","Christmas","claus","father","santa","festival","man","male","xmas","father christmas"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["celebration","Christmas","claus","mother","Mrs.","woman","female","xmas","mother christmas"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["good","hero","heroine","superpower","marvel"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["good","hero","man","superpower","male","superpowers"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["good","hero","heroine","superpower","woman","female","superpowers"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["criminal","evil","superpower","villain","marvel"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["criminal","evil","man","superpower","villain","male","bad","hero","superpowers"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["criminal","evil","superpower","villain","woman","female","bad","heroine","superpowers"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","sorceress","witch","wizard","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","wizard","man","male","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["sorceress","witch","woman","female","mage"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Oberon","Puck","Titania","wings","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["Oberon","Puck","man","male"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","woman","female"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["Dracula","undead","blood","twilight"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["Dracula","undead","man","male","dracula"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["undead","woman","female"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","merman","merwoman","sea"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["Triton","man","male","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["merwoman","woman","female","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["magical","LOTR style"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["magical","man","male"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["magical","woman","female"]},"genie":{"a":"Genie","b":"1F9DE","j":["djinn","(non-human color)","magical","wishes"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["djinn","man","male"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["djinn","woman","female"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["undead","walking dead","(non-human color)","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["undead","walking dead","man","male","dracula"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["undead","walking dead","woman","female"]},"troll":{"a":"⊛ Troll","b":"1F9CC","j":["fairy tale","fantasy","monster","mystical"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["face","massage","salon","relax"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["face","man","massage","male","boy","head"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["face","massage","woman","female","girl","head"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["barber","beauty","haircut","parlor","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["haircut","man","male","boy"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["haircut","woman","female","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["hike","walk","walking","move"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["hike","man","walk","human","feet","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["hike","walk","woman","human","feet","steps","female"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["stand","standing","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["kneeling","man","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["kneeling","woman","respectful","pray"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["accessibility","blind","man","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["accessibility","blind","woman","woman_with_probing_cane"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","wheelchair","disability"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["accessibility","man","wheelchair","disability"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["accessibility","wheelchair","woman","disability"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","wheelchair","disability"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["accessibility","man","wheelchair","disability"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["accessibility","wheelchair","woman","disability"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["marathon","running","move"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["man","marathon","racing","running","walking","exercise","race"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["marathon","racing","running","woman","walking","exercise","race","female"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","dancing","woman","female","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dance","dancing","man","male","boy","fun","dancer"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","person","suit","man_in_suit_levitating","levitate","hover","jump"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["bunny ear","dancer","partying","perform","costume"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["bunny ear","dancer","men","partying","male","bunny","boys"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["bunny ear","dancer","partying","women","female","bunny","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["sauna","steam room","hamam","steambath","relax","spa"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","steam room","male","man","spa","steamroom"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","steam room","female","woman","spa","steamroom"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["climber","sport"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["climber","sports","hobby","man","male","rock"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["climber","sports","hobby","woman","female","rock"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["fencer","fencing","sword","sports"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["horse","jockey","racehorse","racing","animal","betting","competition","gambling","luck"]},"skier":{"a":"Skier","b":"26F7","j":["ski","snow","sports","winter"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["ski","snow","snowboard","sports","winter"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["ball","golf","sports","business"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","man","sport"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["golf","woman","sports","business","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["surfing","sport","sea"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["man","surfing","sports","ocean","sea","summer","beach"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["surfing","woman","sports","ocean","sea","summer","beach","female"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["boat","rowboat","sport","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["boat","man","rowboat","sports","hobby","water","ship"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["boat","rowboat","woman","sports","hobby","water","ship","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["man","swim","sports","exercise","human","athlete","water","summer"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","woman","sports","exercise","human","athlete","water","summer","female"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["ball","sports","human"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["ball","man","sport"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["ball","woman","sports","human","female"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["lifter","weight","sports","training","exercise"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","weight lifter","sport"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["weight lifter","woman","sports","training","exercise","female"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["bicycle","biking","cyclist","sport","move"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["bicycle","biking","cyclist","man","sports","bike","exercise","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["bicycle","biking","cyclist","woman","sports","bike","exercise","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["bicycle","bicyclist","bike","cyclist","mountain","sport","move"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["bicycle","bike","cyclist","man","mountain","transportation","sports","human","race"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["bicycle","bike","biking","cyclist","mountain","woman","transportation","sports","human","race","female"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastics","sport","gymnastic"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","gymnastics","woman"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["wrestle","wrestler","sport"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["men","wrestle","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["women","wrestle","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["polo","water","sport"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["water polo","woman","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["ball","handball","sport"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["handball","man","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["handball","woman","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","multitask","skill","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["juggling","man","multitask","juggle","balance","skill"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["juggling","multitask","woman","juggle","balance","skill"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","man","male","serenity","zen","mindfulness"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","serenity","zen","mindfulness"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","clean","shower","bathroom"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["hotel","sleep","bed","rest"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["couple","hand","hold","holding hands","person","friendship"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["couple","hand","holding hands","women","pair","friendship","love","like","female","people","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["couple","hand","hold","holding hands","man","woman","pair","people","human","love","date","dating","like","affection","valentines","marriage"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["couple","Gemini","holding hands","man","men","twins","zodiac","pair","love","like","bromance","friendship","people","human"]},"kiss":{"a":"Kiss","b":"1F48F","j":["couple","pair","valentines","love","like","dating","marriage"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","woman","love"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","pair","valentines","love","like","dating","marriage"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["couple","kiss","woman","pair","valentines","love","like","dating","marriage"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["couple","love","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","woman"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["couple","couple with heart","love","woman","pair","like","affection","human","dating","valentines","marriage"]},"family":{"a":"Family","b":"1F46A","j":["home","parents","child","mom","dad","father","mother","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["boy","family","man","woman","love"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","child"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","man","woman","home","parents","people","human","children"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","man","woman","home","parents","people","human","children"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","children"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parents","people","human","children"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["boy","family","man","home","parent","people","human","child"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parent","people","human","children"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["family","girl","man","home","parent","people","human","child"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parent","people","human","children"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parent","people","human","children"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["boy","family","woman","home","parent","people","human","child"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parent","people","human","children"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["family","girl","woman","home","parent","people","human","child"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parent","people","human","children"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parent","people","human","children"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["face","head","silhouette","speak","speaking","user","person","human","sing","say","talk"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","silhouette","user","person","human"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["bust","silhouette","user","person","human","group","team"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["goodbye","hello","hug","thanks","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","footprint","print","feet","tracking","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["face","monkey","animal","nature","circus"]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["dog","face","pet","animal","friend","nature","woof","puppy","faithful"]},"dog":{"a":"Dog","b":"1F415","j":["pet","animal","nature","friend","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","blind","guide","animal"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["accessibility","assistance","dog","service","blind","animal"]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"]},"wolf":{"a":"Wolf","b":"1F43A","j":["face","animal","nature","wild"]},"fox":{"a":"Fox","b":"1F98A","j":["face","animal","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","sly","animal","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["cat","face","pet","animal","meow","nature","kitten"]},"cat":{"a":"Cat","b":"1F408","j":["pet","animal","meow","cats"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","cat","unlucky","superstition","luck"]},"lion":{"a":"Lion","b":"1F981","j":["face","Leo","zodiac","animal","nature"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["face","tiger","animal","cat","danger","wild","nature","roar"]},"tiger":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["face","horse","animal","brown","nature"]},"horse":{"a":"Horse","b":"1F40E","j":["equestrian","racehorse","racing","animal","gamble","luck"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["face","animal","nature","mystical"]},"zebra":{"a":"Zebra","b":"1F993","j":["stripe","animal","nature","stripes","safari"]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"]},"bison":{"a":"Bison","b":"1F9AC","j":["buffalo","herd","wisent","ox"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["cow","face","beef","ox","animal","nature","moo","milk"]},"ox":{"a":"Ox","b":"1F402","j":["bull","Taurus","zodiac","animal","cow","beef"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["buffalo","water","animal","nature","ox","cow"]},"cow":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["face","pig","animal","oink","nature"]},"pig":{"a":"Pig","b":"1F416","j":["sow","animal","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["face","nose","pig","animal","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","male","sheep","zodiac","animal","nature"]},"ewe":{"a":"Ewe","b":"1F411","j":["female","sheep","animal","nature","wool","shipit"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","zodiac","animal","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hump","animal","hot","desert"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["bactrian","camel","hump","two-hump camel","two_hump_camel","animal","nature","hot","desert"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","guanaco","vicuña","wool","animal","nature"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["spots","animal","nature","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["extinction","large","tusk","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["face","mouse","animal","nature","cheese_wedge","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["face","pet","animal","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","face","pet","rabbit","animal","nature","spring","magic"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","pet","animal","nature","magic","spring"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["squirrel","animal","nature","rodent"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["dam","animal","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["spiny","animal","nature"]},"bat":{"a":"Bat","b":"1F987","j":["vampire","animal","nature","blind"]},"bear":{"a":"Bear","b":"1F43B","j":["face","animal","nature","wild"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["arctic","bear","white","animal"]},"koala":{"a":"Koala","b":"1F428","j":["face","marsupial","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["face","animal","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["lazy","slow","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","playful","animal"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["Australia","joey","jump","marsupial","animal","nature","australia","hop"]},"badger":{"a":"Badger","b":"1F9A1","j":["honey badger","pester","animal","nature","honey"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["feet","paw","print","animal","tracking","footprints","dog","cat","pet"]},"turkey":{"a":"Turkey","b":"1F983","j":["bird","animal"]},"chicken":{"a":"Chicken","b":"1F414","j":["bird","animal","cluck","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["bird","animal","nature","chicken"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["baby","bird","chick","hatching","animal","chicken","egg","born"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["baby","bird","chick","animal","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["baby","bird","chick","front-facing baby chick","front_facing_baby_chick","animal","chicken"]},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"]},"penguin":{"a":"Penguin","b":"1F427","j":["bird","animal","nature"]},"dove":{"a":"Dove","b":"1F54A","j":["bird","fly","peace","animal"]},"eagle":{"a":"Eagle","b":"1F985","j":["bird","animal","nature"]},"duck":{"a":"Duck","b":"1F986","j":["bird","animal","nature","mallard"]},"swan":{"a":"Swan","b":"1F9A2","j":["bird","cygnet","ugly duckling","animal","nature"]},"owl":{"a":"Owl","b":"1F989","j":["bird","wise","animal","nature","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","large","Mauritius","animal","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["bird","flight","light","plumage","fly"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["flamboyant","tropical","animal"]},"peacock":{"a":"Peacock","b":"1F99A","j":["bird","ostentatious","peahen","proud","animal","nature"]},"parrot":{"a":"Parrot","b":"1F99C","j":["bird","pirate","talk","animal","nature"]},"frog":{"a":"Frog","b":"1F438","j":["face","animal","nature","croak","toad"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"]},"turtle":{"a":"Turtle","b":"1F422","j":["terrapin","tortoise","animal","slow","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["bearer","Ophiuchus","serpent","zodiac","animal","evil","nature","hiss","python"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","face","fairy tale","animal","myth","nature","chinese","green"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","animal","myth","nature","chinese","green"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["brachiosaurus","brontosaurus","diplodocus","animal","nature","dinosaur","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["Tyrannosaurus Rex","t_rex","animal","nature","dinosaur","tyrannosaurus","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["face","spouting","whale","animal","nature","sea","ocean"]},"whale":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["flipper","animal","nature","fish","sea","ocean","fins","beach"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea lion","animal","creature","sea"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","zodiac","animal","food","nature"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["fish","tropical","animal","swim","ocean","beach","nemo"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","animal","nature","food","sea","ocean"]},"shark":{"a":"Shark","b":"1F988","j":["fish","animal","nature","sea","ocean","jaws","fins","beach"]},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["shell","spiral","nature","sea","beach"]},"coral":{"a":"⊛ Coral","b":"1FAB8","j":["ocean","reef","sea"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs","covid"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"lotus":{"a":"⊛ Lotus","b":"1FAB7","j":["Buddhism","flower","Hinduism","India","purity","Vietnam","calm","meditation"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"empty-nest":{"a":"⊛ Empty Nest","b":"1FAB9","j":["nesting","bird"]},"nest-with-eggs":{"a":"⊛ Nest with Eggs","b":"1FABA","j":["nesting","bird"]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","grape","food","wine"]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["fruit","orange","food","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["citrus","fruit","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"]},"mango":{"a":"Mango","b":"1F96D","j":["fruit","tropical","food"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["apple","fruit","red","mac","school"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["apple","fruit","green","nature"]},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"]},"cherries":{"a":"Cherries","b":"1F352","j":["berries","cherry","fruit","red","food"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["berry","fruit","food","nature"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["berry","bilberry","blue","blueberry","fruit"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","fruit","kiwi"]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["palm","piña colada","fruit","nature","food"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["aubergine","vegetable","nature","food"]},"potato":{"a":"Potato","b":"1F954","j":["food","vegetable","tuber","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["corn","ear","maize","maze","food","vegetable","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["hot","pepper","food","spicy","chilli","chili"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["capsicum","pepper","vegetable","fruit","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["bok choy","cabbage","kale","lettuce","food","vegetable","plant"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["wild cabbage","fruit","food","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["flavoring","food","spice","cook"]},"onion":{"a":"Onion","b":"1F9C5","j":["flavoring","cook","food","spice"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut","peanut","vegetable"]},"beans":{"a":"⊛ Beans","b":"1FAD8","j":["food","kidney","legume"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french","france","bakery"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food","bakery"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread","germany","bakery"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread","jewish"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes","brunch"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food","brunch"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder","swiss"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig","brunch"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food","potato"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party","italy"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food","america"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch","toast","bakery"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed","mediterranean"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food","mediterranean"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen","skillet"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking","skillet"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup","hot pot"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce","vegetable"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack","drama"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup","tomatoes"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese","lunch"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese","snack"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature","plant"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food","dessert"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food","gyoza"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food","dessert"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"pouring-liquid":{"a":"⊛ Pouring Liquid","b":"1FAD7","j":["drink","empty","glass","spill","cup","water"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["juice","soda","malt","soft drink","water","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["bubble","milk","pearl","tea","taiwan","boba","milk tea","straw"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["beverage","box","juice","straw","sweet","drink"]},"mate":{"a":"Mate","b":"1F9C9","j":["drink","tea","beverage"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","ice cube","iceberg","water"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["hashi","jeotgarak","kuaizi","food"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["cooking","fork","knife","plate","food","eat","meal","lunch","dinner","restaurant"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","cutlery","fork","knife","kitchen"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","cutlery","kitchen"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["cooking","hocho","knife","tool","weapon","blade","cutlery","kitchen"]},"jar":{"a":"⊛ Jar","b":"1FAD9","j":["condiment","container","empty","sauce","store"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["Aquarius","cooking","drink","jug","zodiac","vase","jar"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Africa","earth","Europe","globe","globe showing Europe-Africa","world","globe_showing_europe_africa","international"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["Americas","earth","globe","globe showing Americas","world","USA","international"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["Asia","Australia","earth","globe","globe showing Asia-Australia","world","globe_showing_asia_australia","east","international"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","globe","meridians","world","international","internet","interweb","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["map","world","location","direction"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["Japan","map","map of Japan","nation","country","japanese","asia"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","navigation","orienteering"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["cold","mountain","snow","snow-capped mountain","snow_capped_mountain","photo","nature","environment","winter"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","nature","environment"]},"volcano":{"a":"Volcano","b":"1F30B","j":["eruption","mountain","photo","nature","disaster"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","mountain","photo","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["beach","umbrella","weather","summer","sunny","sand","mojito"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","warm","saharah"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["desert","island","photo","tropical","mojito"]},"national-park":{"a":"National Park","b":"1F3DE","j":["park","photo","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["photo","place","sports","concert","venue"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["classical","art","culture","history"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["construction","wip","working","progress"]},"brick":{"a":"Brick","b":"1F9F1","j":["bricks","clay","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["boulder","heavy","solid","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","lumber","timber","nature","trunk"]},"hut":{"a":"Hut","b":"1F6D6","j":["house","roundhouse","yurt","structure"]},"houses":{"a":"Houses","b":"1F3D8","j":["buildings","photo"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["derelict","house","abandon","evict","broken","building"]},"house":{"a":"House","b":"1F3E0","j":["home","building"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["garden","home","house","plant","nature"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["Japanese","Japanese post office","post","building","envelope","communication"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["European","post","building","email"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["doctor","medicine","building","health","surgery"]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["hotel","love","like","affection","dating"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["convenience","store","building","shopping","groceries"]},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["department","store","building","shopping","mall"]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","Japanese","photo","building"]},"castle":{"a":"Castle","b":"1F3F0","j":["European","building","royalty","history"]},"wedding":{"a":"Wedding","b":"1F492","j":["chapel","romance","love","like","affection","couple","marriage","bride","groom"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["Tokyo","tower","photo","japanese"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["liberty","statue","american","newyork"]},"church":{"a":"Church","b":"26EA","j":["Christian","cross","religion","building","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","Muslim","religion","worship","minaret"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["hindu","temple","religion"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","Jewish","religion","temple","judaism","worship","jewish"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["religion","shinto","shrine","temple","japan","kyoto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","Muslim","religion","mecca","mosque"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"]},"tent":{"a":"Tent","b":"26FA","j":["camping","photo","outdoors"]},"foggy":{"a":"Foggy","b":"1F301","j":["fog","photo","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","star","evening","city","downtown"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["city","photo","night life","urban"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","mountain","sun","sunrise","view","vacation","photo"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","sun","view","vacation","photo"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["city","dusk","evening","landscape","sunset","photo","sky","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","sun","photo","good morning","dawn"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["bridge","night","photo","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["hot","hotsprings","springs","steaming","bath","warm","relax"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["carousel","horse","photo","carnival"]},"playground-slide":{"a":"⊛ Playground Slide","b":"1F6DD","j":["amusement park","play","fun","park"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["amusement park","ferris","wheel","photo","carnival","londoneye"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["amusement park","coaster","roller","carnival","playground","photo","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["barber","haircut","pole","hair","salon","style"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["circus","tent","festival","carnival","party"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["engine","railway","steam","train","transportation","vehicle"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","railway","train","tram","trolleybus","transportation","vehicle"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["high-speed train","railway","shinkansen","speed","train","high_speed_train","transportation","vehicle"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["bullet","railway","shinkansen","speed","train","transportation","vehicle","fast","public","travel"]},"train":{"a":"Train","b":"1F686","j":["railway","transportation","vehicle"]},"metro":{"a":"Metro","b":"1F687","j":["subway","transportation","blue-square","mrt","underground","tube"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["railway","transportation","vehicle"]},"station":{"a":"Station","b":"1F689","j":["railway","train","transportation","vehicle","public"]},"tram":{"a":"Tram","b":"1F68A","j":["trolleybus","transportation","vehicle"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","mountain","railway","transportation","vehicle"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","tram","trolleybus","transportation","vehicle","carriage","public","travel"]},"bus":{"a":"Bus","b":"1F68C","j":["vehicle","car","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","oncoming","vehicle","transportation"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","tram","trolley","bart","transportation","vehicle"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","car","transportation"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["vehicle","health","911","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["engine","fire","truck","transportation","cars","vehicle"]},"police-car":{"a":"Police Car","b":"1F693","j":["car","patrol","police","vehicle","cars","transportation","law","legal","enforcement"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","oncoming","police","vehicle","law","legal","enforcement","911"]},"taxi":{"a":"Taxi","b":"1F695","j":["vehicle","uber","cars","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["oncoming","taxi","vehicle","cars","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","red","transportation","vehicle"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["automobile","car","oncoming","vehicle","transportation"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["recreational","sport utility","transportation","vehicle"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["pick-up","pickup","truck","car","transportation"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["delivery","truck","cars","transportation"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["lorry","semi","truck","vehicle","cars","transportation","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","racing","sports","race","fast","formula","f1"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","race","sports","fast"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["motor","scooter","vehicle","vespa","sasha"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["tuk tuk","move","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["bike","sports","exercise","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["kick","scooter","vehicle","razor"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","footwear","sports"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","stop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["drum","oil","barrell"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["diesel","fuel","fuelpump","gas","pump","station","gas station","petroleum"]},"wheel":{"a":"⊛ Wheel","b":"1F6DE","j":["circle","tire","turn","car","transport"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["beacon","car","light","police","revolving","ambulance","911","emergency","alert","error","pinged","law","legal"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","signal","traffic","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["light","signal","traffic","transportation","driving"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","sign","stop"]},"construction":{"a":"Construction","b":"1F6A7","j":["barrier","wip","progress","caution","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["ship","tool","ferry","sea","boat"]},"ring-buoy":{"a":"⊛ Ring Buoy","b":"1F6DF","j":["float","life preserver","life saver","rescue","safety"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","resort","sea","yacht","ship","summer","transportation","water","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["boat","ship","transportation","vehicle","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["passenger","ship","yacht","cruise","ferry"]},"ferry":{"a":"Ferry","b":"26F4","j":["boat","passenger","ship","yacht"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["boat","motorboat","ship"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","passenger","transportation","titanic","deploy"]},"airplane":{"a":"Airplane","b":"2708","j":["aeroplane","vehicle","transportation","flight","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["aeroplane","airplane","flight","transportation","fly","vehicle"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["aeroplane","airplane","check-in","departure","departures","airport","flight","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["aeroplane","airplane","arrivals","arriving","landing","airport","flight","boarding"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["chair","sit","airplane","transport","bus","flight","fly"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["railway","suspension","vehicle","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["cable","gondola","mountain","transportation","vehicle","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["aerial","cable","car","gondola","tramway","transportation","vehicle","ski"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["space","communication","gps","orbit","spaceflight","NASA","ISS"]},"rocket":{"a":"Rocket","b":"1F680","j":["space","launch","ship","staffmode","NASA","outer space","outer_space","fly"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["UFO","transportation","vehicle","ufo"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["packing","travel"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["sand","timer","time","clock","oldschool","limit","exam","quiz","test"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["hourglass","sand","timer","oldschool","time","countdown"]},"watch":{"a":"Watch","b":"231A","j":["clock","time","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["alarm","clock","time","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["clock","time","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["clock","time"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["00","12","12:00","clock","o’clock","twelve","twelve_o_clock","time","noon","midnight","midday","late","early","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["12","12:30","clock","thirty","twelve","twelve-thirty","twelve_thirty","time","late","early","schedule"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["00","1","1:00","clock","o’clock","one","one_o_clock","time","late","early","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["1","1:30","clock","one","one-thirty","thirty","one_thirty","time","late","early","schedule"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["00","2","2:00","clock","o’clock","two","two_o_clock","time","late","early","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2","2:30","clock","thirty","two","two-thirty","two_thirty","time","late","early","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["00","3","3:00","clock","o’clock","three","three_o_clock","time","late","early","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["3","3:30","clock","thirty","three","three-thirty","three_thirty","time","late","early","schedule"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["00","4","4:00","clock","four","o’clock","four_o_clock","time","late","early","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["4","4:30","clock","four","four-thirty","thirty","four_thirty","time","late","early","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["00","5","5:00","clock","five","o’clock","five_o_clock","time","late","early","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["5","5:30","clock","five","five-thirty","thirty","five_thirty","time","late","early","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["00","6","6:00","clock","o’clock","six","six_o_clock","time","late","early","schedule","dawn","dusk"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["6","6:30","clock","six","six-thirty","thirty","six_thirty","time","late","early","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["00","7","7:00","clock","o’clock","seven","seven_o_clock","time","late","early","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["7","7:30","clock","seven","seven-thirty","thirty","seven_thirty","time","late","early","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["00","8","8:00","clock","eight","o’clock","eight_o_clock","time","late","early","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["8","8:30","clock","eight","eight-thirty","thirty","eight_thirty","time","late","early","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["00","9","9:00","clock","nine","o’clock","nine_o_clock","time","late","early","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["9","9:30","clock","nine","nine-thirty","thirty","nine_thirty","time","late","early","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["00","10","10:00","clock","o’clock","ten","ten_o_clock","time","late","early","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["10","10:30","clock","ten","ten-thirty","thirty","ten_thirty","time","late","early","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["00","11","11:00","clock","eleven","o’clock","eleven_o_clock","time","late","early","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["11","11:30","clock","eleven","eleven-thirty","thirty","eleven_thirty","time","late","early","schedule"]},"new-moon":{"a":"New Moon","b":"1F311","j":["dark","moon","nature","twilight","planet","space","night","evening","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["crescent","moon","waxing","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["gibbous","moon","waxing","nature","night","sky","gray","twilight","planet","space","evening","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["full","moon","nature","yellow","twilight","planet","space","night","evening","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["gibbous","moon","waning","nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["crescent","moon","waning","nature","twilight","planet","space","night","evening","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["crescent","moon","night","sleep","sky","evening","magic"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["face","moon","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["weather","temperature","hot","cold"]},"sun":{"a":"Sun","b":"2600","j":["bright","rays","sunny","weather","nature","brightness","summer","beach","spring"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["bright","face","full","moon","nature","twilight","planet","space","night","evening","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["bright","face","sun","nature","morning","sky"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","saturnine","outerspace"]},"star":{"a":"Star","b":"2B50","j":["night","yellow"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["glittery","glow","shining","sparkle","star","night","awesome","good","magic"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["falling","shooting","star","night","photo"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["space","photo","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["weather","sky"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["cloud","sun","weather","nature","cloudy","morning","fall","spring"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["cloud","rain","thunder","weather","lightning"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["cloud","sun","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["cloud","sun","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["cloud","rain","sun","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["cloud","rain","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cloud","cold","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["cloud","lightning","weather","thunder"]},"tornado":{"a":"Tornado","b":"1F32A","j":["cloud","whirlwind","weather","cyclone","twister"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["blow","cloud","face","wind","gust","air"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["dizzy","hurricane","twister","typhoon","weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","nature","happy","unicorn_face","photo","sky","spring"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","rain","umbrella","weather","drizzle"]},"umbrella":{"a":"Umbrella","b":"2602","j":["clothing","rain","weather","spring"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","drop","rain","umbrella","rainy","weather","spring"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["rain","sun","umbrella","weather","summer"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["danger","electric","lightning","voltage","zap","thunder","weather","lightning bolt","fast"]},"snowflake":{"a":"Snowflake","b":"2744","j":["cold","snow","winter","season","weather","christmas","xmas"]},"snowman":{"a":"Snowman","b":"2603","j":["cold","snow","winter","season","weather","christmas","xmas","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["cold","snow","snowman","winter","season","weather","christmas","xmas","frozen","without_snow"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["flame","tool","hot","cook"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["cold","comic","drop","sweat","water","drip","faucet","spring"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["ocean","water","wave","sea","nature","tsunami","disaster"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["celebration","halloween","jack","jack-o-lantern","lantern","jack_o_lantern","light","pumpkin","creepy","fall"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["celebration","Christmas","tree","festival","vacation","december","xmas"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["celebration","photo","festival","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["celebration","fireworks","sparkle","stars","night","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["dynamite","explosive","fireworks","boom","explode","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","sparkle","star","stars","shine","shiny","cool","awesome","good","magic"]},"balloon":{"a":"Balloon","b":"1F388","j":["celebration","party","birthday","circus"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["celebration","party","popper","tada","congratulations","birthday","magic","circus"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["ball","celebration","confetti","festival","party","birthday","circus"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["banner","celebration","Japanese","tree","plant","nature","branch","summer"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["bamboo","celebration","Japanese","pine","plant","nature","vegetable","panda"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["celebration","doll","festival","Japanese","Japanese dolls","japanese","toy","kimono"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["carp","celebration","streamer","fish","japanese","koinobori","banner"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["bell","celebration","chime","wind","nature","ding","spring"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["celebration","ceremony","moon","photo","japan","asia","tsukimi"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["gift","good luck","hóngbāo","lai see","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["celebration","decoration","pink","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["box","celebration","gift","present","wrapped","birthday","christmas","xmas"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["celebration","reminder","ribbon","sports","cause","support","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["admission","ticket","sports","concert","entrance"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["admission","event","concert","pass"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["celebration","medal","military","award","winning","army"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["prize","win","award","contest","place","ftw","ceremony"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","award","winning"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["first","gold","medal","award","winning"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["bronze","medal","third","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["ball","football","soccer","sports"]},"baseball":{"a":"Baseball","b":"26BE","j":["ball","sports","balls"]},"softball":{"a":"Softball","b":"1F94E","j":["ball","glove","underarm","sports","balls"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["ball","hoop","sports","balls","NBA"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["ball","game","sports","balls"]},"american-football":{"a":"American Football","b":"1F3C8","j":["american","ball","football","sports","balls","NFL"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["ball","football","rugby","sports","team"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["ball","racquet","sports","balls","green"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["ball","game","sports","fun","play"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["ball","bat","game","sports"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["ball","field","game","hockey","stick","sports"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["game","hockey","ice","puck","stick","sports"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["ball","goal","stick","sports"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["ball","bat","game","paddle","table tennis","sports","pingpong"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","game","racquet","shuttlecock","sports"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","martial arts","taekwondo","uniform"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["goal","net","sports"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["golf","hole","sports","business","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["ice","skate","sports"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["fish","pole","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["diving","scuba","snorkeling","sport","ocean"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["athletics","running","sash","shirt","play","pageant"]},"skis":{"a":"Skis","b":"1F3BF","j":["ski","snow","sports","winter","cold"]},"sled":{"a":"Sled","b":"1F6F7","j":["sledge","sleigh","luge","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","rock","sports"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","game","hit","target","direct_hit","play","bar"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["fluctuate","toy","yo-yo","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["fly","soar","wind"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","ball","billiard","eight","game","pool","hobby","luck","magic"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["ball","crystal","fairy tale","fantasy","fortune","tool","disco","party","magic","circus","fortune_teller"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["magic","witch","wizard","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["bead","charm","evil-eye","nazar","talisman"]},"hamsa":{"a":"⊛ Hamsa","b":"1FAAC","j":["amulet","Fatima","hand","Mary","Miriam","protection","religion"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["controller","game","play","console","PS4"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["game","slot","bet","gamble","vegas","fruit machine","luck","casino"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["dice","die","game","random","tabletop","play","luck"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["clue","interlocking","jigsaw","piece","puzzle"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["plaything","plush","stuffed","toy"]},"piata":{"a":"Piñata","b":"1FA85","j":["celebration","party","piñata","pinata","mexico","candy"]},"mirror-ball":{"a":"⊛ Mirror Ball","b":"1FAA9","j":["dance","disco","glitter","party"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["doll","nesting","russia","matryoshka","toy"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["card","game","poker","cards","suits","magic"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["card","game","poker","cards","magic","suits"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["card","game","poker","cards","magic","suits"]},"club-suit":{"a":"Club Suit","b":"2663","j":["card","game","poker","cards","magic","suits"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["card","game","wildcard","poker","cards","play","magic"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["game","mahjong","red","play","chinese","kanji"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","flower","game","Japanese","playing","sunset","red"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["art","mask","performing","theater","theatre","acting","drama"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["art","frame","museum","painting","picture","photography"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["art","museum","painting","palette","design","paint","draw","colors"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","sewing","spool","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["embroidery","needle","sewing","stitches","sutures","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["ball","crochet","knit"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","tangled","tie","twine","twist","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["clothing","eye","eyeglasses","eyewear","fashion","accessories","eyesight","nerdy","dork","geek"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["dark","eye","eyewear","glasses","face","cool","accessories"]},"goggles":{"a":"Goggles","b":"1F97D","j":["eye protection","swimming","welding","eyes","protection","safety"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["doctor","experiment","scientist","chemist"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["emergency","safety","vest","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["clothing","tie","shirt","suitup","formal","fashion","cloth","business"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["clothing","shirt","t-shirt","t_shirt","fashion","cloth","casual","tee"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","pants","trousers","fashion","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["neck","winter","clothes"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["hand","hands","winter","clothes"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["stocking","stockings","clothes"]},"dress":{"a":"Dress","b":"1F457","j":["clothing","clothes","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","fashion","women","female","japanese"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","one-piece swimsuit","one_piece_swimsuit","fashion"]},"briefs":{"a":"Briefs","b":"1FA72","j":["bathing suit","one-piece","swimsuit","underwear","clothing"]},"shorts":{"a":"Shorts","b":"1FA73","j":["bathing suit","pants","underwear","clothing"]},"bikini":{"a":"Bikini","b":"1F459","j":["clothing","swim","swimming","female","woman","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","woman","woman’s clothes","woman_s_clothes","fashion","shopping_bags","female"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","coin","fashion","accessories","money","sales","shopping"]},"handbag":{"a":"Handbag","b":"1F45C","j":["bag","clothing","purse","fashion","accessory","accessories","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["bag","clothing","pouch","accessories","shopping"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["bag","hotel","shopping","mall","buy","purchase"]},"backpack":{"a":"Backpack","b":"1F392","j":["bag","rucksack","satchel","school","student","education"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["beach sandals","sandals","thong sandals","thongs","zōri","footwear","summer"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["clothing","man","man’s shoe","shoe","man_s_shoe","fashion","male"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["athletic","clothing","shoe","sneaker","shoes","sports","sneakers"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["backpacking","boot","camping","hiking"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["ballet flat","slip-on","slipper","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["clothing","heel","high-heeled shoe","shoe","woman","high_heeled_shoe","fashion","shoes","female","pumps","stiletto"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["clothing","sandal","shoe","woman","woman’s sandal","woman_s_sandal","shoes","fashion","flip flops"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["boot","clothing","shoe","woman","woman’s boot","woman_s_boot","shoes","fashion"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","king","queen","kod","leader","royalty","lord"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman","woman’s hat","woman_s_hat","fashion","accessories","female","lady","spring"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","top","tophat","magic","gentleman","classy","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["cap","celebration","clothing","graduation","hat","school","college","degree","university","legal","learn","education"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["baseball cap","cap","baseball"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["army","helmet","military","soldier","warrior","protection"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["aid","cross","face","hat","helmet","rescue worker’s helmet","rescue_worker_s_helmet","construction","build"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","necklace","prayer","religion","dhikr","religious"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["cosmetics","makeup","female","girl","fashion","woman"]},"ring":{"a":"Ring","b":"1F48D","j":["diamond","wedding","propose","marriage","valentines","fashion","jewelry","gem","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["diamond","gem","jewel","blue","ruby","jewelry"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["mute","quiet","silent","speaker","sound","volume","silence"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["soft","sound","volume","silence","broadcast"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["medium","volume","speaker","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["loud","volume","noise","noisy","speaker","broadcast"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["loud","public address","volume","sound"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["cheering","sound","speaker","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["horn","post","postal","instrument","music"]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["bell","forbidden","mute","quiet","silent","sound","volume"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["music","score","treble","clef","compose"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["music","note","score","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["music","note","notes","score"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","microphone","music","studio","sing","recording","artist","talkshow"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["level","music","slider","scale"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","knobs","music","dial"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["karaoke","mic","sound","music","PA","sing","talkshow"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["earbud","music","score","gadgets"]},"radio":{"a":"Radio","b":"1F4FB","j":["video","communication","music","podcast","program"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["instrument","music","sax","jazz","blues"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","squeeze box","music"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["instrument","keyboard","music","piano","compose"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["instrument","music","brass"]},"violin":{"a":"Violin","b":"1F3BB","j":["instrument","music","orchestra","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["music","stringed","instructment"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","music","instrument","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","conga","drum","rhythm","music"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["cell","mobile","phone","telephone","technology","apple","gadgets","dial"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["arrow","cell","mobile","phone","receive","iphone","incoming"]},"telephone":{"a":"Telephone","b":"260E","j":["phone","technology","communication","dial"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["phone","receiver","telephone","technology","communication","dial"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["fax","communication","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"]},"low-battery":{"a":"⊛ Low Battery","b":"1FAAB","j":["electronic","low energy","drained","dead"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["computer","pc","personal","technology","screen","display","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["computer","desktop","technology","computing","screen"]},"printer":{"a":"Printer","b":"1F5A8","j":["computer","paper","ink"]},"keyboard":{"a":"Keyboard","b":"2328","j":["computer","technology","type","input","text"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["computer","click"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["computer","technology","trackpad"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["computer","disk","minidisk","optical","technology","record","data","90s"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["computer","disk","floppy","oldschool","technology","save","90s","80s"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["cd","computer","disk","optical","technology","dvd","disc","90s"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","computer","disk","optical","cd","disc"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["camera","cinema","movie","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","film","frames","movie"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["cinema","film","movie","projector","video","tape","record"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","film","record"]},"television":{"a":"Television","b":"1F4FA","j":["tv","video","technology","program","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["video","gadgets","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["camera","flash","video","photography","gadgets"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["camera","video","film","record"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["tape","vhs","video","record","oldschool","90s","80s"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["glass","magnifying","search","tool","zoom","find","detective"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["glass","magnifying","search","tool","zoom","find","detective"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["bulb","comic","electric","idea","light","electricity"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","light","tool","torch","dark","camping","sight","night"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["bar","lantern","light","red","paper","halloween","spooky"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["diya","lamp","oil","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["book","cover","decorated","notebook","classroom","notes","record","paper","study"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["book","closed","read","library","knowledge","textbook","learn"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["book","open","read","library","knowledge","literature","learn","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["book","green","read","library","knowledge","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["blue","book","read","library","knowledge","learn","study"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["book","orange","read","library","knowledge","textbook","study"]},"books":{"a":"Books","b":"1F4DA","j":["book","literature","library","study"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notebook","notes","paper"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["curl","document","page","documents","office","paper"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","page","documents","office","paper","information"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["news","paper","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["news","newspaper","paper","rolled","rolled-up newspaper","rolled_up_newspaper","press","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["bookmark","mark","marker","tabs","favorite","save","order","tidy"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["mark","favorite","label","save"]},"label":{"a":"Label","b":"1F3F7","j":["sale","tag"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["bag","dollar","money","moneybag","payment","coins","sale"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","metal","money","silver","treasure","currency"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","bill","currency","money","note","yen","sales","japanese","dollar"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","bill","currency","dollar","money","note","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","bill","currency","euro","money","note","sales","dollar"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","bill","currency","money","note","pound","british","sterling","sales","bills","uk","england"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","fly","money","wings","dollar","bills","payment","sale"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["card","credit","money","sales","dollar","bill","payment","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["accounting","bookkeeping","evidence","proof","expenses"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["chart","graph","growth","money","yen","green-square","presentation","stats"]},"envelope":{"a":"Envelope","b":"2709","j":["email","letter","postal","inbox","communication"]},"email":{"a":"E-Mail","b":"1F4E7","j":["e-mail","letter","mail","e_mail","communication","inbox"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["e-mail","email","envelope","incoming","letter","receive","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["arrow","e-mail","email","envelope","outgoing","communication"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["box","letter","mail","outbox","sent","tray","inbox","email"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["box","inbox","letter","mail","receive","tray","email","documents"]},"package":{"a":"Package","b":"1F4E6","j":["box","parcel","mail","gift","cardboard","moving"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["closed","mail","mailbox","postbox","email","inbox","communication"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["closed","lowered","mail","mailbox","postbox","email","communication","inbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["mail","mailbox","open","postbox","email","inbox","communication"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["lowered","mail","mailbox","open","postbox","email","inbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["mail","mailbox","email","letter","envelope"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["ballot","box","election","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["stationery","write","paper","writing","school","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["nib","pen","stationery","writing","write"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["fountain","pen","stationery","writing","write"]},"pen":{"a":"Pen","b":"1F58A","j":["ballpoint","stationery","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["painting","drawing","creativity","art"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["file","folder","documents","business","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["file","folder","open","documents","load"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["card","dividers","index","organizing","business","stationery"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["calendar","tear-off calendar","tear_off_calendar","schedule","date","planning"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["note","pad","spiral","memo","stationery"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["calendar","pad","spiral","date","schedule","planning"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["card","index","rolodex","business","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["chart","graph","growth","trend","upward","presentation","stats","recovery","business","economics","money","sales","good","success"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["chart","down","graph","trend","presentation","stats","recession","business","economics","money","sales","bad","failure"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["bar","chart","graph","presentation","stats"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["pin","stationery","mark","here"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pin","pushpin","stationery","location","map","here"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["link","paperclip","documents","stationery"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["ruler","straight edge","stationery","calculate","length","math","school","drawing","architect","sketch"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["ruler","set","triangle","stationery","math","architect","sketch"]},"scissors":{"a":"Scissors","b":"2702","j":["cutting","tool","stationery","cut"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["box","card","file","business","stationery"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["bin","trash","rubbish","garbage","toss"]},"locked":{"a":"Locked","b":"1F512","j":["closed","security","password","padlock"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["lock","open","unlock","privacy","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["ink","lock","nib","pen","privacy","security","secret"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["closed","key","lock","secure","security","privacy"]},"key":{"a":"Key","b":"1F511","j":["lock","password","door"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["clue","key","lock","old","door","password"]},"hammer":{"a":"Hammer","b":"1F528","j":["tool","tools","build","create"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","split","wood","tool","cut"]},"pick":{"a":"Pick","b":"26CF","j":["mining","tool","tools","dig"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","pick","tool","tools","build","create"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["hammer","spanner","tool","wrench","tools","build","create"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","swords","weapon"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["australia","rebound","repercussion","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","arrow","bow","Sagittarius","zodiac","sports"]},"shield":{"a":"Shield","b":"1F6E1","j":["weapon","protection","security"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["carpenter","lumber","saw","tool","cut","chop"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","tool","tools","diy","ikea","fix","maintainer"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tool","tools"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["bolt","nut","tool","handy","tools","fix"]},"gear":{"a":"Gear","b":"2699","j":["cog","cogwheel","tool"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","tool","vice"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","justice","Libra","scale","zodiac","law","fairness","weight"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["chain","lock","arrest"]},"hook":{"a":"Hook","b":"1FA9D","j":["catch","crook","curve","ensnare","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["chest","mechanic","tool","tools","diy","fix","maintainer"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["attraction","horseshoe","magnetic"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["climb","rung","step","tools"]},"alembic":{"a":"Alembic","b":"2697","j":["chemistry","tool","distilling","science","experiment"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","chemistry","experiment","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","biologist","biology","culture","lab"]},"dna":{"a":"Dna","b":"1F9EC","j":["biologist","evolution","gene","genetics","life"]},"microscope":{"a":"Microscope","b":"1F52C","j":["science","tool","laboratory","experiment","zoomin","study"]},"telescope":{"a":"Telescope","b":"1F52D","j":["science","tool","stars","space","zoom","astronomy"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["antenna","dish","satellite","communication","future","radio","space"]},"syringe":{"a":"Syringe","b":"1F489","j":["medicine","needle","shot","sick","health","hospital","drugs","blood","doctor","nurse"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["bleed","blood donation","injury","medicine","menstruation","period","hurt","harm","wound"]},"pill":{"a":"Pill","b":"1F48A","j":["doctor","medicine","sick","health","pharmacy","drug"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"crutch":{"a":"⊛ Crutch","b":"1FA7C","j":["cane","disability","hurt","mobility aid","stick","accessibility","assist"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"⊛ X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton","x-ray","medicine"]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["reflection","reflector","speculum"]},"window":{"a":"Window","b":"1FA9F","j":["frame","fresh air","opening","transparent","view","scenery"]},"bed":{"a":"Bed","b":"1F6CF","j":["hotel","sleep","rest"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["couch","hotel","lamp","read","chill"]},"chair":{"a":"Chair","b":"1FA91","j":["seat","sit","furniture"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["force cup","plumber","suction","toilet"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","clean","bathroom"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["bath","clean","shower","bathroom"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["bait","mousetrap","snare","trap","cheese"]},"razor":{"a":"Razor","b":"1FA92","j":["sharp","shave","cut"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["lotion","moisturizer","shampoo","sunscreen"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["farming","laundry","picnic"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["paper towels","toilet paper","roll"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["cask","pail","vat","water","container"]},"soap":{"a":"Soap","b":"1F9FC","j":["bar","bathing","cleaning","lather","soapdish"]},"bubbles":{"a":"⊛ Bubbles","b":"1FAE7","j":["burp","clean","soap","underwater","fun","carbonation","sparkling"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["bathroom","brush","clean","dental","hygiene","teeth"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["absorbing","cleaning","porous"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["extinguish","fire","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["cart","shopping","trolley"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["smoking","kills","tobacco","joint","smoke"]},"coffin":{"a":"Coffin","b":"26B0","j":["death","vampire","dead","die","rip","graveyard","cemetery","casket","funeral","box"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","grave","graveyard","tombstone","death","rip"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["ashes","death","funeral","urn","dead","die","rip"]},"moai":{"a":"Moai","b":"1F5FF","j":["face","moyai","statue","rock","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","protest","sign","announcement"]},"identification-card":{"a":"⊛ Identification Card","b":"1FAAA","j":["credentials","ID","license","security","document"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["atm","ATM sign","automated","bank","teller","money","sales","cash","blue-square","payment"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["litter","litter bin","blue-square","sign","human","info"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["drinking","potable","water","blue-square","liquid","restroom","cleaning","faucet"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["access","blue-square","disabled","accessibility"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","man","men’s room","restroom","wc","men_s_room","toilet","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","restroom","wc","woman","women’s room","women_s_room","purple-square","female","toilet","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","WC","blue-square","toilet","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["closet","lavatory","restroom","water","wc","toilet","blue-square"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","custom","blue-square"]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","blue-square","airport","transport"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","locker","luggage","blue-square","travel"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","alert","error","problem","issue"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["child","crossing","pedestrian","traffic","school","warning","danger","sign","driving","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["entry","forbidden","no","not","prohibited","traffic","limit","security","privacy","bad","denied","stop","circle"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["entry","forbidden","no","not","forbid","stop","limit","denied","disallow","circle"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["bicycle","bike","forbidden","no","prohibited","cyclist","circle"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["forbidden","no","not","prohibited","smoking","cigarette","blue-square","smell","smoke"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["forbidden","litter","no","not","prohibited","trash","bin","garbage","circle"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non-drinking","non-potable","water","non_potable_water","drink","faucet","tap","circle"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["forbidden","no","not","pedestrian","prohibited","rules","crossing","walking","circle"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["cell","forbidden","mobile","no","phone","iphone","mute","circle"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["18","age restriction","eighteen","prohibited","underage","drink","pub","night","minor","circle"]},"radioactive":{"a":"Radioactive","b":"2622","j":["sign","nuclear","danger"]},"biohazard":{"a":"Biohazard","b":"2623","j":["sign","danger"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["arrow","cardinal","direction","north","blue-square","continue","top"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["arrow","direction","intercardinal","northeast","up-right arrow","up_right_arrow","blue-square","point","diagonal"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","cardinal","direction","east","blue-square","next"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["arrow","direction","down-right arrow","intercardinal","southeast","down_right_arrow","blue-square","diagonal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["arrow","cardinal","direction","down","south","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down-left arrow","intercardinal","southwest","down_left_arrow","blue-square","diagonal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["arrow","cardinal","direction","west","blue-square","previous","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["arrow","direction","intercardinal","northwest","up-left arrow","up_left_arrow","blue-square","point","diagonal"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","up-down arrow","up_down_arrow","blue-square","direction","way","vertical"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["arrow","left-right arrow","left_right_arrow","shape","direction","horizontal","sideways"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["arrow","back","return","blue-square","undo","enter"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","blue-square","return","rotate","direction"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["arrow","blue-square","direction","top"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","down","blue-square","direction","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["arrow","clockwise","reload","sync","cycle","round","repeat"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["anticlockwise","arrow","counterclockwise","withershins","blue-square","sync","cycle"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","back","BACK arrow","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","end","END arrow","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","on","ON! arrow","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","soon","SOON arrow","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","top","TOP arrow","up","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","worship","church","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","science","physics","chemistry"]},"om":{"a":"Om","b":"1F549","j":["Hindu","religion","hinduism","buddhism","sikhism","jainism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["David","Jew","Jewish","religion","star","star of David","judaism"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["Buddhist","dharma","religion","wheel","hinduism","buddhism","sikhism","jainism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["religion","tao","taoist","yang","yin","balance"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["Christian","cross","religion","christianity"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["Christian","cross","religion","suppedaneum"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["islam","Muslim","religion"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["candelabrum","candlestick","religion","hanukkah","candles","jewish"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["dotted six-pointed star","fortune","star","dotted_six_pointed_star","purple-square","religion","jewish","hexagram"]},"aries":{"a":"Aries","b":"2648","j":["ram","zodiac","sign","purple-square","astrology"]},"taurus":{"a":"Taurus","b":"2649","j":["bull","ox","zodiac","purple-square","sign","astrology"]},"gemini":{"a":"Gemini","b":"264A","j":["twins","zodiac","sign","purple-square","astrology"]},"cancer":{"a":"Cancer","b":"264B","j":["crab","zodiac","sign","purple-square","astrology"]},"leo":{"a":"Leo","b":"264C","j":["lion","zodiac","sign","purple-square","astrology"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","sign","purple-square","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","justice","scales","zodiac","sign","purple-square","astrology"]},"scorpio":{"a":"Scorpio","b":"264F","j":["scorpion","scorpius","zodiac","sign","purple-square","astrology"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["archer","zodiac","sign","purple-square","astrology"]},"capricorn":{"a":"Capricorn","b":"2651","j":["goat","zodiac","sign","purple-square","astrology"]},"aquarius":{"a":"Aquarius","b":"2652","j":["bearer","water","zodiac","sign","purple-square","astrology"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","zodiac","purple-square","sign","astrology"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["bearer","serpent","snake","zodiac","sign","purple-square","constellation","astrology"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["arrow","crossed","blue-square","shuffle","music","random"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["arrow","clockwise","repeat","loop","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["arrow","clockwise","once","blue-square","loop"]},"play-button":{"a":"Play Button","b":"25B6","j":["arrow","play","right","triangle","blue-square","direction"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["arrow","double","fast","fast-forward button","forward","fast_forward_button","blue-square","play","speed","continue"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["arrow","next scene","next track","triangle","forward","next","blue-square"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["arrow","pause","play","right","triangle","blue-square"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["arrow","left","reverse","triangle","blue-square","direction"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","rewind","play","blue-square"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["arrow","previous scene","previous track","triangle","backward"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["arrow","button","red","blue-square","triangle","direction","point","forward","top"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["arrow","double","blue-square","direction","top"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["arrow","button","down","red","blue-square","direction","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","double","down","blue-square","direction","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["bar","double","pause","vertical","blue-square"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["circle","record","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["camera","film","movie","blue-square","record","curtain","stage","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["brightness","dim","low","sun","afternoon","warm","summer"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["bright","brightness","sun","light"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["antenna","bar","cell","mobile","phone","blue-square","reception","internet","connection","wifi","bluetooth","bars"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["cell","mobile","mode","phone","telephone","vibration","orange-square"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["cell","mobile","off","phone","telephone","mute","orange-square","silence","quiet"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","lady","girl"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["transgender","lgbtq"]},"multiply":{"a":"Multiply","b":"2716","j":["×","cancel","multiplication","sign","x","multiplication_sign","math","calculation"]},"plus":{"a":"Plus","b":"2795","j":["+","math","sign","plus_sign","calculation","addition","more","increase"]},"minus":{"a":"Minus","b":"2796","j":["-","−","math","sign","minus_sign","calculation","subtract","less"]},"divide":{"a":"Divide","b":"2797","j":["÷","division","math","sign","division_sign","calculation"]},"heavy-equals-sign":{"a":"⊛ Heavy Equals Sign","b":"1F7F0","j":["equality","math"]},"infinity":{"a":"Infinity","b":"267E","j":["forever","unbounded","universal"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["!","!!","bangbang","exclamation","mark","surprise"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["!","!?","?","exclamation","interrobang","mark","punctuation","question","wat","surprise"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["?","mark","punctuation","question","question_mark","doubt","confused"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["?","mark","outlined","punctuation","question","doubts","gray","huh","confused"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["!","exclamation","mark","outlined","punctuation","surprise","gray","wow","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["!","exclamation","mark","punctuation","exclamation_mark","heavy_exclamation_mark","danger","surprise","wow","warning"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["dash","punctuation","wavy","draw","line","moustache","mustache","squiggle","scribble"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["bank","currency","exchange","money","sales","dollar","travel"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","dollar","money","sales","payment","buck"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["aesculapius","medicine","staff","health","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","arrow","environment","garbage","trash"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["fleur-de-lis","fleur_de_lis","decorative","scout"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["anchor","emblem","ship","tool","trident","weapon","spear"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["badge","name","fire","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf","badge","shield"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["circle","large","o","red","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["✓","button","check","mark","green-square","ok","agree","vote","election","answer","tick"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["✓","box","check","ok","agree","confirm","black-square","vote","election","yes","tick"]},"check-mark":{"a":"Check Mark","b":"2714","j":["✓","check","mark","ok","nike","answer","yes","tick"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["×","cancel","cross","mark","multiplication","multiply","x","no","delete","remove","red"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["×","mark","square","x","green-square","no","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["curl","loop","scribble","draw","shape","squiggle"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["curl","double","loop","tape","cassette"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["mark","part","graph","presentation","stats","business","economics","bad"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","asterisk","eight-spoked asterisk","eight_spoked_asterisk","star","sparkle","green-square"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","eight-pointed star","star","eight_pointed_star","orange-square","shape","polygon"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","green-square","awesome","good","fireworks"]},"copyright":{"a":"Copyright","b":"00A9","j":["c","ip","license","circle","law","legal"]},"registered":{"a":"Registered","b":"00AE","j":["r","alphabet","circle"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["mark","tm","trademark","brand","law","legal"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["keycap_","star"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["keycap","0","numbers","blue-square","null"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["keycap","blue-square","numbers","1"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["keycap","numbers","2","prime","blue-square"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["keycap","3","numbers","prime","blue-square"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["keycap","4","numbers","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["keycap","5","numbers","blue-square","prime"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["keycap","6","numbers","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["keycap","7","numbers","blue-square","prime"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["keycap","8","blue-square","numbers"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["keycap","blue-square","numbers","9"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["keycap","numbers","10","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["ABCD","input","latin","letters","uppercase","alphabet","words","blue-square"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["abcd","input","latin","letters","lowercase","blue-square","alphabet"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["1234","input","numbers","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","input","blue-square","music","note","ampersand","percent","glyphs","characters"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["abc","alphabet","input","latin","letters","blue-square"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","A button (blood type)","blood type","a_button","red-square","alphabet","letter"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","AB button (blood type)","blood type","ab_button","red-square","alphabet"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["b","B button (blood type)","blood type","b_button","red-square","alphabet","letter"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","CL button","alphabet","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["cool","COOL button","words","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["free","FREE button","blue-square","words"]},"information":{"a":"Information","b":"2139","j":["i","blue-square","alphabet","letter"]},"id-button":{"a":"Id Button","b":"1F194","j":["id","ID button","identity","purple-square","words"]},"circled-m":{"a":"Circled M","b":"24C2","j":["circle","circled M","m","alphabet","blue-circle","letter"]},"new-button":{"a":"New Button","b":"1F195","j":["new","NEW button","blue-square","words","start"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["ng","NG button","blue-square","words","shape","icon"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["blood type","o","O button (blood type)","o_button","alphabet","red-square","letter"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK","OK button","good","agree","yes","blue-square"]},"p-button":{"a":"P Button","b":"1F17F","j":["P button","parking","cars","blue-square","alphabet","letter"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["help","sos","SOS button","red-square","words","emergency","911"]},"up-button":{"a":"Up! Button","b":"1F199","j":["mark","up","UP! button","blue-square","above","high"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["versus","vs","VS button","words","orange-square"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["“here”","Japanese","Japanese “here” button","katakana","ココ","blue-square","here","japanese","destination"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ","japanese","blue-square"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月","chinese","month","moon","japanese","orange-square","kanji"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有","orange-square","chinese","have","kanji"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指","chinese","point","green-square","kanji"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得","chinese","kanji","obtain","get","circle"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","ideograph","Japanese","Japanese “discount” button","割","cut","divide","chinese","kanji","pink-square"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無","nothing","chinese","kanji","japanese","orange-square"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁","kanji","japanese","chinese","forbidden","limit","restricted","red-square"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可","ok","good","chinese","kanji","agree","yes","orange-circle"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["“application”","ideograph","Japanese","Japanese “application” button","申","chinese","japanese","kanji","orange-square"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合","japanese","chinese","join","kanji","red-square"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空","kanji","japanese","chinese","empty","sky","blue-square"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝","chinese","kanji","japanese","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["“secret”","ideograph","Japanese","Japanese “secret” button","秘","privacy","chinese","sshh","kanji","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営","japanese","opening hours","orange-square"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満","full","chinese","japanese","red-square","kanji"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["circle","geometric","red","shape","error","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","orange","round"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["circle","yellow","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["circle","green","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["blue","circle","geometric","shape","icon","button"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","purple","round"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["circle","geometric","shape","button","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["circle","geometric","shape","round"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["red","square"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["orange","square"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["square","yellow"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["blue","square"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["purple","square"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","square","shape","icon","button"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","square","shape","icon","stone","button"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","square","shape","button","icon"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","square","shape","stone","icon"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","square","black_medium_small_square","icon","shape","button"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","square","white medium-small square","white_medium_small_square","shape","stone","icon","button"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["geometric","square","shape","icon"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["geometric","square","shape","icon"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["diamond","geometric","orange","shape","jewel","gem"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["blue","diamond","geometric","shape","jewel","gem"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["diamond","geometric","orange","shape","jewel","gem"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["blue","diamond","geometric","shape","jewel","gem"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["geometric","red","shape","direction","up","top"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["down","geometric","red","shape","direction","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["comic","diamond","geometric","inside","jewel","blue","gem","crystal","fancy"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["button","geometric","radio","input","old","music","circle"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["button","geometric","outlined","square","shape","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["button","geometric","square","shape","input","frame"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["checkered","chequered","racing","contest","finishline","race","gokart"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["post","mark","milestone","place"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["celebration","cross","crossed","Japanese","japanese","nation","country","border"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["waving","losing","loser","lost","surrender","give up","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["pride","rainbow","flag","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["flag","light blue","pink","transgender","white","lgbtq"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["Jolly Roger","pirate","plunder","treasure","skull","crossbones","flag","banner"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","nation","country","banner","andorra"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner","united_arab_emirates"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner","afghanistan"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner","antigua_barbuda"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner","anguilla"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner","albania"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner","armenia"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner","angola"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner","antarctica"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner","argentina"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner","american_samoa"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner","austria"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner","australia"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner","aruba"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner","aland_islands"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner","azerbaijan"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner","bosnia_herzegovina"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner","barbados"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner","bangladesh"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner","belgium"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner","burkina_faso"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner","bulgaria"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner","bahrain"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner","burundi"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner","benin"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner","st_barthelemy"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner","bermuda"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner","brunei"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner","bolivia"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner","caribbean_netherlands"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner","brazil"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner","bahamas"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner","bhutan"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","nation","country","banner","botswana"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner","belarus"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner","belize"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner","canada"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner","cocos_islands"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner","congo_kinshasa"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner","central_african_republic"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner","congo_brazzaville"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner","switzerland"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner","cote_d_ivoire"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner","cook_islands"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner","chile"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner","cameroon"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["flag","china","chinese","prc","country","nation","banner"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["flag","co","nation","country","banner","colombia"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["flag","costa","rica","nation","country","banner","costa_rica"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner","cuba"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner","cape_verde"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner","curacao"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner","christmas_island"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner","cyprus"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner","czechia"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner","germany"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["flag","dj","nation","country","banner","djibouti"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner","denmark"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner","dominica"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner","dominican_republic"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner","algeria"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag","flag_ceuta_melilla"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["flag","ec","nation","country","banner","ecuador"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner","estonia"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner","egypt"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner","western_sahara"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner","eritrea"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["flag","spain","nation","country","banner"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["flag","et","nation","country","banner","ethiopia"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["flag","european","union","banner"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","fi","nation","country","banner","finland"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner","fiji"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner","falkland_islands"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["flag","micronesia","federated","states","nation","country","banner"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["flag","faroe","islands","nation","country","banner","faroe_islands"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","nation","france","french","country"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","nation","country","banner","gabon"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["flag","united","kingdom","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union jack","united_kingdom"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner","grenada"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner","georgia"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner","french_guiana"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner","guernsey"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner","ghana"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner","gibraltar"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner","greenland"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner","gambia"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner","guinea"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner","guadeloupe"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner","equatorial_guinea"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner","greece"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["flag","flag_south_georgia_south_sandwich_islands","south","georgia","sandwich","islands","nation","country","banner","south_georgia_south_sandwich_islands"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner","guatemala"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner","guam"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner","guinea_bissau"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner","guyana"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner","hong_kong_sar_china"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["flag","hn","nation","country","banner","honduras"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner","croatia"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner","haiti"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner","hungary"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner","canary_islands"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner","indonesia"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner","ireland"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner","israel"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner","isle_of_man"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner","india"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner","british_indian_ocean_territory"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner","iraq"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["flag","iran","islamic","republic","nation","country","banner"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","is","nation","country","banner","iceland"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["flag","italy","nation","country","banner"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["flag","je","nation","country","banner","jersey"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner","jamaica"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner","jordan"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner","japan","jp","ja"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner","kenya"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner","kyrgyzstan"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner","cambodia"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner","kiribati"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner","comoros"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner","st_kitts_nevis"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner","north_korea"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner","south_korea"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner","kuwait"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner","cayman_islands"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner","kazakhstan"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner","laos"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner","lebanon"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner","st_lucia"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner","liechtenstein"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner","sri_lanka"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner","liberia"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner","lesotho"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner","lithuania"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner","luxembourg"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner","latvia"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner","libya"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner","morocco"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner","monaco"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","moldova","republic","nation","country","banner"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["flag","me","nation","country","banner","montenegro"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["flag","mg","nation","country","banner","madagascar"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner","marshall_islands"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner","north_macedonia"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner","mali"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner","myanmar"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner","mongolia"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner","macao_sar_china"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner","northern_mariana_islands"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner","martinique"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner","mauritania"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner","montserrat"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner","malta"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner","mauritius"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner","maldives"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner","malawi"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner","mexico"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner","malaysia"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner","mozambique"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner","namibia"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner","new_caledonia"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner","niger"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner","norfolk_island"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner","nigeria"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner","nicaragua"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner","netherlands"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner","norway"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner","nepal"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner","nauru"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner","niue"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner","new_zealand"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner","oman"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner","panama"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner","peru"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner","french_polynesia"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner","papua_new_guinea"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner","philippines"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner","pakistan"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner","poland"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner","st_pierre_miquelon"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner","pitcairn_islands"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner","puerto_rico"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner","palestinian_territories"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner","portugal"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner","palau"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner","paraguay"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner","qatar"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner","reunion"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner","romania"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner","serbia"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner","russia"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner","rwanda"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner","saudi_arabia"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner","solomon_islands"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner","seychelles"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner","sudan"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner","sweden"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner","singapore"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner","st_helena"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner","slovenia"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag","flag_svalbard_jan_mayen"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","nation","country","banner","slovakia"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner","sierra_leone"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner","san_marino"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner","senegal"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner","somalia"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner","suriname"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner","south_sudan"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["flag","flag_sao_tome_principe","sao","tome","principe","nation","country","banner","sao_tome_principe"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner","el_salvador"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner","sint_maarten"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner","syria"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner","eswatini"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["flag","flag_turks_caicos_islands","turks","caicos","islands","nation","country","banner","turks_caicos_islands"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner","chad"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner","french_southern_territories"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner","togo"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner","thailand"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner","tajikistan"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner","tokelau"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner","timor_leste"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner","turkmenistan"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner","tunisia"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner","tonga"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["flag","turkey","nation","country","banner"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag","flag_trinidad_tobago","trinidad","tobago","nation","country","banner","trinidad_tobago"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner","tuvalu"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner","taiwan"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["flag","tanzania","united","republic","nation","country","banner"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","ua","nation","country","banner","ukraine"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner","uganda"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["flag","united","states","america","nation","country","banner","united_states"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner","uruguay"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner","uzbekistan"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner","vatican_city"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner","st_vincent_grenadines"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner","venezuela"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner","british_virgin_islands"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["flag","flag_u_s_virgin_islands","virgin","islands","us","nation","country","banner","u_s_virgin_islands"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner","vietnam"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner","vanuatu"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner","wallis_futuna"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner","samoa"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner","kosovo"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner","yemen"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner","mayotte"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner","south_africa"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner","zambia"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner","zimbabwe"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}} \ No newline at end of file diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index 1df5563686..073f961cb6 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -143,8 +143,6 @@ <string name="event_status_sending_message">يُرسل الرِّسالة…</string> <string name="initial_sync_start_importing_account_data">المُزامنة الأولية: \nيستورد بيانات الحِساب</string> - <string name="initial_sync_start_importing_account_groups">المُزامنة الأولية: -\nيستورد المُجتمعات</string> <string name="initial_sync_start_importing_account_left_rooms">المُزامنة الأولية: \nيستورد الغُرف المُغادَر مِنها</string> <string name="initial_sync_start_importing_account_invited_rooms">المُزامنة الأولية: @@ -229,7 +227,6 @@ <string name="auth_invalid_login_param">اسم المستخدم و/أو كلمة السر خاطئة</string> <string name="auth_invalid_email">لا يبدو هذا وكأنه بريد إلكتروني صالح</string> <string name="auth_forgot_password">أنسيت كلمة السر؟</string> - <string name="auth_reset_password_missing_email">يجب إدخال عنوان البريد الإلكتروني المرتبط بحسابك.</string> <string name="auth_reset_password_error_unauthorized">فشل تأكيد عنوان البريد: تحقق من نقر الرابط في البريد</string> <string name="login_error_invalid_home_server">أدخِل مسارا صالحا</string> <string name="login_error_not_json">لم يحتوي JSON صالح</string> @@ -376,7 +373,6 @@ <item quantity="other">%d رسالة جديدة</item> </plurals> <string name="low_priority_header">أولوية منخفضة</string> - <string name="groups_header">المجتمعات</string> <string name="send_bug_report_rage_shake">هزّ الجهاز بجنون يُرسل بلاغًا بعلة</string> <string name="notification_noisy_notifications">الإخطارات المزعجة</string> <string name="notification_silent_notifications">الإخطارات الصامتة</string> @@ -859,7 +855,6 @@ <string name="room_preview_not_found">لا يمكنك الوصول إلى هذه الغرفة حاليًا. \nحاول لاحقًا، أو اسأل مدير الغرفة إن كان لديك نفاذ لها.</string> <string name="room_preview_no_preview">تتعذر معاية هذه الغرفة</string> - <string name="group_all_communities">كل المجتمعات</string> <string name="please_wait">رجاء انتظر…</string> <string name="change_room_directory_network">غيّر الشبكة</string> <string name="create_new_space">أنشئ فضاء جديد</string> @@ -925,7 +920,6 @@ <string name="ftue_profile_picture_title">أضف صورة لللاحة</string> <string name="ftue_display_name_entry_footer">يمكنك تغييره لاحقًا</string> <string name="ftue_display_name_entry_title">الاسم العلني</string> - <string name="ftue_display_name_subtitle">سيظهر هذا الاسم مع رسائلك.</string> <string name="ftue_display_name_title">اختر الاسم العلني</string> <string name="ftue_account_created_subtitle">أُنشئ حسابك %s.</string> <string name="ftue_account_created_congratulations_title">مبارك!</string> @@ -1173,4 +1167,4 @@ <string name="login_reset_password_email_hint">البريد الإلكتروني</string> <string name="login_reset_password_password_hint">كلمة السر الجديدة</string> <string name="login_reset_password_submit">التالي</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-az/strings.xml b/vector/src/main/res/values-az/strings.xml index cd5820c5fa..84f2772950 100644 --- a/vector/src/main/res/values-az/strings.xml +++ b/vector/src/main/res/values-az/strings.xml @@ -70,8 +70,6 @@ \nDəvət olunmuş otaqların idxalı</string> <string name="initial_sync_start_importing_account_left_rooms">İlkin sinxronizasiya: \nTərk olunmuş otaqların idxalı</string> - <string name="initial_sync_start_importing_account_groups">İlkin sinxronizasiya: -\nİcmaların idxalı</string> <string name="initial_sync_start_importing_account_data">İlkin sinxronizasiya: \nHesab məlumatlarının idxalı</string> @@ -88,4 +86,4 @@ <string name="notice_room_ban_with_reason">%1$s blokladı %2$s. Səbəb: %3$s</string> <string name="notice_room_third_party_registered_invite_with_reason">%1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s</string> <string name="notice_room_withdraw_with_reason">%1$s %2$s dəvətini geri götürdü. Səbəb: %3$s</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-b+sr+Latn/strings.xml b/vector/src/main/res/values-b+sr+Latn/strings.xml index 7d1e61137f..4f734ac224 100644 --- a/vector/src/main/res/values-b+sr+Latn/strings.xml +++ b/vector/src/main/res/values-b+sr+Latn/strings.xml @@ -89,8 +89,6 @@ <string name="auth_invalid_email">Ovo ne izgleda kao validna adresa elektronske pošte</string> <string name="auth_email_already_defined">Ova adresa elektronske pošte je već definisana.</string> <string name="auth_forgot_password">Zaboravili ste lozinku\?</string> - <string name="auth_reset_password_missing_email">Morate uneti adresu elektronske pošte povezane sa vašim nalogom.</string> - <string name="login_error_limit_exceeded">Poslato je previše zahteva</string> diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index 96e719f29f..b29823040f 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -52,8 +52,6 @@ \nИмпортиране на стаи, към които съм поканен</string> <string name="initial_sync_start_importing_account_left_rooms">Начална синхронизация. \nИмпортиране на стаи, които съм напуснал</string> - <string name="initial_sync_start_importing_account_groups">Начална синхронизация. -\nИмпортиране на общности</string> <string name="initial_sync_start_importing_account_data">Начална синхронизация. \nИмпортиране на данни за профила</string> <string name="notice_room_update">%s обнови тази стая.</string> @@ -246,7 +244,6 @@ <string name="matrix_only_filter">Само потребители на Matrix</string> <string name="no_result_placeholder">Няма резултати</string> <string name="rooms_header">Стаи</string> - <string name="groups_header">Общности</string> <string name="send_bug_report_include_logs">Изпращане на логове</string> <string name="send_bug_report_include_crash_logs">Изпращане на логове за забивания</string> <string name="send_bug_report_include_screenshot">Изпращане на снимка на екрана</string> @@ -279,7 +276,6 @@ <string name="auth_email_already_defined">Този имейл адрес е вече използван.</string> <string name="auth_forgot_password">Забравена парола?</string> <string name="auth_recaptcha_message">Този Home сървър би искал да се увери, че не сте робот</string> - <string name="auth_reset_password_missing_email">Имейл адресът, свързан с профила Ви, трябва да бъде въведен.</string> <string name="auth_reset_password_error_unauthorized">Неуспешно потвърждаване на имейл адреса: уверете се, че сте кликнали върху връзката в имейла</string> <string name="send_bug_report_progress">Напредък (%s%%)</string> <string name="login_error_invalid_home_server">Въведете валиден URL адрес</string> @@ -833,7 +829,6 @@ <string name="action_change">Промени</string> <string name="change_room_directory_network">Промени мрежата</string> <string name="please_wait">Изчакайте…</string> - <string name="group_all_communities">Всички общности</string> <string name="room_preview_no_preview">Тази стая не може да бъде прегледана</string> <string name="fab_menu_create_room">Стаи</string> <string name="fab_menu_create_chat">Директни съобщения</string> diff --git a/vector/src/main/res/values-bn-rBD/strings.xml b/vector/src/main/res/values-bn-rBD/strings.xml index 5913cb5e11..2f068f1bf8 100644 --- a/vector/src/main/res/values-bn-rBD/strings.xml +++ b/vector/src/main/res/values-bn-rBD/strings.xml @@ -360,7 +360,6 @@ <string name="auth_accept_policies">পর্যালোচনা করুন এবং এই হোমসার্ভার এর নীতিগুলি গ্রহণ করুন:</string> <string name="auth_reset_password_error_unauthorized">ইমেল ঠিকানা যাচাই করতে ব্যর্থ: ইমেলটিতে লিঙ্কে আপনি ক্লিক করেছেন তা নিশ্চিত করুন</string> - <string name="auth_reset_password_missing_email">আপনার অ্যাকাউন্টের সাথে যুক্ত ইমেল ঠিকানা প্রবেশ করা আবশ্যক।</string> <string name="auth_recaptcha_message">এই হোম সার্ভার আপনি একটি রোবট না সেটা নিশ্চিত করবে</string> <string name="auth_forgot_password">পাসওয়ার্ড ভুলে গেছেন\?</string> @@ -417,8 +416,6 @@ <string name="send_bug_report_include_screenshot">স্ক্রিনশট পাঠান</string> <string name="send_bug_report_include_crash_logs">ক্র্যাশ লগগুলি পাঠান</string> <string name="send_bug_report_include_logs">লগগুলি পাঠান</string> - <string name="groups_header">সম্প্রদায়গুলি</string> - <string name="rooms_header">রুমগুলি</string> <string name="no_result_placeholder">কোন ফলাফল নেই</string> <string name="matrix_only_filter">শুধুমাত্র ম্যাট্রিক্সের যোগাযোগগুলি</string> @@ -679,8 +676,6 @@ <string name="event_status_sending_message">বার্তা প্রেরণ করা হচ্ছে …</string> <string name="initial_sync_start_importing_account_data">প্রাথমিক সিঙ্ক: \nঅ্যাকাউন্ট ডেটা আমদানি করা হচ্ছে</string> - <string name="initial_sync_start_importing_account_groups">প্রাথমিক সিঙ্ক: -\nসম্প্রদায়গুলি আমদানি করা হচ্ছে</string> <string name="initial_sync_start_importing_account_left_rooms">প্রাথমিক সিঙ্ক: \nছেড়ে দেওয়া কক্ষগুলিতে আমদানি করা হিচ্ছে</string> <string name="initial_sync_start_importing_account_invited_rooms">প্রাথমিক সিঙ্ক: diff --git a/vector/src/main/res/values-bn-rIN/strings.xml b/vector/src/main/res/values-bn-rIN/strings.xml index 18f122b2cd..828bc3bd34 100644 --- a/vector/src/main/res/values-bn-rIN/strings.xml +++ b/vector/src/main/res/values-bn-rIN/strings.xml @@ -102,8 +102,6 @@ \nআমন্ত্রিত করা কক্ষগুলিতে আমদানি করা হিচ্ছে</string> <string name="initial_sync_start_importing_account_left_rooms">প্রাথমিক সিঙ্ক: \nছেড়ে দেওয়া কক্ষগুলিতে আমদানি করা হিচ্ছে</string> - <string name="initial_sync_start_importing_account_groups">প্রাথমিক সিঙ্ক: -\nসম্প্রদায়গুলি আমদানি করা হচ্ছে</string> <string name="initial_sync_start_importing_account_data">প্রাথমিক সিঙ্ক: \nঅ্যাকাউন্ট ডেটা আমদানি করা হচ্ছে</string> <string name="event_status_sending_message">বার্তা প্রেরণ করা হচ্ছে …</string> @@ -238,9 +236,6 @@ <string name="rooms_header">রুমগুলি</string> - - <string name="groups_header">সম্প্রদায়গুলি</string> - <string name="send_bug_report_include_logs">লগগুলি পাঠান</string> <string name="send_bug_report_include_crash_logs">ক্র্যাশ লগগুলি পাঠান</string> <string name="send_bug_report_include_screenshot">স্ক্রিনশট পাঠান</string> @@ -293,7 +288,6 @@ <string name="auth_forgot_password">পাসওয়ার্ড ভুলে গেছেন\?</string> <string name="auth_recaptcha_message">এই হোম সার্ভার আপনি একটি রোবট না সেটা নিশ্চিত করবে</string> - <string name="auth_reset_password_missing_email">আপনার অ্যাকাউন্টের সাথে যুক্ত ইমেল ঠিকানা প্রবেশ করা আবশ্যক।</string> <string name="auth_reset_password_error_unauthorized">ইমেল ঠিকানা যাচাই করতে ব্যর্থ: ইমেলটিতে লিঙ্কে আপনি ক্লিক করেছেন তা নিশ্চিত করুন</string> <string name="auth_accept_policies">পর্যালোচনা করুন এবং এই হোমসার্ভার এর নীতিগুলি গ্রহণ করুন:</string> diff --git a/vector/src/main/res/values-bs/strings.xml b/vector/src/main/res/values-bs/strings.xml index 0fdb091ed6..583286f27d 100644 --- a/vector/src/main/res/values-bs/strings.xml +++ b/vector/src/main/res/values-bs/strings.xml @@ -78,7 +78,6 @@ <string name="auth_forgot_password">Zaboravljena lozinka?</string> <string name="auth_recaptcha_message">Ovaj lokalni server provjerava da niste robot</string> - <string name="auth_reset_password_missing_email">Email adresa povezana sa vašim računom mora biti unešena.</string> <string name="auth_reset_password_error_unauthorized">Neuspješna verifikacija email adrese: provjerite da li ste kliknuli link u emailu</string> <string name="login_error_invalid_home_server">Unesite ispravan URL</string> @@ -211,4 +210,4 @@ Da li ste sigurani?</string> <string name="settings_password_updated">Vaša lozinka je ažurirana</string> <string name="settings_unignore_user">Prikaži sve poruke iz %s?</string> <string name="settings_select_country">Izaberite zemlju</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index f7366b7dd3..c98333ffdb 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -112,8 +112,6 @@ <string name="event_status_sending_message">Enviant missatge…</string> <string name="initial_sync_start_importing_account_data">Sincronització inicial: \nImportant dades del compte</string> - <string name="initial_sync_start_importing_account_groups">Sincronització inicial: -\nImportant comunitats</string> <string name="initial_sync_start_importing_account_left_rooms">Sincronització inicial: \nImportant sales que n\'has marxat</string> <string name="initial_sync_start_importing_account">Sincronització inicial: @@ -269,7 +267,6 @@ <string name="matrix_only_filter">Només contactes de Matrix</string> <string name="no_result_placeholder">Sense resultats</string> <string name="rooms_header">Sales</string> - <string name="groups_header">Comunitats</string> <string name="send_bug_report_include_logs">Envia els registres</string> <string name="send_bug_report_include_crash_logs">Envia els registres de fallada</string> <string name="send_bug_report_include_screenshot">Envia una captura de pantalla</string> @@ -302,7 +299,6 @@ <string name="auth_email_already_defined">Aquest correu electrònic ja existeix.</string> <string name="auth_forgot_password">Contrasenya oblidada\?</string> <string name="auth_recaptcha_message">Aquest servidor vol assegurar-se que no ets cap robot</string> - <string name="auth_reset_password_missing_email">Heu d\'introduir el correu electrònic associat al vostre compte.</string> <string name="auth_reset_password_error_unauthorized">No s\'ha pogut verificar l\'adreça del correu electrònic: assegureu-vos que heu fet clic a l\'enllaç del correu electrònic</string> <string name="login_error_invalid_home_server">Introduïu una URL vàlida</string> <string name="login_error_bad_json">JSON mal format</string> @@ -877,7 +873,6 @@ <string name="action_change">Canviar</string> <string name="change_room_directory_network">Canvia de xarxa</string> <string name="please_wait">Espereu, si us plau…</string> - <string name="group_all_communities">Totes les comunitats</string> <string name="room_preview_no_preview">Aquesta sala no es pot pre-visualitzar</string> <string name="fab_menu_create_room">Sales</string> <string name="fab_menu_create_chat">Xats directes</string> @@ -2208,7 +2203,6 @@ <string name="create_spaces_details_public_header">Afegeix alguns detalls per ajudar a destacar-lo. Pots canviar-ho en qualsevol moment.</string> <string name="create_spaces_private_teammates">Un espai privat per tu i els teus</string> <string name="space_type_public_desc">Obert a tothom, perfecte per a comunitats</string> - <string name="ftue_display_name_subtitle">Es mostrarà quan enviïs missatges.</string> <string name="ftue_display_name_title">Escull un àlies</string> <string name="ftue_account_created_subtitle">El teu compte %s s\'ha creat</string> <string name="ftue_account_created_congratulations_title">Felicitats!</string> @@ -2485,7 +2479,6 @@ <string name="labs_enable_live_location_summary">Implementació temporal: les ubicacions persisteixen a l\'històric de la sala</string> <string name="space_leave_radio_buttons_title">Coses en aquest espai</string> <string name="settings_autoplay_animated_images_summary">Reprodueix immediatament les imatges animades a la cronologia</string> - <string name="live_location_bottom_sheet_stop_sharing">Deixa de compartir</string> <string name="labs_enable_live_location">Activa la compartició d\'ubicació en directe</string> <string name="location_share_live_remaining_time">%1$s restants</string> <string name="location_share_live_until">En directe fins %1$s</string> @@ -2603,4 +2596,8 @@ </plurals> <string name="search_space_two_parents">%1$s i %2$s</string> <string name="auth_reset_password_error_unverified">Correu no verificat, mira la teva safata d\'entrada</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">No s\'ha pogut carregar el mapa +\nPotser el servidor utilitzat no està configurat per mostrar mapes.</string> + <string name="a11y_open_settings">Obre configuració</string> + <string name="all_chats">Tots els xats</string> +</resources> diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 5733fde468..960c5e0a40 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -52,8 +52,6 @@ \nPokud jste se připojili k mnoha místnostem, může to chvíli trvat</string> <string name="initial_sync_start_importing_account_left_rooms">Úvodní synchronizace: \nImportuji místnost, jež jste opustili</string> - <string name="initial_sync_start_importing_account_groups">Úvodní synchronizace: -\nImportuji skupiny</string> <string name="initial_sync_start_importing_account_data">Úvodní synchronizace: \nImportuji data účtu</string> <string name="event_status_sending_message">Odesílám zprávu…</string> @@ -279,7 +277,6 @@ <string name="matrix_only_filter">Pouze kontakty Matrix</string> <string name="no_result_placeholder">Žádné výsledky</string> <string name="rooms_header">Místnosti</string> - <string name="groups_header">Komunity</string> <string name="send_bug_report_include_logs">Odeslat záznamy</string> <string name="send_bug_report_include_crash_logs">Odeslat záznamy zřícení</string> <string name="send_bug_report_include_screenshot">Odeslat screenshot</string> @@ -311,7 +308,6 @@ <string name="auth_email_already_defined">Tato e-mailová adresa je již zadána.</string> <string name="auth_forgot_password">Zapomenuté heslo?</string> <string name="auth_recaptcha_message">Tento domovský server by se rád přesvědčil, že nejste robot</string> - <string name="auth_reset_password_missing_email">Musíte zadat e-mailovou adresu spojenou s vaším účtem.</string> <string name="auth_reset_password_error_unauthorized">E-mailovou adresu se nepodařilo ověřit. Přesvědčte se, že jste klepli na zaslaný odkaz</string> <string name="login_error_invalid_home_server">Prosím, zadejte platné URL</string> <string name="login_error_bad_json">Poškozený JSON</string> @@ -948,7 +944,6 @@ <string name="action_change">Změnit</string> <string name="change_room_directory_network">Změnit síť</string> <string name="please_wait">Prosím, čekejte…</string> - <string name="group_all_communities">Všechny komunity</string> <string name="room_preview_no_preview">Nelze provést náhled této místnosti</string> <string name="fab_menu_create_room">Místnosti</string> <string name="fab_menu_create_chat">Přímé zprávy</string> @@ -2479,7 +2474,6 @@ <string name="ftue_profile_picture_title">Přidat profilový obrázek</string> <string name="ftue_display_name_entry_footer">Později to můžete změnit</string> <string name="ftue_display_name_entry_title">Zobrazované jméno</string> - <string name="ftue_display_name_subtitle">Toto se zobrazí při odesílání zpráv.</string> <string name="ftue_display_name_title">Zvolte si zobrazované jméno</string> <string name="ftue_account_created_subtitle">Váš účet %s byl založen</string> <string name="ftue_account_created_congratulations_title">Gratulujeme!</string> @@ -2529,7 +2523,6 @@ <string name="a11y_presence_busy">Zaneprázdněn</string> <string name="keys_backup_settings_signature_from_this_user">Záloha má platný podpis tohoto uživatele.</string> <string name="live_location_bottom_sheet_last_updated_at">Aktualizována před %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Ukončit sdílení</string> <string name="labs_enable_live_location_summary">Dočasná implementace: polohy přetrvávají v historii místností</string> <string name="labs_enable_live_location">Povolit sdílení polohy živě</string> <string name="location_share_live_remaining_time">zbývá %1$s</string> @@ -2653,4 +2646,8 @@ </plurals> <string name="search_space_two_parents">%1$s a %2$s</string> <string name="auth_reset_password_error_unverified">E-mail nebyl ověřen, zkontrolujte si schránku</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Nelze načíst mapu +\nTento domovský server nemusí být nakonfigurován pro zobrazování map.</string> + <string name="a11y_open_settings">Otevřít nastavení</string> + <string name="all_chats">Všechny konverzace</string> +</resources> diff --git a/vector/src/main/res/values-da/strings.xml b/vector/src/main/res/values-da/strings.xml index 8fc117ac4e..13d53b7bb2 100644 --- a/vector/src/main/res/values-da/strings.xml +++ b/vector/src/main/res/values-da/strings.xml @@ -112,7 +112,6 @@ <string name="auth_forgot_password">Glemt adgangskode?</string> <string name="auth_recaptcha_message">Denne Home Server vil gerne være sikker på du ikke er en robot</string> - <string name="auth_reset_password_missing_email">Emailadressen forbundet til din konto skal skrives.</string> <string name="auth_reset_password_error_unauthorized">Kunne ikke verificere emailadresse: vær sikker på du klikkede på linket i emailen</string> <string name="login_error_invalid_home_server">Skriv gyldig URL</string> @@ -180,7 +179,6 @@ Er du sikker?</string> <string name="ssl_expected_existing_expl">Certifikatet er ændret fra et tidligere betroet, til et der ikke er betroet. Serveren kan have fornyet sit certifikat. Kontakt server administratoren for det forventede fingeraftryk.</string> <string name="ssl_only_accept">Accepter kun certifikatet hvis server administratoren har publiceret et fingeraftryk der matcher det ovenstående.</string> <string name="loading">Loader…</string> - <string name="groups_header">Communities</string> <string name="start_voice_call_prompt_msg">Er du sikker på, at du ønsker at starte et opkald?</string> <string name="start_video_call_prompt_msg">Er du sikker på, at du ønsker at starte et videoopkald\?</string> <plurals name="membership_changes"> @@ -293,4 +291,4 @@ Er du sikker?</string> <string name="notice_room_created">%1$s oprettede rummet</string> <string name="notice_room_invite_no_invitee_by_you">Din invitation</string> <string name="link_this_email_settings_link">Forbind denne email med din konto</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index fa42cc44a1..40473e9e27 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -54,8 +54,6 @@ \nImportiere eingeladene Räume</string> <string name="initial_sync_start_importing_account_left_rooms">Erste Synchronisation: \nImportiere verlassene Räume</string> - <string name="initial_sync_start_importing_account_groups">Erste Synchronisation: -\nImportiere Communities</string> <string name="initial_sync_start_importing_account_data">Erste Synchronisation: \nImportiere Benutzerdaten</string> <string name="notice_room_third_party_revoked_invite">%1$s hat die Einladung an %2$s, den Raum zu betreten, zurückgezogen</string> @@ -293,7 +291,6 @@ <string name="auth_email_already_defined">Diese E-Mail-Adresse wird bereits verwendet.</string> <string name="auth_forgot_password">Passwort vergessen?</string> <string name="auth_recaptcha_message">Dieser Homeserver möchte sicherstellen, dass du kein Roboter bist</string> - <string name="auth_reset_password_missing_email">Die E-Mail-Adresse, die mit deinem Account verknüpft ist, muss eingegeben werden.</string> <string name="auth_reset_password_error_unauthorized">Verifizierung der E-Mail-Adresse ist fehlgeschlagen. Stelle sicher, dass du den Link in der E-Mail geöffnet hast</string> <string name="login_error_invalid_home_server">Bitte eine gültige URL eingeben</string> <string name="login_error_bad_json">Fehlerhaftes JSON</string> @@ -499,7 +496,6 @@ <string name="notification_noisy">Laut</string> <string name="encrypted_message">Verschlüsselte Nachricht</string> <string name="loading">Lädt…</string> - <string name="groups_header">Communities</string> <string name="start_voice_call_prompt_msg">Sicher, dass du einen Sprachanruf starten möchtest\?</string> <string name="start_video_call_prompt_msg">Sicher, dass du einen Videoanruf starten möchtest\?</string> <string name="room_participants_ban_prompt_msg">Die Verbannung einer Person entfernt sie aus diesem Raum und hindert sie am erneuten Beitritt.</string> @@ -875,7 +871,6 @@ <string name="action_change">Ändern</string> <string name="change_room_directory_network">Netzwerk wechseln</string> <string name="please_wait">Bitte warten…</string> - <string name="group_all_communities">Alle Communities</string> <string name="room_preview_no_preview">Für diesen Raum kann keine Vorschau angezeigt werden</string> <string name="fab_menu_create_room">Räume</string> <string name="fab_menu_create_chat">Direktnachrichten</string> @@ -2448,7 +2443,6 @@ <string name="screen_sharing_notification_description">Bildschirmfreigabe ist in Arbeit</string> <string name="screen_sharing_notification_title">${app_name} Bildschirmfreigabe</string> <string name="live_location_bottom_sheet_last_updated_at">Aktualisiert vor %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Teilen beenden</string> <string name="labs_enable_live_location">Aktiviere Live-Standortfreigabe</string> <string name="live_location_sharing_notification_description">Standortfreigabe ist in Arbeit</string> <string name="live_location_sharing_notification_title">${app_name} Live-Standort</string> @@ -2522,7 +2516,6 @@ <string name="ftue_profile_picture_title">Profilbild hinzufügen</string> <string name="ftue_display_name_entry_footer">Du kannst dies später ändern</string> <string name="ftue_display_name_entry_title">Anzeigename</string> - <string name="ftue_display_name_subtitle">Dies wird angezeigt, wenn Du Nachrichten sendest.</string> <string name="ftue_account_created_subtitle">Dein Konto %s wurde erstellt</string> <string name="ftue_account_created_congratulations_title">Herzlichen Glückwunsch!</string> <string name="ftue_account_created_personalize">Profil personalisieren</string> @@ -2579,4 +2572,4 @@ <string name="ftue_auth_choose_server_sign_in_subtitle">Wie lautet die Adresse deines Servers\?</string> <string name="ftue_auth_create_account_password_entry_footer">Muss 8 oder mehr Zeichen umfassen</string> <string name="ftue_auth_choose_server_title">Wähle deinen Server</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-el/strings.xml b/vector/src/main/res/values-el/strings.xml index a7da13bdb3..092a01bff4 100644 --- a/vector/src/main/res/values-el/strings.xml +++ b/vector/src/main/res/values-el/strings.xml @@ -92,7 +92,6 @@ <string name="matrix_only_filter">Μόνο οι επαφές Matrix</string> <string name="no_result_placeholder">Δεν βρέθηκαν αποτελέσματα</string> <string name="rooms_header">Δωμάτια</string> - <string name="groups_header">Κοινότητες</string> <string name="send_bug_report_include_crash_logs">Αποστολή καταγραφών σφαλμάτων</string> <string name="send_bug_report">Αναφορά σφάλματος</string> <string name="send_bug_report_description_in_english">Εφόσον είναι δυνατό, παρακαλώ γράψτε την περιγραφή στα αγγλικά.</string> @@ -384,4 +383,4 @@ <string name="location_share_external">Άνοιγμα με</string> <string name="tooltip_attachment_sticker">Αποστολή αυτοκόλλητου</string> <string name="notice_room_created">%1$s δημιούργησε το δωμάτιο</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml index 09361de4ea..7e1925f708 100644 --- a/vector/src/main/res/values-eo/strings.xml +++ b/vector/src/main/res/values-eo/strings.xml @@ -51,8 +51,6 @@ \nEnportante ĉambrojn de invitoj</string> <string name="initial_sync_start_importing_account_left_rooms">Komenca spegulado: \nEnportante forlasitajn ĉambrojn</string> - <string name="initial_sync_start_importing_account_groups">Komenca spegulado: -\nEnportante komunumojn</string> <string name="initial_sync_start_importing_account_data">Komenca spegulado: \nEnportante datumojn de konto</string> <string name="event_status_sending_message">Sendante mesaĝon…</string> @@ -251,7 +249,6 @@ <string name="direct_chats_header">Interparoloj</string> <string name="no_result_placeholder">Neniuj rezultoj</string> <string name="rooms_header">Ĉambroj</string> - <string name="groups_header">Komunumoj</string> <string name="send_bug_report_include_logs">Sendi protokolon</string> <string name="send_bug_report_include_crash_logs">Sendi protokolon pri fiasko</string> <string name="send_bug_report_include_screenshot">Sendi ekrankopion</string> @@ -287,7 +284,6 @@ <string name="auth_email_already_defined">Ĉi tiu retpoŝtadreso jam estas difinita.</string> <string name="auth_forgot_password">Ĉu vi forgesis pasvorton\?</string> <string name="auth_recaptcha_message">Ĉi tiu hejmservilo volas certiĝi, ke vi ne estas roboto</string> - <string name="auth_reset_password_missing_email">Necesas enigi la retpoŝtadreson ligitan al via konto.</string> <string name="auth_reset_password_error_unauthorized">Malsukcesis kontroli retpoŝtadreson: certiĝu, ke vi klakis la ligilon en la retletero</string> <string name="auth_accept_policies">Bonvolu tralegi kaj akcepti la politikojn de ĉi tiu hejmservilo:</string> <string name="compression_opt_list_original">Originala</string> @@ -788,7 +784,6 @@ <string name="fab_menu_create_chat">Individuaj ĉambroj</string> <string name="fab_menu_create_room">Ĉambroj</string> <string name="room_preview_no_preview">Ne eblas antaŭrigardi ĉi tiun ĉambron</string> - <string name="group_all_communities">Ĉiuj komunumoj</string> <string name="please_wait">Bonvolu atendi…</string> <string name="change_room_directory_network">Ŝanĝi reton</string> <string name="action_change">Ŝanĝi</string> diff --git a/vector/src/main/res/values-es-rMX/strings.xml b/vector/src/main/res/values-es-rMX/strings.xml index cdf7fa3e62..0b38fa6a19 100644 --- a/vector/src/main/res/values-es-rMX/strings.xml +++ b/vector/src/main/res/values-es-rMX/strings.xml @@ -123,7 +123,6 @@ <string name="auth_email_already_defined">Este correo electrónico ya está ocupado.</string> <string name="auth_forgot_password">¿Olvidaste tu contraseña?</string> <string name="auth_recaptcha_message">Este servidor quiere verificar que no seas robot</string> - <string name="auth_reset_password_missing_email">El correo electrónico de esta cuenta es requrido.</string> <string name="auth_reset_password_error_unauthorized">Falló la verificación de correo electrónico: asegúrese de haber hecho clic en el correo</string> <string name="login_error_invalid_home_server">Escribe un enlace válido</string> <string name="login_error_bad_json">JSON malformado</string> @@ -276,7 +275,6 @@ <string name="directory_server_native_rooms">All native %s rooms</string> <string name="loading">Cargando…</string> <string name="action_download">Descargar</string> - <string name="groups_header">Comunidades</string> <string name="option_send_voice">Enviar audio</string> <string name="option_send_sticker">Enviar sticker</string> <string name="option_take_photo">Tomar foto</string> @@ -327,4 +325,4 @@ <string name="settings_call_ringtone_dialog_title">Elegir sonido de llamadas:</string> <string name="title_activity_keys_backup_setup">Copia de seguridad de la clave</string> <string name="title_activity_keys_backup_restore">Usar copia de seguridad de la clave</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 24909dce3b..4eec90fbd6 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -44,8 +44,6 @@ \nImportando cuenta…</string> <string name="initial_sync_start_importing_account_rooms">Sincronización inicial: \nImportando salas</string> - <string name="initial_sync_start_importing_account_groups">Sincronización inicial: -\nImportando comunidades</string> <string name="initial_sync_start_importing_account_data">Sincronización inicial: \nImportando datos de la cuenta</string> <string name="event_status_sending_message">Enviando mensaje…</string> @@ -290,7 +288,6 @@ <string name="auth_email_already_defined">Esta dirección de correo electrónico ya está definida.</string> <string name="auth_forgot_password">¿Olvidaste tu contraseña?</string> <string name="auth_recaptcha_message">Este Servidor Doméstico quiere asegurarse de que no eres un robot</string> - <string name="auth_reset_password_missing_email">Debes ingresar la dirección de correo electrónico vinculada a tu cuenta.</string> <string name="auth_reset_password_error_unauthorized">No se pudo verificar la dirección de correo electrónico: asegúrate de hacer clic en el enlace del correo electrónico</string> <string name="login_error_invalid_home_server">Por favor introduce una URL válida</string> <string name="login_error_bad_json">JSON mal formado</string> @@ -496,7 +493,6 @@ <string name="notification_noisy">Ruidoso</string> <string name="encrypted_message">Mensaje cifrado</string> <string name="loading">Cargando…</string> - <string name="groups_header">Comunidades</string> <string name="start_voice_call_prompt_msg">¿Seguro que quieres iniciar una llamada de voz?</string> <string name="start_video_call_prompt_msg">¿Seguro que quieres iniciar una llamada de vídeo?</string> <string name="room_participants_ban_prompt_msg">Vetar un usuario lo echará de esta sala y evitará que se una nuevamente.</string> @@ -871,7 +867,6 @@ <string name="action_change">Cambiar</string> <string name="change_room_directory_network">Cambiar red</string> <string name="please_wait">Espere por favor…</string> - <string name="group_all_communities">Todas la comunidades</string> <string name="room_preview_no_preview">Esta sala no se puede previsualizar</string> <string name="fab_menu_create_room">Salas</string> <string name="fab_menu_create_chat">Chats</string> @@ -2450,7 +2445,6 @@ <string name="sent_live_location">Compartieron su localización en vivo</string> <string name="sent_location">Compartieron su localización</string> <string name="ftue_profile_picture_subtitle">Puedes cambiar esto en todo momento.</string> - <string name="ftue_display_name_subtitle">Esto se mostrará cuando mandes mensajes.</string> <string name="ftue_account_created_take_me_home">Llévame a casa</string> <string name="ftue_account_created_personalize">Personalizar perfil</string> <string name="ftue_auth_use_case_connect_to_server">Conectar a servidor</string> @@ -2478,7 +2472,6 @@ <string name="settings_show_latest_profile">Mostrar última información del usuario</string> <string name="a11y_presence_busy">Ocupado</string> <string name="live_location_bottom_sheet_last_updated_at">Actualizado hace %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Dejar de compartir</string> <string name="labs_enable_live_location_summary">Implementación temporal: las ubicaciones persisten en el historial de la sala</string> <string name="labs_enable_live_location">Activar compartir ubicación en tiempo real</string> <string name="location_share_live_remaining_time">Queda %1$s</string> diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 124ca1eae7..89460d921c 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -52,8 +52,6 @@ \nImpordin kutsutud jututubasid</string> <string name="initial_sync_start_importing_account_left_rooms">Esmane laadimine: \nImpordin lahkutud jututubasid</string> - <string name="initial_sync_start_importing_account_groups">Esmane laadimine: -\nImpordin kogukondi</string> <string name="initial_sync_start_importing_account_data">Esmane laadimine: \nImpordin kontoandmeid</string> <string name="event_status_sending_message">Saadan sõnumit…</string> @@ -295,7 +293,6 @@ <string name="matrix_only_filter">Vaid need, kellel on Matrixi konto</string> <string name="no_result_placeholder">Tulemusi ei ole</string> <string name="rooms_header">Jututoad</string> - <string name="groups_header">Kogukonnad</string> <string name="send_bug_report_include_logs">Saada logikirjed</string> <string name="send_bug_report_include_crash_logs">Saada kokkujooksmise logikirjed</string> <string name="send_bug_report_include_screenshot">Saada ekraanipilt</string> @@ -336,7 +333,6 @@ <string name="auth_email_already_defined">Selline e-posti aadress on juba kasutusel.</string> <string name="auth_forgot_password">Kas unustasid oma salasõna\?</string> <string name="auth_recaptcha_message">See koduserver soovib olla kindel, et sa ei ole robot</string> - <string name="auth_reset_password_missing_email">Sinu kontoga seotud e-posti aadress peab olema sisestatud.</string> <string name="auth_reset_password_error_unauthorized">E-posti aadressi verifitseerimine ei õnnestunud. Palun kontrolli, et sa avasid kirjas leidunud lingi</string> <string name="auth_accept_policies">Palun loe läbi ja nõustu koduserveri kasutusjuhendiga:</string> <string name="login_error_invalid_home_server">Palun sisesta korrektne URL</string> @@ -519,7 +515,6 @@ <string name="action_change">Muuda</string> <string name="change_room_directory_network">Vaheta võrku</string> <string name="please_wait">Palun oota…</string> - <string name="group_all_communities">Kõik kogukonnad</string> <string name="room_preview_no_preview">Sellel jututoal puudub eelvaade</string> <string name="fab_menu_create_room">Jututoad</string> <string name="fab_menu_create_chat">Isiklikud sõnumid</string> @@ -2432,7 +2427,6 @@ <string name="ftue_profile_picture_title">Lisa profiilipilt</string> <string name="ftue_display_name_entry_footer">Sa võid seda hiljem muuta</string> <string name="ftue_display_name_entry_title">Kuvatav nimi</string> - <string name="ftue_display_name_subtitle">Seda näidatakse sõnumite saatmisel.</string> <string name="ftue_display_name_title">Vali kuvatav nimi</string> <string name="ftue_account_created_subtitle">Sinu kasutajakonto %s on nüüd olemas</string> <string name="ftue_account_created_congratulations_title">Õnnitlused!</string> @@ -2482,7 +2476,6 @@ <string name="a11y_presence_busy">Hõivatud</string> <string name="keys_backup_settings_signature_from_this_user">Varukoopial on kehtiv allkiri sellelt kasutajalt.</string> <string name="live_location_bottom_sheet_last_updated_at">Uuendamise välp: %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Lõpeta asukoha jagamine</string> <string name="labs_enable_live_location_summary">Tegemist on ajutise ja esialgse lahendusega: asukohad on jututoa ajaloos näha</string> <string name="labs_enable_live_location">Luba asukohta jagada reaalajas</string> <string name="location_share_live_remaining_time">Jäänud on %1$s</string> @@ -2594,4 +2587,8 @@ </plurals> <string name="search_space_two_parents">%1$s ja %2$s</string> <string name="auth_reset_password_error_unverified">E-posti aadress on kinnitamata, palun vaata oma saabunud e-kirju</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Kaardi laadimine ei õnnestu. +\nSee koduserver ei pruugi olla seadistatud kuvama kaarte.</string> + <string name="a11y_open_settings">Ava seadistused</string> + <string name="all_chats">Kõik vestlused</string> +</resources> diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index f1465684e2..7b27d1cc1d 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -67,8 +67,6 @@ \nGonbidatutako gelak inportatzen</string> <string name="initial_sync_start_importing_account_left_rooms">Hasierako sinkronizazioa: \nUtzitako gelak inportatzen</string> - <string name="initial_sync_start_importing_account_groups">Hasierako sinkronizazioa: -\nKomunitateak inportatzen</string> <string name="initial_sync_start_importing_account_data">Hasierako sinkronizazioa: \nKontuaren datuak inportatzen</string> @@ -194,7 +192,6 @@ <string name="auth_forgot_password">Pasahitza ahaztuta\?</string> <string name="auth_recaptcha_message">Hasiera zerbitzari honek robot bat ez zarela egiaztatu nahi du</string> - <string name="auth_reset_password_missing_email">Zure kontura gehitutako e-mail helbidea sartu behar da.</string> <string name="auth_reset_password_error_unauthorized">Huts egin du e-mail helbidearen egiaztaketak, egin klik e-mailean zetorren estekan</string> <string name="login_error_invalid_home_server">Sartu baliozko URL bat</string> @@ -504,11 +501,6 @@ Kontuan izan ekintza honek aplikazioa berrabiaraziko duela eta denbora bat behar <string name="call">Deia</string> <string name="loading">Kargatzen…</string> - - - - <string name="groups_header">Komunitateak</string> - <string name="start_voice_call_prompt_msg">Ziur ahots-dei bat hasi nahi duzula?</string> <string name="start_video_call_prompt_msg">Ziur bideo-dei bat hasi nahi duzula?</string> @@ -1045,8 +1037,6 @@ Errore hau ${app_name}-en kontroletik kanpo dago. Ez dago Google konturik gailua <string name="action_change">Aldatu</string> <string name="change_room_directory_network">Aldatu sarea</string> <string name="please_wait">Itxaron mesedez…</string> - <string name="group_all_communities">Komunitate guztiak</string> - <string name="room_preview_no_preview">Gela hau ezin da aurreikusi</string> <string name="fab_menu_create_room">Gelak</string> diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 591ae21198..c534356e92 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -54,8 +54,6 @@ \nدرونریزی اتاقهای دعوتشده</string> <string name="initial_sync_start_importing_account_left_rooms">همگامسازی نخستین: \nدرونریزی اتاقهای ترک شده</string> - <string name="initial_sync_start_importing_account_groups">همگامسازی نخستین: -\nدرونریزی اجتماعها</string> <string name="initial_sync_start_importing_account_data">همگامسازی نخستین: \nدرونریزی دادههای حساب</string> <string name="event_status_sending_message">در حال فرستادن پیام…</string> @@ -249,7 +247,6 @@ <string name="system_alerts_header">هشدارهای سیستمی</string> <string name="matrix_only_filter">فقط آشنایان ماتریکس</string> <string name="no_result_placeholder">نتیجهای نیست</string> - <string name="groups_header">انجمنها</string> <string name="send_bug_report_include_logs">ارسال رخدادنگارها</string> <string name="send_bug_report_include_crash_logs">ارسال رخدادنگارهای خطا</string> <string name="send_bug_report_include_screenshot">ارسال تصویر صفحه</string> @@ -438,7 +435,6 @@ <string name="action_change">تغییر</string> <string name="change_room_directory_network">تغییر شبکه</string> <string name="please_wait">لطفاً شکیبایی کنید…</string> - <string name="group_all_communities">تمام اجتماعها</string> <string name="room_preview_no_preview">این اتاق نمیتواند پیشنمایش یابد</string> <string name="fab_menu_create_room">اتاقها</string> <string name="fab_menu_create_chat">پیامهای مستقیم</string> @@ -505,7 +501,6 @@ <string name="settings_dev_tools">ابزارهای توسعه</string> <string name="none">هیچ</string> <string name="action_revoke">ابطال</string> - <string name="auth_reset_password_missing_email">باید نشانی رایانامهٔ پیوسته به حسابتان وارد شود.</string> <string name="auth_reset_password_error_unauthorized">شکست در تأیید نشانی رایانامه: مطمئن شوید که پیوند درون رایانامه را کلیک کردهاید</string> <string name="auth_accept_policies">لطفاً سیاستهای این کارساز خانگی را بررسی کرده و بپذیرید:</string> <string name="login_error_invalid_home_server">لطفا یک نشانی معتبر وارد کنید</string> @@ -2433,7 +2428,6 @@ <string name="ftue_profile_picture_title">افزودن یک تصویر نمایه</string> <string name="ftue_display_name_entry_footer">میتوانید بعدها تغییرش دهید</string> <string name="ftue_display_name_entry_title">نام نمایشی</string> - <string name="ftue_display_name_subtitle">هنگامی که پیام میفرستید نشان داده خواهد شد.</string> <string name="ftue_account_created_subtitle">حسابتان %s ایجاد شد</string> <string name="ftue_account_created_congratulations_title">تبریک!</string> <string name="ftue_account_created_take_me_home">مرا به خانه ببر</string> @@ -2482,7 +2476,6 @@ <string name="a11y_presence_busy">مشغول</string> <string name="keys_backup_settings_signature_from_this_user">پشتیبان امضای معتبری از این کاربر دارد.</string> <string name="live_location_bottom_sheet_last_updated_at">%1$s پیش بهروز شده</string> - <string name="live_location_bottom_sheet_stop_sharing">توقّف همرسانی</string> <string name="labs_enable_live_location_summary">پیادهسازی موقّتی: موقعیتها در تاریخچهٔ اتاق میمانند</string> <string name="labs_enable_live_location">به کار انداختن همرسانی مکان زنده</string> <string name="location_share_live_remaining_time">%1$s مانده</string> @@ -2603,4 +2596,8 @@ </plurals> <string name="search_space_two_parents">%1$s و %2$s</string> <string name="auth_reset_password_error_unverified">رایانامه تأیید نشده. صندوق ورودیتان را بررسی کنید</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">ناتوان در بار کردن نقشه +\nشاید این کارساز خانگی برای نمایش نقشهها پیکربندی نشده باشد.</string> + <string name="a11y_open_settings">گشودن تنظیمات</string> + <string name="all_chats">تمامی گپها</string> +</resources> diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 1a1db03232..fde2502ae0 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -52,8 +52,6 @@ \nTuodaan kutsuttuja huoneita</string> <string name="initial_sync_start_importing_account_left_rooms">Alkusynkronointi: \nTuodaan poistuttuja huoneita</string> - <string name="initial_sync_start_importing_account_groups">Alkusynkronointi: -\nTuodaan yhteisöjä</string> <string name="initial_sync_start_importing_account_data">Alkusynkronointi: \nTuodaan tilin tietoja</string> <string name="notice_room_update">%s päivitti tämän huoneen.</string> @@ -240,7 +238,6 @@ <string name="auth_email_already_defined">Tämä sähköpostiosoite on jo käytössä.</string> <string name="auth_forgot_password">Unohditko salasanasi?</string> <string name="auth_recaptcha_message">Tämä kotipalvelin haluaa varmistaa, ettet ole robotti</string> - <string name="auth_reset_password_missing_email">Anna tiliisi liitetty sähköpostiosoite.</string> <string name="auth_reset_password_error_unauthorized">Sähköpostiosoitteesi vahvistaminen epäonnistui. Varmista, että klikkasit sähköpostissa olevaa linkkiä</string> <string name="login_error_invalid_home_server">Syötäthän toimivan osoitteen</string> <string name="login_error_bad_json">Epämuotoinen JSON</string> @@ -447,7 +444,6 @@ <string name="notification_noisy">Äänekäs</string> <string name="encrypted_message">Salattu viesti</string> <string name="loading">Ladataan…</string> - <string name="groups_header">Yhteisöt</string> <string name="start_voice_call_prompt_msg">Haluatko varmasti aloittaa äänipuhelun\?</string> <string name="start_video_call_prompt_msg">Haluatko varmasti aloittaa videopuhelun\?</string> <string name="room_participants_ban_prompt_msg">Käyttäjän estäminen poistaa hänet tästä huoneesta ja estää häntä liittymästä huoneeseen uudelleen.</string> @@ -815,7 +811,6 @@ <string name="create_new_room">Luo uusi huone</string> <string name="error_no_network">Ei verkkoa. Tarkista internet-yhteytesi.</string> <string name="please_wait">Odota…</string> - <string name="group_all_communities">Kaikki yhteisöt</string> <string name="room_preview_no_preview">Tätä huonetta ei voi esikatsella</string> <string name="fab_menu_create_room">Huoneet</string> <string name="fab_menu_create_chat">Yksityisviestit</string> @@ -2070,7 +2065,6 @@ <string name="ftue_profile_picture_title">Lisää profiilikuva</string> <string name="ftue_display_name_entry_footer">Voit vaihtaa tämän myöhemmin</string> <string name="ftue_display_name_entry_title">Näyttönimi</string> - <string name="ftue_display_name_subtitle">Tämä näytetään, kun lähetät viestejä.</string> <string name="ftue_display_name_title">Valitse näyttönimi</string> <string name="ftue_account_created_subtitle">Tilisi %s on luotu.</string> <string name="ftue_account_created_congratulations_title">Onnittelut!</string> diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml index 2ce845a632..29a618f415 100644 --- a/vector/src/main/res/values-fr-rCA/strings.xml +++ b/vector/src/main/res/values-fr-rCA/strings.xml @@ -359,7 +359,6 @@ <string name="room_preview_not_found">Ce salon n’est pas accessible en ce moment. \nRéessayez plus tard, ou demandez à un administrateur de ce salon de vérifier que vous pouvez y accéder.</string> <string name="room_preview_no_preview">Impossible d’avoir un aperçu de ce salon</string> - <string name="group_all_communities">Toutes les communautés</string> <string name="please_wait">Veuillez patienter…</string> <string name="change_room_directory_network">Changer de réseau</string> <string name="action_change">Changer</string> @@ -569,7 +568,6 @@ <string name="auth_reset_password_error_unauthorized">Impossible de vérifier l’adresse courriel : assurez-vous d’avoir cliqué sur le lien dans l’courriel</string> <string name="settings_emails_empty">Aucune adresse courriel n’a été ajoutée à votre compte</string> <string name="auth_email_already_defined">Cette adresse courriel est déjà utilisée.</string> - <string name="auth_reset_password_missing_email">L’adresse courriel liée à votre compte doit être saisie.</string> <string name="notice_crypto_unable_to_decrypt_final">Vous n’avez pas accès à ce message</string> <string name="room_settings_set_avatar">Définir l’avatar</string> <string name="room_settings_save_success">Vous avez bien changé les paramètres du salon</string> @@ -1465,7 +1463,6 @@ <string name="send_bug_report_include_key_share_history">Inclure l’historique d’échange de clés</string> <string name="send_bug_report_include_crash_logs">Envoyer les journaux d’erreur</string> <string name="send_bug_report_include_logs">Envoyer les journaux</string> - <string name="groups_header">Communautés</string> <string name="settings_room_directory_show_all_rooms_summary">Afficher tous les salons dans le répertoire, y compris ceux au contenu choquant.</string> <string name="settings_room_directory_show_all_rooms">Afficher les salons au contenu choquant</string> <string name="settings_category_room_directory">Répertoire des salons</string> @@ -1746,8 +1743,6 @@ <string name="event_status_sent_message">Message envoyé</string> <string name="initial_sync_start_importing_account_data">Synchronisation initiale : \nImportation des données du compte</string> - <string name="initial_sync_start_importing_account_groups">Synchronisation initiale : -\nImportation des communautés</string> <string name="initial_sync_start_importing_account_left_rooms">Synchronisation initiale : \nImportation des salons que vous avez quittés</string> <string name="initial_sync_start_importing_account_invited_rooms">Synchronisation initiale : diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 07849141b6..c5a7a7b2b7 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -52,8 +52,6 @@ \nImportation des salons où vous avez été invité</string> <string name="initial_sync_start_importing_account_left_rooms">Synchronisation initiale : \nImportation des salons que vous avez quittés</string> - <string name="initial_sync_start_importing_account_groups">Synchronisation initiale : -\nImportation des communautés</string> <string name="initial_sync_start_importing_account_data">Synchronisation initiale : \nImportation des données du compte</string> <string name="notice_room_update">%s a mis à niveau ce salon.</string> @@ -375,7 +373,6 @@ <string name="auth_invalid_email">Ceci ne ressemble pas à une adresse électronique valide</string> <string name="auth_email_already_defined">Cette adresse électronique est déjà utilisée.</string> <string name="auth_recaptcha_message">Ce serveur d’accueil souhaite s’assurer que vous n’êtes pas un robot</string> - <string name="auth_reset_password_missing_email">L’adresse électronique liée à votre compte doit être saisie.</string> <string name="auth_reset_password_error_unauthorized">Impossible de vérifier l’adresse électronique : assurez-vous d’avoir cliqué sur le lien dans le courriel</string> <string name="login_error_limit_exceeded">Trop de requêtes ont été envoyées</string> <string name="action_leave">Quitter</string> @@ -474,7 +471,6 @@ <string name="notification_noisy">Notification sonore</string> <string name="encrypted_message">Message chiffré</string> <string name="loading">Chargement…</string> - <string name="groups_header">Communautés</string> <string name="start_voice_call_prompt_msg">Voulez-vous vraiment engager un nouvel appel audio \?</string> <string name="start_video_call_prompt_msg">Voulez-vous vraiment engager un nouvel appel vidéo \?</string> <string name="room_participants_ban_prompt_msg">Bannir un utilisateur va l’expulser du salon et l’empêcher de le rejoindre à nouveau.</string> @@ -854,7 +850,6 @@ <string name="create_new_room">Créer un nouveau salon</string> <string name="error_no_network">Aucun réseau. Vérifiez votre connexion Internet.</string> <string name="action_change">Changer</string> - <string name="group_all_communities">Toutes les communautés</string> <string name="room_preview_no_preview">Impossible d’avoir un aperçu de ce salon</string> <string name="fab_menu_create_room">Salons</string> <string name="fab_menu_create_chat">Conversations privées</string> @@ -2426,7 +2421,6 @@ <string name="ftue_profile_picture_title">Ajouter une photo de profil</string> <string name="ftue_display_name_entry_footer">Vous pourrez le changer plus tard</string> <string name="ftue_display_name_entry_title">Nom d’affichage</string> - <string name="ftue_display_name_subtitle">Il sera affiché quand vous enverrez des messages.</string> <string name="ftue_display_name_title">Choisissez un nom d’affichage</string> <string name="ftue_account_created_subtitle">Votre compte %s a été créé</string> <string name="ftue_account_created_congratulations_title">Félicitations !</string> @@ -2482,7 +2476,6 @@ \nCette action va redémarrer l’application et pourra prendre du temps.</string> <string name="initial_sync_request_title">Requête de synchronisation initiale</string> <string name="live_location_bottom_sheet_last_updated_at">Mis-à-jour il y a %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Arrêter le partage</string> <string name="labs_enable_live_location_summary">Implémentation temporaire : les positions sont persistantes dans l’historique du salon</string> <string name="labs_enable_live_location">Activer le partage de position en continu</string> <string name="location_share_live_remaining_time">%1$s restant</string> @@ -2603,4 +2596,8 @@ </plurals> <string name="search_space_two_parents">%1$s et %2$s</string> <string name="auth_reset_password_error_unverified">Courriel non vérifié, relevez votre boîte de réception</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Impossible de charger la carte +\nCe serveur d’accueil n’a peut-être pas été configuré pour afficher les cartes.</string> + <string name="a11y_open_settings">Ouvrir les paramètres</string> + <string name="all_chats">Toutes les conversations</string> +</resources> diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml index d218203739..02f1e6ab2a 100644 --- a/vector/src/main/res/values-fy/strings.xml +++ b/vector/src/main/res/values-fy/strings.xml @@ -69,7 +69,6 @@ <string name="matrix_only_filter">Allinnich Matrix-kontakten</string> <string name="no_result_placeholder">Gjin resultaten</string> <string name="rooms_header">Petearen</string> - <string name="groups_header">Mienskippen</string> <string name="send_bug_report_include_logs">Lochboek ferstjoere</string> <string name="send_bug_report_include_crash_logs">Ungeloklochboek ferstjoere</string> <string name="send_bug_report_include_screenshot">Skermôfdruk ferstjoere</string> @@ -103,8 +102,6 @@ <string name="event_status_sent_message">Berjocht ferstjoerd</string> <string name="initial_sync_start_importing_account_data">Inisjele syngronisaasje: \nAccountgegevens ymportearje</string> - <string name="initial_sync_start_importing_account_groups">Inisjele Syngronisaasje: -\nMienskippen ymportearje</string> <string name="initial_sync_start_importing_account_left_rooms">Inisjele Syngronisaasje: \nFerlitten keamers ymportearje</string> <string name="initial_sync_start_importing_account_invited_rooms">Inisjele syngronisaasje: @@ -139,7 +136,6 @@ <string name="notice_room_visibility_joined">alle dielnimmers, fan it momint ôf dat se de keamer ynkaam binne.</string> <string name="notice_room_visibility_invited">alle dielnimmers oan it petear, fan it momint ôf dat se útnûge binne.</string> <string name="space_type_public_desc">Iepen foar eltsenien, it beste foar mienskippen</string> - <string name="group_all_communities">Alle mienskippen</string> <string name="dialog_title_success">Sukses</string> <string name="room_settings_enable_encryption_no_permission">Jo hawwe gjin tastimming om fersifering yn dizze keamer yn te skeakeljen.</string> <string name="sound_device_phone">Telefoan</string> @@ -231,7 +227,6 @@ <string name="call_camera_back">Efterkant</string> <string name="call_camera_front">Foarkant</string> <string name="auth_recaptcha_message">Dizze thússerver wol der graach wis fan wêze dat jo gjin robot binne</string> - <string name="auth_reset_password_missing_email">It e-mailadres dat oan jo account keppele is moat ynfierd wurde.</string> <string name="settings_security_and_privacy">Befeiliging & privacy</string> <string name="room_widget_resource_grant_permission">Tastean</string> <string name="room_settings_room_version_title">Keamerferzje</string> diff --git a/vector/src/main/res/values-ga/strings.xml b/vector/src/main/res/values-ga/strings.xml index 78b4f873a8..d6ee441255 100644 --- a/vector/src/main/res/values-ga/strings.xml +++ b/vector/src/main/res/values-ga/strings.xml @@ -39,7 +39,6 @@ <string name="sound_device_phone">Guthán</string> <string name="search">Cuardaigh</string> <string name="username">Ainm úsáideora</string> - <string name="groups_header">Pobail</string> <string name="rooms_header">Seomraí</string> <string name="direct_chats_header">Comhráite</string> <string name="invitations_header">Cuirí</string> @@ -150,4 +149,4 @@ <string name="notice_room_invite_no_invitee_by_you">Do chuireadh</string> <string name="notice_room_invite_no_invitee">Cuireadh %s</string> <string name="search_hint">Cuardaigh</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-gl/strings.xml b/vector/src/main/res/values-gl/strings.xml index 295f0ebafb..e6d26a63e5 100644 --- a/vector/src/main/res/values-gl/strings.xml +++ b/vector/src/main/res/values-gl/strings.xml @@ -109,8 +109,6 @@ <string name="event_status_sending_message">Enviando mensaxe…</string> <string name="initial_sync_start_importing_account_data">Sincr. inicial: \nImportando datos da conta</string> - <string name="initial_sync_start_importing_account_groups">Sincr. inicial: -\nImportando comunidades</string> <string name="initial_sync_start_importing_account_left_rooms">Sincr. inicial: \nImportando salas das que saíches</string> <string name="initial_sync_start_importing_account_invited_rooms">Sincr. inicial: @@ -273,8 +271,6 @@ <string name="rooms_header">Salas</string> <string name="title_activity_choose_sticker">Enviar unha icona</string> <string name="action_download">Descargar</string> - - <string name="groups_header">Comunidades</string> <string name="send_bug_report_include_logs">Enviar informes</string> <string name="send_bug_report_include_crash_logs">Enviar informes de fallos</string> <string name="send_bug_report_include_screenshot">Enviar captura de pantalla</string> @@ -312,7 +308,6 @@ <string name="auth_forgot_password">Esqueceu o contrasinal?</string> <string name="auth_recaptcha_message">Este servidor local quere asegurarse de que non é un robot</string> - <string name="auth_reset_password_missing_email">Debe introducir o correo electrónico ligado a súa conta.</string> <string name="auth_reset_password_error_unauthorized">Fallo na verificación do enderezo de correo: asegúrese de ter picado na ligazón do correo</string> <string name="call">Chamar</string> <string name="_continue">Continuar</string> diff --git a/vector/src/main/res/values-hr/strings.xml b/vector/src/main/res/values-hr/strings.xml index b47babedde..dc5930b933 100644 --- a/vector/src/main/res/values-hr/strings.xml +++ b/vector/src/main/res/values-hr/strings.xml @@ -69,7 +69,6 @@ <string name="matrix_only_filter">Samo kontakti u Matrixu</string> <string name="no_result_placeholder">Nema rezultata</string> <string name="rooms_header">Sobe</string> - <string name="groups_header">Zajednice</string> <string name="send_bug_report_include_logs">Pošalji zapise</string> <string name="send_bug_report_include_crash_logs">Pošalji zapise o rušenju</string> <string name="send_bug_report_include_screenshot">Pošalji sliku zaslona</string> @@ -110,7 +109,6 @@ <string name="auth_email_already_defined">Ova je adresa e-pošte već određena.</string> <string name="auth_forgot_password">Zaboravljena lozinka\?</string> <string name="auth_recaptcha_message">Vaš poslužitelj želi provjeriti jeste li robot</string> - <string name="auth_reset_password_missing_email">Nužan je unos adrese e-pošte vezane za Vaš račun.</string> <string name="auth_reset_password_error_unauthorized">Neuspješna provjera adrese e-pošte: pobrinite se kliknuti na poveznicu u e-pošti</string> <string name="auth_accept_policies">Pročitajte i prihvatite pravila ovog poslužitelja:</string> <string name="login_error_invalid_home_server">Unesite ispravan URL</string> @@ -507,7 +505,6 @@ <string name="action_change">Promijeni</string> <string name="change_room_directory_network">Promijeni mrežu</string> <string name="please_wait">Pričekajte…</string> - <string name="group_all_communities">Sve zajednice</string> <string name="room_preview_no_preview">Nije moguće pregledati sobu</string> <string name="fab_menu_create_room">Sobe</string> <string name="fab_menu_create_chat">Izravne poruke</string> @@ -849,4 +846,4 @@ <string name="notice_room_created_by_you">Ti si kreirao sobu</string> <string name="notice_room_created">%1$s je kreirao sobu</string> <string name="notice_room_invite_no_invitee_by_you">Tvoja pozivnica</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 33c0044843..a35595fb36 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -52,8 +52,6 @@ \nMeghívott szobák betöltése</string> <string name="initial_sync_start_importing_account_left_rooms">Induló szinkronizáció: \nElhagyott szobák betöltése</string> - <string name="initial_sync_start_importing_account_groups">Induló szinkronizáció: -\nKözösségek betöltése</string> <string name="initial_sync_start_importing_account_data">Induló szinkronizáció: \nFiók adatok betöltése</string> <string name="notice_room_update">%s frissítette ezt a szobát.</string> @@ -228,7 +226,6 @@ <string name="auth_email_already_defined">Ez az e-mail cím már használatban van.</string> <string name="auth_forgot_password">Elfelejtetted a jelszavad?</string> <string name="auth_recaptcha_message">A Matrix-kiszolgáló szeretné ellenőrizni, hogy nem vagy robot</string> - <string name="auth_reset_password_missing_email">Meg kell adnod a fiókodhoz tartozó e-mail-címet.</string> <string name="auth_reset_password_error_unauthorized">Az e-mail-címed ellenőrzés sikertelen: győződj meg róla, hogy rákattintottál az e-mailben található hivatkozásra</string> <string name="login_error_invalid_home_server">Adj meg egy érvényes URL-t</string> <string name="login_error_bad_json">Hibás JSON</string> @@ -434,7 +431,6 @@ <string name="notification_noisy">Hangos</string> <string name="encrypted_message">Titkosított üzenet</string> <string name="loading">Betöltés…</string> - <string name="groups_header">Közösségek</string> <string name="start_voice_call_prompt_msg">Biztos, hogy hanghívást akarsz indítani\?</string> <string name="start_video_call_prompt_msg">Biztos, hogy videóhívást akarsz indítani\?</string> <string name="room_participants_ban_prompt_msg">A felhasználó kitiltása eltávolítja őt a szobából, és megakadályozza, hogy újra csatlakozhasson.</string> @@ -813,7 +809,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <string name="action_change">Változtat</string> <string name="change_room_directory_network">Hálózat megváltoztatása</string> <string name="please_wait">Kérlek várj…</string> - <string name="group_all_communities">Minden közösség</string> <string name="room_preview_no_preview">Ennek a szobának nincs előnézete</string> <string name="fab_menu_create_room">Szobák</string> <string name="fab_menu_create_chat">Közvetlen üzenetek</string> @@ -1233,7 +1228,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <item quantity="one">%d aktív munkamenet</item> <item quantity="other">%d aktív munkamenet</item> </plurals> - <string name="crosssigning_verify_this_session">Munkamenet ellenőrzése</string> + <string name="crosssigning_verify_this_session">Az eszköz ellenőrzése</string> <string name="verification_open_other_to_verify">A titkosított üzenetekhez való hozzáféréshez nyiss meg egy létező munkamenetet és használd ennek a hitelesítésére.</string> <string name="verification_profile_verify">Ellenőriz</string> <string name="verification_profile_verified">Hitelesített</string> @@ -1394,7 +1389,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <item quantity="other">Meghívó elküldve neki: %1$s és még %2$d helyre</item> </plurals> <string name="invite_users_to_room_failure">Felhasználókat nem tudtuk meghívni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra.</string> - <string name="event_redacted">Üzenet törölve</string> + <string name="event_redacted">Üzenet eltávolítva</string> <string name="settings_show_redacted_summary">Helykitöltő mutatása a törölt szövegek helyett</string> <string name="settings_discovery_confirm_mail_not_clicked">Megerősítő levelet küldtünk ide: %s, először ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra</string> <string name="uploads_media_title">MÉDIA</string> @@ -2432,7 +2427,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <string name="ftue_profile_picture_title">Profilkép hozzáadása</string> <string name="ftue_display_name_entry_footer">Ezt később meg lehet változtatni</string> <string name="ftue_display_name_entry_title">Megjelenítendő név</string> - <string name="ftue_display_name_subtitle">Ez fog megjelenni amikor üzenetet küldesz.</string> <string name="ftue_display_name_title">Válassz egy megjelenítendő nevet</string> <string name="ftue_account_created_subtitle">A fiókod elkészült. A Matrix címed: %s</string> <string name="ftue_account_created_congratulations_title">Gratulálunk!</string> @@ -2482,7 +2476,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <string name="a11y_presence_busy">Foglalt</string> <string name="keys_backup_settings_signature_from_this_user">A mentésnek érvényes aláírása van ettől a felhasználótól.</string> <string name="live_location_bottom_sheet_last_updated_at">Frissítve ekkor: %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Megosztás megállítása</string> <string name="labs_enable_live_location_summary">Átmeneti megvalósítás: a helyadatok megmaradnak a szoba naplójában</string> <string name="labs_enable_live_location">Élő helymegosztás engedélyezése</string> <string name="location_share_live_remaining_time">%1$s kilépett</string> @@ -2575,4 +2568,36 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <string name="ftue_auth_create_account_choose_server_header">Itt lesznek tárolva a beszélgetéseid</string> <string name="ftue_auth_choose_server_subtitle">Mi a szervered címe\? Itt lesz tárolva az összes üzeneted</string> <string name="auth_reset_password_error_unverified">Az email cím nem lett ellenőrizve, kérlek nézd meg a beérkező email-jeidet</string> -</resources> \ No newline at end of file + <string name="labs_enable_element_call_permission_shortcuts_summary">Element Call kisalkalmazás kamera és mikrofon használatának automatikus engedélyezése</string> + <string name="labs_enable_element_call_permission_shortcuts">Element Call jogosultságok engedélyezése</string> + <plurals name="room_removed_messages"> + <item quantity="one">%d üzenet eltávolítva</item> + <item quantity="other">%d üzenet eltávolítva</item> + </plurals> + <string name="live_location_description">Élő földrajzi helyzet</string> + <string name="live_location_share_location_item_share">Tartózkodási hely megosztása</string> + <string name="live_location_not_enough_permission_dialog_description">Az élő helymegosztáshoz ebben a szobában megfelelő jogosultságokra van szükséged.</string> + <string name="live_location_not_enough_permission_dialog_title">Nincs jogosultságod az élő helymegosztáshoz</string> + <string name="location_share_loading_map_error">A térkép betöltése sikertelen +\nEz a matrix szerver nincs beállítva, hogy térképet mutasson.</string> + <string name="a11y_open_settings">Beállítások megnyitása</string> + <string name="verify_invalid_qr_notice">Ez a QR code nem tűnik jónak. Próbáld meg az ellenőrzést másik metódussal.</string> + <string name="crosssigning_cannot_verify_this_session_desc">A régi titkosított üzenetekhez nem férsz majd hozzá. Állítsd be újra a Biztonságos Üzenet Mentést és ellenőrizd a kulcsokat az újrakezdéshez.</string> + <string name="crosssigning_cannot_verify_this_session">Ennek az eszköznek az ellenőrzése nem lehetséges</string> + <string name="permalink_unsupported_groups">Ez a hivatkozás nem nyitható meg: a közösségeket felváltották a terek</string> + <string name="ftue_auth_new_password_title">Válassz jelszót</string> + <string name="ftue_auth_new_password_entry_title">Új jelszó</string> + <string name="ftue_auth_reset_password_breaker_title">Ellenőrizd az e-mailed.</string> + <string name="ftue_auth_reset_password_email_subtitle">%s ellenőrző hivatkozást fog küldeni</string> + <string name="updating_your_data">Adatok frissítése…</string> + <string name="font_size_use_system">Rendszerbeállítás használata</string> + <string name="font_size_section_manually">Kézi beállítás</string> + <string name="font_size_section_auto">Automatikus beállítás</string> + <string name="font_size_title">Válassz betűméretet</string> + <plurals name="search_space_multiple_parents"> + <item quantity="one">%1$s és %2$d másik</item> + <item quantity="other">%1$s és %2$d másik</item> + </plurals> + <string name="search_space_two_parents">%1$s és %2$s</string> + <string name="all_chats">Minden beszélgetés</string> +</resources> diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index cbe5173920..4dea8c3f92 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -93,7 +93,6 @@ <string name="send_bug_report_progress">Kemajuan (%s%%)</string> <string name="username">Nama Pengguna</string> <string name="auth_invalid_login_param">Nama pengguna dan/atau kata sandi salah</string> - <string name="auth_reset_password_missing_email">Anda perlu memasukkan alamat email yang tertaut pada akun.</string> <string name="auth_reset_password_error_unauthorized">Verifikasi alamat email gagal: pastikan tautan yang termuat di email telah diklik</string> <string name="login_error_bad_json">JSON amburadul</string> <string name="login_error_not_json">Tidak berisi JSON yang sah</string> @@ -115,7 +114,6 @@ <string name="loading">Memuat…</string> <string name="action_download">Unduh</string> <string name="system_alerts_header">Peringatan Sistem</string> - <string name="groups_header">Komunitas</string> <string name="send_bug_report_description_in_english">Mohon deskripsikan dengan bahasa Inggris apabila memungkinkan.</string> <string name="send_bug_report_rage_shake">Guncang perangkat untuk laporan gangguan</string> <string name="option_send_voice">Kirim Pesan Suara</string> @@ -755,8 +753,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="event_status_sent_message">Pesan terkirim</string> <string name="initial_sync_start_importing_account_data">Sinkronisasi awal: \nMengimpor data akun</string> - <string name="initial_sync_start_importing_account_groups">Sinkronisasi awal: -\nMengimpor komunitas</string> <string name="initial_sync_start_importing_account_left_rooms">Sinkronisasi Awal: \nMengimpor ruangan yang ditinggalkan</string> <string name="initial_sync_start_importing_account_invited_rooms">Sinkronisasi awal: @@ -965,7 +961,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="room_settings_room_read_history_dialog_subtitle">Perubahan siapa yang dapat membaca riwayat hanya akan berlaku untuk pesan berikutnya di ruangan ini. Visibilitas riwayat yang ada tidak akan berubah.</string> <string name="room_settings_room_notifications_account_settings">Pengaturan akun</string> <string name="room_settings_room_notifications_manage_notifications">Anda dapat mengelola notifikasi di %1$s.</string> - <string name="room_settings_room_notifications_encryption_notice">Harap dicatat bahwa pemberitahuan sebutan & kata kunci tidak tersedia di ruangan terenkripsi di ponsel.</string> + <string name="room_settings_room_notifications_encryption_notice">Harap dicatat bahwa pemberitahuan sebutan & kata kunci tidak tersedia dalam ruangan terenkripsi di ponsel.</string> <string name="room_settings_room_notifications_notify_me">Beritahu saya untuk</string> <string name="settings_play_shutter_sound">Putar suara rana</string> <string name="media_source_choose">Pilih</string> @@ -1005,11 +1001,11 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="settings_background_fdroid_sync_mode_disabled">Tidak ada sinkronisasi latar belakang</string> <string name="settings_background_fdroid_sync_mode_real_time_description">${app_name} akan disinkronkan di latar belakang secara berkala pada waktu yang tepat (dapat dikonfigurasi). \nIni akan memengaruhi penggunaan radio dan baterai, dan ada juga pemberitahuan yang ditampilkan permanen menyatakan bahwa ${app_name} sedang mendengarkan peristiwa.</string> - <string name="settings_mentions_and_keywords_encryption_notice">Anda tidak akan mendapatkan notifikasi untuk sebutan & keyword di ruangan terenkripsi di ponsel.</string> + <string name="settings_mentions_and_keywords_encryption_notice">Anda tidak akan mendapatkan notifikasi untuk sebutan & kata kunci dalam ruangan terenkripsi di ponsel.</string> <string name="settings_room_upgrades">Peningkatan ruangan</string> <string name="settings_messages_by_bot">Pesan dari bot</string> <string name="settings_room_invitations">Undangan ruangan</string> - <string name="settings_messages_containing_keywords">Keyword</string> + <string name="settings_messages_containing_keywords">Kata kunci</string> <string name="settings_mentions_at_room">\@room</string> <string name="settings_encrypted_group_messages">Pesan grup terenkripsi</string> <string name="settings_group_messages">Pesan grup</string> @@ -1021,16 +1017,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="settings_when_rooms_are_upgraded">Saat ruangan ditingkatkan</string> <string name="settings_messages_in_e2e_group_chat">Pesan terenkripsi di obrolan grup</string> <string name="settings_messages_in_e2e_one_to_one">Pesan terenkripsi di chat satu-ke-satu</string> - <string name="settings_notification_keyword_contains_invalid_character">Keyword tidak boleh berisi \'%s\'</string> - <string name="settings_notification_keyword_contains_dot">Keyword tidak boleh diawali dengan \'.\'</string> - <string name="settings_notification_new_keyword">Tambahkan keyword baru</string> - <string name="settings_notification_your_keywords">Keyword Anda</string> + <string name="settings_notification_keyword_contains_invalid_character">Kata kunci tidak boleh berisi \'%s\'</string> + <string name="settings_notification_keyword_contains_dot">Kata kunci tidak boleh diawali dengan \'.\'</string> + <string name="settings_notification_new_keyword">Tambahkan kata kunci baru</string> + <string name="settings_notification_your_keywords">Kata kunci Anda</string> <string name="settings_notification_notify_me_for">Beritahu saya untuk</string> <string name="settings_notification_other">Lainnya</string> - <string name="settings_notification_mentions_and_keywords">Sebutan dan Keyword</string> + <string name="settings_notification_mentions_and_keywords">Sebutan dan Kata Kunci</string> <string name="settings_notification_default">Notifikasi Bawaan</string> <string name="room_settings_none">Tidak ada</string> - <string name="room_settings_mention_and_keyword_only">Hanya sebutan & keyword</string> + <string name="room_settings_mention_and_keyword_only">Hanya sebutan & kata kunci</string> <string name="call_remove_jitsi_widget_progress">Mengakhiri panggilan…</string> <string name="call_ended_invite_timeout_title">Tidak ada jawaban</string> <string name="call_ended_user_busy_description">Pengguna yang Anda panggil sedang sibuk.</string> @@ -1097,7 +1093,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="room_join_rules_public">%1$s membuat ruangan publik untuk siapa pun yang mengetahui tautannya.</string> <string name="help_long_click_on_room_for_more_options">Tekan lama pada sebuah ruangan untuk melihat lebih banyak pilihan</string> <string name="no_ignored_users">Anda tidak mengabaikan pengguna apa pun</string> - <string name="reaction_search_type_hint">Ketik keyword untuk mencari reaksi.</string> + <string name="reaction_search_type_hint">Ketik kata kunci untuk mencari sebuah reaksi.</string> <string name="spoiler">Spoiler</string> <string name="command_description_spoiler">Mengirim pesan sebagai spoiler</string> <string name="notice_member_no_changes_by_you">Anda tidak membuat perubahan</string> @@ -1382,7 +1378,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="room_preview_not_found">Ruangan ini tidak dapat di akses di waktu ini. \nCoba lagi nanti, atau tanya admin ruangan untuk memeriksa jika Anda punya akses.</string> <string name="room_preview_no_preview">Ruangan ini tidak dapat di tampilkan</string> - <string name="group_all_communities">Semua Komunitas</string> <string name="please_wait">Mohon menunggu…</string> <string name="change_room_directory_network">Ganti jaringan</string> <string name="action_change">Ganti</string> @@ -1420,7 +1415,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="verification_profile_verified">Terverifikasi</string> <string name="verification_profile_verify">Verifikasi</string> <string name="verification_open_other_to_verify">Gunakan sesi yang ada untuk memverifikasi yang satu ini, memberikan aksesnya ke pesan terenkripsi.</string> - <string name="crosssigning_verify_this_session">Verifikasi login ini</string> + <string name="crosssigning_verify_this_session">Verifikasi perangkat ini</string> <plurals name="settings_active_sessions_count"> <item quantity="other">%d sesi aktif</item> </plurals> @@ -2387,7 +2382,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="ftue_profile_picture_title">Tambahkan sebuah foto profil</string> <string name="ftue_display_name_entry_footer">Anda dapat mengubahnya nanti</string> <string name="ftue_display_name_entry_title">Nama Tampilan</string> - <string name="ftue_display_name_subtitle">Ini akan ditampilkan ketika Anda mengirim pesan.</string> <string name="ftue_display_name_title">Pilih nama tampilan</string> <string name="ftue_account_created_subtitle">Akun %s Anda telah dibuat</string> <string name="ftue_account_created_congratulations_title">Selamat!</string> @@ -2438,7 +2432,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="keys_backup_settings_signature_from_this_user">Cadangan memiliki tandatangan yang valid dari pengguna ini.</string> <string name="location_share_live_until">Langsung sampai %1$s</string> <string name="live_location_bottom_sheet_last_updated_at">Diperbarui %1$s yang lalu</string> - <string name="live_location_bottom_sheet_stop_sharing">Berhenti membagikan</string> <string name="labs_enable_live_location_summary">Implementasi sementara: lokasi tetap di riwayat ruangan</string> <string name="labs_enable_live_location">Aktifkan Pembagian Lokasi Langsung</string> <string name="location_share_live_remaining_time">Tinggal %1$s</string> @@ -2490,7 +2483,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="direct_room_encryption_enabled_tile_description_future">Pesan di obrolan ini akan dienkripsi secara ujung-ke-ujung.</string> <string name="create_room_action_go">Mulai</string> <string name="ftue_auth_password_reset_email_confirmation_subtitle">Ikuti petunjuk yang terkirim ke %s</string> - <string name="ftue_auth_email_verification_subtitle">Untuk mengonfirmasi email Anda, ketuk tombol dalam email yang kami kirim ke %s</string> + <string name="ftue_auth_email_verification_subtitle">Ikuti petunjuk yang terkirim ke %s</string> <plurals name="room_removed_messages"> <item quantity="other">%d pesan dihapus</item> </plurals> @@ -2504,7 +2497,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="ftue_auth_forgot_password">Lupa kata sandi</string> <string name="ftue_auth_email_resend_email">Kirim ulang email</string> <string name="ftue_auth_email_verification_footer">Belum menerima email\?</string> - <string name="ftue_auth_email_verification_title">Periksa email Anda untuk memverifikasi.</string> + <string name="ftue_auth_email_verification_title">Verifikasi email Anda</string> <string name="ftue_auth_phone_confirmation_resend_code">Kirim ulang kode</string> <string name="ftue_auth_phone_confirmation_subtitle">Sebuah kode terkirim ke %s</string> <string name="ftue_auth_phone_confirmation_title">Konfirmasi nomor telepon Anda</string> @@ -2541,4 +2534,22 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="font_size_section_manually">Pilih secara manual</string> <string name="font_size_section_auto">Atur secara otomatis</string> <string name="font_size_title">Pilih ukuran font</string> -</resources> \ No newline at end of file + <string name="labs_enable_element_call_permission_shortcuts_summary">Terima akses kamera / mikrofon widget Element Call secara otomatis</string> + <string name="labs_enable_element_call_permission_shortcuts">Aktifkan pintasan izin Element Call</string> + <string name="live_location_description">Lokasi langsung</string> + <string name="location_share_loading_map_error">Tidak dapat memuat peta +\nHomeserver ini mungkin tidak diatur untuk menampilkan peta.</string> + <string name="a11y_open_settings">Buka pengaturan</string> + <string name="verify_invalid_qr_notice">Kode QR ini tampaknya tidak benar. Mohon coba verifikasi dengan metode lain.</string> + <string name="crosssigning_cannot_verify_this_session_desc">Anda tidak akan dapat mengakses riwayat pesan terenkripsi. Atur ulang Cadangan Pesan Aman dan kunci-kunci verifikasi Anda untuk memulai secara segar.</string> + <string name="crosssigning_cannot_verify_this_session">Tidak dapat memverifikasi perangkat ini</string> + <string name="ftue_auth_choose_server_sign_in_subtitle">Apa alamat server Anda\?</string> + <string name="ftue_auth_sign_in_choose_server_header">Di mana percakapan Anda tinggal</string> + <string name="updating_your_data">Memperbarui data Anda…</string> + <plurals name="search_space_multiple_parents"> + <item quantity="other">%1$s dan %2$d lainnya</item> + </plurals> + <string name="search_space_two_parents">%1$s dan %2$s</string> + <string name="auth_reset_password_error_unverified">Email belum diverifikasi, periksa kotak masuk Anda</string> + <string name="all_chats">Semua Obrolan</string> +</resources> diff --git a/vector/src/main/res/values-is/strings.xml b/vector/src/main/res/values-is/strings.xml index 53af7bff5c..7818761145 100644 --- a/vector/src/main/res/values-is/strings.xml +++ b/vector/src/main/res/values-is/strings.xml @@ -236,7 +236,6 @@ <string name="action_quick_reply">Snöggt svar</string> <string name="home_filter_placeholder_home">Leita að spjallrásum</string> <string name="matrix_only_filter">Einungis tengiliðir í Matrix</string> - <string name="groups_header">Samfélög</string> <string name="send_bug_report_progress">Framvinda (%s%%)</string> <string name="auth_invalid_email">Þetta lítur ekki út eins og gilt tölvupóstfang</string> <string name="auth_email_already_defined">Þetta tölvupóstfang er nú þegar skráð.</string> @@ -316,7 +315,6 @@ <string name="send_bug_report_sent">Það tókst að senda villuskýrsluna</string> <string name="send_bug_report_failed">Mistókst að senda villuskýrsluna (%s)</string> <string name="auth_recaptcha_message">Þessi heimavefþjónn vill ganga úr skugga um að þú sért ekki vélmenni</string> - <string name="auth_reset_password_missing_email">Það þarf að setja inn tölvupóstfangið sem tengt er notandaaðgangnum þínum.</string> <string name="auth_reset_password_error_unauthorized">Gat ekki sannprófað tölvupóstfang: gakktu úr skugga um að þú hafir smellt á tengilinn í tölvupóstinum</string> <string name="room_participants_power_level_prompt">Þú getur ekki afturkallað þessa aðgerð, þar sem þú ert að gefa notandanum jafn mikil völd og þú hefur sjálf/ur. \nErtu alveg viss\?</string> @@ -535,8 +533,6 @@ <string name="notice_room_invite_no_invitee_with_reason">Boð um þátttöku til %1$s. Ástæða: %2$s</string> <string name="initial_sync_start_importing_account_data">Upphaf samstillingar: \nFlyt inn gögn úr notandaaðgangi</string> - <string name="initial_sync_start_importing_account_groups">Upphaf samstillingar: -\nFlyt inn samfélög</string> <string name="initial_sync_start_importing_account_left_rooms">Upphaf samstillingar: \nFlyt inn yfirgefnar spjallrásir</string> <string name="initial_sync_start_importing_account_invited_rooms">Upphaf samstillingar: @@ -822,7 +818,6 @@ <string name="send_file_step_compressing_image">Þjappa mynd…</string> <string name="send_file_step_sending_file">Sendi skrá (%1$s / %2$s)</string> <string name="send_file_step_encrypting_file">Dulrita skrá…</string> - <string name="group_all_communities">Öll samfélög</string> <string name="settings_show_redacted">Sýna fjarlægð skilaboð</string> <string name="room_list_catchup_empty_body">Þú átt engin fleiri ólesin skilaboð</string> <string name="invited_by">Boðið af %s</string> @@ -2017,4 +2012,4 @@ <string name="action_try_it_out">Prófaðu það</string> <string name="action_disable">Gera óvirkt</string> <string name="initial_sync_request_title">Upphafleg samstillingarbeiðni</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 46bb5453a8..b14b9d8fbd 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -52,8 +52,6 @@ \nImportazione stanze con invito</string> <string name="initial_sync_start_importing_account_left_rooms">Sincronizzazione iniziale: \nImportazione stanze abbandonate</string> - <string name="initial_sync_start_importing_account_groups">Sincronizzazione iniziale: -\nImportazione delle comunità</string> <string name="initial_sync_start_importing_account_data">Sincronizzazione iniziale: \nImportazione dati account</string> <string name="notice_room_update">%s ha aggiornato questa stanza.</string> @@ -293,7 +291,6 @@ <string name="auth_email_already_defined">L\'indirizzo email è già stato impostato.</string> <string name="auth_forgot_password">Hai dimenticato la password\?</string> <string name="auth_recaptcha_message">Questo homeserver vuole assicurarsi che tu non sia un robot</string> - <string name="auth_reset_password_missing_email">Va inserito l\'indirizzo email associato al tuo account.</string> <string name="auth_reset_password_error_unauthorized">La verifica del tuo indirizzo email è fallita: assicurati di aver cliccato sul link contenuto nella mail</string> <string name="login_error_invalid_home_server">Inserisci un URL valido</string> <string name="login_error_bad_json">JSON malformato</string> @@ -488,7 +485,6 @@ <string name="call">Chiama</string> <string name="encrypted_message">Messaggio criptato</string> <string name="loading">Caricamento…</string> - <string name="groups_header">Comunità</string> <string name="send_bug_report_rage_shake">Per segnalare un errore agita il dispositivo con rabbia</string> <string name="start_voice_call_prompt_msg">Sicuro di voler fare una chiamata audio\?</string> <string name="start_video_call_prompt_msg">Sicuro di voler fare una videochiamata\?</string> @@ -874,7 +870,6 @@ <string name="action_change">Cambia</string> <string name="change_room_directory_network">Cambia rete</string> <string name="please_wait">Attendere prego…</string> - <string name="group_all_communities">Tutte le comunità</string> <string name="room_preview_no_preview">Anteprima non disponibile per questa stanza</string> <string name="fab_menu_create_room">Stanze</string> <string name="fab_menu_create_chat">Messaggi diretti</string> @@ -2424,7 +2419,6 @@ <string name="ftue_profile_picture_title">Aggiungi un\'immagine del profilo</string> <string name="ftue_display_name_entry_footer">Puoi cambiarlo in seguito</string> <string name="ftue_display_name_entry_title">Nome da mostrare</string> - <string name="ftue_display_name_subtitle">Verrà mostrato quando invii messaggi.</string> <string name="ftue_display_name_title">Scegli un nome da mostrare</string> <string name="ftue_account_created_subtitle">Il tuo account %s è stato creato</string> <string name="ftue_account_created_congratulations_title">Congratulazioni!</string> @@ -2474,7 +2468,6 @@ <string name="keys_backup_settings_signature_from_this_user">Il backup ha una firma valida da questo utente.</string> <string name="labs_enable_live_location_summary">Implementazione temporanea: le posizioni restano nella cronologia della stanza</string> <string name="live_location_bottom_sheet_last_updated_at">Aggiornato %1$s fa</string> - <string name="live_location_bottom_sheet_stop_sharing">Ferma condivisione</string> <string name="labs_enable_live_location">Attiva condivisione posizione in tempo reale</string> <string name="location_share_live_remaining_time">%1$s rimasti</string> <string name="location_share_live_until">In tempo reale fino alle %1$s</string> @@ -2594,4 +2587,8 @@ </plurals> <string name="search_space_two_parents">%1$s e %2$s</string> <string name="auth_reset_password_error_unverified">Email non verificata, controlla la posta in arrivo</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Impossibile caricare la mappa +\nQuesto homeserver potrebbe non essere configurato per mostrare mappe.</string> + <string name="a11y_open_settings">Apri le impostazioni</string> + <string name="all_chats">Tutte le chat</string> +</resources> diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml index be02a1e772..6d9533852b 100644 --- a/vector/src/main/res/values-iw/strings.xml +++ b/vector/src/main/res/values-iw/strings.xml @@ -114,7 +114,6 @@ <string name="send_bug_report_include_key_share_history">שלח היסטוריית בקשות לשיתוף מפתח</string> <string name="send_bug_report_include_crash_logs">שלח יומני תקלות</string> <string name="send_bug_report_include_logs">שלח יומנים</string> - <string name="groups_header">קהילות</string> <string name="login_error_limit_exceeded">יותר מדי בקשות נשלחו</string> <string name="login_error_not_json">לא מכיל JSON חוקי</string> <string name="login_error_bad_json">JSON פגום</string> @@ -125,7 +124,6 @@ <string name="login_error_invalid_home_server">אנא הכנס כתובת תקינה</string> <string name="auth_accept_policies">אנא עיין וקבל את המדיניות של שרת בית זה:</string> <string name="auth_reset_password_error_unauthorized">אימות כתובת הדוא\"ל נכשל: ודא שלחצת על הקישור בדוא\"ל</string> - <string name="auth_reset_password_missing_email">יש להזין את כתובת הדוא\"ל המקושרת לחשבונך.</string> <string name="auth_recaptcha_message">שרת הבית רוצה לוודא שאתה לא רובוט</string> <string name="auth_forgot_password">שכחת סיסמה\?</string> <string name="auth_msisdn_already_defined">מספר הטלפון הזה כבר קיים ומעודכן במערכת.</string> @@ -1155,7 +1153,6 @@ <string name="room_preview_not_found">בשלב זה אין גישה לחדר זה. \nנסה שוב מאוחר יותר, או בקש ממנהל החדר לבדוק אם יש לך גישה.</string> <string name="room_preview_no_preview">לא ניתן להציג תצוגה מקדימה של חדר זה</string> - <string name="group_all_communities">כל הקהילות</string> <string name="please_wait">אנא המתינו…</string> <string name="change_room_directory_network">שנה רשת</string> <string name="action_change">שנה</string> @@ -1769,8 +1766,6 @@ <string name="event_status_sent_message">הודעה נשלחה</string> <string name="initial_sync_start_importing_account_data">סנכרון ראשוני: \nייבוא נתוני חשבון</string> - <string name="initial_sync_start_importing_account_groups">סנכרון ראשוני: -\nייבוא קהילות</string> <string name="initial_sync_start_importing_account_left_rooms">סנכרון ראשוני: \nייבוא חדרים עזובים</string> <string name="initial_sync_start_importing_account_invited_rooms">סנכרון ראשוני: diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 0aef5124c9..b781e4d7f0 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -218,7 +218,6 @@ <string name="logout">サインアウト</string> <string name="auth_submit">送信</string> <string name="auth_recaptcha_message">このホームサーバーは、あなたがロボットではないことの確認を求めています</string> - <string name="auth_reset_password_missing_email">アカウントに登録されたメールアドレスの入力が必要です。</string> <string name="auth_reset_password_error_unauthorized">メールアドレスの認証に失敗しました:電子メールのリンクをクリックしたことを確認してください</string> <string name="login_error_bad_json">不正な形式のJSON</string> <string name="login_error_not_json">有効なJSONを含んでいませんでした</string> @@ -334,7 +333,6 @@ <string name="notification_noisy">音量大</string> <string name="encrypted_message">暗号化されたメッセージ</string> <string name="loading">読み込んでいます…</string> - <string name="groups_header">コミュニティー</string> <string name="start_voice_call_prompt_msg">音声通話を開始してよろしいですか?</string> <string name="start_video_call_prompt_msg">ビデオ通話を開始してよろしいですか?</string> <string name="room_participants_ban_prompt_msg">ユーザーをブロックすると、ユーザーはこのルームから追放され、二度と参加できなくなります。</string> @@ -500,7 +498,6 @@ <string name="create_new_room">新しいルームを作成</string> <string name="action_change">変更</string> <string name="change_room_directory_network">ネットワークを変更</string> - <string name="group_all_communities">全てのコミュニティー</string> <string name="fab_menu_create_room">ルーム</string> <string name="fab_menu_create_chat">ダイレクトメッセージ</string> <string name="create_room_action_create">作成</string> @@ -899,8 +896,6 @@ <string name="event_status_sent_message">メッセージを送りました</string> <string name="initial_sync_start_importing_account_data">初期同期: \nアカウントデータをインポートしています</string> - <string name="initial_sync_start_importing_account_groups">初期同期: -\nコミュニティーをインポートしています</string> <string name="initial_sync_start_importing_account_rooms">初期同期: \nルームをインポートしています</string> <string name="initial_sync_start_importing_account_crypto">初期同期: @@ -2344,7 +2339,6 @@ <string name="ftue_profile_picture_title">プロフィール画像を追加</string> <string name="ftue_display_name_entry_footer">これは後から変更できます</string> <string name="ftue_display_name_entry_title">表示名</string> - <string name="ftue_display_name_subtitle">メッセージを送信する際に表示されます。</string> <string name="a11y_location_share_option_pinned_icon">この位置情報を共有</string> <string name="location_share_option_pinned">この位置情報を共有</string> <string name="a11y_location_share_option_user_live_icon">位置情報(ライブ)を共有</string> diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml index 15b96813c8..a79b72efde 100644 --- a/vector/src/main/res/values-kab/strings.xml +++ b/vector/src/main/res/values-kab/strings.xml @@ -103,8 +103,6 @@ \nAktar n texxamin iɣer tettwanecdeḍ</string> <string name="initial_sync_start_importing_account_left_rooms">Amtawi n tazwara: \nAktar n texxamin i teǧǧiḍ</string> - <string name="initial_sync_start_importing_account_groups">Amtawi n tazwara: -\nAktar n tmezdagnutin</string> <string name="initial_sync_start_importing_account_data">Amtawi n tazwara: \nAktar n yisefka n umiḍan</string> <string name="event_status_sending_message">Tuzzna n yizen…</string> @@ -208,7 +206,6 @@ <string name="system_alerts_header">Ilɣa n unagraw</string> <string name="no_result_placeholder">Ulac igmad</string> <string name="rooms_header">Tixxamin</string> - <string name="groups_header">Tamɣiwnin</string> <string name="send_bug_report_include_logs">Azen iɣmisen</string> <string name="join_room">Rnu ɣer texxamt</string> <string name="username">Isem n useqdac</string> @@ -547,7 +544,6 @@ <string name="auth_email_already_defined">Tansa-a n yimayl tettuseqdec yakan.</string> <string name="auth_forgot_password">Tettuḍ awal uffir\?</string> <string name="auth_recaptcha_message">Aqeddac-a agejdan yesra ad iẓer ma mačči d aṛubut i telliḍ</string> - <string name="auth_reset_password_missing_email">Tansa n yimayl i icudden ɣer umiḍan-ik·im ilaq ad tettwasekcem.</string> <string name="auth_accept_policies">Ttxil-k·m senqed syen qbel tisertiyin n uqeddac-a agejdan:</string> <string name="login_error_invalid_home_server">Ttxil-k·m sekcem URL ameɣtu</string> <string name="login_error_no_homeserver_found">Tagi mačči d tansa n uqeddac n Matrix ameɣtu</string> @@ -710,7 +706,6 @@ <string name="action_agree">Qbel</string> <string name="create_new_room">Rnu taxxamt tamaynut</string> <string name="change_room_directory_network">Snifel azeṭṭa</string> - <string name="group_all_communities">Meṛṛa timɣiwnin</string> <string name="create_room_action_create">RNU</string> <string name="create_room_name_hint">Isem</string> <string name="create_room_public_description">Yal yiwen yezmer ad yernu ɣer texxamt-a</string> diff --git a/vector/src/main/res/values-ko/strings.xml b/vector/src/main/res/values-ko/strings.xml index 5d07800726..ba0cbe5abd 100644 --- a/vector/src/main/res/values-ko/strings.xml +++ b/vector/src/main/res/values-ko/strings.xml @@ -52,8 +52,6 @@ \n초대받은 방 가져오는 중</string> <string name="initial_sync_start_importing_account_left_rooms">초기 동기화: \n떠난 방 가져오는 중</string> - <string name="initial_sync_start_importing_account_groups">초기 동기화: -\n커뮤니티 가져오는 중</string> <string name="initial_sync_start_importing_account_data">초기 동기화: \n계정 데이터 가져오는 중</string> <string name="event_status_sending_message">메시지 보내는 중…</string> @@ -133,7 +131,6 @@ <string name="done">완료</string> <string name="action_sign_out_confirmation_simple">정말 로그아웃하시겠습니까\?</string> <string name="action_mark_room_read">읽음으로 표시</string> - <string name="groups_header">커뮤니티</string> <string name="send_bug_report_app_crashed">최근에 애플리케이션이 충돌한 것 같습니다. 충돌 보고서를 열까요\?</string> <string name="username">사용자 이름</string> <string name="option_send_voice">음성 보내기</string> @@ -165,7 +162,6 @@ <string name="auth_login_sso">통합 인증으로 로그인</string> <string name="auth_email_already_defined">이 이메일 주소는 이미 정의되었습니다.</string> <string name="auth_recaptcha_message">이 홈서버는 당신이 로봇인지 아닌 지를 확인하고 싶습니다</string> - <string name="auth_reset_password_missing_email">계정에 연결된 이메일 주소를 입력해야 합니다.</string> <string name="auth_reset_password_error_unauthorized">이메일 주소를 확인할 수 없습니다: 이메일에 있는 링크를 클릭했는 지 확인하세요</string> <string name="auth_accept_policies">이 홈서버의 규칙을 숙지한 후 수락하세요:</string> <string name="login_error_invalid_home_server">올바른 URL을 입력하세요</string> @@ -700,7 +696,6 @@ <string name="action_change">변경</string> <string name="change_room_directory_network">네트워크 변경</string> <string name="please_wait">기다려주세요…</string> - <string name="group_all_communities">모든 커뮤니티</string> <string name="room_preview_no_preview">이 방은 미리 볼 수 없습니다</string> <string name="fab_menu_create_room">방</string> <string name="fab_menu_create_chat">다이렉트 메시지</string> diff --git a/vector/src/main/res/values-lo/strings.xml b/vector/src/main/res/values-lo/strings.xml index 5af621359f..1a9a2820b8 100644 --- a/vector/src/main/res/values-lo/strings.xml +++ b/vector/src/main/res/values-lo/strings.xml @@ -259,7 +259,6 @@ <string name="login_error_invalid_home_server">ກະລຸນາໃສ່ URL ທີ່ຖືກຕ້ອງ</string> <string name="auth_accept_policies">ກະລຸນາກວດເບິ່ງ ແລະຍອມຮັບນະໂຍບາຍຂອງ homeserver ນີ້:</string> <string name="auth_reset_password_error_unauthorized">ການກວດສອບທີ່ຢູ່ອີເມວບໍ່ສຳເລັດ: ໃຫ້ແນ່ໃຈວ່າທ່ານໄດ້ກົດໃສ່ການເຊື່ອມຕໍ່ໃນອີເມວ</string> - <string name="auth_reset_password_missing_email">ຕ້ອງໃສ່ອິເມວທີ່ເຊື່ອມຕໍ່ກັບບັນຊີຂອງທ່ານ.</string> <string name="auth_recaptcha_message">homeserver ນີ້ຕ້ອງການໃຫ້ແນ່ໃຈວ່າທ່ານບໍ່ແມ່ນຫຸ່ນຍົນ</string> <string name="auth_forgot_password">ລືມລະຫັດຜ່ານ\?</string> <string name="auth_msisdn_already_defined">ເບີໂທລະສັບນີ້ຖືກກໍານົດໄວ້ແລ້ວ.</string> @@ -320,7 +319,6 @@ <string name="send_bug_report_include_crash_logs">ສົ່ງບັນທຶກການຂັດຂ້ອງ</string> <string name="send_bug_report_include_logs">ສົ່ງບັນທຶກ</string> <string name="spaces_header">ພື້ນທີ່</string> - <string name="groups_header">ຊຸມຊົນ</string> <string name="settings_room_directory_show_all_rooms_summary">ສະແດງໃຫ້ເຫັນຫ້ອງທັງຫມົດໃນລະບົບຫ້ອງ, ລວມທັງຫ້ອງທີ່ມີເນື້ອຫາຊັດເຈນ.</string> <string name="settings_room_directory_show_all_rooms">ສະແດງຫ້ອງທີ່ມີເນື້ອຫາບໍ່ຈະແຈ້ງ</string> <string name="rooms_header">ຫ້ອງ</string> @@ -515,8 +513,6 @@ <string name="event_status_sent_message">ຂໍ້ຄວາມຖືກສົ່ງ</string> <string name="initial_sync_start_importing_account_data">ການຊິງຄ໌ເບື້ອງຕົ້ນ: \nການນໍາເຂົ້າຂໍ້ມູນບັນຊີ</string> - <string name="initial_sync_start_importing_account_groups">ການຊິງຄ໌ເບື້ອງຕົ້ນ: -\nຊຸມຊົນນໍາເຂົ້າ</string> <string name="initial_sync_start_importing_account_left_rooms">ການຊິງຄ໌ເບື້ອງຕົ້ນ: \nການນໍາເຂົ້າຫ້ອງຊ້າຍ</string> <string name="initial_sync_start_importing_account_invited_rooms">ການຊິງຄ໌ເບື້ອງຕົ້ນ: @@ -737,7 +733,6 @@ <string name="room_preview_not_found">ຫ້ອງນີ້ບໍ່ສາມາດເຂົ້າເຖິງໄດ້ໃນເວລານີ້. \nລອງໃໝ່ໃນພາຍຫຼັງ, ຫຼືຖາມຜູ້ເບິ່ງແຍງຫ້ອງເພື່ອກວດເບິ່ງວ່າທ່ານມີການເຂົ້າເຖິງຫຼືບໍ່.</string> <string name="room_preview_no_preview">ຫ້ອງນີ້ບໍ່ສາມາດເບິ່ງຕົວຢ່າງໄດ້</string> - <string name="group_all_communities">ຊຸມຊົນທັງໝົດ</string> <string name="please_wait">ກະລຸນາລໍຖ້າ…</string> <string name="change_room_directory_network">ປ່ຽນເຄືອຂ່າຍ</string> <string name="error_no_network">ບໍ່ມີເຄືອຂ່າຍ. ກະລຸນາກວດເບິ່ງການເຊື່ອມຕໍ່ອິນເຕີເນັດຂອງທ່ານ.</string> @@ -2243,7 +2238,6 @@ <string name="direct_room_profile_section_more_leave">ອອກໄປ</string> <string name="room_profile_section_more_leave">ອອກຈາກຫ້ອງ</string> <string name="room_profile_section_more_uploads">ອັບໂຫຼດ</string> - <string name="ftue_display_name_subtitle">ສິ່ງນີ້ຈະຖືກສະແດງເມື່ອທ່ານສົ່ງຂໍ້ຄວາມ.</string> <string name="ftue_display_name_title">ເລືອກຊື່ທີ່ຈະສະແດງ</string> <string name="ftue_account_created_subtitle">ບັນຊີຂອງທ່ານ%sໄດ້ຖືກສ້າງຂື້ນແລ້ວ.</string> <string name="ftue_account_created_congratulations_title">ຊົມເຊີຍ!</string> diff --git a/vector/src/main/res/values-lt/strings.xml b/vector/src/main/res/values-lt/strings.xml index d2e7fa9be8..454bf8a4db 100644 --- a/vector/src/main/res/values-lt/strings.xml +++ b/vector/src/main/res/values-lt/strings.xml @@ -127,7 +127,6 @@ <string name="send_bug_report_include_key_share_history">Siųsti raktų bendrinimo užklausų istoriją</string> <string name="send_bug_report_include_crash_logs">Siųsti sutrikimų žurnalus</string> <string name="send_bug_report_include_logs">Siųsti žurnalus</string> - <string name="groups_header">Bendruomenės</string> <string name="settings_room_directory_show_all_rooms_summary">Rodyti visus kambarių kataloge esančius kambarius, įskaitant kambarius su eksplicitiniu turiniu.</string> <string name="settings_room_directory_show_all_rooms">Rodyti eksplicitinio turinio kambarius</string> <string name="settings_category_room_directory">Kambarių katalogas</string> @@ -448,4 +447,4 @@ <item quantity="other">%d praleistų balso skambučių</item> </plurals> <string name="call_ended">Skambutis baigtas</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index f16e61980e..f1fa1502c1 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -152,8 +152,6 @@ <string name="event_status_sending_message">Sūta ziņu…</string> <string name="initial_sync_start_importing_account_data">Sākotnējā sinhronizācija: \nImportē konta datus</string> - <string name="initial_sync_start_importing_account_groups">Sākotnējā sinhronizācija: -\nImportē kopienas</string> <string name="initial_sync_start_importing_account_left_rooms">Sākotnējā sinhronizācija: \nImportē pamestās istabas</string> <string name="initial_sync_start_importing_account_invited_rooms">Sākotnējā sinhronizācija: @@ -279,7 +277,6 @@ <string name="notification_silent_notifications">Klusi paziņojumi</string> <string name="title_activity_bug_report">Kļūdas atskaite</string> <string name="loading">Ielādējas…</string> - <string name="groups_header">Kopienas</string> <string name="start_voice_call_prompt_msg">Tiešām vēlies uzsākt balss zvanu\?</string> <string name="start_video_call_prompt_msg">Tiešām vēlies uzsākt video zvanu\?</string> <string name="option_take_photo">Uzņemt foto</string> @@ -290,7 +287,6 @@ <string name="auth_forgot_password">Aizmirsāt paroli\?</string> <string name="auth_recaptcha_message">Mājasservers vēlas pārbaudīt, vai neesat robots</string> - <string name="auth_reset_password_missing_email">Ir jābūt ievadītai kontam piesaistītajai epasta adresei.</string> <string name="auth_reset_password_error_unauthorized">Neizdevās verificēt epasta adresi: pārbaudiet, vai esi noklikšķinājis(usi) uz saiti atsūtītajā epastā</string> <string name="login_error_invalid_home_server">Ievadi korektu URL adresi</string> @@ -699,7 +695,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.</string> <string name="fab_menu_create_chat">Tiešās ziņas</string> <string name="fab_menu_create_room">Istabas</string> <string name="room_preview_no_preview">Šo istabu nevar priekšskatīt</string> - <string name="group_all_communities">Visas kopienas</string> <string name="event_redacted">Ziņa ir dzēsta</string> <string name="reactions">Reaģēšana</string> <string name="title_activity_emoji_reaction_picker">Reaģēšana</string> diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index e7e57d9e21..20bc2d4354 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -403,8 +403,6 @@ <string name="event_status_sending_message">സന്ദേശം അയയ്ക്കുന്നു…</string> <string name="initial_sync_start_importing_account_data">പ്രാരംഭ സമന്വയം: \nഅക്കൗണ്ട് ഡാറ്റ ഇറക്കുമതി ചെയ്യുന്നു</string> - <string name="initial_sync_start_importing_account_groups">പ്രാരംഭ സമന്വയം: -\nജനസമൂഹങ്ങൾ ഇറക്കുമതി ചെയ്യുന്നു</string> <string name="initial_sync_start_importing_account_left_rooms">പ്രാരംഭ സമന്വയം: \nഉപേക്ഷിച്ച മുറികൾ ഇറക്കുമതി ചെയ്യുന്നു</string> <string name="initial_sync_start_importing_account_invited_rooms">പ്രാരംഭ സമന്വയം: @@ -474,7 +472,6 @@ <string name="call_camera_front">മുന്നിലുള്ള</string> <string name="call_camera_back">പിന്നിലുള്ള</string> <string name="sound_device_headset">ഹെഡ്സെറ്റ്</string> - <string name="groups_header">കമ്മ്യൂണിറ്റികൾ</string> <string name="dialog_title_error">പിശക്</string> <string name="action_disconnect">വിച്ഛേദിക്കുക</string> <string name="action_revoke">അസാധുവാക്കുക</string> diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 683961e590..031b380c7e 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -42,7 +42,6 @@ <string name="low_priority_header">Lavprioritet</string> <string name="no_result_placeholder">Ingen treff</string> <string name="rooms_header">Rom</string> - <string name="groups_header">Samfunn</string> <string name="send_bug_report">Meld fra om en bug</string> <string name="send_bug_report_progress">Fremgang (%s%%)</string> <string name="join_room">Bli med i rommet</string> @@ -524,7 +523,6 @@ <string name="event_redacted">Meldingen ble slettet</string> <string name="settings_show_redacted">Vis fjernede meldinger</string> <string name="settings_show_redacted_summary">Vis en stattholder for fjernede meldinger</string> - <string name="group_all_communities">Alle samfunn</string> <string name="settings_sdk_version">Matrix SDK-versjon</string> <string name="send_suggestion">Send inn et forslag</string> <string name="send_file_step_encrypting_file">Krypterer filen …</string> @@ -604,7 +602,6 @@ <string name="auth_accept_policies">Vennligst gjennomgå og godta retningslinjene til denne hjemmeserveren:</string> <string name="auth_reset_password_error_unauthorized">Klarte ikke verifisere e-postadressen: Pass på at du har klikket på lenken i e-posten</string> - <string name="auth_reset_password_missing_email">Du må skrive inn e-postadressen som er knyttet til din konto.</string> <string name="auth_recaptcha_message">Denne hjemmetjeneren vil vite om du er en robot</string> <string name="call_failed_no_connection_description">Klarte ikke å starte en sanntidskopling. diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index a79f5e7aee..513fb42fa1 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -52,8 +52,6 @@ \nUitgenodigde gesprekken worden geïmporteerd</string> <string name="initial_sync_start_importing_account_left_rooms">Initiële synchronisatie: \nVerlaten gesprekken worden geïmporteerd</string> - <string name="initial_sync_start_importing_account_groups">Initiële synchronisatie: -\nGemeenschappen worden geïmporteerd</string> <string name="initial_sync_start_importing_account_data">Initiële synchronisatie: \nAccountgegevens worden geïmporteerd</string> <string name="notice_room_update">%s heeft dit gesprek geüpgraded.</string> @@ -150,7 +148,6 @@ <string name="auth_email_already_defined">Dit e-mailadres is al in gebruik.</string> <string name="auth_forgot_password">Wachtwoord vergeten?</string> <string name="auth_recaptcha_message">Deze server wil graag weten of u geen robot bent</string> - <string name="auth_reset_password_missing_email">Het e-mailadres dat aan uw account gekoppeld is moet ingevoerd worden.</string> <string name="auth_reset_password_error_unauthorized">Verifiëren van het e-mailadres is mislukt: zorg dat u op de koppeling in de e-mail hebt geklikt</string> <string name="login_error_invalid_home_server">Voer een geldige URL in</string> <string name="login_error_bad_json">Ongeldige JSON</string> @@ -356,7 +353,6 @@ <string name="notification_noisy">Lawaaierig</string> <string name="encrypted_message">Versleuteld bericht</string> <string name="loading">Laden…</string> - <string name="groups_header">Gemeenschappen</string> <string name="send_bug_report_rage_shake">Schudden om een probleem te melden</string> <string name="start_voice_call_prompt_msg">Weet u zeker dat u een spraakoproep wilt beginnen\?</string> <string name="start_video_call_prompt_msg">Weet u zeker dat u een video-oproep wilt beginnen\?</string> @@ -783,7 +779,6 @@ <string name="action_change">Wijzigen</string> <string name="change_room_directory_network">Wijzig netwerk</string> <string name="please_wait">Even wachten…</string> - <string name="group_all_communities">Alle Gemeenschappen</string> <string name="room_preview_no_preview">Dit gesprek kan niet worden voorvertoond</string> <string name="fab_menu_create_room">Gesprekken</string> <string name="fab_menu_create_chat">Directe Berichten</string> @@ -2420,7 +2415,6 @@ <string name="ftue_profile_picture_title">Voeg een profielfoto toe</string> <string name="ftue_display_name_entry_footer">U kunt dit later wijzigen</string> <string name="ftue_display_name_entry_title">Weergavenaam</string> - <string name="ftue_display_name_subtitle">Dit wordt weergegeven wanneer u berichten verzendt.</string> <string name="ftue_display_name_title">Kies een weergavenaam</string> <string name="ftue_account_created_subtitle">Uw account %s is aangemaakt</string> <string name="ftue_account_created_congratulations_title">Gefeliciteerd!</string> @@ -2482,7 +2476,6 @@ <string name="a11y_presence_busy">Bezet</string> <string name="keys_backup_settings_signature_from_this_user">Back-up heeft een geldige handtekening van deze persoon.</string> <string name="live_location_bottom_sheet_last_updated_at">%1$s geleden bijgewerkt</string> - <string name="live_location_bottom_sheet_stop_sharing">Stop met delen</string> <string name="labs_enable_live_location_summary">Tijdelijke implementatie: locaties blijven bestaan in kamergeschiedenis</string> <string name="labs_enable_live_location">Live locatie delen inschakelen</string> <string name="location_share_live_remaining_time">%1$s te gaan</string> @@ -2603,4 +2596,4 @@ </plurals> <string name="search_space_two_parents">%1$s en %2$s</string> <string name="auth_reset_password_error_unverified">E-mailadres niet geverifieerd, controleer je inbox</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-nn/strings.xml b/vector/src/main/res/values-nn/strings.xml index c7d96037e2..a56ba0ac30 100644 --- a/vector/src/main/res/values-nn/strings.xml +++ b/vector/src/main/res/values-nn/strings.xml @@ -90,8 +90,6 @@ <string name="matrix_only_filter">Berre Matrix-kontaktar</string> <string name="no_result_placeholder">Ingen treff</string> <string name="rooms_header">Rom</string> - - <string name="groups_header">Fellesskap</string> <string name="send_bug_report_include_logs">Send loggar</string> <string name="send_bug_report_include_crash_logs">Send krasjrapportar</string> <string name="send_bug_report_include_screenshot">Send skjermbilde</string> @@ -131,7 +129,6 @@ <string name="auth_forgot_password">Gløymt passord\?</string> <string name="auth_recaptcha_message">Heimtenaren ynskjer å stadfeste at du ikkje er ein robot</string> - <string name="auth_reset_password_missing_email">Du må skriva inn e-postadressa som er knytt til brukaren din.</string> <string name="auth_reset_password_error_unauthorized">Fekk ikkje til å stadfesta e-postadressa: sjå til at du klikka på lenken i e-posten</string> <string name="login_error_invalid_home_server">Skriv inn ein gyldig URL</string> <string name="login_error_bad_json">Feilformatert JSON</string> diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 71669740db..ecc9316084 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -53,8 +53,6 @@ \nImportowanie zaproszonych pokojów</string> <string name="initial_sync_start_importing_account_left_rooms">Wstępna synchronizacja: \nImportowanie opuszczonych pokojów</string> - <string name="initial_sync_start_importing_account_groups">Wstępna synchronizacja: -\nImportowanie grup</string> <string name="initial_sync_start_importing_account_data">Wstępna synchronizacja: \nImportowanie danych Konta</string> <string name="event_status_sending_message">Wysyłanie wiadomości…</string> @@ -123,7 +121,6 @@ <string name="auth_email_already_defined">Ten adres e-mail został już użyty.</string> <string name="auth_forgot_password">Zapomniałeś(-aś) hasła?</string> <string name="auth_recaptcha_message">Serwer domowy prosi o potwierdzenie, że nie jesteś robotem</string> - <string name="auth_reset_password_missing_email">Musi zostać wprowadzony e-mail powiązany z kontem.</string> <string name="auth_reset_password_error_unauthorized">Nie udało się zweryfikować adresu e-mail: upewnij się, że kliknąłeś w odnośnik z wiadomości</string> <string name="login_error_invalid_home_server">Proszę wprowadzić prawidłowy adres URL</string> <string name="login_error_not_json">Nie zawiera prawidłowego JSON</string> @@ -268,7 +265,6 @@ <string name="notification_silent_notifications">Ciche powiadomienia</string> <string name="title_activity_bug_report">Zgłoś błąd</string> <string name="loading">Wczytywanie…</string> - <string name="groups_header">Społeczności</string> <string name="send_bug_report_rage_shake">Potrząśnij wściekle, aby zgłosić błąd</string> <string name="start_voice_call_prompt_msg">Czy jesteś pewien, że chcesz rozpocząć rozmowę głosową?</string> <string name="start_video_call_prompt_msg">Czy jesteś pewien, że chcesz rozpocząć wideorozmowę?</string> @@ -552,7 +548,6 @@ <string name="room_list_rooms_empty_title">Pokoje</string> <string name="message_add_reaction">Dodaj reakcję</string> <string name="create_new_room">Utwórz nowy pokój</string> - <string name="group_all_communities">Wszystkie społeczności</string> <string name="fab_menu_create_chat">Wiadomości Bezpośrednie</string> <string name="create_room_action_create">STWÓRZ</string> <string name="create_room_name_hint">Nazwa</string> @@ -2385,7 +2380,6 @@ <string name="ftue_display_name_entry_footer">Możesz zmienić ją później</string> <string name="room_message_autocomplete_notification">Powiadomienie pokoju</string> <string name="ftue_display_name_entry_title">Wyświetlana nazwa</string> - <string name="ftue_display_name_subtitle">Będzie ona widoczna podczas wysyłania wiadomości.</string> <string name="ftue_display_name_title">Wybierz wyświetlaną nazwę</string> <string name="ftue_account_created_subtitle">Twoje konto %s zostało utworzone</string> <string name="ftue_account_created_congratulations_title">Gratulacje!</string> @@ -2577,7 +2571,6 @@ <string name="live_location_bottom_sheet_last_updated_at">Zaktualizowano %1$s temu</string> <string name="location_share_live_remaining_time">%1$s pozostało</string> <string name="location_share_live_until">Na żywo do %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Przestań udostępniać</string> <string name="labs_enable_live_location_summary">Tymczasowa implementacja: lokalizacje pozostaną w historii pokoju</string> <string name="labs_enable_live_location">Włącz udostępnianie lokalizacji na żywo</string> <string name="location_share_live_view">Pokaż lokalizację na żywo</string> @@ -2693,4 +2686,4 @@ </plurals> <string name="search_space_two_parents">%1$s i %2$s</string> <string name="auth_reset_password_error_unverified">Email nie został zweryfikowany, sprawdź swoją skrzynkę</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 8e4e1942da..a148ea90cb 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -102,8 +102,6 @@ \nImportando salas convidadas</string> <string name="initial_sync_start_importing_account_left_rooms">Sinc inicial: \nImportando salas saídas</string> - <string name="initial_sync_start_importing_account_groups">Sinc inicial: -\nImportando comunidades</string> <string name="initial_sync_start_importing_account_data">Sinc inicial: \nImportando dados de conta</string> <string name="event_status_sending_message">Enviando mensagem…</string> @@ -293,7 +291,6 @@ <string name="auth_email_already_defined">Este endereço de email já está definido.</string> <string name="auth_forgot_password">Esqueceu senha\?</string> <string name="auth_recaptcha_message">Este servidorcasa gostaria de assegurar que você não é um robô</string> - <string name="auth_reset_password_missing_email">O endereço de email linkado a sua conta deve ser entrado.</string> <string name="auth_reset_password_error_unauthorized">Falha para verificar endereço de email: assegure-se que clicou no link no email</string> <string name="login_error_invalid_home_server">Por favor entre um URL válido</string> <string name="login_error_bad_json">JSON malformado</string> @@ -472,7 +469,6 @@ <string name="notification_silent_notifications">Notificações silenciosas</string> <string name="title_activity_bug_report">Reporte de bug</string> <string name="loading">Carregando…</string> - <string name="groups_header">Comunidades</string> <string name="start_voice_call_prompt_msg">Você tem certeza que você quer começar uma chamada de voz\?</string> <string name="start_video_call_prompt_msg">Você tem certeza que você quer começar uma chamada de vídeo\?</string> <string name="option_take_photo">Tirar foto</string> @@ -992,7 +988,6 @@ <string name="action_change">Mudar</string> <string name="change_room_directory_network">Mudar rede</string> <string name="please_wait">Por favor espere…</string> - <string name="group_all_communities">Todas as Comunidades</string> <string name="room_preview_no_preview">Esta sala não pode ser previsualizada</string> <string name="fab_menu_create_room">Salas</string> <string name="fab_menu_create_chat">Mensagens Diretas</string> @@ -2432,7 +2427,6 @@ <string name="ftue_profile_picture_title">Adicione uma imagem de perfil</string> <string name="ftue_display_name_entry_footer">Você pode mudar isto mais tarde</string> <string name="ftue_display_name_entry_title">Nome de Exibição</string> - <string name="ftue_display_name_subtitle">Isto vai ser mostrado quando você enviar mensagens.</string> <string name="ftue_display_name_title">Escolha um nome de exibição</string> <string name="ftue_account_created_subtitle">Sua conta %s tem sido criada</string> <string name="ftue_account_created_congratulations_title">Parabéns!</string> @@ -2482,7 +2476,6 @@ <string name="a11y_presence_busy">Ocupada(o)</string> <string name="keys_backup_settings_signature_from_this_user">Backup tem uma assinatura válida desta(e) usuária(o).</string> <string name="live_location_bottom_sheet_last_updated_at">Atualizada %1$s atrás</string> - <string name="live_location_bottom_sheet_stop_sharing">Parar de compartilhar</string> <string name="labs_enable_live_location_summary">Implementação tempoária: locais persistem em histórico de sala</string> <string name="labs_enable_live_location">Habilitar Compartilhament de Localização Ao Vivo</string> <string name="location_share_live_remaining_time">%1$s restando</string> @@ -2603,4 +2596,8 @@ </plurals> <string name="search_space_two_parents">%1$s e %2$s</string> <string name="auth_reset_password_error_unverified">Email não verificado, cheque sua inbox</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Incapaz de carregar mapa +\nEste servidor casa pode não estar configurado para exibir mapas.</string> + <string name="a11y_open_settings">Abrir configurações</string> + <string name="all_chats">Todos os Chats</string> +</resources> diff --git a/vector/src/main/res/values-pt/strings.xml b/vector/src/main/res/values-pt/strings.xml index c5f6329f3d..4daaef83b0 100644 --- a/vector/src/main/res/values-pt/strings.xml +++ b/vector/src/main/res/values-pt/strings.xml @@ -107,7 +107,6 @@ <string name="auth_forgot_password">Esqueceu-se da palavra-passe?</string> <string name="auth_recaptcha_message">Este servidor quer ter a certeza de que você não é um robô</string> - <string name="auth_reset_password_missing_email">O endereço de e-mail associado à sua conta tem que ser introduzido.</string> <string name="auth_reset_password_error_unauthorized">Falha ao verificar o endereço de e-mail: verifique se clicou no link no e-mail</string> <string name="login_error_invalid_home_server">Introduza um URL válido</string> @@ -316,7 +315,6 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.< <string name="action_download">Descarregar</string> <string name="dialog_title_error">Erro</string> <string name="system_alerts_header">Alertas de Sistema</string> - <string name="groups_header">Comunidades</string> <string name="send_bug_report_description_in_english">Se possível, escrever a descrição em inglês.</string> <string name="send_bug_report_rage_shake">Agitar furiosamente para relatar um erro</string> <string name="option_send_voice">Enviar voz</string> diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index 09a5863b77..01e4761983 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -267,8 +267,6 @@ <string name="event_status_sent_message">Mesaj trimis</string> <string name="initial_sync_start_importing_account_data">Sincronizare inițială: \nSe importă datele contului</string> - <string name="initial_sync_start_importing_account_groups">Sincronizare inițială: -\nSe importă comunitățile</string> <string name="initial_sync_start_importing_account_left_rooms">Sincronizare inițială: \nSe importă camerele pe care le-ați părăsit</string> <string name="initial_sync_start_importing_account_invited_rooms">Sincronizare inițială: @@ -403,4 +401,4 @@ <string name="notice_room_join_with_reason">%1$s s-a alăturat camerei. Motivul: %2$s</string> <string name="notice_room_invite_you_with_reason">%1$s te-a invitat. Motivul: %2$s</string> <string name="notice_room_invite_with_reason">%1$s la invitat pe %2$s. Motivul este: %3$s</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 1fd522c7d5..01ce2f1bf6 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -52,10 +52,8 @@ \nИмпорт приглашенных комнат</string> <string name="initial_sync_start_importing_account_left_rooms">Начальная синхронизация: \nИмпорт покинутых комнат</string> - <string name="initial_sync_start_importing_account_groups">Начальная синхронизация: -\nИмпорт сообществ</string> <string name="initial_sync_start_importing_account_data">Начальная синхронизация: -\nИмпорт данных учетной записи</string> +\nИмпорт данных учётной записи</string> <string name="notice_room_update">%s обновил эту комнату.</string> <string name="event_status_sending_message">Отправка сообщения…</string> <string name="notice_room_third_party_revoked_invite">%1$s отозвал приглашение %2$s присоединиться к комнате</string> @@ -194,7 +192,7 @@ <string name="room_displayname_3_members">%1$s, %2$s и %3$s</string> <string name="notice_room_server_acl_allow_is_empty">🎉 Всем серверам запрещено участвовать! Эта комната больше не может быть использована.</string> <string name="notice_room_server_acl_updated_no_change">Без изменений.</string> - <string name="room_displayname_empty_room_was">Пустая комната (был(а) %s)</string> + <string name="room_displayname_empty_room_was">Пустая комната (без %s)</string> <string name="notice_room_server_acl_set_banned">• Соответствующие серверы %s заблокированы.</string> <string name="notice_room_server_acl_updated_ip_literals_not_allowed">• Серверы, соответствующие буквальным IP-адресам, теперь запрещены.</string> <string name="notice_room_server_acl_updated_ip_literals_allowed">• Серверы, соответствующие буквальным IP-адресам, теперь разрешены.</string> @@ -290,7 +288,7 @@ <string name="send_bug_report_sent">Отчет об ошибке успешно отправлен</string> <string name="send_bug_report_failed">Сбой отправки отчета об ошибке (%s)</string> <string name="send_bug_report_progress">Прогресс (%s%%)</string> - <string name="send_bug_report_app_crashed">В прошлый раз приложение некорректно завершило работу. Хотите отправить отчет о сбое?</string> + <string name="send_bug_report_app_crashed">В прошлый раз приложение некорректно завершило работу. Хотите отправить отчёт о сбое\?</string> <string name="join_room">Войти в Комнату</string> <string name="username">Имя пользователя</string> <string name="logout">Выйти</string> @@ -307,7 +305,6 @@ <string name="auth_email_already_defined">Этот адрес электронной почты уже используется.</string> <string name="auth_forgot_password">Забыли пароль?</string> <string name="auth_recaptcha_message">Этот домашний сервер хочет убедиться, что вы не робот</string> - <string name="auth_reset_password_missing_email">Должен быть введен адрес электронной почты привязанный к учетной записи.</string> <string name="auth_reset_password_error_unauthorized">Не удалось проверить адрес электронной почты: убедитесь, что вы перешли по ссылке из сообщения</string> <string name="login_error_invalid_home_server">Пожалуйста, введите корректный URL</string> <string name="login_error_bad_json">Неверный формат JSON</string> @@ -513,7 +510,6 @@ <string name="notification_noisy">Громко</string> <string name="encrypted_message">Зашифрованное сообщение</string> <string name="loading">Загрузка…</string> - <string name="groups_header">Сообщества</string> <string name="start_voice_call_prompt_msg">Вы уверены, что хотите начать голосовой вызов?</string> <string name="start_video_call_prompt_msg">Вы уверены, что хотите начать видеовызов?</string> <string name="room_participants_ban_prompt_msg">Блокировка пользователя удалит его из этой комнаты и не позволит ему присоединиться вновь.</string> @@ -916,7 +912,6 @@ <string name="action_change">Изменить</string> <string name="change_room_directory_network">Изменить сеть</string> <string name="please_wait">Пожалуйста, подождите…</string> - <string name="group_all_communities">Все сообщества</string> <string name="room_preview_no_preview">Эту комнату нельзя предварительно просмотреть</string> <string name="fab_menu_create_room">Комнаты</string> <string name="fab_menu_create_chat">Личные сообщения</string> @@ -960,7 +955,7 @@ <string name="message_edits">Изменение сообщения</string> <string name="no_message_edits_found">Изменения не найдены</string> <string name="room_filtering_filter_hint">Отфильтровать беседы…</string> - <string name="room_filtering_footer_title">Не можете найти то, что ищете\?</string> + <string name="room_filtering_footer_title">Не можете найти нужное\?</string> <string name="room_filtering_footer_create_new_room">Создать новую комнату</string> <string name="room_filtering_footer_create_new_direct_message">Отправить новое личное сообщение</string> <string name="room_filtering_footer_open_room_directory">Просмотр каталога комнат</string> @@ -1743,10 +1738,10 @@ <string name="settings_security_application_protection_summary">Защитите доступ с помощью PIN-кода и биометрии.</string> <string name="settings_security_application_protection_title">Защита доступа</string> <string name="secure_backup_reset_no_history">Вы перезапустите приложение без истории, сообщений, доверенных устройств или доверенных пользователей</string> - <string name="secure_backup_reset_if_you_reset_all">Если сбросить все</string> + <string name="secure_backup_reset_if_you_reset_all">Если сбросить всё</string> <string name="secure_backup_reset_all_no_other_devices">Делайте это только в том случае, если у вас нет другого устройства, с которого вы можете проверить это устройство.</string> - <string name="secure_backup_reset_all">Сбросить все</string> - <string name="bad_passphrase_key_reset_all_action">Забыли или потеряли все варианты восстановления\? Сбросить все</string> + <string name="secure_backup_reset_all">Сбросить всё</string> + <string name="bad_passphrase_key_reset_all_action">Забыли или потеряли все варианты восстановления\? Сбросить всё</string> <string name="direct_room_created_summary_item_by_you">Вы вошли.</string> <string name="direct_room_created_summary_item">%s вошёл(ла).</string> <string name="direct_room_encryption_enabled_tile_description">Сообщения в этой переписке защищены сквозным шифрованием.</string> @@ -2235,7 +2230,7 @@ <string name="call_tile_video_incoming">Входящий видеовызов</string> <string name="call_tile_voice_incoming">Входящий голосовой вызов</string> <string name="call_tile_you_declined_this_call">Вы отклонили этот вызов</string> - <string name="room_settings_room_notifications_account_settings">Настройки учетной записи</string> + <string name="room_settings_room_notifications_account_settings">Настройки учётной записи</string> <string name="room_settings_room_notifications_manage_notifications">Вы можете управлять уведомлениями в %1$s.</string> <string name="room_settings_room_notifications_encryption_notice">Обратите внимание, что уведомления об упоминаниях и ключевых словах недоступны в зашифрованных комнатах на мобильных устройствах.</string> <string name="room_settings_room_notifications_notify_me">Уведомлять меня о</string> @@ -2449,7 +2444,7 @@ <string name="ftue_auth_use_case_option_two">Команды</string> <string name="ftue_auth_use_case_option_one">Друзья и семья</string> <string name="ftue_auth_use_case_subtitle">Мы поможем вам подключиться</string> - <string name="ftue_auth_use_case_title">С кем вы будете общаться больше всего\?</string> + <string name="ftue_auth_use_case_title">С кем вы будете общаться чаще всего\?</string> <string name="navigate_to_thread_when_already_in_the_thread">Вы уже просматриваете это обсуждение!</string> <string name="view_in_room">Просмотр в Комнате</string> <string name="reply_in_thread">Обсудить</string> @@ -2487,7 +2482,6 @@ <string name="ftue_profile_picture_title">Добавить аватар</string> <string name="ftue_display_name_entry_footer">Вы сможете изменить это позже</string> <string name="ftue_display_name_entry_title">Отображаемое имя</string> - <string name="ftue_display_name_subtitle">Показывается, когда вы отправляете сообщения.</string> <string name="ftue_display_name_title">Выберите отображаемое имя</string> <string name="ftue_account_created_subtitle">Ваша учётная запись %s создана</string> <string name="ftue_account_created_congratulations_title">Поздравляем!</string> @@ -2513,7 +2507,6 @@ <string name="live_location_sharing_notification_description">Идёт отправка местоположения</string> <string name="location_share_live_remaining_time">Осталось %1$s</string> <string name="live_location_bottom_sheet_last_updated_at">Обновлено %1$s назад</string> - <string name="live_location_bottom_sheet_stop_sharing">Остановить трансляцию</string> <string name="labs_enable_live_location">Включить функцию \"Поделиться трансляцией местоположения\"</string> <string name="live_location_sharing_notification_title">${app_name} Трансляция местоположения</string> <string name="location_share_live_until">Транслировать до %1$s</string> @@ -2661,6 +2654,10 @@ <string name="ftue_auth_choose_server_entry_hint">URL-адрес сервера</string> <string name="ftue_auth_welcome_back_title">С возвращением!</string> <string name="ftue_auth_create_account_sso_section_header">Или</string> - <string name="ftue_auth_create_account_title">Создать учётную запись</string> + <string name="ftue_auth_create_account_title">Создайте учётную запись</string> <string name="search_space_two_parents">%1$s и %2$s</string> -</resources> \ No newline at end of file + <string name="a11y_open_settings">Открыть настройки</string> + <string name="location_share_loading_map_error">Не удалось загрузить карту +\nВозможно, этот домашний сервер не настроен для отображения карт.</string> + <string name="all_chats">Все Беседы</string> +</resources> diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index cac6187d8a..cb2d371ccc 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -53,8 +53,6 @@ \nImport pozvaných miestností</string> <string name="initial_sync_start_importing_account_left_rooms">Úvodná synchronizácia: \nPrebieha import opustených miestností</string> - <string name="initial_sync_start_importing_account_groups">Úvodná synchronizácia: -\nPrebieha import komunít</string> <string name="initial_sync_start_importing_account_data">Úvodná synchronizácia: \nPrebieha import údajov účtu</string> <string name="event_status_sending_message">Odosielanie správy…</string> @@ -246,7 +244,6 @@ <string name="auth_email_already_defined">Táto emailová adresa sa už používa.</string> <string name="auth_forgot_password">Zabudli ste heslo?</string> <string name="auth_recaptcha_message">Tento domovský server by sa rád uistil, že nieste robot</string> - <string name="auth_reset_password_missing_email">Musíte zadať emailovú adresu prepojenú s vašim účtom.</string> <string name="auth_reset_password_error_unauthorized">Nepodarilo sa overiť emailovú adresu: Uistite sa, že ste správne klikli na odkaz v emailovej správe</string> <string name="login_error_invalid_home_server">Zadajte platnú adresu URL</string> <string name="login_error_bad_json">Chybné údaje vo formáte JSON</string> @@ -448,7 +445,6 @@ <string name="notification_noisy">Hlasné</string> <string name="encrypted_message">Šifrovaná správa</string> <string name="loading">Načítavanie…</string> - <string name="groups_header">Komunity</string> <string name="start_voice_call_prompt_msg">Ste si istý, že chcete začať hlasový hovor?</string> <string name="start_video_call_prompt_msg">Ste si istí, že chcete začať video hovor?</string> <string name="room_participants_ban_prompt_msg">Zakázanie používateľa ho odstráni z tejto miestnosti a zabráni mu v ďalšom vstupe.</string> @@ -972,7 +968,6 @@ <string name="action_change">Zmeniť</string> <string name="change_room_directory_network">Zmeniť sieť</string> <string name="please_wait">Prosím čakajte…</string> - <string name="group_all_communities">Všetky komunity</string> <string name="room_preview_no_preview">Nie je možné zobraziť náhľad tejto miestnosti</string> <string name="fab_menu_create_room">Miestnosti</string> <string name="fab_menu_create_chat">Priame konverzácie</string> @@ -2480,7 +2475,6 @@ <string name="ftue_profile_picture_title">Pridať profilový obrázok</string> <string name="ftue_display_name_entry_footer">Neskôr to môžete zmeniť</string> <string name="ftue_display_name_entry_title">Zobrazované meno</string> - <string name="ftue_display_name_subtitle">Toto sa zobrazí pri odosielaní správ.</string> <string name="ftue_display_name_title">Vyberte si zobrazované meno</string> <string name="ftue_account_created_congratulations_title">Gratulujeme!</string> <string name="ftue_account_created_take_me_home">Zober ma domov</string> @@ -2529,7 +2523,6 @@ <string name="a11y_presence_busy">Obsadený/zaneprázdnený</string> <string name="keys_backup_settings_signature_from_this_user">Zálohovanie má platný podpis od tohto používateľa.</string> <string name="live_location_bottom_sheet_last_updated_at">Aktualizované pred %1$s</string> - <string name="live_location_bottom_sheet_stop_sharing">Zastaviť zdieľanie</string> <string name="labs_enable_live_location_summary">Dočasná implementácia: polohy pretrvávajú v histórii miestnosti</string> <string name="labs_enable_live_location">Povoliť zdieľanie polohy v reálnom čase</string> <string name="location_share_live_remaining_time">ostáva %1$s</string> @@ -2653,4 +2646,8 @@ </plurals> <string name="search_space_two_parents">%1$s a %2$s</string> <string name="auth_reset_password_error_unverified">E-mail nie je overený, skontrolujte si schránku</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Nie je možné načítať mapu +\nTento domovský server nemusí byť nakonfigurovaný na zobrazovanie máp.</string> + <string name="a11y_open_settings">Otvoriť nastavenia</string> + <string name="all_chats">Všetky konverzácie</string> +</resources> diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 80b6b27793..8fdf4ee310 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -53,8 +53,6 @@ \nPo importohen dhoma me ftesë</string> <string name="initial_sync_start_importing_account_left_rooms">Njëkohësimi fillestar: \nPo importohen dhoma të braktisura</string> - <string name="initial_sync_start_importing_account_groups">Njëkohësimi fillestar: -\nPo importohen bashkësi</string> <string name="initial_sync_start_importing_account_data">Njëkohësimi fillestar: \nPo importohet të dhëna llogarie</string> <string name="event_status_sending_message">Po dërgohet mesazh…</string> @@ -266,7 +264,6 @@ <string name="matrix_only_filter">Vetëm kontakte të Matrix-it</string> <string name="no_result_placeholder">S’ka përfundime</string> <string name="rooms_header">Dhoma</string> - <string name="groups_header">Bashkësi</string> <string name="send_bug_report_include_logs">Dërgo regjistra</string> <string name="send_bug_report_include_crash_logs">Dërgoni regjistrime vithisjesh</string> <string name="send_bug_report_include_screenshot">Dërgo foto ekrani</string> @@ -293,7 +290,6 @@ <string name="auth_email_already_defined">Kjo adresë email është e përkufizuar tashmë.</string> <string name="auth_forgot_password">Harruat fjalëkalimin?</string> <string name="auth_recaptcha_message">Ky shërbyes Home do të donte të sigurohej se s’jeni robot</string> - <string name="auth_reset_password_missing_email">Duhet dhënë adresa email e lidhur me llogarinë tuaj.</string> <string name="login_error_invalid_home_server">Ju lutemi, jepni një URL të vlefshme</string> <string name="login_error_bad_json">JSON e keqformuar</string> <string name="login_error_not_json">S’përmbante JSON të vlefshëm</string> @@ -871,7 +867,6 @@ <string name="action_change">Ndryshoje</string> <string name="change_room_directory_network">Ndryshoni rrjetin</string> <string name="please_wait">Ju lutemi, pritni…</string> - <string name="group_all_communities">Krejt Bashkësitë</string> <string name="room_preview_no_preview">Kjo dhomë s’mund të parashihet</string> <string name="fab_menu_create_room">Dhoma</string> <string name="fab_menu_create_chat">Mesazhe të Drejtpërdrejtë</string> @@ -2423,7 +2418,6 @@ <string name="ftue_profile_picture_title">Shtoni një foto profili</string> <string name="ftue_display_name_entry_footer">Këtë mund ta ndryshoni më vonë</string> <string name="ftue_display_name_entry_title">Emër Në Ekran</string> - <string name="ftue_display_name_subtitle">Kjo do të shfaqet kur dërgoni mesazhe.</string> <string name="ftue_display_name_title">Zgjidhni një emër për në ekran</string> <string name="ftue_account_created_subtitle">Llogaria juaj %s u krijua.</string> <string name="ftue_account_created_congratulations_title">Përgëzime!</string> diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml index 69265899af..fcaf45aaa3 100644 --- a/vector/src/main/res/values-sr/strings.xml +++ b/vector/src/main/res/values-sr/strings.xml @@ -128,8 +128,6 @@ <string name="notice_room_invite_no_invitee_with_reason">%1$s - позивница. Разлог: %2$s</string> <string name="initial_sync_start_importing_account_data">Почетна синхронизација: \nувозим податке о налогу</string> - <string name="initial_sync_start_importing_account_groups">Почетна синхронизација: -\nувозим заједнице</string> <string name="initial_sync_start_importing_account_left_rooms">Почетна синхронизација: \nувозим напуштене собе</string> <string name="initial_sync_start_importing_account_invited_rooms">Почетна синхронизација: @@ -326,8 +324,6 @@ <string name="send_bug_report_include_screenshot">Шаљи снимак екрана</string> <string name="send_bug_report_include_crash_logs">Слање записника грешке</string> <string name="send_bug_report_include_logs">Слање записника</string> - <string name="groups_header">Заједнице</string> - <string name="no_more_results">Нема више резултата</string> <string name="system_alerts_header">Системска упозорења</string> <string name="bottom_action_notification">Обавештења</string> @@ -386,7 +382,6 @@ <string name="send_bug_report_include_key_share_history">Укључите историју размене кључева</string> <string name="audio_video_meeting_description">Састанци користе Jitsi безбедносне и допунске политике. Сви људи тренутно у соби видеће позив да се придруже састанку.</string> <string name="auth_reset_password_error_unauthorized">Није успело верификовати имејл адресу: обавезно кликните на линк из примљеног имејла</string> - <string name="auth_reset_password_missing_email">Мора се унети имејл адреса коришћена са вашим налогом.</string> <string name="auth_recaptcha_message">Овај кућни сервер жели да се увери да нисте робот</string> <string name="auth_forgot_password">Заборавили сте лозинку\?</string> <string name="auth_msisdn_already_defined">Овај број телефона је већ коришћен.</string> @@ -505,4 +500,4 @@ <string name="permissions_rationale_popup_title">Информација</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index f797974e87..025713272c 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -102,8 +102,6 @@ \nImporterar inbjudna rum</string> <string name="initial_sync_start_importing_account_left_rooms">Inledande synk: \nImporterar lämnade rum</string> - <string name="initial_sync_start_importing_account_groups">Inledande synk: -\nImporterar gemenskaper</string> <string name="initial_sync_start_importing_account_data">Inledande synk: \nImporterar kontodata</string> <string name="event_status_sending_message">Skickar meddelande…</string> @@ -294,7 +292,6 @@ <string name="matrix_only_filter">Bara Matrix-kontakter</string> <string name="no_result_placeholder">Inga resultat</string> <string name="rooms_header">Rum</string> - <string name="groups_header">Gemenskaper</string> <string name="send_bug_report_include_logs">Skicka loggar</string> <string name="send_bug_report_include_crash_logs">Skicka kraschloggar</string> <string name="send_bug_report_include_screenshot">Skicka skärmdump</string> @@ -336,7 +333,6 @@ <string name="auth_email_already_defined">Den här e-postadressen är redan definierad.</string> <string name="auth_forgot_password">Glömt lösenordet\?</string> <string name="auth_recaptcha_message">Denna hemserver skulle vilja verifiera att du inte är en robot</string> - <string name="auth_reset_password_missing_email">Du måste skriva in e-postadressen länkad till ditt konto.</string> <string name="auth_reset_password_error_unauthorized">Misslyckades att verifiera e-postadressen: se till att du klickade på länken i e-brevet</string> <string name="auth_accept_policies">Vänligen granska och acceptera villkoren för denna hemserver:</string> <string name="login_error_invalid_home_server">Vänligen skriv in en giltig URL</string> @@ -454,7 +450,6 @@ \nLagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp)</string> <string name="keys_backup_setup_step3_text_line2_no_passphrase">Lagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp)</string> <string name="change_room_directory_network">Byt nätverk</string> - <string name="group_all_communities">Alla gemenskaper</string> <string name="settings_general_title">Allmänt</string> <string name="settings_preferences">Alternativ</string> <string name="room_directory_search_hint">Namn eller ID (#example:matrix.org)</string> @@ -2432,7 +2427,6 @@ <string name="ftue_profile_picture_title">Lägg till en profilbild</string> <string name="ftue_display_name_entry_footer">Du kan ändra detta senare</string> <string name="ftue_display_name_entry_title">Visningsnamn</string> - <string name="ftue_display_name_subtitle">Det här kommer att visas när du skickar meddelanden.</string> <string name="ftue_display_name_title">Välj ett visningsnamn</string> <string name="ftue_account_created_subtitle">Ditt konto %s har skapats</string> <string name="ftue_account_created_congratulations_title">Grattis!</string> @@ -2482,7 +2476,6 @@ <string name="a11y_presence_busy">Upptagen</string> <string name="keys_backup_settings_signature_from_this_user">Säkerhetskopian har en giltig signatur från den här användaren.</string> <string name="live_location_bottom_sheet_last_updated_at">Uppdaterades för %1$s sen</string> - <string name="live_location_bottom_sheet_stop_sharing">Sluta dela</string> <string name="labs_enable_live_location_summary">Temporär implementation: platser ligger kvar i rumshistoriken</string> <string name="labs_enable_live_location">Aktivera platsdelning i realtid</string> <string name="location_share_live_remaining_time">%1$s kvar</string> @@ -2588,4 +2581,4 @@ <string name="font_size_section_manually">Välj manuellt</string> <string name="font_size_section_auto">Ställ in automatiskt</string> <string name="font_size_title">Välj teckenstorlek</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-te/strings.xml b/vector/src/main/res/values-te/strings.xml index b53d568b2b..5ed2462ce8 100644 --- a/vector/src/main/res/values-te/strings.xml +++ b/vector/src/main/res/values-te/strings.xml @@ -125,7 +125,6 @@ <string name="auth_invalid_email">ఇది చెల్లుబాటు అయ్యే ఇమెయిల్ చిరునామా లాగా లేదు</string> <string name="auth_email_already_defined">ఈ ఇమెయిల్ చిరునామా ఇప్పటికే నిర్వచించబడింది.</string> <string name="auth_recaptcha_message">ఈ హోమ్ సర్వర్ మీరు రోబోట్ కాదని నిర్ధారించుకోవాలనుకుంటుంది</string> - <string name="auth_reset_password_missing_email">మీ ఖాతాకు లంకె చేయబడిన ఇమెయిల్ చిరునామా తప్పక ఇవ్వాలి.</string> <string name="auth_reset_password_error_unauthorized">ఇమెయిల్ చిరునామాను ధృవీకరించడంలో విఫలమైంది: మీరు ఇమెయిల్ లో లంకెను క్లిక్ చేసారా లేదా అని నిర్ధారించుకోండి</string> <string name="login_error_invalid_home_server">చెల్లుబాటు అయ్యే URL ను ఇవ్వండి</string> <string name="login_error_bad_json">తప్పు JSON</string> diff --git a/vector/src/main/res/values-th/strings.xml b/vector/src/main/res/values-th/strings.xml index b1e69355b1..d189471cea 100644 --- a/vector/src/main/res/values-th/strings.xml +++ b/vector/src/main/res/values-th/strings.xml @@ -27,8 +27,6 @@ <string name="invitations_header">คำเชิญ</string> <string name="system_alerts_header">การแจ้งเตือนจากระบบ</string> <string name="no_result_placeholder">ไม่มีผลลัพธ์</string> - - <string name="groups_header">ชุมชน</string> <string name="send_bug_report_description_in_english">ถ้าเป็นไปได้ โปรดเขียนคำอธิบายเป็นภาษาอังกฤษ</string> <string name="send_bug_report_placeholder">อธิบายปัญหาของคุณที่นี่</string> <string name="join_room">เข้าร่วมห้อง</string> @@ -190,4 +188,4 @@ <string name="dialog_edit_hint">ค่าใหม่</string> <string name="action_switch">สลับ</string> <string name="settings_category_room_directory">ไดเรกทอรีห้อง</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-tlh/strings.xml b/vector/src/main/res/values-tlh/strings.xml index 793c4c32e7..9e9f7ed4e8 100644 --- a/vector/src/main/res/values-tlh/strings.xml +++ b/vector/src/main/res/values-tlh/strings.xml @@ -52,10 +52,6 @@ <string name="no_result_placeholder">gher\'ID tu\'lu\'be\'</string> <string name="rooms_header">pa\'mey</string> - - - <string name="groups_header">tuqmey</string> - <string name="send_bug_report_include_logs">QonoS ngeH</string> <string name="send_bug_report_include_crash_logs">vonlu\' QonoS ngeH</string> <string name="send_bug_report_include_screenshot">jIH mIllogh ngeH</string> diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index b3c2895b9a..c097bfce6a 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -26,7 +26,6 @@ <string name="action_delete">Sil</string> <string name="action_rename">Yeniden Adlandır</string> <string name="report_content">Rapor İçeriği</string> - <string name="or">veya</string> <string name="action_invite">Davet et</string> <string name="action_sign_out">Oturumu kapat</string> @@ -49,8 +48,6 @@ <string name="matrix_only_filter">Sadece Matrix kullanıcıları</string> <string name="no_result_placeholder">Sonuç bulunamadı</string> <string name="rooms_header">Odalar</string> - - <string name="groups_header">Topluluk</string> <string name="send_bug_report_include_logs">Sistem günlüğünü (log) gönder</string> <string name="send_bug_report_include_crash_logs">Çökme günlüğünü (crash log) gönder</string> <string name="send_bug_report_include_screenshot">Ekran görüntüsünü gönder</string> @@ -106,7 +103,6 @@ <string name="keys_backup_banner_update_line1">Yeni şifreli mesaj anahtarları</string> <string name="keys_backup_banner_update_line2">Anahtar Yedekleme\'yi Yönet</string> <string name="keys_backup_banner_recover_line1">Şifrelenmiş mesajları asla kaybetmeyin</string> - <string name="keys_backup_settings_deleting_backup">Yedek siliniyor…</string> <string name="keys_backup_settings_delete_confirm_title">Yedeği Sil</string> <string name="keys_backup_settings_delete_confirm_message">Yedeklenmiş şifreleme anahtarlarınız sunucudan silinsin mi? Bundan sonra şifrelenmiş mesaj geçmişini okumak için kurtarma anahtarınızı kullanamayacaksınız.</string> @@ -117,11 +113,8 @@ <string name="auth_invalid_email">Bu, geçerli bir e-posta adresi gibi gözükmüyor</string> <string name="auth_email_already_defined">Bu e-posta adresi zaten tanımlanmış.</string> <string name="auth_forgot_password">Şifreni mi unuttun\?</string> - <string name="auth_recaptcha_message">Bu ana sunucu bir robot olmadığından emin olmak istiyor</string> - <string name="auth_reset_password_missing_email">Hesaba bağlı e-posta adresi mutlaka girilmeli.</string> <string name="auth_reset_password_error_unauthorized">E-posta doğrulaması başarısız: e-postanızdaki bağlantıya tıkladığınızdan emin olun</string> - <string name="auth_accept_policies">Lütfen ana sunucunun ilkelerini gözden geçirin ve kabul edin:</string> <string name="login_error_invalid_home_server">Lütfen geçerli bir URL girin</string> <string name="login_error_bad_json">Hatalı JSON</string> @@ -150,14 +143,10 @@ <string name="video_call_in_progress">Görüntülü Çağrıda…</string> <string name="call_error_user_not_responding">Uzak sunucu alınamadı.</string> <string name="permissions_rationale_popup_title">Bilgilendirme</string> - - <string name="permissions_rationale_msg_record_audio">${app_name}\'in sesli arama yapması için mikrofonunuza erişmeye ihtiyacı var.</string> - <string name="permissions_rationale_msg_camera_and_audio">${app_name}\'in görüntülü arama yapması için kameranıza ve mikrofonunuza erişmeye ihtiyacı var. \n \nLütfen çıkacak ekranda kamera ve mikrofon erişimine izin verin.</string> - <string name="yes">EVET</string> <string name="no">HAYIR</string> <string name="_continue">Devam et</string> @@ -165,16 +154,10 @@ <string name="action_join">Katıl</string> <string name="action_reject">Reddet</string> <string name="list_members">Üyeleri Listele</string> - - <plurals name="room_title_members"> <item quantity="one">%d üye</item> <item quantity="other">%d adet üye</item> </plurals> - - - - <string name="room_participants_leave_prompt_title">Odadan ayrıl</string> <string name="room_participants_leave_prompt_msg">Odadan ayrılmak istediğine emin misin\?</string> <string name="room_participants_header_direct_chats">DİREKT SOHBETLER</string> @@ -211,12 +194,9 @@ <item quantity="one">%d adet seçili</item> <item quantity="other">%d adet seçili</item> </plurals> - <string name="search_hint">Bul</string> <string name="search_members_hint">Oda üyelerini filtrele</string> <string name="search_no_results">Sonuç bulunamadı</string> - - <string name="room_settings_all_messages">Tüm mesajlar</string> <string name="room_settings_add_homescreen_shortcut">Ana ekrana ekle</string> <string name="settings_profile_picture">Profil Resmi</string> @@ -347,7 +327,6 @@ <string name="settings_preview_media_before_sending">Medyayı göndermeden önce önizleme göster</string> <string name="settings_deactivate_account_section">Hesabı devre dışı bırak</string> <string name="settings_deactivate_my_account">Hesabımı devre dışı bırak</string> - <string name="settings_analytics">Analitik</string> <string name="settings_opt_in_of_analytics">Analitik verilerini gönder</string> <string name="settings_opt_in_of_analytics_summary">${app_name} uygulamayı geliştirmemiz için anonim analitik veriler toplar.</string> @@ -356,7 +335,6 @@ <string name="devices_details_device_name">Görünür Adı Güncelle</string> <string name="devices_details_last_seen_title">Son görülme</string> <string name="devices_details_last_seen_format">%1$s @ %2$s</string> - <string name="devices_delete_dialog_title">Yetkilendirme</string> <string name="settings_logged_in">Olarak giriş yaptın</string> <string name="settings_home_server">Ana Sunucu</string> @@ -395,7 +373,6 @@ <string name="room_settings_labs_warning_message">Bu deneysel özellikler beklenmeyen şekilde hata verebilir. Dikkatli kullanın.</string> <string name="room_settings_set_main_address">Ana adres olarak ayarla</string> <string name="room_settings_unset_main_address">Ana adres olarak ayarlanmadı</string> - <string name="settings_theme">Tema</string> <string name="encryption_information_decryption_error">Çözme hatası</string> <string name="encryption_information_device_name">Görünür Ad</string> @@ -406,7 +383,6 @@ <string name="encryption_export_room_keys_summary">Anahtarları yerel dosyaya aktar</string> <string name="encryption_export_export">Dışa aktar</string> <string name="encryption_export_notice">Lütfen dışa aktarılmış anahtarları şifrelemek için parola oluşturun. Tekrar içe aktarmak için aynı parolayı girmeniz gerekli.</string> - <string name="encryption_message_recovery">Şifrelenmiş Mesajları Kurtarma</string> <string name="encryption_settings_manage_message_recovery_summary">Anahtar yedeklemesini yönet</string> <string name="encryption_import_e2e_room_keys">E2E oda anahtarlarını içe aktar</string> @@ -420,7 +396,6 @@ <string name="encryption_information_verify">Doğrula</string> <string name="encryption_information_verify_device_warning">Bu oturumun güvenilir olduğunu doğrulamak için, lütfen sahibi ile iletişime geçin (örn. yüz yüze ya da telefonla arama) ve onlara Kullanıcı Ayarları altında hangi anahtarın aşağıdaki anahtar ile uyuştuğunu sorun:</string> <string name="encryption_information_verify_device_warning2">Eğer uyuşursa aşağıdaki doğrulama tuşuna basın. Eğer uyuşmaz ise biri bu oturum engelliyor demektir bu durumda sen o kişi karalisteye eklemelisin. Gelecekte doğrulama işlemi daha gelişmiş olacak.</string> - <string name="select_room_directory">Bir oda dizini seç</string> <string name="directory_server_placeholder">Ana sunucu URL</string> <string name="directory_server_all_rooms_on_server">%s sunucusundaki tüm odalar</string> @@ -429,7 +404,6 @@ <item quantity="one">%d okunmamış bildirim mesajı</item> <item quantity="other">%d okunmamış bildirim mesajları</item> </plurals> - <plurals name="notification_unread_notified_messages_in_room_rooms"> <item quantity="one">%d oda</item> <item quantity="other">%d odalar</item> @@ -489,8 +463,6 @@ <string name="group_details_home">Anasayfa</string> <string name="rooms">Odalar</string> <string name="invited">Davet edildi</string> - - <string name="has_been_removed">%1$s dan/den %2$s tarafından atıldın</string> <string name="has_been_banned">%1$s dan/den %2$s tarafından engellendin</string> <string name="reason_colon">Nedeni: %1$s</string> @@ -556,7 +528,6 @@ <string name="keys_backup_setup_step3_copy_button_title">Kurtarma Anahtarını Kaydet</string> <string name="keys_backup_setup_step3_share_recovery_file">Paylaş</string> <string name="keys_backup_setup_step3_save_button_title">Dosya olarak farklı kaydet</string> - <string name="keys_backup_setup_step3_please_make_copy">Lütfen bir kopya oluşturun</string> <string name="keys_backup_setup_step3_share_intent_chooser_title">Kurtarma anahtarını bununla paylaş…</string> <string name="keys_backup_setup_step3_generating_key_status">Parola kullanılarak Kurtarma anahtarı oluşturuluyor bu işlem birkaç saniye sürecek.</string> @@ -616,14 +587,12 @@ <string name="auth_login_sso">Tek oturum açma ile giriş yap</string> <string name="settings_send_message_with_enter">Enter ile mesaj gönder</string> <string name="settings_send_message_with_enter_summary">Klavyedeki Enter tuşuna bastığında alt satıra geçmek yerine mesajı gönder</string> - <string name="settings_background_fdroid_sync_mode_battery_description">${app_name}, cihazın sınırlı kaynaklarını (pil) koruyacak şekilde arka planda senkronize olur. \nCihazınızın kaynak durumuna bağlı olarak, senkronizasyon işletim sistemi tarafından ertelenebilir.</string> <string name="settings_background_fdroid_sync_mode_real_time">Gerçek zamanlı için optimize</string> <string name="settings_background_fdroid_sync_mode_real_time_description">${app_name} periyodik olarak belirli bir zamanda (ayarlanabilir) arka planda senkronize olur. \nBu pil ve radyo kullanımını etkileyecek ve ${app_name}\'in olayları dinlediğini belirten kalıcı bir bildirim gösterecektir.</string> <string name="settings_background_fdroid_sync_mode_disabled">Arka plan senkronizasyonu yok</string> - <string name="settings_integrations">Entegrasyonlar</string> <string name="settings_integrations_summary">Botları, köprüleri, widget\'ları ve çıkartma paketlerini yönetmek için bir entegrasyon yöneticisi kullanın. \n Entegrasyon yöneticileri, yapılandırma verilerini alır ve widget\'ları değiştirebilir, oda davetleri gönderebilir ve sizin adınıza güç seviyeleri ayarlayabilir.</string> @@ -693,11 +662,8 @@ <string name="keys_backup_setup_override_replace">Değiştir</string> <string name="keys_backup_setup_override_stop">Dur</string> <string name="keys_backup_settings_checking_backup_state">Yedek durumu kontrol ediliyor</string> - <string name="sas_verified">Doğrulandı!</string> <string name="sas_got_it">Anlaşıldı</string> - - <string name="sas_incoming_request_notif_title">Doğrulama Talebi</string> <string name="sas_incoming_request_notif_content">%s oturumunu doğrulamak istiyor</string> <string name="sas_error_unknown">Bilinmeyen Hata</string> @@ -727,7 +693,6 @@ <string name="action_change">Değiştir</string> <string name="change_room_directory_network">Ağı değiştir</string> <string name="please_wait">Lütfen bekleyin…</string> - <string name="group_all_communities">Tüm Topluluklar</string> <string name="room_preview_no_preview">Bu oda ön izlenemez</string> <string name="fab_menu_create_room">Odalar</string> <string name="fab_menu_create_chat">Doğrudan Mesajlar</string> @@ -1055,7 +1020,6 @@ <string name="send_attachment">Ek gönder</string> <string name="settings_text_message_sent_wrong_code">Onay kodu doğru değil.</string> <string name="settings_text_message_sent_hint">Kod</string> - <string name="settings_discovery_disconnect_identity_server_info">Kimlik sunucunuzdan ayrılmanız, diğer kullanıcılar tarafından keşfedilemeyeceğiniz ve başkalarını e-posta ya da telefonla davet edemeyeceğiniz anlamına gelir.</string> <string name="settings_discovery_identity_server_info_none">Henüz bir kimlik sunucusu kullanmıyorsunuz. Tanıdığınız mevcut kişileri bulmak ve bu kişiler tarafından görünür olmak için aşağıdakilerden birini yapılandırın.</string> <string name="change_identity_server">Kimlik sunucusunu değiştir</string> @@ -1394,8 +1358,6 @@ <string name="event_status_sent_message">Mesajı gönderildi</string> <string name="initial_sync_start_importing_account_data">İlk senkronizasyon: \nHesap verilerini içe aktarma</string> - <string name="initial_sync_start_importing_account_groups">İlk senkronizasyon: -\nToplulukları içe aktarma</string> <string name="initial_sync_start_importing_account_left_rooms">İlk senkronizasyon: \nSol odalar içe aktarılıyor</string> <string name="initial_sync_start_importing_account_invited_rooms">İlk senkronizasyon: @@ -1414,7 +1376,6 @@ <string name="initial_sync_start_server_computing">İlk senkronizasyon: \nSunucu yanıtı bekleniyor…</string> <string name="room_displayname_empty_room_was">Boş oda (%s idi)</string> - <plurals name="room_displayname_four_and_more_members"> <item quantity="one">%1$s, %2$s, %3$s ve %4$d diğer</item> <item quantity="other">%1$s, %2$s, %3$s ve %4$d kişi daha</item> @@ -1640,7 +1601,6 @@ <string name="spoiler">Spoiler</string> <string name="command_description_spoiler">Verilen mesajı spoiler olarak gönderir</string> <string name="room_list_quick_actions_notifications_all_noisy">Tüm mesajlar (gürültülü)</string> - <string name="content_reported_content">Bu içerik rapor edildi. \n \n Bu kullanıcıdan daha fazla içerik görmek istemiyorsanız, mesajlarını gizlemek için onları yok sayabilirsiniz.</string> @@ -1673,7 +1633,6 @@ <string name="settings_discovery_please_enter_server">Lütfen kimlik sunucusu url\'sini girin</string> <string name="identity_server_consent_dialog_content_question">Bu bilgiyi göndermeyi kabul ediyor musunuz\?</string> <string name="identity_server_consent_dialog_content_3">Mevcut kişileri keşfetmek için, iletişim bilgilerini (e-postalar ve telefon numaraları) kimlik sunucunuza göndermeniz gerekir. Gizlilik için göndermeden önce verilerinizi hash ederiz.</string> - <string name="identity_server_consent_dialog_title_2">%s\'e e-posta ve telefon numarası gönder</string> <string name="settings_discovery_consent_notice_off_2">Kişileriniz özeldir. Kişilerinizden kullanıcıları keşfetmek için, kimlik sunucunuza iletişim bilgilerini göndermek için izninize ihtiyacımız var.</string> <string name="settings_discovery_confirm_mail_not_clicked">Size %s adresine bir onay e-postası gönderdik, lütfen önce e-postanızı kontrol edin ve onay bağlantısını tıklayın</string> @@ -1746,7 +1705,6 @@ \nMesajlarınız şifrelerle korunur ve yalnızca siz ve alıcı, mesajların kilidini açmak için benzersiz anahtarlara sahip olursunuz.</string> <string name="direct_room_profile_not_encrypted_subtitle">Buradaki mesajlar uçtan uca şifrelenmez.</string> <string name="room_profile_not_encrypted_subtitle">Bu odadaki mesajlar uçtan uca şifrelenmez.</string> - <string name="verification_request_waiting_for">%s bekleniyor…</string> <string name="verification_verified_user">%s doğrulandı</string> <string name="verification_verify_user">%s\'yi doğrula</string> @@ -1774,4 +1732,6 @@ <string name="action_thread_copy_link_to_thread">Konu bağlantısını kopyala</string> <string name="action_thread_view_in_room">Odada görüntüle</string> <string name="action_view_threads">Konuları Görüntüle</string> + <string name="initial_sync_request_title">Başlangıç eşitleme isteği</string> + <string name="all_chats">Tüm Sohbetler</string> </resources> diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 8300199e8a..bf786be0c4 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -111,8 +111,6 @@ <string name="notice_room_invite_no_invitee_with_reason_by_you">Ваше запрошення. Причина: %1$s</string> <string name="notice_room_invite_no_invitee_with_reason">Запрошення від %1$s. Причина: %2$s</string> <string name="event_status_sending_message">Надсилання повідомлень…</string> - <string name="initial_sync_start_importing_account_groups">Початкова синхронізація: -\nІмпортування спільнот</string> <string name="initial_sync_start_importing_account_left_rooms">Початкова синхронізація: \nІмпортування кімнат, які ви залишили</string> <string name="initial_sync_start_importing_account_invited_rooms">Початкова синхронізація: @@ -213,7 +211,6 @@ <string name="auth_email_already_defined">Ця email адреса вже використовується.</string> <string name="auth_forgot_password">Забули пароль?</string> <string name="auth_recaptcha_message">Цей домашній сервер хоче переконатися, що ви не робот</string> - <string name="auth_reset_password_missing_email">Необхідно ввести е-пошту прив\'язану до вашого облікового запису.</string> <string name="auth_reset_password_error_unauthorized">Не вдалося перевірити email: переконайтеся, що ви перейшли за посиланням у листі</string> <string name="login_error_invalid_home_server">Введіть дійсний URL</string> <string name="login_error_bad_json">Викривлений JSON</string> @@ -414,7 +411,6 @@ <string name="command_error">Помилка виконання команди</string> <string name="unrecognized_command">Команду %s не розпізнано</string> <string name="loading">Завантаження…</string> - <string name="groups_header">Спільноти</string> <string name="send_bug_report_rage_shake">Струснути пристрій, щоб повідомити про ваду</string> <string name="start_voice_call_prompt_msg">Ви впевнені, що бажаєте розпочати голосовий виклик\?</string> <string name="start_video_call_prompt_msg">Ви впевнені, що бажаєте розпочати відео виклик\?</string> @@ -665,7 +661,6 @@ <string name="invite_friends_rich_title">🔐️ Приєднуйтесь до мене в ${app_name}</string> <string name="invite_friends_text">Привіт! Спілкуймося в ${app_name}: %s</string> <string name="invite_friends">Запросити друзів</string> - <string name="group_all_communities">Всі спільноти</string> <string name="settings_show_redacted_summary">Показувати заглушку на місці видалених повідомлень</string> <string name="settings_general_title">Загальні</string> <string name="send_suggestion_failed">Не вдалось надіслати пропозицію (%s)</string> @@ -2293,15 +2288,15 @@ <string name="this_invite_to_this_space_was_sent">Це запрошення до простору надіслане %s, не пов\'язаній із вашим обліковим записом</string> <string name="this_invite_to_this_room_was_sent">Це запрошення до кімнати надіслане %s, не пов\'язаній із вашим обліковим записом</string> <string name="upgrade_room_for_restricted_note">Зауважте, що поліпшення створить нову версію кімнати. Всі наявні повідомлення залишаться в цій архівованій кімнаті.</string> - <string name="upgrade_room_for_restricted_no_param">Будь-хто в батьківському просторі зможемо знайти кімнату й долучитись — нема потреба вручну запрошувати всіх. Можна змінити це в налаштуваннях кімнати будь-коли.</string> - <string name="upgrade_room_for_restricted">Будь-хто в %s зможе знайти кімнату й долучитись — нема потреби вручну запрошувати всіх. Можна змінити це в налаштуваннях кімнати будь-коли.</string> + <string name="upgrade_room_for_restricted_no_param">Будь-хто в батьківському просторі зможе знайти кімнату й приєднатись — непотрібно вручну запрошувати всіх. Це можна змінити в налаштуваннях кімнати будь-коли.</string> + <string name="upgrade_room_for_restricted">Будь-хто в %s зможе знайти кімнату й приєднатись — непотрібно вручну запрошувати всіх. Це можна змінити в налаштуваннях кімнати будь-коли.</string> <string name="error_voice_message_cannot_reply_or_edit">Не вдалося відповісти чи редагувати, бо голосове повідомлення активне</string> <string name="room_using_unstable_room_version">Кімната — версії %s, яку домашній сервер позначив нестабільною.</string> <string name="upgrade_room_warning">Поліпшення кімнати — серйозна операція. Її зазвичай радять, коли кімната нестабільна через вади, брак функціоналу чи вразливості безпеки. \nЗазвичай це впливає лише на деталі опрацювання кімнати сервером.</string> <string name="this_space_has_no_rooms_admin">Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення.</string> - <string name="this_space_has_no_rooms_not_admin">Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення. -\nУ вас нема дозволу додавати кімнати.</string> + <string name="this_space_has_no_rooms_not_admin">Деякі кімнати можуть бути приховані, оскільки вони приватні й потребують запрошення. +\nУ вас немає дозволу додавати кімнати.</string> <string name="space_leave_prompt_msg_as_admin">Ви єдиний адміністратор цього простору. Якщо вийдете, він залишиться без керівництва.</string> <string name="space_leave_prompt_msg_private">Ви не зможете приєднатись, поки вас не запросять знову.</string> <string name="space_leave_prompt_msg_only_you">Ви єдина особа тут. Якщо ви вийдете, більше ніхто не зможе приєднатись, навіть ви ж.</string> @@ -2526,7 +2521,6 @@ <string name="ftue_profile_picture_title">Додати зображення профілю</string> <string name="ftue_display_name_entry_footer">Ви можете змінити його пізніше</string> <string name="ftue_display_name_entry_title">Показуване ім\'я</string> - <string name="ftue_display_name_subtitle">Його буде показано у надісланих повідомленнях.</string> <string name="ftue_display_name_title">Виберіть показуване ім\'я</string> <string name="ftue_account_created_subtitle">Ваш обліковий запис %s створений</string> <string name="ftue_account_created_congratulations_title">Вітаємо!</string> @@ -2576,7 +2570,6 @@ <string name="a11y_presence_busy">Зайнятий</string> <string name="keys_backup_settings_signature_from_this_user">Резервна копія має дійсний підпис від цього користувача.</string> <string name="live_location_bottom_sheet_last_updated_at">Оновлено %1$s тому</string> - <string name="live_location_bottom_sheet_stop_sharing">Припинити поширення</string> <string name="labs_enable_live_location_summary">Тимчасова реалізація: місця зберігаються в історії кімнат</string> <string name="labs_enable_live_location">Увімкнути поширення місцеперебування наживо</string> <string name="location_share_live_remaining_time">Залишилося %1$s</string> @@ -2703,4 +2696,8 @@ </plurals> <string name="search_space_two_parents">%1$s і %2$s</string> <string name="auth_reset_password_error_unverified">Електронна пошта не підтверджена, перевірте свою поштову скриньку</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">Неможливо завантажити карту +\nМожливо, цей домашній сервер не налаштовано для показу карт.</string> + <string name="a11y_open_settings">Відкрити налаштування</string> + <string name="all_chats">Усі бесіди</string> +</resources> diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 61b8728ca0..2803128843 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -8,7 +8,6 @@ <string name="send_bug_report">Báo lỗi</string> <string name="send_bug_report_include_screenshot">Gửi ảnh chụp màn hình</string> <string name="send_bug_report_include_key_share_history">Gửi lịch sử yêu cầu chia sẻ khóa</string> - <string name="groups_header">Cộng đồng</string> <string name="rooms_header">Phòng</string> <string name="no_more_results">Không còn kết quả nào nữa</string> <string name="action_ignore">Bỏ qua</string> @@ -328,7 +327,6 @@ <string name="notice_crypto_unable_to_decrypt_friendly">Đang đợi tin nhắn này, việc này có thể mất một lúc</string> <string name="notice_room_leave_with_reason_by_you">Bạn đã rời phòng. Lý do: %1$s</string> <string name="notice_room_leave_with_reason">%1$s đã rời phòng. Lý do: %2$s</string> - <string name="auth_reset_password_missing_email">Địa chỉ email được liên kết đến tài khoản của bạn phải được nhập.</string> <string name="auth_recaptcha_message">Máy chủ nhà này muốn chắc chắn bạn không phải rô bốt</string> <string name="auth_msisdn_already_defined">Số điện thoại này đã được định nghĩa rồi.</string> <string name="auth_email_already_defined">Địa chỉ email này đã được định nghĩa rồi.</string> @@ -491,8 +489,6 @@ <string name="event_status_sent_message">Đã gửi tin nhắn</string> <string name="initial_sync_start_importing_account_data">Đồng bộ ban đầu: \nĐang nhập dữ liệu tài khoản</string> - <string name="initial_sync_start_importing_account_groups">Đồng bộ ban đầu: -\nĐang nhập các Community</string> <string name="initial_sync_start_importing_account_left_rooms">Đồng bộ ban đầu: \nĐang nhập các phòng đã rời khỏi</string> <string name="initial_sync_start_importing_account_invited_rooms">Đồng bộ ban đầu: @@ -1846,7 +1842,6 @@ <string name="room_preview_not_found">Căn phòng này không thể truy cập vào thời điểm này. \nHãy thử lại sau, hoặc yêu cầu quản trị viên phòng kiểm tra xem bạn có quyền truy cập hay không.</string> <string name="room_preview_no_preview">Phòng này không thể được xem trước</string> - <string name="group_all_communities">Tất cả Community</string> <string name="please_wait">Vui lòng chờ…</string> <string name="change_room_directory_network">Thay đổi mạng</string> <string name="action_change">Thay đổi</string> @@ -2277,4 +2272,4 @@ <string name="unifiedpush_getdistributors_dialog_title">Chọn cách nhận thông báo</string> <string name="settings_notification_method">Phương thức thông báo</string> <string name="settings_troubleshoot_test_endpoint_registration_quick_fix">Đặt lại phương thức thông báo</string> -</resources> \ No newline at end of file +</resources> diff --git a/vector/src/main/res/values-vls/strings.xml b/vector/src/main/res/values-vls/strings.xml index 02902711f6..f84da57ccf 100644 --- a/vector/src/main/res/values-vls/strings.xml +++ b/vector/src/main/res/values-vls/strings.xml @@ -66,8 +66,6 @@ \nUutgenodigde gesprekkn wordn geïmporteerd</string> <string name="initial_sync_start_importing_account_left_rooms">Initiële synchronisoasje: \nVerloatn gesprekkn wordn geïmporteerd</string> - <string name="initial_sync_start_importing_account_groups">Initiële synchronisoasje: -\nGemeenschappn wordn geïmporteerd</string> <string name="initial_sync_start_importing_account_data">Initiële synchronisoasje: \nAccountgegeevns wordn geïmporteerd</string> diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 0549bd84a9..3083978734 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -52,12 +52,10 @@ \n正在导入已邀请的房间</string> <string name="initial_sync_start_importing_account_left_rooms">初始化同步: \n正在导入已离开的房间</string> - <string name="initial_sync_start_importing_account_groups">初始化同步: -\n正在导入社区</string> <string name="initial_sync_start_importing_account_data">初始化同步: \n正在导入账户数据</string> <string name="notice_room_update">%s 升级了此房间。</string> - <string name="event_status_sending_message">正在发送消息…</string> + <string name="event_status_sending_message">正在发送消息……</string> <string name="notice_room_third_party_revoked_invite">%1$s 撤回了对 %2$s 加入房间的邀请</string> <string name="notice_room_invite_no_invitee_with_reason">%1$s 的邀请。理由:%2$s</string> <string name="notice_room_invite_with_reason">%1$s 邀请了 %2$s。理由:%3$s</string> @@ -65,7 +63,7 @@ <string name="notice_room_join_with_reason">%1$s 加入了房间。理由:%2$s</string> <string name="notice_room_leave_with_reason">%1$s 离开了房间。理由:%2$s</string> <string name="notice_room_reject_with_reason">%1$s 已拒绝邀请。理由:%2$s</string> - <string name="notice_room_remove_with_reason">%1$s 踢走了 %2$s。理由:%3$s</string> + <string name="notice_room_remove_with_reason">%1$s移除了%2$s。理由:%3$s</string> <string name="notice_room_unban_with_reason">%1$s 解封了 %2$s。理由:%3$s</string> <string name="notice_room_ban_with_reason">%1$s 封禁了 %2$s。理由:%3$s</string> <string name="notice_room_third_party_registered_invite_with_reason">%1$s 接受 %2$s 的邀請。理由:%3$s</string> @@ -81,8 +79,8 @@ <string name="notice_room_canonical_alias_unset">%1$s 为此房间移除了主要地址。</string> <string name="notice_room_guest_access_can_join">%1$s 已允许访客加入房间。</string> <string name="notice_room_guest_access_forbidden">%1$s 已禁止访客加入房间。</string> - <string name="notice_end_to_end_ok">%1$s 已开启端对端加密。</string> - <string name="notice_end_to_end_unknown_algorithm">%1$s 已开启端对端加密(无法识别的演算法 %2$s)。</string> + <string name="notice_end_to_end_ok">%1$s已开启端到端加密。</string> + <string name="notice_end_to_end_unknown_algorithm">%1$s已开启端到端加密(无法识别的算法%2$s)。</string> <string name="notice_room_created">%1$s 创建了这个房间</string> <string name="notice_room_invite_no_invitee_by_you">你的邀请</string> <string name="notice_room_created_by_you">你创建了这个房间</string> @@ -90,7 +88,7 @@ <string name="notice_room_join_by_you">你加入了房间</string> <string name="notice_room_leave_by_you">你离开了房间</string> <string name="notice_room_reject_by_you">你拒绝了邀请</string> - <string name="notice_room_remove_by_you">你踢了 %1$s</string> + <string name="notice_room_remove_by_you">你移除了 %1$s</string> <string name="notice_room_unban_by_you">你解封了 %1$s</string> <string name="notice_room_ban_by_you">你封禁了 %1$s</string> <string name="notice_room_withdraw_by_you">你撤回了对 %1$s 的邀请</string> @@ -136,7 +134,7 @@ <string name="notice_room_join_with_reason_by_you">你加入了房间。理由:%1$s</string> <string name="notice_room_leave_with_reason_by_you">你离开了房间。理由:%1$s</string> <string name="notice_room_reject_with_reason_by_you">你拒绝了邀请。理由:%1$s</string> - <string name="notice_room_remove_with_reason_by_you">你踢走了 %1$s。理由:%2$s</string> + <string name="notice_room_remove_with_reason_by_you">你移除了%1$s。理由:%2$s</string> <string name="notice_room_unban_with_reason_by_you">你解封了 %1$s。理由:%2$s</string> <string name="notice_room_ban_with_reason_by_you">你封禁了 %1$s。理由:%2$s</string> <string name="notice_room_third_party_registered_invite_with_reason_by_you">你接受了 %1$s 的邀请。理由:%2$s</string> @@ -152,8 +150,8 @@ <string name="notice_room_canonical_alias_unset_by_you">你移除了此房间的主要地址。</string> <string name="notice_room_guest_access_can_join_by_you">你已允许访客加入房间。</string> <string name="notice_room_guest_access_forbidden_by_you">你已禁止访客加入房间。</string> - <string name="notice_end_to_end_ok_by_you">你已开启端对端加密。</string> - <string name="notice_end_to_end_unknown_algorithm_by_you">你已开启端对端加密(无法识别的算法 %1$s)。</string> + <string name="notice_end_to_end_ok_by_you">你已开启端到端加密。</string> + <string name="notice_end_to_end_unknown_algorithm_by_you">你已开启端到端加密(无法识别的算法%1$s)。</string> <string name="notice_direct_room_leave_with_reason_by_you">你已离开。理由:%1$s</string> <string name="notice_direct_room_leave_with_reason">%1$s 已离开。理由:%2$s</string> <string name="notice_direct_room_join_with_reason_by_you">你已加入。理由:%1$s</string> @@ -219,7 +217,6 @@ <string name="auth_invalid_email">此电子邮箱地址似乎无效</string> <string name="auth_email_already_defined">此电子邮箱地址已被使用。</string> <string name="auth_forgot_password">忘记密码?</string> - <string name="auth_reset_password_missing_email">必须输入与你账户关联的电子邮箱地址。</string> <string name="login_error_invalid_home_server">请输入有效的 URL</string> <string name="login_error_not_json">没有包含有效的 JSON</string> <string name="login_error_limit_exceeded">发送了过多的请求</string> @@ -312,13 +309,13 @@ <string name="room_settings_category_advanced_title">高级</string> <string name="room_settings_room_internal_id">此房间的内部 ID</string> <string name="room_settings_labs_warning_message">这些是实验性功能,可能会出现不可预料的错误。请谨慎使用。</string> - <string name="encryption_export_e2e_room_keys">导出端对端房间密钥</string> + <string name="encryption_export_e2e_room_keys">导出端到端房间密钥</string> <string name="encryption_export_room_keys">导出房间密钥</string> <string name="encryption_export_room_keys_summary">导出密钥到本地文件</string> <string name="encryption_export_export">导出</string> - <string name="passphrase_enter_passphrase">输入密语</string> - <string name="passphrase_confirm_passphrase">确认密语</string> - <string name="encryption_import_e2e_room_keys">导入端对端房间密钥</string> + <string name="passphrase_enter_passphrase">输入口令词组</string> + <string name="passphrase_confirm_passphrase">确认口令词组</string> + <string name="encryption_import_e2e_room_keys">导入端到端房间密钥</string> <string name="encryption_import_room_keys">导入房间密钥</string> <string name="encryption_import_room_keys_summary">从本地文件导入密钥</string> <string name="encryption_never_send_to_unverified_devices_title">仅向已验证的会话发送加密消息</string> @@ -455,13 +452,12 @@ <string name="room_participants_ban_prompt_msg">封禁用户会把他们移出此房间并阻止他们再次加入。</string> <string name="room_settings_all_messages">全部消息</string> <string name="room_settings_add_homescreen_shortcut">添加到主屏幕</string> - <string name="settings_inline_url_preview">启用链接预览</string> + <string name="settings_inline_url_preview">行内URL预览</string> <string name="settings_vibrate_on_mention">提及用户时震动</string> <string name="create">创建</string> <plurals name="room_new_messages_notification"> <item quantity="other">%d 条新消息</item> </plurals> - <string name="groups_header">社群</string> <string name="send_bug_report_rage_shake">摇一摇快捷反馈问题</string> <plurals name="membership_changes"> <item quantity="other">%d个成员状态变动</item> @@ -524,7 +520,7 @@ <string name="command_description_join_room">用给定地址加入房间</string> <string name="command_description_part_room">离开房间</string> <string name="command_description_topic">设置房间主题</string> - <string name="command_description_remove_user">按照 ID 踢出用户</string> + <string name="command_description_remove_user">从此房间移除指定ID的用户</string> <string name="command_description_nick">更改你显示的显示名称</string> <string name="command_description_markdown">打开/关闭 markdown</string> <string name="command_description_clear_scalar_token">修复 Matrix Apps 管理</string> @@ -592,7 +588,7 @@ <string name="settings_troubleshoot_test_service_boot_quickfix">启用开机时启动</string> <string name="settings_troubleshoot_test_bg_restricted_title">检查后台限制</string> <string name="settings_troubleshoot_test_battery_title">电池优化</string> - <string name="settings_inline_url_preview_summary">若主服务器支持此功能,在聊天中预览链接内容。</string> + <string name="settings_inline_url_preview_summary">当主服务器支持此功能时,在聊天中预览链接。</string> <string name="settings_send_typing_notifs">发送正在输入通知</string> <string name="settings_send_typing_notifs_summary">让房间中的其他用户知道你正在输入。</string> <string name="settings_send_markdown">Markdown 格式化</string> @@ -623,9 +619,9 @@ <string name="settings_troubleshoot_test_battery_success">${app_name} 未被电池优化影响。</string> <string name="settings_troubleshoot_test_battery_failed">如果设备在未充电的情况下关屏静置一段时间,其将进入低电耗模式(Doze)。这将阻止应用访问网络并延后其运行、同步与响铃。</string> <string name="settings_troubleshoot_test_battery_quickfix">忽略电池优化</string> - <string name="encryption_export_notice">请输入用于加密被导出密钥的密语。恢复此备份时,必须输入相同的密语才能导入密钥。</string> - <string name="passphrase_create_passphrase">创建密语</string> - <string name="passphrase_passphrase_does_not_match">密语必须对应</string> + <string name="encryption_export_notice">请输入用于加密被导出密钥的口令词组。恢复此备份时,必须输入相同的口令词组才能导入密钥。</string> + <string name="passphrase_create_passphrase">创建口令词组</string> + <string name="passphrase_passphrase_does_not_match">口令词组不匹配</string> <string name="command_problem_with_parameters">指令 %s 需要更多参数,或者有些参数不正确。</string> <string name="no_valid_google_play_services_apk">没有可用的 Google Play Services APK。消息通知可能不能正常工作。</string> <string name="title_activity_keys_backup_setup">密钥备份</string> @@ -662,27 +658,27 @@ <string name="encryption_settings_manage_message_recovery_summary">管理密钥备份</string> <string name="notification_silent">静音</string> <string name="error_empty_field_enter_user_name">请输入一个用户名。</string> - <string name="passphrase_empty_error_message">请输入密语</string> - <string name="passphrase_passphrase_too_weak">密语太弱了</string> - <string name="keys_backup_passphrase_not_empty_error_message">如果你想要 ${app_name} 生成一个恢复密钥,请删除密语。</string> + <string name="passphrase_empty_error_message">请输入口令词组</string> + <string name="passphrase_passphrase_too_weak">口令词组太弱了</string> + <string name="keys_backup_passphrase_not_empty_error_message">如果你想要 ${app_name} 生成一个恢复密钥,请删除口令词组。</string> <string name="keys_backup_setup_step1_title">永不丢失已加密消息</string> - <string name="keys_backup_setup_step1_description">加密房间中的信息会被端对端加密以确保安全。只有你和拥有密钥的接收方可以读取这些信息。 + <string name="keys_backup_setup_step1_description">加密房间中的消息是端到端加密的,以确保安全。只有你和接收者拥有密钥读取这些消息。 \n -\n安全地备份你的密钥以免丢失信息。</string> +\n安全地备份你的密钥以免丢失消息。</string> <string name="keys_backup_setup">开始使用备份密钥</string> <string name="keys_backup_setup_step1_advanced">(高级)</string> <string name="keys_backup_setup_step1_manual_export">手动导出密钥</string> - <string name="keys_backup_setup_step2_text_title">使用密语保护你的备份。</string> - <string name="keys_backup_setup_step2_text_description">我们将会在主服务器上为你的密钥保存一份加密拷贝。设置一个密语来保护你的备份的安全。 + <string name="keys_backup_setup_step2_text_title">使用口令词组保护你的备份。</string> + <string name="keys_backup_setup_step2_text_description">我们将会在主服务器上为你的密钥保存一份加密拷贝。设置一个口令词组来保护你的备份的安全。 \n -\n为了最大的安全性,此密语应当与你的账户密码不同。</string> - <string name="keys_backup_setup_step2_button_title">设置密语</string> +\n为了最大的安全性,此口令词组应当与你的账户密码不同。</string> + <string name="keys_backup_setup_step2_button_title">设置口令词组</string> <string name="keys_backup_setup_creating_backup">正在创建备份</string> <string name="keys_backup_setup_step1_recovery_key_alternative">或者用一个恢复密钥来保护你的备份,将其保存到另一个安全的地方。</string> <string name="keys_backup_setup_step2_skip_button_title">(高级)设置一个恢复密钥</string> <string name="keys_backup_setup_step3_success_title">成功!</string> <string name="keys_backup_setup_step3_text_line1">正在备份你的密钥。</string> - <string name="keys_backup_setup_step3_text_line2">你的恢复密钥是一张安全网——如果你忘记了密语,你可以利用它重获你的已加密消息的访问权。 + <string name="keys_backup_setup_step3_text_line2">你的恢复密钥是一张安全网——如果你忘记了口令词组,你可以利用它重获你的已加密消息的访问权。 \n请将你的恢复密钥保存在一个非常安全的地方,比如密码管理器中(或保险箱里)</string> <string name="keys_backup_setup_step3_text_line2_no_passphrase">将你的恢复密钥保存在一个非常安全的地方,比如密码管理器中(或保险箱里)</string> <string name="keys_backup_setup_step3_button_title">完成</string> @@ -692,19 +688,19 @@ <string name="keys_backup_setup_step3_save_button_title">保存为文件</string> <string name="keys_backup_setup_step3_please_make_copy">请制作一份拷贝</string> <string name="keys_backup_setup_step3_share_intent_chooser_title">分享恢复密钥…</string> - <string name="keys_backup_setup_step3_generating_key_status">正在使用密语来生成恢复密钥,此过程可能会花费几秒钟。</string> + <string name="keys_backup_setup_step3_generating_key_status">正在使用口令词组来生成恢复密钥,此过程可能会花费几秒钟。</string> <string name="recovery_key">恢复密钥</string> <string name="unexpected_error">意外错误</string> <string name="keys_backup_setup_skip_title">你确定吗?</string> <string name="keys_backup_setup_skip_msg">如果你登出账户或者丢失此设备,你可能再也无法访问你的信息。</string> <string name="keys_backup_restore_is_getting_backup_version">正在获取备份的版本 …</string> - <string name="keys_backup_restore_with_passphrase">使用恢复密语解锁你的已加密历史消息</string> + <string name="keys_backup_restore_with_passphrase">使用恢复口令词组解锁你的已加密消息历史</string> <string name="keys_backup_restore_use_recovery_key">使用你的恢复密钥</string> - <string name="keys_backup_restore_with_passphrase_helper_with_link">如果不知道你的恢复密语,你可以 %s。</string> + <string name="keys_backup_restore_with_passphrase_helper_with_link">如果不知道你的恢复口令词组,你可以 %s。</string> <string name="keys_backup_restore_with_recovery_key">使用恢复密钥解锁你的已加密历史消息</string> <string name="keys_backup_restore_key_enter_hint">输入恢复密钥</string> <string name="keys_backup_restore_with_key_helper">丢失了恢复密钥?你可以在设置中新建一个。</string> - <string name="keys_backup_passphrase_error_decrypt">无法使用此密语解密备份:请检查你输入的恢复密语是否正确。</string> + <string name="keys_backup_passphrase_error_decrypt">无法使用此口令词组解密备份:请检查你输入的恢复口令词组是否正确。</string> <string name="keys_backup_restoring_waiting_message">正在恢复备份:</string> <string name="keys_backup_restoring_computing_key_waiting_message">正在计算恢复密钥…</string> <string name="keys_backup_restoring_downloading_backup_waiting_message">正在下载密钥…</string> @@ -731,13 +727,13 @@ <string name="keys_backup_settings_valid_signature_from_unverified_device">备份具有来自未验证会话%s的有效签名</string> <string name="keys_backup_settings_invalid_signature_from_verified_device">备份具有来自已验证会话%s的无效签名</string> <string name="keys_backup_settings_invalid_signature_from_unverified_device">备份具有来自未验证会话%s的无效签名</string> - <string name="keys_backup_settings_untrusted_backup">要在此会话中使用密钥备份,请立即使用密语或恢复密钥进行恢复。</string> + <string name="keys_backup_settings_untrusted_backup">要在此会话中使用密钥备份,请立即使用口令词组或恢复密钥进行恢复。</string> <string name="keys_backup_settings_deleting_backup">正在删除备份…</string> <string name="keys_backup_settings_delete_confirm_title">删除备份</string> <string name="keys_backup_settings_delete_confirm_message">要从此服务器中删除你备份的加密密钥吗?你将无法再使用恢复密钥来读取加密的历史消息。</string> <string name="keys_backup_banner_recover_line1">永不丢失已加密消息</string> <string name="keys_backup_banner_recover_line2">使用备份密钥</string> - <string name="keys_backup_banner_update_line1">新加密信息密钥</string> + <string name="keys_backup_banner_update_line1">新安全消息密钥</string> <string name="keys_backup_banner_update_line2">管理密钥备份</string> <string name="keys_backup_banner_in_progress">正在备份密钥…</string> <string name="keys_backup_info_keys_all_backup_up">所有密钥都已备份</string> @@ -808,7 +804,6 @@ <string name="create_new_room">创建新房间</string> <string name="action_change">修改</string> <string name="please_wait">请稍候……</string> - <string name="group_all_communities">所有社群</string> <string name="room_preview_no_preview">无法预览此房间</string> <string name="fab_menu_create_room">房间</string> <string name="create_room_action_create">创建</string> @@ -867,8 +862,8 @@ <string name="room_participants_action_unignore_prompt_msg">取消忽略此用户将重新显示来自他们的全部消息。</string> <string name="room_participants_action_cancel_invite_title">取消邀请</string> <string name="room_participants_action_cancel_invite_prompt_msg">你确定想要取消邀请此用户吗?</string> - <string name="room_participants_remove_title">踢掉用户</string> - <string name="room_participants_remove_reason">踢掉理由</string> + <string name="room_participants_remove_title">移除用户</string> + <string name="room_participants_remove_reason">移除的理由</string> <string name="room_participants_remove_prompt_msg">用户将从此房间移除。 \n \n为防止他们再次加入,你应改为封禁他们。</string> @@ -892,7 +887,7 @@ <string name="settings_secure_backup_setup">设置安全备份</string> <string name="settings_secure_backup_reset">重置安全备份</string> <string name="settings_secure_backup_enter_to_setup">在此设备上设置</string> - <string name="settings_secure_backup_section_info">通过在你的服务器上备份加密密钥,防止失去对加密信息和数据的访问。</string> + <string name="settings_secure_backup_section_info">通过在你的服务器上备份加密密钥,防止失去对加密消息和数据的访问。</string> <string name="reset_secure_backup_title">为你已有的备份生成新的安全密钥或设置新的安全短语。</string> <string name="reset_secure_backup_warning">这将替换你的当前密钥或短语。</string> <string name="settings_discovery_category">发现</string> @@ -936,7 +931,7 @@ <string name="keys_backup_setup_override_backup_prompt_tile">你的主服务器上已存在备份</string> <string name="keys_backup_setup_override_backup_prompt_description">你似乎已在另一个会话中设置密钥备份。你想要将其替换为正在创建的吗?</string> <string name="secure_backup_banner_setup_line1">安全备份</string> - <string name="secure_backup_banner_setup_line2">保护加密信息及数据的访问权</string> + <string name="secure_backup_banner_setup_line2">保护加密消息及数据的访问权</string> <string name="secure_backup_setup">设置安全备份</string> <string name="identity_server_not_defined">你未使用身份服务器</string> <string name="error_user_already_logged_in">你似乎正在试图连接到另一个主服务器。你想要登出吗?</string> @@ -967,7 +962,7 @@ <string name="send_suggestion_failed">建议发送失败 (%s)</string> <string name="settings_labs_show_hidden_events_in_timeline">在时间线上显示隐藏事件</string> <string name="bottom_action_people_x">私聊消息</string> - <string name="send_file_step_idle">正在等待…</string> + <string name="send_file_step_idle">正在等待……</string> <string name="send_file_step_encrypting_thumbnail">正在加密缩略图…</string> <string name="send_file_step_sending_thumbnail">正在发送缩略图 (%1$s / %2$s)</string> <string name="send_file_step_encrypting_file">正在加密文件…</string> @@ -1117,7 +1112,7 @@ <string name="login_reset_password_email_hint">电子邮件</string> <string name="login_reset_password_password_hint">新密码</string> <string name="login_reset_password_warning_title">注意!</string> - <string name="login_reset_password_warning_content">更改你的密码将重置所有会话上的端对端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出房间密钥。</string> + <string name="login_reset_password_warning_content">更改你的密码将重置所有会话上的端到端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出房间密钥。</string> <string name="login_reset_password_warning_submit">继续</string> <string name="login_reset_password_error_not_found">电子邮件未链接到任何账户</string> <string name="login_reset_password_mail_confirmation_title">检查你的收件箱</string> @@ -1239,7 +1234,7 @@ <string name="sent_an_audio_file">音频</string> <string name="sent_a_file">文件</string> <string name="send_a_sticker">贴纸</string> - <string name="verification_request_waiting">正在等待…</string> + <string name="verification_request_waiting">正在等待……</string> <string name="verification_request_other_cancelled">%s 已取消</string> <string name="verification_request_you_cancelled">你已取消</string> <string name="verification_request_other_accepted">%s 已接受</string> @@ -1254,9 +1249,9 @@ <string name="verification_no_scan_emoji_title">通过比较表情符号验证</string> <string name="verification_verify_user">验证 %s</string> <string name="verification_verified_user">已验证 %s</string> - <string name="verification_request_waiting_for">正在等待 %s…</string> - <string name="room_profile_not_encrypted_subtitle">此房间的消息未经端对端加密。</string> - <string name="room_profile_encrypted_subtitle">该房间的消息已被端对端加密。 + <string name="verification_request_waiting_for">正在等待%s……</string> + <string name="room_profile_not_encrypted_subtitle">此房间的消息未经端到端加密。</string> + <string name="room_profile_encrypted_subtitle">该房间的消息已端到端加密。 \n \n你的消息受加密保护,并且只有你和消息接收者拥有唯一解密密钥。</string> <string name="room_profile_section_security">安全</string> @@ -1289,7 +1284,7 @@ <string name="command_description_rainbow_emote">和彩虹一样给给定的表情上色后发送</string> <string name="settings_category_timeline">时间线</string> <string name="settings_category_composer">消息编辑器</string> - <string name="room_settings_enable_encryption">启用端对端加密…</string> + <string name="room_settings_enable_encryption">启用端到端加密……</string> <string name="room_settings_enable_encryption_dialog_title">是否启用加密?</string> <string name="room_settings_enable_encryption_dialog_content">房间加密一经启用,便无法禁用。在加密房间中,发送的消息无法被服务器看到,只能被房间的参与者看到。启用加密可能会使许多机器人和桥接无法正常运作。</string> <string name="room_settings_enable_encryption_dialog_submit">启用加密</string> @@ -1297,7 +1292,7 @@ <string name="verification_request_start_notice">为保证安全,请当面验证,或者使用其他通讯方式验证。</string> <string name="verification_emoji_notice">比较独特表情,确保它们以相同顺序出现。</string> <string name="verification_code_notice">与其他用户设备上显示的代码比较。</string> - <string name="verification_conclusion_ok_notice">与此用户的消息端对端加密,无法被第三方读取。</string> + <string name="verification_conclusion_ok_notice">与此用户的消息是端到端加密的,无法被第三方读取。</string> <string name="verification_conclusion_ok_self_notice">你的新会话已验证。它可以访问你的加密消息,其他用户会将其视为可信任。</string> <string name="encryption_information_cross_signing_state">交叉签名</string> <string name="encryption_information_dg_xsigning_complete">交叉签名已启用 @@ -1308,7 +1303,7 @@ <string name="encryption_information_dg_xsigning_not_trusted">交叉签名已启用。 \n密钥未信任</string> <string name="encryption_information_dg_xsigning_disabled">交叉签名未启用</string> - <string name="settings_hs_admin_e2e_disabled">你的服务器管理员已默认禁用私有房间和私聊消息端对端加密。</string> + <string name="settings_hs_admin_e2e_disabled">你的服务器管理员已默认禁用私有房间和私聊消息端到端加密。</string> <string name="settings_active_sessions_list">活跃的会话</string> <string name="settings_active_sessions_show_all">显示全部会话</string> <string name="settings_active_sessions_manage">管理会话</string> @@ -1341,10 +1336,10 @@ <string name="no_connectivity_to_the_server_indicator_airplane">飞行模式已打开</string> <string name="settings_dev_tools">开发工具</string> <string name="settings_account_data">账户数据</string> - <string name="verification_cannot_access_other_session">使用恢复密语或密钥</string> + <string name="verification_cannot_access_other_session">使用恢复口令词组或密钥</string> <string name="verification_use_passphrase">如果你无法访问已有会话</string> <string name="enter_secret_storage_invalid">无法在存储中找到秘密</string> - <string name="message_action_item_redact">移除…</string> + <string name="message_action_item_redact">移除……</string> <string name="share_confirm_room">你想要发送此附件到 %1$s 吗?</string> <plurals name="send_images_with_original_size"> <item quantity="other">发送原始尺寸图片</item> @@ -1375,7 +1370,7 @@ \n我们推荐你在设置中立即更换你的密码和恢复密钥。</string> <string name="verify_cancelled_notice">已取消验证。 您可以重新开始验证。</string> <string name="verification_cancelled">验证已取消</string> - <string name="recovery_passphrase">恢复密语</string> + <string name="recovery_passphrase">恢复口令词组</string> <string name="message_key">消息密钥</string> <string name="enter_account_password">输入你的 %s 以继续。</string> <string name="bootstrap_dont_reuse_pwd">不要使用你的账户密码。</string> @@ -1386,7 +1381,7 @@ <string name="keep_it_safe">保持安全</string> <string name="finish">完成</string> <string name="bootstrap_crosssigning_progress_initializing">发布创建的身份密钥</string> - <string name="bootstrap_crosssigning_progress_pbkdf2">从密语生成安全密钥</string> + <string name="bootstrap_crosssigning_progress_pbkdf2">从口令词组生成安全密钥</string> <string name="bootstrap_crosssigning_progress_default_key">正在定义 SSSS 默认密钥</string> <string name="bootstrap_crosssigning_progress_save_msk">正在同步主密钥</string> <string name="bootstrap_crosssigning_progress_save_usk">正在同步用户密钥</string> @@ -1409,7 +1404,7 @@ \n \n你也可以通过设置菜单来建立保护备份以及管理你的密钥。</string> <string name="encryption_enabled">加密已开启</string> - <string name="encryption_enabled_tile_description">本房间信息已经端对端加密。验证成员时,请查看其个人档案以了解更多信息。</string> + <string name="encryption_enabled_tile_description">本房间的消息端到端加密。在成员用户资料中了解更多信息与验证成员。</string> <string name="encryption_not_enabled">加密未开启</string> <string name="encryption_unknown_algorithm_tile_description">不支持本房间使用的加密方式</string> <string name="room_created_summary_item">%s 创建并配置了房间。</string> @@ -1419,15 +1414,15 @@ <string name="qr_code_scanned_verif_waiting">正在等候 %s…</string> <string name="error_failed_to_import_keys">导入密钥失败</string> <string name="settings_notification_configuration">通知配置</string> - <string name="settings_messages_in_e2e_one_to_one">一对一聊天的加密信息</string> - <string name="settings_messages_in_e2e_group_chat">群聊的加密信息</string> + <string name="settings_messages_in_e2e_one_to_one">一对一聊天的加密消息</string> + <string name="settings_messages_in_e2e_group_chat">群聊的加密消息</string> <string name="settings_messages_at_room">消息包含 @room</string> <string name="settings_when_rooms_are_upgraded">当房间升级</string> <string name="settings_troubleshoot_title">故障诊断</string> <string name="command_description_plain">以纯文本形式发送消息,而不将其解释为 markdown</string> <string name="auth_invalid_login_param_space_in_password">用户名和/或密码不正确。输入的密码以空格开头或结尾,请检查。</string> <string name="auth_invalid_login_deactivated_account">此账户已停用。</string> - <string name="room_message_placeholder">消息…</string> + <string name="room_message_placeholder">消息……</string> <string name="upgrade_security">加密升级可用</string> <string name="security_prompt_text">验证你自己和其他人以保证你的聊天安全</string> <string name="bootstrap_enter_recovery">输入你的 %s 以继续</string> @@ -1437,18 +1432,18 @@ <string name="bootstrap_progress_checking_backup">检查备份密钥</string> <string name="bootstrap_progress_checking_backup_with_info">检查备份密钥 (%s)</string> <string name="bootstrap_progress_compute_curve_key">获取曲线密钥</string> - <string name="bootstrap_progress_generating_ssss">从密语生成 SSSS 密钥</string> - <string name="bootstrap_progress_generating_ssss_with_info">从密语生成 SSSS 密钥(%s)</string> + <string name="bootstrap_progress_generating_ssss">从口令词组生成 SSSS 密钥</string> + <string name="bootstrap_progress_generating_ssss_with_info">从口令词组生成 SSSS 密钥(%s)</string> <string name="bootstrap_progress_generating_ssss_recovery">从恢复密钥生成 SSSS 密钥</string> <string name="bootstrap_progress_storing_in_sss">正在在 SSSS 中保存密钥备份秘密</string> - <string name="bootstrap_migration_enter_backup_password">输入你的密钥备份密语以继续。</string> + <string name="bootstrap_migration_enter_backup_password">输入你的密钥备份口令词组以继续。</string> <string name="bootstrap_migration_use_recovery_key">使用你的密钥备份恢复密钥</string> - <string name="bootstrap_migration_with_passphrase_helper_with_link">不知道你的密钥备份密语,你可以 %s。</string> + <string name="bootstrap_migration_with_passphrase_helper_with_link">不知道你的密钥备份口令词组,你可以 %s。</string> <string name="bootstrap_migration_backup_recovery_key">密钥备份恢复密钥</string> <string name="settings_security_prevent_screenshots_title">阻止应用内屏幕截图</string> <string name="settings_security_prevent_screenshots_summary">启用此设置添加 FLAG_SECURE 到所有活动。重启应用使更改生效。</string> <string name="error_saving_media_file">无法保存媒体文件</string> - <string name="change_password_summary">选择新的账户密码…</string> + <string name="change_password_summary">设置新账户密码……</string> <string name="use_other_session_content_description">在你的其他设备上使用最新的${app_name} 网页版、${app_name} 桌面版、${app_name} iOS 版、${app_name} 安卓版,或其他能够交叉签名的 Matrix 客户端</string> <string name="app_desktop_web">${app_name} Web \n${app_name} Desktop</string> @@ -1513,7 +1508,7 @@ <string name="a11y_stop_camera">停止相机</string> <string name="a11y_start_camera">启动相机</string> <string name="bottom_sheet_setup_secure_backup_title">安全备份</string> - <string name="bottom_sheet_setup_secure_backup_subtitle">通过在你的服务器上备份加密密钥,防止失去对加密信息和数据的访问。</string> + <string name="bottom_sheet_setup_secure_backup_subtitle">通过在你的服务器上备份加密密钥,防止失去对加密消息和数据的访问。</string> <string name="bottom_sheet_setup_secure_backup_submit">设置</string> <string name="bottom_sheet_setup_secure_backup_security_key_title">使用安全密钥</string> <string name="bottom_sheet_setup_secure_backup_security_key_subtitle">生成安全密钥存储在安全的地方如密码管理器或保险箱。</string> @@ -1595,8 +1590,8 @@ <string name="settings_emails_and_phone_numbers_title">电子邮件和电话号码</string> <string name="settings_emails_and_phone_numbers_summary">管理链接到你的 Matrix 账户的电子邮件和电话号码</string> <string name="settings_text_message_sent_hint">代码</string> - <string name="login_msisdn_notice">请使用国际格式(电话号码必须以 ‘+’ 开始)</string> - <string name="confirm_your_identity_quad_s">通过验证此登录确认你的身份,授权它访问加密信息。</string> + <string name="login_msisdn_notice">请使用国际格式(电话号码必须以“+”开始)</string> + <string name="confirm_your_identity_quad_s">验证此登录来确认你的身份,授权其访问加密消息。</string> <string name="error_opening_banned_room">无法打开你被封禁的房间。</string> <string name="room_error_not_found">无法找到此房间。请确认它存在。</string> <string name="no_permissions_to_start_webrtc_call">你没有权限在此房间发起通话</string> @@ -1632,15 +1627,15 @@ <string name="bad_passphrase_key_reset_all_action">忘记或丢失了所有的恢复选项?重置一切</string> <string name="direct_room_created_summary_item_by_you">你已加入。</string> <string name="direct_room_created_summary_item">%s 已加入。</string> - <string name="direct_room_encryption_enabled_tile_description">此聊天的消息是端对端加密的。</string> + <string name="direct_room_encryption_enabled_tile_description">此聊天的消息是端到端加密的。</string> <string name="direct_room_profile_section_more_leave">离开</string> <string name="direct_room_profile_section_more_settings">设置</string> - <string name="direct_room_profile_encrypted_subtitle">此处的消息已被端对端加密。 + <string name="direct_room_profile_encrypted_subtitle">此处的消息已端到端加密。 \n \n你的消息受加密保护,并且只有你和消息接收者拥有唯一解密密钥。</string> - <string name="direct_room_profile_not_encrypted_subtitle">此处的消息未经端对端加密。</string> + <string name="direct_room_profile_not_encrypted_subtitle">此处的消息未经端到端加密。</string> <string name="login_error_outdated_homeserver_warning_content">此主服务器正在运行较旧版本。要求你的主服务器管理员升级。你可以继续,但一些功能可能无法正确工作。</string> - <string name="direct_room_join_rules_invite_by_you">你仅发出此邀请。</string> + <string name="direct_room_join_rules_invite_by_you">你将此房间设为仅邀请。</string> <string name="direct_room_join_rules_invite">%1$s 仅发出此邀请。</string> <string name="settings_labs_show_complete_history_in_encrypted_room">在加密房间显示完整历史</string> <string name="notification_unread_notified_messages_and_invitation">%1$s 和 %2$s</string> @@ -1670,7 +1665,7 @@ <string name="room_alias_published_alias_add_manually">手动发布新地址</string> <string name="room_alias_published_other">其它发布的地址:</string> <string name="room_alias_published_alias_main">这是主要地址</string> - <string name="room_alias_published_alias_subtitle">发布的地址可以被任何服务器上的任何人用来加入你的房间。要发布一个地址,它必须先被设为一个本地地址。</string> + <string name="room_alias_published_alias_subtitle">任何服务器上的任何人都可用发布的地址加入你的房间。一个地址必须先设置为本地地址才可发布。</string> <string name="room_alias_published_alias_title">发布的地址</string> <string name="room_settings_alias_subtitle">查看和管理此房间的地址,以及它在房间目录中的可见性。</string> <string name="room_settings_alias_title">房间地址</string> @@ -1755,7 +1750,7 @@ <string name="room_permissions_notify_everyone">通知每个人</string> <string name="room_permissions_remove_messages_sent_by_others">移除其他人发送的消息</string> <string name="room_permissions_ban_users">封禁用户</string> - <string name="room_permissions_remove_users">踢掉用户</string> + <string name="room_permissions_remove_users">移除用户</string> <string name="room_permissions_change_settings">更改设置</string> <string name="room_permissions_invite_users">邀请用户</string> <string name="room_permissions_send_messages">发送消息</string> @@ -1819,7 +1814,7 @@ <string name="initial_sync_start_downloading">初始化同步: \n正在下载数据…</string> <string name="initial_sync_start_server_computing">初始化同步: -\n正在等待服务器响应…</string> +\n正在等待服务器响应……</string> <string name="room_displayname_empty_room_was">空房间(曾为 %s)</string> <plurals name="room_displayname_four_and_more_members"> <item quantity="other">%1$s,%2$s,%3$s 和 %4$d 位其他成员</item> @@ -1877,7 +1872,7 @@ <string name="looking_for_someone_not_in_space">正在寻找不在 %s 中的人?</string> <string name="user_invites_you">%s 邀请了你</string> <string name="you_are_invited">你被邀请</string> - <string name="spaces_beta_welcome_to_spaces_desc">空间是一种将房间和人们进行重新分组的新方式。</string> + <string name="spaces_beta_welcome_to_spaces_desc">空间是把房间和人分组的新方式。</string> <string name="space_add_existing_rooms">添加现有房间和空间</string> <string name="space_leave_prompt_msg_as_admin">你是此空间唯一的管理员。离开就意味着没人能控制它。</string> <string name="space_leave_prompt_msg_private">除非你被重新邀请,否则你将无法重新加入。</string> @@ -1970,7 +1965,7 @@ <string name="give_feedback">提供反馈</string> <string name="feedback_failed">反馈发送失败(%s)</string> <string name="feedback_sent">感谢,你的反馈已成功送达</string> - <string name="you_may_contact_me">如果你后续有任何问题,欢迎联系我</string> + <string name="you_may_contact_me">如果你有任何后续问题,可以联系我</string> <string name="send_feedback_space_info">你正在使用空间的测试版。你的反馈将有助于改善下一版本。我们将会记录你的平台和用户名以帮助我们尽我们所能多发挥你的反馈的作用。</string> <string name="feedback">反馈</string> <string name="send_feedback_space_title">空间反馈</string> @@ -2167,7 +2162,7 @@ <string name="command_description_add_to_space">添加至给定的空间</string> <string name="create_space_in_progress">创建空间中…</string> <string name="settings_developer_mode_show_info_on_screen_summary">显示一些有用的信息以帮助调试应用程序</string> - <string name="settings_developer_mode_show_info_on_screen_title">在屏幕商显示调试信息</string> + <string name="settings_developer_mode_show_info_on_screen_title">在屏幕上显示调试信息</string> <string name="does_not_look_like_valid_email">看起来不像是有效的邮箱地址</string> <string name="open_discovery_settings">打开“发现”设置</string> <string name="user_directory_search_hint_2">按名称、ID或邮箱搜索</string> @@ -2342,7 +2337,6 @@ <string name="live_location_not_enough_permission_dialog_description">你需要有正确的权限,才能在这个房间里分享实时位置。</string> <string name="live_location_not_enough_permission_dialog_title">你没有权限分享实时位置</string> <string name="live_location_bottom_sheet_last_updated_at">%1$s前已更新</string> - <string name="live_location_bottom_sheet_stop_sharing" tools:ignore="UnusedResources">停止分享</string> <string name="labs_enable_live_location_summary">临时执行:地点在房间历史中持续存在</string> <string name="labs_enable_live_location">启用实时位置共享</string> <string name="live_location_sharing_notification_description">位置共享正在进行中</string> @@ -2456,7 +2450,7 @@ <string name="error_forbidden_digits_only_username">主服务器不接收仅有数字的用户名。</string> <string name="send_your_first_msg_to_invite">发送你的第一条消息邀请%s聊天</string> <string name="encryption_misconfigured">加密配置错误</string> - <string name="direct_room_encryption_enabled_tile_description_future">此聊天中的消息会被端对端加密。</string> + <string name="direct_room_encryption_enabled_tile_description_future">此聊天中的消息会端到端加密。</string> <string name="room_profile_section_restore_security">还原加密</string> <string name="contact_admin_to_restore_encryption">请联系管理员将加密还原到有效状态。</string> <string name="encryption_has_been_misconfigured">加密被错误地配置了。</string> @@ -2469,7 +2463,6 @@ <string name="ftue_personalize_complete_title">看起来不错!</string> <string name="ftue_personalize_lets_go">走吧</string> <string name="ftue_display_name_entry_footer">你可以稍后更改这个</string> - <string name="ftue_display_name_subtitle">这会在你发送消息时显示。</string> <string name="ftue_auth_login_username_entry">用户名/电子邮件/电话号码</string> <string name="ftue_auth_captcha_title">你是人类吗?</string> <string name="ftue_auth_password_reset_email_confirmation_subtitle">按照发送到%s的操作说明</string> @@ -2553,4 +2546,8 @@ <string name="ftue_auth_sign_in_choose_server_header">你的对话发生的地方</string> <string name="search_space_two_parents">%1$s和%2$s</string> <string name="auth_reset_password_error_unverified">电子邮件未确认,检查你的收件箱</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">无法加载地图 +\n此主服务器可能没有设置好显示地图。</string> + <string name="a11y_open_settings">打开设置</string> + <string name="all_chats">全部聊天</string> +</resources> diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index effb6e7410..91388ed561 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -52,8 +52,6 @@ \n正在匯入已邀請的聊天室</string> <string name="initial_sync_start_importing_account_left_rooms">初始化同步: \n正在匯入已離開的聊天室</string> - <string name="initial_sync_start_importing_account_groups">初始化同步: -\n正在匯入社群</string> <string name="initial_sync_start_importing_account_data">初始化同步: \n正在匯入帳號資料</string> <string name="notice_room_update">%s 已升級此聊天室。</string> @@ -264,7 +262,6 @@ <string name="matrix_only_filter">僅 Matrix 聯絡人</string> <string name="no_result_placeholder">沒有結果</string> <string name="rooms_header">聊天室</string> - <string name="groups_header">社群</string> <string name="send_bug_report_include_logs">傳送記錄</string> <string name="send_bug_report_include_crash_logs">傳送當機紀錄</string> <string name="send_bug_report_include_screenshot">傳送螢幕截圖</string> @@ -298,7 +295,6 @@ <string name="auth_email_already_defined">此電子郵件位址已經被定義。</string> <string name="auth_forgot_password">忘記密碼?</string> <string name="auth_recaptcha_message">這個家伺服器想要確定您不是機器人</string> - <string name="auth_reset_password_missing_email">必須輸入和您帳號關聯的電子郵件地址。</string> <string name="auth_reset_password_error_unauthorized">電子郵件地址驗證失敗: 請確保您已點擊郵件中的連結</string> <string name="login_error_invalid_home_server">請輸入有效的網址</string> <string name="login_error_bad_json">異常的 JSON</string> @@ -858,7 +854,6 @@ <string name="action_change">變更</string> <string name="change_room_directory_network">變更網路</string> <string name="please_wait">請稍候……</string> - <string name="group_all_communities">所有社群</string> <string name="room_preview_no_preview">無法預覽此聊天室</string> <string name="fab_menu_create_room">聊天室</string> <string name="fab_menu_create_chat">直接訊息</string> @@ -2385,7 +2380,6 @@ <string name="ftue_profile_picture_title">新增個人資訊照片</string> <string name="ftue_display_name_entry_footer">您可以在稍後變更這個</string> <string name="ftue_display_name_entry_title">顯示名稱</string> - <string name="ftue_display_name_subtitle">這將會在您傳送訊息時顯示。</string> <string name="ftue_display_name_title">選擇顯示名稱</string> <string name="ftue_account_created_subtitle">您的帳號 %s 已建立</string> <string name="ftue_account_created_congratulations_title">恭喜!</string> @@ -2435,7 +2429,6 @@ <string name="a11y_presence_busy">忙碌</string> <string name="keys_backup_settings_signature_from_this_user">備份具有來自該使用者的有效簽名。</string> <string name="live_location_bottom_sheet_last_updated_at">%1$s 前已更新</string> - <string name="live_location_bottom_sheet_stop_sharing">停止分享</string> <string name="labs_enable_live_location_summary">暫時的實作:位置會保留在聊天室歷史紀錄中</string> <string name="labs_enable_live_location">啟用即時位置分享</string> <string name="location_share_live_remaining_time">剩餘 %1$s</string> @@ -2553,4 +2546,8 @@ </plurals> <string name="search_space_two_parents">%1$s 與 %2$s</string> <string name="auth_reset_password_error_unverified">電子郵件未驗證,請檢查您的收件匣</string> -</resources> \ No newline at end of file + <string name="location_share_loading_map_error">無法載入地圖 +\n此家伺服器可能未設定好顯示地圖。</string> + <string name="a11y_open_settings">開啟設定</string> + <string name="all_chats">所有聊天</string> +</resources> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b553549102..a3e76feadb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -137,6 +137,10 @@ <!-- Home Screen --> <string name="all_chats">All Chats</string> + <string name="start_chat">Start Chat</string> + <string name="create_room">Create Room</string> + <string name="change_space">Change Space</string> + <string name="explore_rooms">Explore Rooms</string> <!-- Last seen time --> @@ -175,8 +179,6 @@ <string name="initial_sync_start_importing_account_joined_rooms">Initial sync:\nLoading your conversations\nIf you\'ve joined lots of rooms, this might take a while</string> <string name="initial_sync_start_importing_account_invited_rooms">Initial sync:\nImporting invited rooms</string> <string name="initial_sync_start_importing_account_left_rooms">Initial sync:\nImporting left rooms</string> - <!--TODO:delete--> - <string name="initial_sync_start_importing_account_groups" tools:ignore="UnusedResources">Initial sync:\nImporting communities</string> <string name="initial_sync_start_importing_account_data">Initial sync:\nImporting account data</string> <string name="initial_sync_request_title">Initial sync request</string> @@ -423,6 +425,15 @@ <!-- Home screen --> <string name="home_filter_placeholder_home">Filter room names</string> + <string name="home_layout_preferences">Layout preferences</string> + + + <!-- Home screen layout settings --> + <string name="home_layout_preferences_filters">Show filters</string> + <string name="home_layout_preferences_recents">Show recents</string> + <string name="home_layout_preferences_sort_by">Sort by</string> + <string name="home_layout_preferences_sort_activity">Activity</string> + <string name="home_layout_preferences_sort_name">A - Z</string> <!-- Home fragment --> <string name="invitations_header">Invites</string> @@ -430,6 +441,9 @@ <string name="system_alerts_header">"System Alerts"</string> <string name="suggested_header">Suggested Rooms</string> + <!-- Invites fragment --> + <string name="invites_title">Invites</string> + <!-- People fragment --> <string name="direct_chats_header">Conversations</string> <string name="matrix_only_filter">Matrix contacts only</string> @@ -443,10 +457,6 @@ <string name="settings_room_directory_show_all_rooms">Show rooms with explicit content</string> <string name="settings_room_directory_show_all_rooms_summary">Show all rooms in the room directory, including rooms with explicit content.</string> - <!-- Groups fragment --> - <!--TODO: delete--> - <string name="groups_header" tools:ignore="UnusedResources">Communities</string> - <string name="spaces_header">Spaces</string> <string name="send_bug_report_include_logs">Send logs</string> @@ -523,7 +533,6 @@ <string name="auth_msisdn_already_defined">This phone number is already defined.</string> <string name="auth_forgot_password">Forgot password?</string> <string name="auth_recaptcha_message">This homeserver would like to make sure you are not a robot</string> - <string name="auth_reset_password_missing_email">The email address linked to your account must be entered.</string> <string name="auth_reset_password_error_unauthorized">Failed to verify email address: make sure you clicked the link in the email</string> <string name="auth_reset_password_error_unverified">Email not verified, check your inbox</string> <string name="auth_accept_policies">"Please review and accept the policies of this homeserver:"</string> @@ -1633,9 +1642,6 @@ <string name="change_room_directory_network">"Change network"</string> <string name="please_wait">"Please wait…"</string> <string name="updating_your_data">Updating your data…</string> - <!--TODO: delete--> - <string name="group_all_communities" tools:ignore="UnusedResources">"All Communities"</string> - <string name="room_preview_no_preview">"This room can't be previewed"</string> <string name="room_preview_not_found">This room is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.</string> <string name="room_preview_no_preview_join">"This room can't be previewed. Do you want to join it?"</string> @@ -1996,9 +2002,6 @@ <string name="ftue_display_name_title">Choose a display name</string> - <!-- TODO remove --> - <!--suppress UnusedResources --> - <string name="ftue_display_name_subtitle">This will be shown when you send messages.</string> <string name="ftue_display_name_entry_title">Display Name</string> <string name="ftue_display_name_entry_footer">You can change this later</string> @@ -3138,8 +3141,6 @@ <string name="live_location_sharing_notification_description">Location sharing is in progress</string> <string name="labs_enable_live_location">Enable Live Location Sharing</string> <string name="labs_enable_live_location_summary">Temporary implementation: locations persist in room history</string> - <!-- TODO remove key --> - <string name="live_location_bottom_sheet_stop_sharing" tools:ignore="UnusedResources">Stop sharing</string> <string name="live_location_bottom_sheet_last_updated_at">Updated %1$s ago</string> <string name="live_location_not_enough_permission_dialog_title">You don’t have permission to share live location</string> <string name="live_location_not_enough_permission_dialog_description">You need to have the right permissions in order to share live location in this room.</string> diff --git a/vector/src/main/res/values/strings_login_v2.xml b/vector/src/main/res/values/strings_login_v2.xml deleted file mode 100644 index c84455a665..0000000000 --- a/vector/src/main/res/values/strings_login_v2.xml +++ /dev/null @@ -1,49 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <!-- Those strings are not final, so do not put them into Weblate for the moment --> - - <string name="login_welcome_back">Welcome back %s!</string> - <string name="login_please_enter_your_password">Please enter your password</string> - <string name="login_please_enter_your_matrix_identifier">Please enter your Matrix identifier</string> - <string name="login_please_enter_your_matrix_identifier_help">Matrix identifiers start with @, for instance @alice:server.org</string> - <string name="login_enter_identifier_help">If you do not know your Matrix identifier, or if your account has been created using Single Sign On (for instance using a Google account), or if you want to connect using your simple name, or an email associated to your account, you have to select your server first.</string> - <string name="login_choose_a_server">Choose a server</string> - <string name="login_please_choose_a_server">Please choose a server</string> - <string name="login_please_select_your_server">Please select your server</string> - <string name="login_please_choose_a_password">Please choose a password</string> - <string name="login_please_choose_a_new_password">Please choose a new password</string> - <string name="login_your_matrix_identifier">Your Matrix identifier</string> - <string name="login_press_back_to_change">Press back to change</string> - <string name="login_choose_a_password">Choose a password</string> - <string name="login_enter_your_email">Enter an email associated to your Matrix account</string> - <string name="login_choose_a_new_password">Choose a new password</string> - <string name="login_please_choose_a_user_name">Please choose an identifier</string> - <string name="login_please_choose_a_user_name_help_2">Once your account is created, your identifier cannot be modified. However you will be able to change your display name.</string> - <string name="login_if_you_re_not_sure_select_this_option">If you\'re not sure, select this option</string> - <string name="login_element_matrix_server_and_others">Element Matrix Server and others</string> - <string name="login_create_a_new_account">Create a new account</string> - <string name="login_i_already_have_an_account">I already have an account</string> - - <string name="login_unknown_user_warning">Warning: no profile information can be retrieved with this Matrix identifier. Please check that there is no mistake.</string> - - <string name="login_wait_for_email_notice_2">We just sent an email to %1$s.</string> - <string name="login_wait_for_email_help">Click on the link it contains to continue the account creation.</string> - <string name="login_account_created_title">Congratulations!</string> - <string name="login_account_created_subtitle">You account %s has been successfully created.</string> - <string name="login_account_created_notice">To complete your profile, you can set a profile image and/or a display name. This can also be done later from the settings.</string> - <string name="login_account_created_notice_2">This is how your messages will appear:</string> - <string name="login_account_created_message">Hello Matrix world!</string> - <string name="login_account_created_instruction">Click on the image and on your name to configure them.</string> - - <string name="login_set_email_title_2">Associate an email</string> - <string name="login_set_email_notice_2">Associate an email to be able to later recover your account, in case you forget your password.</string> - <string name="login_set_email_mandatory_notice_2">The server %s requires you to associate an email to create an account.</string> - - <string name="login_set_msisdn_title_2">Associate a phone number</string> - <string name="login_set_msisdn_notice_2">Associate a phone number to optionally allow people you know to discover you.</string> - <string name="login_set_msisdn_mandatory_notice_2">The server %s requires you to associate a phone number to create an account.</string> - <!-- %S will be replaced by the value of login_server_modular_learn_more ("Learn more" in English)--> - <string name="login_server_modular_learn_more_about_ems">%s about Element Matrix Service.</string> - -</resources> \ No newline at end of file diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index b505d05944..0e4a4704b9 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -48,6 +48,7 @@ import im.vector.app.test.fakes.FakeVectorOverrides import im.vector.app.test.fakes.toTestString import im.vector.app.test.fixtures.a401ServerError import im.vector.app.test.fixtures.aHomeServerCapabilities +import im.vector.app.test.fixtures.anUnrecognisedCertificateError import im.vector.app.test.test import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo @@ -58,6 +59,7 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.network.ssl.Fingerprint import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -65,10 +67,12 @@ private const val A_DISPLAY_NAME = "a display name" private const val A_PICTURE_FILENAME = "a-picture.png" private val A_SERVER_ERROR = a401ServerError() private val AN_ERROR = RuntimeException("an error!") +private val AN_UNRECOGNISED_CERTIFICATE_ERROR = anUnrecognisedCertificateError() private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L) private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.SendAgainThreePid private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true) +private val A_FINGERPRINT = Fingerprint(ByteArray(1), Fingerprint.HashType.SHA1) private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationActionHandler.Result.NextStage(Stage.Dummy(mandatory = true)) private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name") private const val A_HOMESERVER_URL = "https://edited-homeserver.org" @@ -191,21 +195,6 @@ class OnboardingViewModelTest { .finish() } - @Test - fun `given registration started with currentThreePid, when handling InitWith, then emits restored session OnSendEmailSuccess`() = runTest { - val test = viewModel.test() - fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { - it.givenRegistrationStarted(hasStarted = true) - it.givenCurrentThreePid(AN_EMAIL) - }) - - viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, identityServerUrl = null))) - - test - .assertEvents(OnboardingViewEvents.OnSendEmailSuccess(AN_EMAIL, isRestoredSession = true)) - .finish() - } - @Test fun `given registration not started, when handling InitWith, then does nothing`() = runTest { val test = viewModel.test() @@ -320,6 +309,25 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given has sign in with matrix id sign mode, when handling login or register action fails with certificate error, then emits error`() = runTest { + viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId)) + fakeDirectLoginUseCase.givenFailureResult(A_DIRECT_LOGIN, config = null, cause = AN_UNRECOGNISED_CERTIFICATE_ERROR) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(A_DIRECT_LOGIN) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.UnrecognisedCertificateFailure(A_DIRECT_LOGIN, AN_UNRECOGNISED_CERTIFICATE_ERROR)) + .finish() + } + @Test fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runTest { givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) @@ -404,23 +412,59 @@ class OnboardingViewModelTest { } @Test - fun `given unavailable deeplink, when selecting homeserver, then emits failure with default homeserver as retry action`() = runTest { - fakeContext.givenHasConnection() - fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG) - fakeStartAuthenticationFlowUseCase.givenHomeserverUnavailable(A_HOMESERVER_CONFIG) + fun `given in sign in flow, when selecting homeserver fails with network error, then emits Failure`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn)) + fakeVectorFeatures.givenCombinedLoginEnabled() + givenHomeserverSelectionFailsWith(AN_ERROR) val test = viewModel.test() - viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, null))) viewModel.handle(OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL)) - val expectedRetryAction = OnboardingAction.HomeServerChange.SelectHomeServer("${R.string.matrix_org_server_url.toTestString()}/") test .assertStatesChanges( initialState, { copy(isLoading = true) }, { copy(isLoading = false) } ) - .assertEvents(OnboardingViewEvents.DeeplinkAuthenticationFailure(expectedRetryAction)) + .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) + .finish() + } + + @Test + fun `given in sign in flow, when selecting homeserver fails with network error, then emits EditServerSelection`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn)) + fakeVectorFeatures.givenCombinedLoginEnabled() + givenHomeserverSelectionFailsWithNetworkError() + val test = viewModel.test() + + viewModel.handle(OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL)) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.EditServerSelection) + .finish() + } + + @Test + fun `given in sign up flow, when selecting homeserver fails with network error, then emits EditServerSelection`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp)) + fakeVectorFeatures.givenCombinedRegisterEnabled() + givenHomeserverSelectionFailsWithNetworkError() + val test = viewModel.test() + + viewModel.handle(OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL)) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.EditServerSelection) .finish() } @@ -548,6 +592,44 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `when editing homeserver errors with certificate error, then emits error`() = runTest { + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenErrors(A_HOMESERVER_CONFIG, AN_UNRECOGNISED_CERTIFICATE_ERROR) + val editAction = OnboardingAction.HomeServerChange.EditHomeServer(A_HOMESERVER_URL) + val test = viewModel.test() + + viewModel.handle(editAction) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.UnrecognisedCertificateFailure(editAction, AN_UNRECOGNISED_CERTIFICATE_ERROR)) + .finish() + } + + @Test + fun `when selecting homeserver errors with certificate error, then emits error`() = runTest { + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenErrors(A_HOMESERVER_CONFIG, AN_UNRECOGNISED_CERTIFICATE_ERROR) + val selectAction = OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL) + val test = viewModel.test() + + viewModel.handle(selectAction) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.UnrecognisedCertificateFailure(selectAction, AN_UNRECOGNISED_CERTIFICATE_ERROR)) + .finish() + } + @Test fun `given unavailable full matrix id, when a register username is entered, then emits availability error`() = runTest { viewModelWith(initialRegistrationState("ignored-url")) @@ -724,6 +806,76 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given in sign in mode, when accepting user certificate with SelectHomeserver retry action, then emits OnHomeserverEdited`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn)) + val test = viewModel.test() + fakeVectorFeatures.givenCombinedLoginEnabled() + givenCanSuccessfullyUpdateHomeserver( + A_HOMESERVER_URL, + SELECTED_HOMESERVER_STATE, + config = A_HOMESERVER_CONFIG.copy(allowedFingerprints = listOf(A_FINGERPRINT)), + fingerprint = A_FINGERPRINT, + ) + + viewModel.handle(OnboardingAction.UserAcceptCertificate(A_FINGERPRINT, OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL))) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(selectedHomeserver = SELECTED_HOMESERVER_STATE) }, + { copy(signMode = SignMode.SignIn) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OpenCombinedLogin) + .finish() + } + + @Test + fun `given in sign up mode, when accepting user certificate with EditHomeserver retry action, then emits OnHomeserverEdited`() = runTest { + viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp)) + givenCanSuccessfullyUpdateHomeserver( + A_HOMESERVER_URL, + SELECTED_HOMESERVER_STATE, + config = A_HOMESERVER_CONFIG.copy(allowedFingerprints = listOf(A_FINGERPRINT)), + fingerprint = A_FINGERPRINT, + ) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.UserAcceptCertificate(A_FINGERPRINT, OnboardingAction.HomeServerChange.EditHomeServer(A_HOMESERVER_URL))) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(selectedHomeserver = SELECTED_HOMESERVER_STATE) }, + { copy(isLoading = false) } + + ) + .assertEvents(OnboardingViewEvents.OnHomeserverEdited) + .finish() + } + + @Test + fun `given DirectLogin retry action, when accepting user certificate, then logs in directly`() = runTest { + fakeHomeServerConnectionConfigFactory.givenConfigFor("https://dummy.org", A_FINGERPRINT, A_HOMESERVER_CONFIG) + fakeDirectLoginUseCase.givenSuccessResult(A_DIRECT_LOGIN, config = A_HOMESERVER_CONFIG, result = fakeSession) + givenInitialisesSession(fakeSession) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.UserAcceptCertificate(A_FINGERPRINT, A_DIRECT_LOGIN)) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OnAccountSignedIn) + .finish() + } + @Test fun `given can successfully start password reset, when resetting password, then emits confirmation email sent`() = runTest { viewModelWith(initialState.copy(selectedHomeserver = SELECTED_HOMESERVER_STATE_SUPPORTED_LOGOUT_DEVICES)) @@ -991,15 +1143,20 @@ class OnboardingViewModelTest { fakeRegistrationActionHandler.givenResultsFor(results) } - private fun givenCanSuccessfullyUpdateHomeserver(homeserverUrl: String, resultingState: SelectedHomeserverState) { - fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, A_HOMESERVER_CONFIG) - fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) + private fun givenCanSuccessfullyUpdateHomeserver( + homeserverUrl: String, + resultingState: SelectedHomeserverState, + config: HomeServerConnectionConfig = A_HOMESERVER_CONFIG, + fingerprint: Fingerprint? = null, + ) { + fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint, config) + fakeStartAuthenticationFlowUseCase.givenResult(config, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.StartRegistration) - fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString()) + fakeHomeServerHistoryService.expectUrlToBeAdded(config.homeServerUri.toString()) } private fun givenUpdatingHomeserverErrors(homeserverUrl: String, resultingState: SelectedHomeserverState, error: Throwable) { - fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, A_HOMESERVER_CONFIG) + fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint = null, A_HOMESERVER_CONFIG) fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, resultingState)) givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error)) fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString()) @@ -1021,6 +1178,18 @@ class OnboardingViewModelTest { private fun initialRegistrationState(homeServerUrl: String) = initialState.copy( onboardingFlow = OnboardingFlow.SignUp, selectedHomeserver = SelectedHomeserverState(userFacingUrl = homeServerUrl) ) + + private fun givenHomeserverSelectionFailsWithNetworkError() { + fakeContext.givenHasConnection() + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenHomeserverUnavailable(A_HOMESERVER_CONFIG) + } + + private fun givenHomeserverSelectionFailsWith(cause: Throwable) { + fakeContext.givenHasConnection() + fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, fingerprint = null, A_HOMESERVER_CONFIG) + fakeStartAuthenticationFlowUseCase.givenErrors(A_HOMESERVER_CONFIG, cause) + } } private fun HomeServerCapabilities.toPersonalisationState(displayName: String? = null) = PersonalizationState( diff --git a/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt index 48cd32c84d..d15a6cf042 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.onboarding import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.SsoState import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeUri @@ -32,7 +33,11 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider private const val A_DECLARED_HOMESERVER_URL = "https://foo.bar" private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance) -private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>() +private val FALLBACK_SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>() +private val SSO_IDENTITY_PROVIDERS = listOf(SsoIdentityProvider(id = "id", "name", null, "sso-brand")) +private val SSO_LOGIN_TYPE = listOf(LoginFlowTypes.SSO) +private val SSO_AND_PASSWORD_LOGIN_TYPES = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) +private val PASSWORD_LOGIN_TYPE = listOf(LoginFlowTypes.PASSWORD) class StartAuthenticationFlowUseCaseTest { @@ -47,7 +52,7 @@ class StartAuthenticationFlowUseCaseTest { @Test fun `given empty login result when starting authentication flow then returns empty result`() = runTest { - val loginResult = aLoginResult() + val loginResult = aLoginResult(supportedLoginTypes = emptyList()) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) @@ -57,55 +62,81 @@ class StartAuthenticationFlowUseCaseTest { } @Test - fun `given login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest { - val supportedLoginTypes = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) - val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes) + fun `given empty sso providers and login supports SSO and Password when starting authentication flow then prefers fallback SsoAndPassword`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = emptyList()) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) result shouldBeEqualTo expectedResult( - supportedLoginTypes = supportedLoginTypes, - preferredLoginMode = LoginMode.SsoAndPassword(SSO_IDENTITY_PROVIDERS), + supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, + preferredLoginMode = LoginMode.SsoAndPassword(SsoState.Fallback), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @Test - fun `given login supports SSO when starting authentication flow then prefers Sso`() = runTest { - val supportedLoginTypes = listOf(LoginFlowTypes.SSO) - val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes) + fun `given sso providers and login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, ssoProviders = SSO_IDENTITY_PROVIDERS) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) result shouldBeEqualTo expectedResult( - supportedLoginTypes = supportedLoginTypes, - preferredLoginMode = LoginMode.Sso(SSO_IDENTITY_PROVIDERS), + supportedLoginTypes = SSO_AND_PASSWORD_LOGIN_TYPES, + preferredLoginMode = LoginMode.SsoAndPassword(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)), + ) + verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) + } + + @Test + fun `given empty sso providers and login supports SSO when starting authentication flow then prefers fallback Sso`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = emptyList()) + fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) + + val result = useCase.execute(A_HOMESERVER_CONFIG) + + result shouldBeEqualTo expectedResult( + supportedLoginTypes = SSO_LOGIN_TYPE, + preferredLoginMode = LoginMode.Sso(SsoState.Fallback), + ) + verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) + } + + @Test + fun `given identity providers and login supports SSO when starting authentication flow then prefers Sso`() = runTest { + val loginResult = aLoginResult(supportedLoginTypes = SSO_LOGIN_TYPE, ssoProviders = SSO_IDENTITY_PROVIDERS) + fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) + + val result = useCase.execute(A_HOMESERVER_CONFIG) + + result shouldBeEqualTo expectedResult( + supportedLoginTypes = SSO_LOGIN_TYPE, + preferredLoginMode = LoginMode.Sso(SsoState.IdentityProviders(SSO_IDENTITY_PROVIDERS)), ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } @Test fun `given login supports Password when starting authentication flow then prefers Password`() = runTest { - val supportedLoginTypes = listOf(LoginFlowTypes.PASSWORD) - val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes) + val loginResult = aLoginResult(supportedLoginTypes = PASSWORD_LOGIN_TYPE) fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult) val result = useCase.execute(A_HOMESERVER_CONFIG) result shouldBeEqualTo expectedResult( - supportedLoginTypes = supportedLoginTypes, + supportedLoginTypes = PASSWORD_LOGIN_TYPE, preferredLoginMode = LoginMode.Password, ) verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG) } private fun aLoginResult( - supportedLoginTypes: List<String> = emptyList() + supportedLoginTypes: List<String>, + ssoProviders: List<SsoIdentityProvider> = FALLBACK_SSO_IDENTITY_PROVIDERS ) = LoginFlowResult( supportedLoginTypes = supportedLoginTypes, - ssoIdentityProviders = SSO_IDENTITY_PROVIDERS, + ssoIdentityProviders = ssoProviders, isLoginAndRegistrationSupported = true, homeServerUrl = A_DECLARED_HOMESERVER_URL, isOutdatedHomeserver = false, diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt index 553a35ad8c..c0cfe5375b 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerConnectionConfigFactory.kt @@ -20,11 +20,12 @@ import im.vector.app.features.login.HomeServerConnectionConfigFactory import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.network.ssl.Fingerprint class FakeHomeServerConnectionConfigFactory { val instance: HomeServerConnectionConfigFactory = mockk() - fun givenConfigFor(url: String, config: HomeServerConnectionConfig) { - every { instance.create(url) } returns config + fun givenConfigFor(url: String, fingerprint: Fingerprint? = null, config: HomeServerConnectionConfig) { + every { instance.create(url, fingerprint) } returns config } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt index bfbef9e565..4b2709facc 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStartAuthenticationFlowUseCase.kt @@ -31,6 +31,10 @@ class FakeStartAuthenticationFlowUseCase { coEvery { instance.execute(config) } returns result } + fun givenErrors(config: HomeServerConnectionConfig, error: Throwable) { + coEvery { instance.execute(config) } throws error + } + fun givenHomeserverUnavailable(config: HomeServerConnectionConfig) { coEvery { instance.execute(config) } throws aHomeserverUnavailableError() } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt index 0f44976ab3..8437401294 100644 --- a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt +++ b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt @@ -18,6 +18,7 @@ package im.vector.app.test.fixtures import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.network.ssl.Fingerprint import java.net.UnknownHostException import javax.net.ssl.HttpsURLConnection @@ -38,3 +39,5 @@ fun aLoginEmailUnknownError() = Failure.ServerError( ) fun aHomeserverUnavailableError() = Failure.NetworkConnection(UnknownHostException()) + +fun anUnrecognisedCertificateError() = Failure.UnrecognizedCertificateFailure("a-url", Fingerprint(ByteArray(1), Fingerprint.HashType.SHA1))