diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 933c442501..be175c0436 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: with: name: vector-${{ matrix.target }}-debug path: | - vector/build/outputs/apk/*/debug/*.apk + vector-app/build/outputs/apk/*/debug/*.apk release: name: Build unsigned GPlay APKs @@ -65,7 +65,7 @@ jobs: with: name: vector-gplay-release-unsigned path: | - vector/build/outputs/apk/*/release/*.apk + vector-app/build/outputs/apk/*/release/*.apk exodus: runs-on: ubuntu-latest diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index d36f2d0765..8a892b9b15 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -11,7 +11,7 @@ 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: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 544d9081f8..da70d13a86 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -7,7 +7,7 @@ on: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false + GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -XX:MaxPermSize=512m -Dkotlin.daemon.jvm.options="-Xmx2g" -Dkotlin.incremental=false CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon jobs: @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v3 - name: Run knit run: | - ./gradlew knitCheck + ./gradlew knitCheck $CI_GRADLE_ARG_PROPERTIES # Check the project: ktlint, detekt, lint lint: @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v3 - name: Run ktlint run: | - ./gradlew ktlintCheck --continue + ./gradlew ktlintCheck $CI_GRADLE_ARG_PROPERTIES --continue - name: Run detekt if: always() run: | @@ -49,8 +49,8 @@ jobs: - name: Run lint # Not always, if ktlint or detekt fail, avoid running the long lint check. run: | - ./gradlew lintGplayRelease $CI_GRADLE_ARG_PROPERTIES - ./gradlew lintFdroidRelease $CI_GRADLE_ARG_PROPERTIES + ./gradlew vector-app:lintGplayRelease $CI_GRADLE_ARG_PROPERTIES + ./gradlew vector-app:lintFdroidRelease $CI_GRADLE_ARG_PROPERTIES - name: Upload reports if: always() uses: actions/upload-artifact@v3 @@ -66,7 +66,7 @@ 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: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2a89ed3040..cd7e26f3cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,9 +1,9 @@ name: Test on: - pull_request: {} + pull_request: { } push: - branches: [main, develop] + branches: [ main, develop ] # Enrich gradle.properties for CI/CD env: @@ -48,11 +48,12 @@ jobs: emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true emulator-build: 7425822 - script: | + script: | + ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES -# NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure' + # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure' - name: Run all the codecoverage tests at once (retry if emulator failed) uses: reactivecircus/android-emulator-runner@v2 if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded. @@ -65,15 +66,19 @@ jobs: disable-animations: true emulator-build: 7425822 script: | + ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES - - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES - if: always() # we may have failed a previous step and retried, that's OK + + # we may have failed a previous step and retried, that's OK + - name: Publish results to Sonar env: GITHUB_TOKEN: ${{ secrets.SONARQUBE_GITHUB_API_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} + if: ${{ always() && env.GITHUB_TOKEN != '' && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }} + run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES - name: Format unit test results if: always() diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index f478d2bd7b..174e3c54c0 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -142,32 +142,6 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc2KCw" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} - - move_threads_issues: - name: A-Threads to Thread board - runs-on: ubuntu-latest - # Skip in forks - if: > - github.repository == 'vector-im/element-android' && - contains(github.event.issue.labels.*.name, 'A-Threads') - steps: - - uses: octokit/graphql-action@v2.x - with: - headers: '{"GraphQL-Features": "projects_next_graphql"}' - query: | - mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { - id - } - } - } - projectid: ${{ env.PROJECT_ID }} - contentid: ${{ github.event.issue.node_id }} - env: - PROJECT_ID: "PN_kwDOAM0swc0rRA" - GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} - move_message_bubbles_issues: name: A-Message-Bubbles to Message bubbles board runs-on: ubuntu-latest diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 6cde154370..e762102226 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -27,7 +27,7 @@ jobs: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: project: Android App Team - column: P1 + column: Important Issues & Topics (P1) repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }} P1_issues_to_crypto_team_workboard: diff --git a/CHANGES.md b/CHANGES.md index 4615ec8ff0..518bbd8b67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,69 @@ +Changes in Element v1.4.36 (2022-09-10) +======================================= + +New App Layout can be enabled in the Labs settings. Please give it a try! + +Features ✨ +---------- + - Adds New App Layout into Labs ([#7038](https://github.com/vector-im/element-android/issues/7038)) + - Try to detect devices that lack Opus encoder support, use bundled libopus library for those. ([#7010](https://github.com/vector-im/element-android/issues/7010)) + - Suggest @room when @channel, @everyone, or @here is typed in composer ([#6529](https://github.com/vector-im/element-android/issues/6529)) + +Bugfixes 🐛 +---------- + - Fix long incremental sync. ([#6917](https://github.com/vector-im/element-android/issues/6917)) + - Fix push with FCM ([#7068](https://github.com/vector-im/element-android/issues/7068)) + - FTUE - Fixes optional email registration step always being mandatory ([#6969](https://github.com/vector-im/element-android/issues/6969)) + - Fixes /addToSpace and /joinSpace commands showing invalid syntax warnings ([#6844](https://github.com/vector-im/element-android/issues/6844)) + - Fix low occurrence crashes. ([#6967](https://github.com/vector-im/element-android/issues/6967)) + - Fix crash when opening an unknown room ([#6978](https://github.com/vector-im/element-android/issues/6978)) + - Fix crash on PIN code settings screen. ([#6979](https://github.com/vector-im/element-android/issues/6979)) + - Fix autoplayed animated stickers ([#6982](https://github.com/vector-im/element-android/issues/6982)) + - Catch race condition crash in voice recording ([#6989](https://github.com/vector-im/element-android/issues/6989)) + - Fix invite to room when in a space buttons not working. ([#7054](https://github.com/vector-im/element-android/issues/7054)) + +In development 🚧 +---------------- + - Create DM room only on first message - Create the DM and navigate to the new room after sending an event ([#5525](https://github.com/vector-im/element-android/issues/5525)) + - [App Layout] New empty states for home screen ([#6835](https://github.com/vector-im/element-android/issues/6835)) + - [App Layout] Bottom navigation tabs are removed for new home screen ([#6565](https://github.com/vector-im/element-android/issues/6565)) + - [App Layout] fixed space switching dialog measured with wrong height sometimes ([#6750](https://github.com/vector-im/element-android/issues/6750)) + - [App Layout] Fabs doesn't go off screen anymore ([#6765](https://github.com/vector-im/element-android/issues/6765)) + - [New Layout] Adds back navigation through spaces ([#6877](https://github.com/vector-im/element-android/issues/6877)) + - [App Layout] new room invites screen ([#6889](https://github.com/vector-im/element-android/issues/6889)) + - [App Layout] - Invites now show empty screen after you reject last invite ([#6876](https://github.com/vector-im/element-android/issues/6876)) + - [App Layout] - space switcher now has empty state ([#6754](https://github.com/vector-im/element-android/issues/6754)) + - [App Layout] - Improves Developer Mode Debug Button UX and adds it to New App Layout ([#6871](https://github.com/vector-im/element-android/issues/6871)) + - [New Layout] Changes space sheet to accordion-style with expandable subspaces ([#6907](https://github.com/vector-im/element-android/issues/6907)) + - [New Layout] Adds space invites ([#6924](https://github.com/vector-im/element-android/issues/6924)) + - [App Layout] fixed invites count badge bottom margin on a home screen ([#6947](https://github.com/vector-im/element-android/issues/6947)) + - [New Layout] Improves talkback accessibility ([#7016](https://github.com/vector-im/element-android/issues/7016)) + - [New Layout] Changes space icon in fab and in release notes screen ([#7039](https://github.com/vector-im/element-android/issues/7039)) + - [New Layout] Adds header to spaces bottom sheet ([#7040](https://github.com/vector-im/element-android/issues/7040)) + - [App Layout] New App Layout is enabled by default (Edit: has to be enabled in Labs) ([#6958](https://github.com/vector-im/element-android/issues/6958)) + - [App Layout] Obsolete settings are not shown when App Layout flag is enabled ([#6646](https://github.com/vector-im/element-android/issues/6646)) + - [Devices Management] Session overview screen ([#6961](https://github.com/vector-im/element-android/issues/6961)) + - [Devices Management] Refactor some code to improve testability ([#7043](https://github.com/vector-im/element-android/issues/7043)) + - [Device Manager] Current Session Section ([#6902](https://github.com/vector-im/element-android/issues/6902)) + - [Device Manager] Other Sessions Section ([#6945](https://github.com/vector-im/element-android/issues/6945)) + - [Device Manager] Render Security Recommendations ([#6964](https://github.com/vector-im/element-android/issues/6964)) + +Improved Documentation 📚 +------------------------ + - Clarify that setting up a FCM Rewrite Proxy is not necessary for use of the UnifiedPush FCM distributor. ([#6727](https://github.com/vector-im/element-android/issues/6727)) + +Other changes +------------- + - Increase sticker size ([#6982](https://github.com/vector-im/element-android/issues/6982)) + - Focus input field when editing homeserver address to speed up login and registration. ([#6926](https://github.com/vector-im/element-android/issues/6926)) + - Log basic Http information in production. ([#6925](https://github.com/vector-im/element-android/issues/6925)) + - Converts the vector module to a library with a parent vector-app application module ([#6407](https://github.com/vector-im/element-android/issues/6407)) + - Creates a dedicated strings module ([#3955](https://github.com/vector-im/element-android/issues/3955)) + - Remove FragmentModule and the Fragment factory. No need to Inject the constructor on your Fragment, just add @AndroidEntryPoint annotation and @Inject class members. ([#6894](https://github.com/vector-im/element-android/issues/6894)) + - Small refactor of UnifiedPushHelper ([#6936](https://github.com/vector-im/element-android/issues/6936)) + - CI: only run sonarqube task when token is known ([#7057](https://github.com/vector-im/element-android/issues/7057)) + + Changes in Element v1.4.34 (2022-08-23) ======================================= diff --git a/build.gradle b/build.gradle index 38cbc3af0f..a40790d441 100644 --- a/build.gradle +++ b/build.gradle @@ -28,10 +28,11 @@ buildscript { 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 "com.likethesalad.android:stem-plugin:2.2.2" + 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" + classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -39,12 +40,12 @@ buildscript { plugins { // ktlint Plugin - id "org.jlleitschuh.gradle.ktlint" version "10.3.0" + id "org.jlleitschuh.gradle.ktlint" version "11.0.0" // Detekt id "io.gitlab.arturbosch.detekt" version "1.21.0" // Dependency Analysis - id 'com.autonomousapps.dependency-analysis' version "1.12.0" + id 'com.autonomousapps.dependency-analysis' version "1.13.1" } // https://github.com/jeremylong/DependencyCheck diff --git a/coverage.gradle b/coverage.gradle index f335ed8063..716f9b7cc7 100644 --- a/coverage.gradle +++ b/coverage.gradle @@ -28,8 +28,8 @@ def initializeReport(report, projects, classExcludes) { report.executionData { fileTree(rootProject.rootDir.absolutePath).include( - "**/build/**/*.exec", - "**/build/outputs/code_coverage/**/coverage.ec", + "**/build/**/*.exec", + "**/build/outputs/code_coverage/**/coverage.ec", ) } report.reports { @@ -74,18 +74,18 @@ def collectProjects(predicate) { task generateCoverageReport(type: JacocoReport) { outputs.upToDateWhen { false } rootProject.apply plugin: 'jacoco' - def projects = collectProjects { ['vector', 'matrix-sdk-android'].contains(it.name) } + def projects = collectProjects { ['vector-app', 'vector', 'matrix-sdk-android'].contains(it.name) } initializeReport(it, projects, excludes) } task unitTestsWithCoverage(type: GradleBuild) { // the 7.1.3 android gradle plugin has a bug where enableTestCoverage generates invalid coverage - startParameter.projectProperties.coverage = [enableTestCoverage: false] + startParameter.projectProperties.coverage = [enableTestCoverage: false] tasks = [':vector:testGplayDebugUnitTest', ':matrix-sdk-android:testDebugUnitTest'] } task instrumentationTestsWithCoverage(type: GradleBuild) { - startParameter.projectProperties.coverage = [enableTestCoverage: true] + startParameter.projectProperties.coverage = [enableTestCoverage: true] startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui' - tasks = [':vector:connectedGplayDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest'] + tasks = [':vector-app:connectedGplayDebugAndroidTest', ':vector:connectedGplayDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest'] } diff --git a/dependencies.gradle b/dependencies.gradle index 5083dd4556..3759763fb7 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.157.0" +def flipper = "0.163.0" def epoxy = "4.6.2" def mavericks = "2.7.0" def glide = "4.13.2" @@ -85,6 +85,8 @@ ext.libs = [ 'material' : "com.google.android.material:material:1.6.1", 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", + // Phone number https://github.com/google/libphonenumber + 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.12.54" ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", @@ -104,6 +106,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/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/docs/nightly_build.md b/docs/nightly_build.md index 7750e0466a..77cc676c7f 100644 --- a/docs/nightly_build.md +++ b/docs/nightly_build.md @@ -47,7 +47,7 @@ git checkout develop mv towncrier.toml towncrier.toml.bak sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml rm towncrier.toml.bak -yes n | towncrier --version nightly +yes n | towncrier build --version nightly ./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES ``` diff --git a/docs/unifiedpush.md b/docs/unifiedpush.md index 2851644e66..9f44c6b2f9 100644 --- a/docs/unifiedpush.md +++ b/docs/unifiedpush.md @@ -18,7 +18,7 @@ The recently started UnifiedPush project is an Android protocol and library for The *F-Droid* and *Gplay* flavors of Element Android support UnifiedPush, so the user can use any distributor installed on their devices. This would make it possible to have push notifications without depending on Google services or libraries. Currently, the main distributors are [ntfy](https://ntfy.sh) which does not require any setup (like manual registration) to use the public server and [NextPush](https://github.com/UP-NextPush/android), available as a nextcloud application. -The *Gplay* variant uses a UnifiedPush library which basically embed a FCM distributor built into the application (so a user doesn't need to do anything other than install the app to get FCM notifications). This variant uses Google Services to receive notifications if the user has not installed any distributor. +The *Gplay* variant uses a UnifiedPush library which basically embed a FCM distributor built into the application (so a user doesn't need to do anything other than install the app to get FCM notifications). This variant uses Google Services to receive notifications if the user has not installed any distributor. A [FCM Rewrite Proxy](https://unifiedpush.org/developers/embedded_fcm/#fcm-rewrite-proxy) is not required for Element Android's implementation of the FCM distributor - it will work with an existing Matrix push provider, such as [Sygnal](https://github.com/matrix-org/sygnal). The *F-Droid* variant does not use this library to avoid any proprietary blob. It will use a polling service if the user has not installed any distributor. diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104320.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104320.txt new file mode 100644 index 0000000000..578549ce6c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104340.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104340.txt new file mode 100644 index 0000000000..578549ce6c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104260.txt b/fastlane/metadata/android/de-DE/changelogs/40104260.txt new file mode 100644 index 0000000000..0298255dad --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104260.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: UnifiedPush wird unterstützt; Dies ermöglicht Push-Benachrichtigungen ohne FCM. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104270.txt b/fastlane/metadata/android/de-DE/changelogs/40104270.txt new file mode 100644 index 0000000000..50b5647608 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104270.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Verschiedene Fehlerbehebungen und Stabilitätsverbesserungen. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104280.txt b/fastlane/metadata/android/de-DE/changelogs/40104280.txt new file mode 100644 index 0000000000..50b5647608 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104280.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Verschiedene Fehlerbehebungen und Stabilitätsverbesserungen. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104300.txt b/fastlane/metadata/android/de-DE/changelogs/40104300.txt new file mode 100644 index 0000000000..c906fedd54 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Verbessertes Anmelde- und Registrierungserlebnis. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104310.txt b/fastlane/metadata/android/de-DE/changelogs/40104310.txt new file mode 100644 index 0000000000..c906fedd54 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Verbessertes Anmelde- und Registrierungserlebnis. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104320.txt b/fastlane/metadata/android/de-DE/changelogs/40104320.txt new file mode 100644 index 0000000000..50b5647608 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Verschiedene Fehlerbehebungen und Stabilitätsverbesserungen. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104340.txt b/fastlane/metadata/android/de-DE/changelogs/40104340.txt new file mode 100644 index 0000000000..50b5647608 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Verschiedene Fehlerbehebungen und Stabilitätsverbesserungen. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104360.txt b/fastlane/metadata/android/en-US/changelogs/40104360.txt new file mode 100644 index 0000000000..da03f28760 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104360.txt @@ -0,0 +1,3 @@ +New App Layout can be enabled in the Labs settings. Please give it a try! +Fix issues about missing notification, and long incremental sync. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104320.txt b/fastlane/metadata/android/et/changelogs/40104320.txt new file mode 100644 index 0000000000..1df5ac4176 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: erinevate vigade parandused ja stabiilsust edendavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40104340.txt b/fastlane/metadata/android/et/changelogs/40104340.txt new file mode 100644 index 0000000000..1df5ac4176 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: erinevate vigade parandused ja stabiilsust edendavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104320.txt b/fastlane/metadata/android/fa/changelogs/40104320.txt new file mode 100644 index 0000000000..29efb95925 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104320.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رفع اشکال‌های مختلف و بهبودهای پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104340.txt b/fastlane/metadata/android/fa/changelogs/40104340.txt new file mode 100644 index 0000000000..29efb95925 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104340.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رفع اشکال‌های مختلف و بهبودهای پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104320.txt b/fastlane/metadata/android/fr-FR/changelogs/40104320.txt new file mode 100644 index 0000000000..fe61fd021c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104340.txt b/fastlane/metadata/android/fr-FR/changelogs/40104340.txt new file mode 100644 index 0000000000..fe61fd021c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104100.txt b/fastlane/metadata/android/hu-HU/changelogs/40104100.txt new file mode 100644 index 0000000000..97746bdcc6 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Görgetés a hangüzenetben. Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104110.txt b/fastlane/metadata/android/hu-HU/changelogs/40104110.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104110.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104120.txt b/fastlane/metadata/android/hu-HU/changelogs/40104120.txt new file mode 100644 index 0000000000..79df59cf5e --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Lehetővé teszi a felhasználók számára, hogy offline jelenjenek meg, és audio lejátszót ad hozzá a hangmellékletekhez. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104130.txt b/fastlane/metadata/android/hu-HU/changelogs/40104130.txt new file mode 100644 index 0000000000..79df59cf5e --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Lehetővé teszi a felhasználók számára, hogy offline jelenjenek meg, és audio lejátszót ad hozzá a hangmellékletekhez. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104140.txt b/fastlane/metadata/android/hu-HU/changelogs/40104140.txt new file mode 100644 index 0000000000..2ea8acda97 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: A figyelmen kívül hagyott felhasználók kezelésének javítása. Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104160.txt b/fastlane/metadata/android/hu-HU/changelogs/40104160.txt new file mode 100644 index 0000000000..d92018adb0 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: A titkosított üzenetek jobb kezelése. Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104180.txt b/fastlane/metadata/android/hu-HU/changelogs/40104180.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104180.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104190.txt b/fastlane/metadata/android/hu-HU/changelogs/40104190.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104190.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104200.txt b/fastlane/metadata/android/hu-HU/changelogs/40104200.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104200.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104220.txt b/fastlane/metadata/android/hu-HU/changelogs/40104220.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104220.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104230.txt b/fastlane/metadata/android/hu-HU/changelogs/40104230.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104230.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104240.txt b/fastlane/metadata/android/hu-HU/changelogs/40104240.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104240.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104250.txt b/fastlane/metadata/android/hu-HU/changelogs/40104250.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104250.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104260.txt b/fastlane/metadata/android/hu-HU/changelogs/40104260.txt new file mode 100644 index 0000000000..54d881323f --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104260.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: UnifiedPush használata, és lehetővé teszi a felhasználó számára, hogy FCM nélkül tolja. +Teljes változásnapló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104270.txt b/fastlane/metadata/android/hu-HU/changelogs/40104270.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104270.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104280.txt b/fastlane/metadata/android/hu-HU/changelogs/40104280.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104280.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104300.txt b/fastlane/metadata/android/hu-HU/changelogs/40104300.txt new file mode 100644 index 0000000000..9882e09368 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Lehetővé teszi a továbbfejlesztett bejelentkezési és regisztrációs utakat. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104310.txt b/fastlane/metadata/android/hu-HU/changelogs/40104310.txt new file mode 100644 index 0000000000..9882e09368 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Lehetővé teszi a továbbfejlesztett bejelentkezési és regisztrációs utakat. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104320.txt b/fastlane/metadata/android/hu-HU/changelogs/40104320.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104340.txt b/fastlane/metadata/android/hu-HU/changelogs/40104340.txt new file mode 100644 index 0000000000..25772a8ea1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Főbb változások ebben a verzióban: Különböző hibajavítások és stabilitásjavítások. +Teljes változásjegyzék: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104320.txt b/fastlane/metadata/android/id/changelogs/40104320.txt new file mode 100644 index 0000000000..1017951d47 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40104340.txt b/fastlane/metadata/android/id/changelogs/40104340.txt new file mode 100644 index 0000000000..1017951d47 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104320.txt b/fastlane/metadata/android/it-IT/changelogs/40104320.txt new file mode 100644 index 0000000000..556a6fc7ea --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: varie correzioni di errori e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104340.txt b/fastlane/metadata/android/it-IT/changelogs/40104340.txt new file mode 100644 index 0000000000..556a6fc7ea --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: varie correzioni di errori e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100100.txt b/fastlane/metadata/android/pl-PL/changelogs/40100100.txt new file mode 100644 index 0000000000..7960e8a961 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100100.txt @@ -0,0 +1,2 @@ +Ta nowa wersja zawiera głównie poprawki błędów i ulepszenia. Wysyłanie wiadomości jest teraz znacznie szybsze. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100110.txt b/fastlane/metadata/android/pl-PL/changelogs/40100110.txt new file mode 100644 index 0000000000..ce95f4ab46 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100110.txt @@ -0,0 +1,2 @@ +Ta nowa wersja zawiera głównie ulepszenia interfejsu i doświadczenia użytkownika. Teraz możesz zapraszać znajomych i bardzo szybko tworzyć bezpośrednie rozmowy, skanując kody QR. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.11 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100120.txt b/fastlane/metadata/android/pl-PL/changelogs/40100120.txt new file mode 100644 index 0000000000..5b79301d97 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100120.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Podgląd URL, nowa klawiatura Emoji, nowe możliwości ustawień pokoju i śnieg na Boże Narodzenie! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.12 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100130.txt b/fastlane/metadata/android/pl-PL/changelogs/40100130.txt new file mode 100644 index 0000000000..6a8f3ac662 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100130.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Podgląd URL, nowa klawiatura Emoji, nowe możliwości ustawień pokoju i śnieg na Boże Narodzenie! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100140.txt b/fastlane/metadata/android/pl-PL/changelogs/40100140.txt new file mode 100644 index 0000000000..084f75dbf7 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100140.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Edytuj uprawnienia do pokoju, automatyczny jasny/ciemny motyw i kilka poprawek błędów. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100150.txt b/fastlane/metadata/android/pl-PL/changelogs/40100150.txt new file mode 100644 index 0000000000..f2cb216b06 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100150.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Obsługa logowania społecznościowego. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40100160.txt b/fastlane/metadata/android/pl-PL/changelogs/40100160.txt new file mode 100644 index 0000000000..354d794070 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40100160.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Obsługa logowania społecznościowego. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.0.15 i https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104300.txt b/fastlane/metadata/android/pl-PL/changelogs/40104300.txt new file mode 100644 index 0000000000..d50c5a3f0e --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Włącza ulepszone podróże w logowaniu i przy rejestracji. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104310.txt b/fastlane/metadata/android/pl-PL/changelogs/40104310.txt new file mode 100644 index 0000000000..d50c5a3f0e --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Włącza ulepszone podróże w logowaniu i przy rejestracji. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104320.txt b/fastlane/metadata/android/pl-PL/changelogs/40104320.txt new file mode 100644 index 0000000000..005f2d2b71 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104340.txt b/fastlane/metadata/android/pl-PL/changelogs/40104340.txt new file mode 100644 index 0000000000..e175a61725 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Rozmaite poprawki błędów i usprawnienia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104320.txt b/fastlane/metadata/android/pt-BR/changelogs/40104320.txt new file mode 100644 index 0000000000..6e11e92579 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104340.txt b/fastlane/metadata/android/pt-BR/changelogs/40104340.txt new file mode 100644 index 0000000000..6e11e92579 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104320.txt b/fastlane/metadata/android/sk/changelogs/40104320.txt new file mode 100644 index 0000000000..50670f18c2 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40104340.txt b/fastlane/metadata/android/sk/changelogs/40104340.txt new file mode 100644 index 0000000000..50670f18c2 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104320.txt b/fastlane/metadata/android/uk/changelogs/40104320.txt new file mode 100644 index 0000000000..9664c615c1 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Усунуто різні вади й поліпшено стабільність. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40104340.txt b/fastlane/metadata/android/uk/changelogs/40104340.txt new file mode 100644 index 0000000000..9664c615c1 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Усунуто різні вади й поліпшено стабільність. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-CN/changelogs/40104080.txt b/fastlane/metadata/android/zh-CN/changelogs/40104080.txt index 44d178cfce..09b5bf2cd3 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/40104080.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/40104080.txt @@ -1,2 +1,2 @@ -此版本的主要变化:Thread timeline are now live and faster. 多个bug修复及稳定性改进。 +此版本的主要变化:消息列时间线 are now live and faster. 多个bug修复及稳定性改进。 完整更改日志:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-CN/changelogs/40104300.txt b/fastlane/metadata/android/zh-CN/changelogs/40104300.txt new file mode 100644 index 0000000000..f6e5f8ddfc --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/40104300.txt @@ -0,0 +1,2 @@ +此版本的主要变化:启用改善的登录与注册流程。 +完整更改日志:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-CN/changelogs/40104310.txt b/fastlane/metadata/android/zh-CN/changelogs/40104310.txt new file mode 100644 index 0000000000..f6e5f8ddfc --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/40104310.txt @@ -0,0 +1,2 @@ +此版本的主要变化:启用改善的登录与注册流程。 +完整更改日志:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-CN/changelogs/40104320.txt b/fastlane/metadata/android/zh-CN/changelogs/40104320.txt new file mode 100644 index 0000000000..be6a485fb4 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/40104320.txt @@ -0,0 +1,2 @@ +此版本的主要变化:多个bug修复与稳定性改善。 +完整更改日志:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104320.txt b/fastlane/metadata/android/zh-TW/changelogs/40104320.txt new file mode 100644 index 0000000000..4bcca9a0b8 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104320.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104340.txt b/fastlane/metadata/android/zh-TW/changelogs/40104340.txt new file mode 100644 index 0000000000..4bcca9a0b8 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104340.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/ui-strings/build.gradle b/library/ui-strings/build.gradle new file mode 100644 index 0000000000..860fc3c980 --- /dev/null +++ b/library/ui-strings/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'com.likethesalad.stem-library' +} + +android { + compileSdk versions.compileSdk + defaultConfig { + minSdk versions.minSdk + targetSdk versions.targetSdk + } + + compileOptions { + sourceCompatibility versions.sourceCompat + targetCompatibility versions.targetCompat + } + + kotlinOptions { + jvmTarget = "11" + } +} diff --git a/library/ui-strings/src/main/AndroidManifest.xml b/library/ui-strings/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..deff03ee0a --- /dev/null +++ b/library/ui-strings/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/vector/src/main/res/values-ab/strings.xml b/library/ui-strings/src/main/res/values-ab/strings.xml similarity index 100% rename from vector/src/main/res/values-ab/strings.xml rename to library/ui-strings/src/main/res/values-ab/strings.xml diff --git a/vector/src/main/res/values-ang/strings.xml b/library/ui-strings/src/main/res/values-ang/strings.xml similarity index 100% rename from vector/src/main/res/values-ang/strings.xml rename to library/ui-strings/src/main/res/values-ang/strings.xml diff --git a/vector/src/main/res/values-ar/strings.xml b/library/ui-strings/src/main/res/values-ar/strings.xml similarity index 100% rename from vector/src/main/res/values-ar/strings.xml rename to library/ui-strings/src/main/res/values-ar/strings.xml diff --git a/vector/src/main/res/values-ar/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ar/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ar/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ar/strings_no_weblate.xml diff --git a/vector/src/main/res/values-az/strings.xml b/library/ui-strings/src/main/res/values-az/strings.xml similarity index 100% rename from vector/src/main/res/values-az/strings.xml rename to library/ui-strings/src/main/res/values-az/strings.xml diff --git a/vector/src/main/res/values-az/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-az/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-az/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-az/strings_no_weblate.xml diff --git a/vector/src/main/res/values-b+sr+Latn/strings.xml b/library/ui-strings/src/main/res/values-b+sr+Latn/strings.xml similarity index 100% rename from vector/src/main/res/values-b+sr+Latn/strings.xml rename to library/ui-strings/src/main/res/values-b+sr+Latn/strings.xml diff --git a/vector/src/main/res/values-b+sr+Latn/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-b+sr+Latn/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-b+sr+Latn/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-b+sr+Latn/strings_no_weblate.xml diff --git a/vector/src/main/res/values-bg/strings.xml b/library/ui-strings/src/main/res/values-bg/strings.xml similarity index 100% rename from vector/src/main/res/values-bg/strings.xml rename to library/ui-strings/src/main/res/values-bg/strings.xml diff --git a/vector/src/main/res/values-bg/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-bg/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-bg/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-bg/strings_no_weblate.xml diff --git a/vector/src/main/res/values-bn-rBD/strings.xml b/library/ui-strings/src/main/res/values-bn-rBD/strings.xml similarity index 100% rename from vector/src/main/res/values-bn-rBD/strings.xml rename to library/ui-strings/src/main/res/values-bn-rBD/strings.xml diff --git a/vector/src/main/res/values-bn-rBD/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-bn-rBD/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-bn-rBD/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-bn-rBD/strings_no_weblate.xml diff --git a/vector/src/main/res/values-bn-rIN/strings.xml b/library/ui-strings/src/main/res/values-bn-rIN/strings.xml similarity index 100% rename from vector/src/main/res/values-bn-rIN/strings.xml rename to library/ui-strings/src/main/res/values-bn-rIN/strings.xml diff --git a/vector/src/main/res/values-bn-rIN/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-bn-rIN/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-bn-rIN/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-bn-rIN/strings_no_weblate.xml diff --git a/vector/src/main/res/values-bn/strings.xml b/library/ui-strings/src/main/res/values-bn/strings.xml similarity index 100% rename from vector/src/main/res/values-bn/strings.xml rename to library/ui-strings/src/main/res/values-bn/strings.xml diff --git a/vector/src/main/res/values-bs/strings.xml b/library/ui-strings/src/main/res/values-bs/strings.xml similarity index 100% rename from vector/src/main/res/values-bs/strings.xml rename to library/ui-strings/src/main/res/values-bs/strings.xml diff --git a/vector/src/main/res/values-bs/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-bs/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-bs/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-bs/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml similarity index 98% rename from vector/src/main/res/values-ca/strings.xml rename to library/ui-strings/src/main/res/values-ca/strings.xml index c98333ffdb..13a5b6c119 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -2600,4 +2600,40 @@ \nPotser el servidor utilitzat no està configurat per mostrar mapes. Obre configuració Tots els xats + Preferències de disseny + Explora sales + Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis. + Altres sessions + Sessions + Obre la llista d\'espais + Crea un nou xat o sala + Gent + Preferits + No llegits + Tot + A - Z + Activitat + Ordena per + Mostra recents + Mostra filtres + Mostra totes les sessions (V2, WIP) + Crea sala + Inicia xat + Verifica la teva sessió actual per a missatges segurs millorats. + Verificada · Última activitat %1$s + No verificada · Última activitat %1$s + Veure-ho tot (%1$d) + Sessió actual + Veure detalls + Verifica sessió + La sessió actual està llesta per la missatgeria segura. + Sessió no verificada + Sessió verificada + Tipus de dispositiu desconegut + Ordinador + Web + Mòbil + Aquesta sala no s\'ha trobat. +\nTorna-ho a provar més tard.%s + Invitacions diff --git a/vector/src/main/res/values-ca/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ca/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ca/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ca/strings_no_weblate.xml diff --git a/vector/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml similarity index 98% rename from vector/src/main/res/values-cs/strings.xml rename to library/ui-strings/src/main/res/values-cs/strings.xml index 960c5e0a40..b7bfeac444 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2650,4 +2650,40 @@ \nTento domovský server nemusí být nakonfigurován pro zobrazování map. Otevřít nastavení Všechny konverzace + Zobrazit všechny relace (V2, WIP) + V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte. + Ostatní relace + Relace + Seznam otevřených prostorů + Vytvořit novou konverzaci nebo místnost + Lidé + Oblíbené + Nepřečtené + Všechny + A - Z + Aktivita + Seřadit podle + Zobrazit nedávné + Zobrazit filtry + Předvolby uspořádání + Prozkoumat místnosti + Vytvořit místnost + Zahájit konverzaci + Neověřeno · Poslední aktivita %1$s + Ověřeno · Poslední aktivita %1$s + Zobrazit všechny (%1$d) + Aktuální relace + Zobrazit podrobnosti + Ověřit relaci + Ověřte svou aktuální relaci pro vylepšené zabezpečené zasílání zpráv. + Vaše aktuální relace je připravena pro bezpečné zasílání zpráv. + Neověřená relace + Ověřená relace + Neznámý typ zařízení + Desktop + Web + Mobil + Je nám líto, tato místnost nebyla nalezena. +\nZkuste to prosím později.%s + Pozvánky diff --git a/vector/src/main/res/values-cs/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-cs/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-cs/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-cs/strings_no_weblate.xml diff --git a/vector/src/main/res/values-cy/strings.xml b/library/ui-strings/src/main/res/values-cy/strings.xml similarity index 100% rename from vector/src/main/res/values-cy/strings.xml rename to library/ui-strings/src/main/res/values-cy/strings.xml diff --git a/vector/src/main/res/values-cy/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-cy/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-cy/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-cy/strings_no_weblate.xml diff --git a/vector/src/main/res/values-da/strings.xml b/library/ui-strings/src/main/res/values-da/strings.xml similarity index 100% rename from vector/src/main/res/values-da/strings.xml rename to library/ui-strings/src/main/res/values-da/strings.xml diff --git a/vector/src/main/res/values-da/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-da/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-da/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-da/strings_no_weblate.xml diff --git a/vector/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml similarity index 97% rename from vector/src/main/res/values-de/strings.xml rename to library/ui-strings/src/main/res/values-de/strings.xml index 40473e9e27..8e502a6392 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -320,7 +320,7 @@ Zu ungelesenen Nachrichten springen Raum verlassen Raum wirklich verlassen\? - DIREKT-CHATS + Direktnachrichten Einladen Verbannen Verbannung aufheben @@ -543,21 +543,21 @@ Du hast aktuell keine Stickerpakete aktiviert. \n \nMöchtest du welche hinzufügen\? - Account deaktivieren - Meinen Account deaktivieren + Konto deaktivieren + Mein Konto deaktivieren Sende Analysedaten ${app_name} sammelt anonyme Analysedaten um uns zu helfen, die App zu verbessern. Ein benötigter Parameter fehlt. Um %1$s weiter zu verwenden, musst die Geschäftsbedingungen begutachten und ihnen zustimmen. Jetzt prüfen - Account deaktivieren + Konto deaktivieren Dies wird dein Konto permanent unbenutzbar machen. Du wirst dich nicht anmelden können und keiner wird denselben Nutzernamen erneut registrieren können. Du verlässt automatisch alle Räume, in denen du bist, und deine Kontoangaben werden vom Identitätsserver gelöscht. Diese Aktion ist unumkehrbar. \n \nDie Deaktivierung deines Konto wird standardmäßig keine deiner gesendeten Nachrichten löschen. Wenn du möchtest, dass auch deine Nachrichten gelöscht werden, wähle zusätzlich die Option unten. \n \nDie Sichtbarkeit deiner Nachrichten ist ähnlich wie bei E-Mails: Wenn deine Nachrichten gelöscht werden, bedeutet dies, dass von dir verschickte Nachrichten nicht mit neuen oder unregistrierten Nutzer geteilt werden. Aber registrierte Nutzer, die bereits Zugang zu diesen Nachrichten haben, behalten weiterhin Zugriff auf ihre Kopie. Bitte alle Nachrichten, die ich gesendet habe, löschen, wenn mein Konto deaktiviert wird (Warnung: Unterhaltungen werden für zukünftige Nutzer unvollständig erscheinen) - Account deaktivieren + Konto deaktivieren Download Schlüssel von deinen anderen Sitzungen erneut anfordern. Bitte öffne ${app_name} auf einem anderen Gerät, das die Nachricht entschlüsseln kann, damit es die Schlüssel an diese Sitzung senden kann. @@ -604,7 +604,7 @@ zusammenklappen %1$s: %2$s +%d - Entfernen + Aus Unterhaltung entfernen Linkvorschau im Chat aktivieren, falls dein Homeserver diese Funktion unterstützt. Schreibbenachrichtigungen senden Lasse andere Benutzer wissen, dass du tippst. @@ -834,9 +834,9 @@ \nSitzungsname: %1$s \nZuletzt gesehen: %2$s \nWenn du nicht mit einer anderen Sitzung angemeldet bist, ignoriere diese Anfrage. - Eine unverifizierte Sitzung fordert Verschlüsselungs-Schlüssel an. -\nSitzungsname: %1$s -\nZuletzt gesehen: %2$s + Eine nicht verifizierte Sitzung fordert Verschlüsselungs-Schlüssel an. +\nSitzungsname: %1$s +\nZuletzt gesehen: %2$s \nWenn du nicht eine andere Sitzung angemeldet hast, ignoriere diese Anfrage. Teilen Ignorieren @@ -1426,7 +1426,7 @@ Wähle deinen Wiederherstellungsschlüssel, gib ihn ein oder füge ihn aus der Zwischenablage ein Konnte nicht auf gesicherten Speicher zugreifen Unverschlüsselt - Verschlüsselt von einem unbekannten Gerät + Verschlüsselt von einem nicht verifiziertem Gerät Überprüfe, wo du angemeldet bist Verifiziere alle deine Sitzungen, um sicherzustellen, dass dein Konto und deine Nachrichten sicher sind Bestätige neue Anmeldung zu deinem Konto: %1$s @@ -2572,4 +2572,54 @@ Wie lautet die Adresse deines Servers\? Muss 8 oder mehr Zeichen umfassen Wähle deinen Server - + + %1$s und %2$d anderer + %1$s und %2$d andere + + A - Z + Aktivität + Sortierung + Filter anzeigen + Layouteinstellungen + Räume erkunden + Raum erstellen + Ungelesene + Personen + Schreibe deine erste Nachricht, um %s zur Konversation einzuladen + Alle Sitzungen anzeigen (V2, in Arbeit) + Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt. + Andere Sitzungen + Sitzungen + Space-Liste öffnen + Beginne ein Gespräch oder erstelle einen Raum + Favoriten + Alle + Karte laden nicht möglich +\nDieser Heimserver könnte für die Kartendarstellung nicht konfiguriert sein. + Einstellungen öffnen + Dieser QR-Code ist fehlerhaft. Bitte versuche es mit einer anderen Methode. + Du wirst deinen verschlüsselten Nachrichtenverlauf nicht abrufen können. Um neu zu beginnen, setze deine Sicherung und Verifizierungsschlüssel zurück. + Verifizierung dieses Gerätes nicht möglich + Aktualisiere deine Daten … + Standort teilen + Du musst die Berechtigung erhalten, um deinen Live-Standort mit diesem Raum zu teilen. + Dir fehlt die Berechtigung, deinen Live-Standort teilen zu dürfen + Passwort zurückgesetzt + Code erneut schicken + Ein Code wurde an %s gesendet + Bestätigungscode + %s muss dein Konto verifizieren + Gib deine Telefonnummer ein + %s muss dein Konto verifizieren + Kontakt aufnehmen + Element Matrix Services (EMS) ist ein robuster und zuverlässiger Hosting-Dienst für schnelle und sichere Echtzeitkommunikation. Erfahre mehr unter element.io/ems + Willst du deinen eigenen Server betreiben\? + Web + Mobil + Entschuldigung, dieser Raum wurde nicht gefunden. +\nBitte versuche es später erneut.%s + Einladungen + Nicht verifiziert · Letzte Aktivität %1$s + Verifiziere deine aktuelle Sitzung für besonders sichere Nachrichtenübertragung. + Nicht verifizierte Sitzung + \ No newline at end of file diff --git a/vector/src/main/res/values-de/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-de/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-de/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-de/strings_no_weblate.xml diff --git a/vector/src/main/res/values-el/strings.xml b/library/ui-strings/src/main/res/values-el/strings.xml similarity index 100% rename from vector/src/main/res/values-el/strings.xml rename to library/ui-strings/src/main/res/values-el/strings.xml diff --git a/vector/src/main/res/values-el/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-el/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-el/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-el/strings_no_weblate.xml diff --git a/vector/src/main/res/values-en-rGB/strings.xml b/library/ui-strings/src/main/res/values-en-rGB/strings.xml similarity index 100% rename from vector/src/main/res/values-en-rGB/strings.xml rename to library/ui-strings/src/main/res/values-en-rGB/strings.xml diff --git a/vector/src/main/res/values-en-rGB/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-en-rGB/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-en-rGB/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-en-rGB/strings_no_weblate.xml diff --git a/vector/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml similarity index 100% rename from vector/src/main/res/values-eo/strings.xml rename to library/ui-strings/src/main/res/values-eo/strings.xml diff --git a/vector/src/main/res/values-eo/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-eo/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-eo/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-eo/strings_no_weblate.xml diff --git a/vector/src/main/res/values-es-rMX/strings.xml b/library/ui-strings/src/main/res/values-es-rMX/strings.xml similarity index 100% rename from vector/src/main/res/values-es-rMX/strings.xml rename to library/ui-strings/src/main/res/values-es-rMX/strings.xml diff --git a/vector/src/main/res/values-es-rMX/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-es-rMX/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-es-rMX/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-es-rMX/strings_no_weblate.xml diff --git a/vector/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml similarity index 100% rename from vector/src/main/res/values-es/strings.xml rename to library/ui-strings/src/main/res/values-es/strings.xml diff --git a/vector/src/main/res/values-es/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-es/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-es/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-es/strings_no_weblate.xml diff --git a/vector/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml similarity index 98% rename from vector/src/main/res/values-et/strings.xml rename to library/ui-strings/src/main/res/values-et/strings.xml index 89460d921c..55fb9dfef0 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2591,4 +2591,40 @@ \nSee koduserver ei pruugi olla seadistatud kuvama kaarte. Ava seadistused Kõik vestlused + Näita kõiki sessioone (V2, WIP) + Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta. + Muud sessioonid + Sessionid + Ava kogukondade loend + Alusta uut vestlust või loo uus jututuba + Inimesed + Lemmikud + Lugemata + Kõik + A - Z + Aktiivsuse alusel + Järjestamisviis + Näita hiljutisi sõnumeid + Näita otsinguvalikuid + Paigutuse seadistused + Tutvu jututubadega + Loo jututuba + Alusta vestlust + Verifitseerimata · Viimati kasutusel %1$s + Verifitseeritud · Viimati kasutusel %1$s + Näita kõiki (%1$d) + Praegune sessioon + Vaata lisateavet + Verifitseeri sessioon + Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon. + Sinu praegune sessioon on valmis turvaliseks sõnumivahetuseks. + Verifitseerimata sessioon + Verifitseeritud sessioon + Tundmatu seadme tüüp + Töölauarakendus + Veebiliides + Mobiiltelefon + Vabandust, aga seda jututuba ei õnnestu leida. +\nPalun proovi hiljem uuesti.%s + Kutsed diff --git a/vector/src/main/res/values-et/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-et/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-et/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-et/strings_no_weblate.xml diff --git a/vector/src/main/res/values-eu/strings.xml b/library/ui-strings/src/main/res/values-eu/strings.xml similarity index 100% rename from vector/src/main/res/values-eu/strings.xml rename to library/ui-strings/src/main/res/values-eu/strings.xml diff --git a/vector/src/main/res/values-eu/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-eu/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-eu/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-eu/strings_no_weblate.xml diff --git a/vector/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml similarity index 98% rename from vector/src/main/res/values-fa/strings.xml rename to library/ui-strings/src/main/res/values-fa/strings.xml index c534356e92..e104225389 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2600,4 +2600,40 @@ \nشاید این کارساز خانگی برای نمایش نقشه‌ها پیکربندی نشده باشد. گشودن تنظیمات تمامی گپ‌ها + نمایش تمامی نشست‌ها (ن۲، دح‌ت) + برای امنیت بیش‌تر، نشست‌هایتان را تأیید و از هر نشستی که تشخیصش نمی‌دهید یا دیگر استفاده نمی‌کنید خارج شوید. + دیگر نشست‌ها + نشست‌ها + گشودن سیاههٔ فضاها + ایجاد اتاق یا گفت‌وگویی جدید + افراد + محبوب‌ها + نخوانده‌ها + همه + آ - ی + فعّالیت + چینش بر اساس + نمایش تازگی‌ها + نمایش پالایه‌ها + ترجیحات چینش + کاوش اتاق‌ها + ایجاد اتاق + آغاز گپ + تأیید نشده · آخرین فعّالیت %1$s + تأیید شده · آخرین فعّالیت %1$s + دیدن همه (%1$d) + نشست کنونی + دیدن جزییات + تأیید نشست + نشست کنونیتان را برای پیام‌رسانی امن بهبود یافته تأیید کنید. + نشست کنونیتان برای پیام‌رسانی امن آماده است. + نشست تأیید نشده + نشست تأیید شده + گونهٔ افزاره ناشناخته + میزکار + وب + تلفن همراه + متأسفانه این اتاق پیدا نشد. +\nلطفاً بعداً دوباره تلاش کنید.%s + دعوت‌ها diff --git a/vector/src/main/res/values-fa/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-fa/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-fa/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-fa/strings_no_weblate.xml diff --git a/vector/src/main/res/values-fi/strings.xml b/library/ui-strings/src/main/res/values-fi/strings.xml similarity index 100% rename from vector/src/main/res/values-fi/strings.xml rename to library/ui-strings/src/main/res/values-fi/strings.xml diff --git a/vector/src/main/res/values-fi/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-fi/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-fi/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-fi/strings_no_weblate.xml diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml similarity index 100% rename from vector/src/main/res/values-fr-rCA/strings.xml rename to library/ui-strings/src/main/res/values-fr-rCA/strings.xml diff --git a/vector/src/main/res/values-fr-rCA/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-fr-rCA/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-fr-rCA/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-fr-rCA/strings_no_weblate.xml diff --git a/vector/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml similarity index 98% rename from vector/src/main/res/values-fr/strings.xml rename to library/ui-strings/src/main/res/values-fr/strings.xml index c5a7a7b2b7..55b5f88134 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2600,4 +2600,40 @@ \nCe serveur d’accueil n’a peut-être pas été configuré pour afficher les cartes. Ouvrir les paramètres Toutes les conversations + Afficher toutes les sessions (V2, en cours) + Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus. + Autres sessions + Sessions + Ouvrir la liste des espaces + Créer une nouvelle conversation ou salon + Personnes + Favoris + Non lus + Tous + A - Z + Activité + Trier par + Afficher les récents + Afficher les filtres + Préférences de présentation + Parcourir les salons + Créer un salon + Commencer une discussion + Non vérifiée · Dernière activité %1$s + Vérifié · Dernière activité %1$s + Tout voir (%1$d) + Cette session + Voir les détails + Vérifier la session + Vérifiez votre session pour une sécurité renforcée de votre messagerie. + Votre session est prête pour l’envoi de messages sécurisés. + Session non vérifiée + Session vérifiée + Type de périphérique inconnu + Ordinateur + Web + Portable + Désolé, impossible de trouver ce salon. +\nVeuillez réessayer plus tard.%s + Invitations diff --git a/vector/src/main/res/values-fr/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-fr/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-fr/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-fr/strings_no_weblate.xml diff --git a/vector/src/main/res/values-fy/strings.xml b/library/ui-strings/src/main/res/values-fy/strings.xml similarity index 100% rename from vector/src/main/res/values-fy/strings.xml rename to library/ui-strings/src/main/res/values-fy/strings.xml diff --git a/vector/src/main/res/values-fy/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-fy/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-fy/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-fy/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ga/strings.xml b/library/ui-strings/src/main/res/values-ga/strings.xml similarity index 100% rename from vector/src/main/res/values-ga/strings.xml rename to library/ui-strings/src/main/res/values-ga/strings.xml diff --git a/vector/src/main/res/values-ga/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ga/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ga/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ga/strings_no_weblate.xml diff --git a/vector/src/main/res/values-gl/strings.xml b/library/ui-strings/src/main/res/values-gl/strings.xml similarity index 100% rename from vector/src/main/res/values-gl/strings.xml rename to library/ui-strings/src/main/res/values-gl/strings.xml diff --git a/vector/src/main/res/values-gl/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-gl/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-gl/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-gl/strings_no_weblate.xml diff --git a/vector/src/main/res/values-hr/strings.xml b/library/ui-strings/src/main/res/values-hr/strings.xml similarity index 100% rename from vector/src/main/res/values-hr/strings.xml rename to library/ui-strings/src/main/res/values-hr/strings.xml diff --git a/vector/src/main/res/values-hr/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-hr/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-hr/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-hr/strings_no_weblate.xml diff --git a/vector/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml similarity index 98% rename from vector/src/main/res/values-hu/strings.xml rename to library/ui-strings/src/main/res/values-hu/strings.xml index a35595fb36..af8bf26b2e 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -157,7 +157,7 @@ %s megváltoztatta a szerver ACL-eket ehhez a szobához. • IP címet hosztnévként használó szerverek tiltva vannak. • IP címet hosztnévként használó szerverek engedélyezve vannak. - • Engedélyezve vannak azok a szerverek, amik illeszkednek erre: %s + • Engedélyezve vannak azok a szerverek, amik illeszkednek erre: %s. • Tiltva vannak azok a szerverek, amik illeszkednek erre: %s Beállítottad a szerver ACL-eket ehhez a szobához. %s beállította a szerver ACL-eket ehhez a szobához. @@ -2600,4 +2600,40 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze %1$s és %2$s Minden beszélgetés + Nem ellenőrzött - Utolsó aktivitás %1$s + Ellenőrzött - Utolsó tevékenység %1$s + Összes megtekintése (%1$d) + Jelenlegi munkamenet + Részletek megtekintése + Munkamenet hitelesítése + Az aktuális munkamenet készen áll a biztonságos üzenetküldésre. + Az aktuális munkamenet készen áll a biztonságos üzenetküldésre. + Ellenőrizetlen munkamenet + Ellenőrzött munkamenet + Ismeretlen eszköztípus + Asztali + Web + Mobil + Minden munkamenet megjelenítése (V2, WIP) + A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz. + Más munkamenetek + Munkamenetek + Nyitott területek listája + Új beszélgetés vagy szoba létrehozása + Résztvevők + Kedvencek + Olvasatlan + Mind + Sajnáljuk, ez a szoba nem található. +\nKérjük, próbáld meg később újra.%s + Meghívók + A - Z + Aktivitás + Rendezés + Legfrissebbek megjelenítése + Szűrők megjelenítése + Elrendezési beállítások + Szobák felfedezése + Szoba létrehozása + Chat indítása diff --git a/vector/src/main/res/values-hu/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-hu/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-hu/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-hu/strings_no_weblate.xml diff --git a/vector/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml similarity index 96% rename from vector/src/main/res/values-in/strings.xml rename to library/ui-strings/src/main/res/values-in/strings.xml index 4dea8c3f92..d1e68b4529 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -47,8 +47,8 @@ Hanya kontak Matrix Ruangan Laporan kutu - Aplikasi gagal saat terakhir digunakan. Apakah Anda ingin membuka halaman laporan kegagalan\? - Gabung di Ruangan + Aplikasi mogok saat terakhir digunakan. Apakah Anda ingin membuka halaman laporan kemogokan\? + Bergabung ke Ruangan Mulai Panggilan Suara Masuk Mulai Panggilan Video @@ -69,7 +69,7 @@ TIDAK Lanjut Hapus - Gabung + Bergabung Tolak Nanti Kirim catatan gangguan @@ -88,7 +88,7 @@ Kirim tampilan layar Mohon uraikan kutu tersebut. Apa yang Anda lakukan\? Apa yang Anda harapkan terjadi\? Apa yang sebenarnya terjadi\? Catatan dari klien akan dikirim bersama laporan gangguan ini untuk mendalami kendala yang Anda temukan. Laporan gangguan ini, termasuk catatan dan tangkapan layar, tidak akan terlihat secara umum. Jika Anda hanya ingin mengirimkan tulisan di atas, silakan hapus centang: - Sepertinya Anda mengguncang ponsel akibat frustrasi. Apakah Anda ingin membuka halaman laporan kutu\? + Sepertinya Anda mengguncang ponsel akibat emosi. Apakah Anda ingin membuka halaman laporan kutu\? Pengiriman laporan kutu gagal (%s) Kemajuan (%s%%) Nama Pengguna @@ -122,9 +122,9 @@ Kirim Sticker Ambil foto Ambil video - Saat ini Anda belum memiliki pak stiker. + Saat ini Anda belum memiliki paket stiker apa pun. \n -\nMau tambah sekarang\? +\nIngin tambah sekarang\? Maaf, tidak ada aplikasi eksternal yang mendukung apa yang ingin dilakukan. Meminta ulang kunci enkripsi dari perangkat Anda yang lain. Jalankan ${app_name} di perangkat yang dapat mendekripsi pesan tersebut agar kunci dapat dikirim ke perangkat ini. @@ -146,8 +146,8 @@ Sembunyikan semua pesan dari pengguna ini Tunjukkan semua pesan dari pengguna ini Sebut - Anda tidak akan dapat mengembalikan perubahan ini setelah Anda mengangkat pengguna ini agar memiliki kuasa yang setara dengan Anda. -\nApakah anda yakin untuk melanjutkan\? + Anda tidak akan dapat mengembalikan perubahan ini setelah Anda mengangkat pengguna ini agar memiliki daya yang setara dengan Anda. +\nApakah Anda yakin untuk melanjutkan\? Melakukan pencekalan pengguna akan mengeluarkannya dari ruangan ini dan mencegahnya untuk kembali masuk. Gagal terjawab oleh pihak lain. %s sedang mengetik… @@ -210,7 +210,7 @@ Tidak dapat membuat widget. Gagal mengirim permohonan. Tingkat energi harus bilangan positif. - Anda tidak tergabung dengan ruangan ini. + Anda tidak di ruangan ini. Anda tidak memiliki permisi untuk melakukan itu di ruangan ini. Tidak ada room_id dalam permohonan. Tidak ada user_id dalam permohonan. @@ -228,7 +228,7 @@ Menghapus cekalan pengguna dengan id berikut Tentukan tingkat kuasa seorang pengguna Undang pengguna dengan id berikut bergabung ke ruangan ini - Gabung ke ruangan dengan alamat berikut + Bergabung ke ruangan dengan alamat berikut Tinggalkan ruang Tentukan topik ruang Keluarkan pengguna dengan id berikut @@ -251,10 +251,10 @@ Nonaktifkan Akun Ini akan mengakibatkan akun Anda tidak dapat digunakan secara permanen. Anda tidak akan dapat masuk dan orang lain tidak dapat mendaftar ulang dengan ID pengguna yang sama. Ini akan mengakibatkan akun Anda keluar dari semua ruangan tempat Anda berpartisipasi serta menghapus semua detail akun dari server identitas Anda. Tindakan ini tidak dapat diubah. \n -\nMenonaktifkan akun Anda tidak membuat kami melupakan pesan-pesan yang Anda kirim secara default. Jika Anda ingin kami melupakan pesan-pesan Anda, mohon centang kotak berikut. +\nMenonaktifkan akun Anda tidak membuat kami melupakan pesan-pesan yang Anda kirim secara bawaan. Jika Anda ingin kami melupakan pesan-pesan Anda, mohon centang kotak berikut. \n \nKeterbacaan pesan di Matrix serupa dengan email. Dengan kami melupakan pesan-pesan Anda berarti pesan-pesan yang Anda kirim tidak akan dibagikan kepada pengguna baru ataupun yang belum terdaftar, tetapi pengguna yang terdaftar yang mempunyai mengakses pesan-pesan tersebut masih dapat mengakses salinan mereka. - Mohon lupakan semua pesan yang telah saya kirim ketika akun saya dideaktivasi (Peringatan: ini akan mengakibatkan pengguna di masa depan melihat percakapan yang tidak lengkap) + Mohon lupakan semua pesan yang telah saya kirim ketika akun saya dinonaktifkan (Peringatan: ini akan mengakibatkan pengguna di masa depan melihat percakapan yang tidak lengkap) Nonaktifkan Akun Mohon masukkan kata sandi Anda. Ruangan ini telah berubah dan tidak lagi aktif. @@ -276,7 +276,7 @@ Jangan kirim pesan terenkripsi ke perangkat yang tidak terverifikasi dari perangkat ini. TIDAK terverifikasi Verifikasi - Untuk memastikan perangkat dapat dipercaya, mohon kontak pengguna dengan medium lain (misalnya tatap muka atau panggilan telepon) dan tanya apakah kunci yang mereka lihat di Pengaturan Pengguna untuk perangkat ini cocok dengan kunci berikut: + Konfirmasi dengan membandingkan berikut ini dengan Pengaturan Pengguna di sesi Anda yang lain: Apabila cocok, tekan tombol verifikasi berikut. Apabila tidak, seseorang sedang menyadap perangkat ini dan mungkin perlu diblokir. Di masa mendatang proses verifikasi ini akan dimutakhirkan. @@ -290,7 +290,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Siapa pun Hanya anggota (dimulai sejak opsi ini dipilih) Hanya anggota (dimulai sejak mereka diundang) - Hanya anggota (dimulai sejak mereka bergabung) + Hanya anggota (sejak mereka bergabung) Pengguna yang dicekal Lanjutan ID internal ruangan ini @@ -378,11 +378,11 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Keluarkan Periksa Keadaan Pemberitahuan Hasil diagnosa pemeriksaan keadaan - Lansungkan Ujicoba + Jalankan Pengujian Berlangsung… (%1$d of %2$d) Diagnosa dasar berlangsung lancar. Apabila Anda masih belum dapat menerima pemberitahuan, mohon kirim laporan kutu untuk kami selidiki. - Satu atau beberapa ujicoba gagal, coba sugesti yang kami tawarkan. - Satu atau beberapa ujicoba gagal, mohon kirim laporan kutu untuk kami selidiki. + Satu atau beberapa ujian gagal, coba saran yang kami tawarkan. + Satu atau beberapa ujian gagal, mohon kirim laporan kutu untuk kami selidiki. Pengaturan Sistem. Pemberitahuan diperbolehkan dalam pengaturan sistem. Notifikasi dinonaktifkan dalam pengaturan sistem. @@ -400,7 +400,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Perbolehkan Pemeriksaan Layanan Google Play APK Layanan Google Play ditemukan dan telah diperbaharui. - ${app_name} menggunakan Layanan Google Play untuk mendorong pesan tapi tampaknya tidak diatur sebagaimana harusnya. + ${app_name} menggunakan Layanan Google Play untuk mendorong pesan tapi tampaknya tidak diatur sebagaimana harusnya: \n%1$s Perbaiki Layanan Google Play Token Firebase @@ -417,7 +417,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Layanan tidak akan mulai ketika perangkat dinyalakan kembali, Anda tidak akan menerima pemberitahuan hingga Anda membuka ${app_name}. Perbolehkan memulai ketika perangkat dinyalakan Periksa halangan di balik layar - Larangan background dinonaktifkan untuk ${app_name}. Percobaan ini sebaiknya dijalankan menggunakan jaringan mobile data (bukan WIFI). + Larangan latar belakang dinonaktifkan untuk ${app_name}. Percobaan ini sebaiknya dijalankan menggunakan jaringan data ponsel (bukan WiFi). \n%1$s Larangan background dinonaktifkan untuk ${app_name}. \nAktivitas yang dilakukan aplikasi ini akan terhalang ketika beroperasi di balik layar, dan ini dapat mempengaruhi pemunculan notifikasi. @@ -446,7 +446,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. [%1$s] \nError ini di luar kendali ${app_name} dan menurut Google, error ini muncul ketika terlalu banyak aplikasi terdaftar dengan FCM pada perangkat tersebut. Error ini tidak seharusnya mempengaruhi pengguna biasa. [%1$s] -\nError ini di luar kendali ${app_name}, dan dapat muncul karena berbagai alasan. Coba lagi nanti, atau Anda juga dapat memeriksa apabila penggunaan jaringan data Layanan Google Play tidak terhalang oleh sistem, atau waktu pada perangkat sudah benar, atau ini dapat terjadi pada ROM tidak resmi. +\nKesalahan ini di luar kendali ${app_name}, dan dapat muncul karena berbagai alasan. Coba lagi nanti, atau Anda juga dapat memeriksa apabila penggunaan jaringan data Layanan Google Play tidak terhalang oleh sistem, atau waktu pada perangkat sudah benar, atau ini dapat terjadi pada ROM tidak resmi. [%1$s] \nError ini di luar kendali ${app_name}. Tidak terdapat akun Google pada perangkat. Mohon buka pengelola akun dan tambahkan akun Google. Tambah Akun @@ -493,7 +493,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %s melakukan panggilan suara. Anda melakukan panggilan video. %s melakukan panggilan video. - Anda mengubah nama kamar menjadi: %1$s + Anda mengubah nama ruangan menjadi: %1$s %1$s mengubah nama ruangan menjadi: %2$s Anda mengubah avatar ruangan ini %1$s mengubah avatar ruangan ini @@ -546,7 +546,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Aplikasi ini sedang menunggu push Aplikasi ini menerima push Gagal menerima push. Solusinya adalah untuk menginstal ulang aplikasi. - Percobaan Push + Percobaan Dorongan Pastikan Anda mengeklik tautan di email yang telah kami kirimkan kepada Anda. Hapus %s\? Tidak ada nomor telepon yang ditambahkan ke akun Anda @@ -578,7 +578,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Batalkan pencekalan pengguna Alasan untuk mencekal Cekal pengguna - Pengguna yang dikeluarkan akan menghilangkannya dari ruangan ini. + Pengguna akan dikeluarkan dari ruangan ini. \n \nUntuk mencegah mereka bergabung lagi, Anda seharusnya mencekalnya. Alasan untuk mengeluarkan @@ -657,7 +657,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Gagal menambahkan widget Anda tidak dapat melakukan panggilan dengan diri sendiri, tunggu untuk peserta untuk menerima undangan Anda tidak dapat melakukan panggilan dengan diri sendiri - Pertemuan menggunakan kebijakan keamanan dan izin Jitsi. Semua orang saat ini berada di ruangan akan melihat undangan untuk bergabung saat pertemuan Anda sedang berlangsung. + Pertemuan menggunakan kebijakan keamanan dan perizinan Jitsi. Semua orang saat ini berada di ruangan akan melihat undangan untuk bergabung saat pertemuan Anda sedang berlangsung. Mulai rapat video Mulai rapat audio Anda tidak memiliki izin untuk memulai panggilan @@ -675,19 +675,19 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Putuskan Batalkan Tidak Ada - Standar Sistem + Bawaan Sistem Anda mengaktifkan enkripsi ujung-ke-ujung. (algoritma tidak dikenali %1$s). %1$s mengaktifkan enkripsi ujung-ke-ujung. (algoritma tidak dikenali %2$s). Anda mengaktifkan enkripsi ujung-ke-ujung. %1$s mengaktifkan enkripsi ujung-ke-ujung. - Anda telah mencegah para tamu untuk bergabung ruangan. - %1$s telah mencegah para tamu untuk bergabung ruangan. - Anda telah mencegah para tamu untuk bergabung ruangan. - %1$s telah mencegah para tamu untuk bergabung ruangan. - %1$s telah mengizinkan para tamu untuk bergabung ruangan. - Anda telah mengizinkan para tamu untuk bergabung ruangan. - Anda telah mengizinkan para tamu untuk bergabung disini. - %1$s telah mengizinkan para tamu untuk bergabung disini. + Anda telah mencegah para tamu untuk bergabung ke ruangan. + %1$s telah mencegah para tamu untuk bergabung ke ruangan. + Anda telah mencegah para tamu untuk bergabung ke ruangan. + %1$s telah mencegah para tamu untuk bergabung ke ruangan. + %1$s telah mengizinkan para tamu untuk bergabung ke ruangan. + Anda telah mengizinkan para tamu untuk bergabung ke ruangan. + Anda telah mengizinkan para tamu untuk bergabung di sini. + %1$s telah mengizinkan para tamu untuk bergabung di sini. Anda mengubah alamat untuk ruangan ini. %1$s mengubah alamat untuk ruangan ini. Anda mengubah alamat utama dan alamat alternatif untuk ruangan ini. @@ -740,12 +740,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %1$s meninggalkan ruangan. Alasan: %2$s Anda meninggalkan ruangan. Alasan: %1$s %1$s meninggalkan ruangan. Alasan: %2$s - Anda bergabung. Alasan %1$s - %1$s bergabung. Alasan %2$s - %1$s bergabung ruangan. Alasan: %2$s + Anda bergabung. Alasan: %1$s + %1$s bergabung. Alasan: %2$s + %1$s bergabung ke ruangan. Alasan: %2$s %1$s mengundang Anda. Alasan: %2$s Anda mengundang %1$s. Alasan: %2$s - Anda bergabung ruangan. Alasan %1$s + Anda bergabung ke ruangan. Alasan: %1$s %1$s mengundang %2$s. Alasan: %3$s Undangan Anda. Alasan: %1$s Undangan %1$s. Alasan: %2$s @@ -800,12 +800,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %1$s menerima undangan untuk %2$s Anda membatalkan undangan untuk %1$s %1$s membatalkan undangan untuk %2$s - Anda membatalkan undangan untuk %1$s untuk bergabung ruangan - %1$s membatalkan undangan untuk %2$s untuk bergabung ruangan + Anda membatalkan undangan untuk %1$s untuk bergabung ke ruangan + %1$s membatalkan undangan untuk %2$s untuk bergabung ke ruangan Anda mengundang %1$s %1$s mengundang %2$s - Anda mengirimkan undangan ke %1$s untuk bergabung ruangan - %1$s mengirimkan undangan ke %2$s untuk bergabung ruangan + Anda mengirimkan undangan ke %1$s untuk bergabung ke ruangan + %1$s mengirimkan undangan ke %2$s untuk bergabung ke ruangan Anda menghapus avatar ruangan %1$s menghapus avatar ruangan Anda menghapus topik ruangan @@ -833,8 +833,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Maaf, terjadi kesalahan Mohon masukkan nama pengguna. Diam - Penurunan harga telah dinonaktifkan. - Penurunan harga telah diaktifkan. + Markdown telah dinonaktifkan. + Markdown telah diaktifkan. Perintah \"%s\" membutuhkan parameter tambahan, atau beberapa parameter salah. Abaikan Permintaan Pembagian Kunci @@ -965,9 +965,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Beritahu saya untuk Putar suara rana Pilih - Sumber media default + Sumber media bawaan Pilih - Kompresi default + Kompresi bawaan Media Kelola email dan nomor telepon yang ditautkan ke akun Matrix Anda Email dan nomor telepon @@ -992,7 +992,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Gunakan perintah /confetti atau kirim pesan yang berisi ❄️ atau 🎉 Tampilkan efek chat Gunakan pengelola integrasi untuk mengelola bot, jembatan, widget, dan paket stiker. -\nPengelola integrasi menerima data konfigurasi, dan dapat memodifikasi widget, mengirim undangan ruang, dan mengatur tingkat daya dengan sepengetahuan Anda. +\nPengelola integrasi menerima data konfigurasi, dan dapat memodifikasi widget, mengirim undangan ruang, dan mengatur tingkat daya dengan pengetahuan Anda. Integrasi %d detik @@ -1047,11 +1047,11 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Email ini tidak terkait dengan akun apa pun. Aplikasi tidak dapat membuat akun di homeserver ini. \n -\nApakah Anda ingin mendaftar menggunakan client web\? +\nApakah Anda ingin mendaftar menggunakan klien web\? Maaf, server ini tidak menerima akun baru. Aplikasi tidak dapat masuk ke homeserver ini. Homeserver mendukung jenis masuk berikut: %1$s. \n -\nApakah Anda ingin masuk menggunakan client web\? +\nApakah Anda ingin masuk menggunakan klien web\? Ada kesalahan terjadi saat memuat halaman: %1$s (%2$d) Masukkan alamat server yang ingin Anda gunakan Masukkan alamat Modular Element atau Server yang ingin Anda gunakan @@ -1076,7 +1076,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Lainnya Pelajari lebih lanjut Hosting premium untuk organisasi - Bergabunglah dengan jutaan orang secara gratis di server publik terbesar + Bergabung dengan jutaan orang secara gratis di server publik terbesar Sama seperti email, akun memiliki satu tempat, tetapi Anda dapat berkomunikasi dengan siapa saja Pilih server Mulai @@ -1374,7 +1374,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. BUAT Pesan Langsung Ruangan - Ruangan ini tidak dapat ditampilkan. Apakah Anda masih mau bergabung\? + Ruangan ini tidak dapat ditampilkan. Apakah Anda masih ingin bergabung\? Ruangan ini tidak dapat di akses di waktu ini. \nCoba lagi nanti, atau tanya admin ruangan untuk memeriksa jika Anda punya akses. Ruangan ini tidak dapat di tampilkan @@ -1424,7 +1424,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tidak ada informasi cryptographic unstable stable - Versi Default + Versi Bawaan Versi Ruangan 👓 Batas tidak diketahui. Homeserver Anda menerima lampiran (file, media, dsb.) dengan ukuran hingga %s. @@ -1435,7 +1435,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Kelola Sesi Tampilkan Semua Sesi Sesi Aktif - Admin server Anda telah menonaktifkan enkripsi ujung-ke-ujung secara default di kamar pribadi & pesan langsung. + Admin server Anda telah menonaktifkan enkripsi ujung-ke-ujung secara bawaan di ruangan & Pesan Langsung privat. Tanda Tangan Silang dinonaktifkan Tanda Tangan Silang diaktifkan. \nKunci dipercaya. @@ -1710,7 +1710,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tambahkan ruangan dan space yang sudah ada Anda adalah admin satu-satunya di space ini. Meninggalkannya berarti siapa saja tidak akan mempunyai kontrol atas space-nya. Anda tidak akan dapat bergabung lagi kecuali jika Anda diundang lagi. - Anda orang satu-satunya di sini. Jika Anda tinggalkan, siapa saja tidak dapat bergabung di masa depan, termasuk Anda. + Anda adalah orang satu-satunya di sini. Jika Anda tinggalkan, siapa saja tidak dapat bergabung di masa depan, termasuk Anda. Apakah Anda yakin untuk meninggalkan %s\? Tinggalkan Tambahkan ruangan @@ -2015,7 +2015,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Gagal mengimpor kunci Menunggu untuk %s… Hampir selesai! Menunggu untuk konfirmasi… - Hampir selesai! Apakah perangkat yang lain menunjukkan centang yang sama\? + Hampir selesai! Apakah perangkat yang lain menunjukkan sebuah centang\? "Topik: " Tambahkan topik %s untuk memberi tahu orang-orang tentang ruangan ini. @@ -2043,7 +2043,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Mengsinkronisasikan Kunci Penandatanganan Diri Mengsinkronisasikan Kunci Pengguna Mengsinkronisasikan Kunci Utama - Mendefinisikan Kunci SSSS default + Mendefinisikan Kunci SSSS bawaan Membuat kunci aman dari frasa sandi Mempublikasikan kunci identitas yang telah dibuat Selesai @@ -2071,7 +2071,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Jika Anda batalkan, Anda tidak dapat membaca pesan terenkripsi di perangkat ini dan pengguna lain tidak akan mempercayainya Akun Anda mungkin dikompromikan Ini bukan saya - Login baru. Apakah itu Anda\? + Pemasukan baru. Apakah itu Anda\? Segarkan Akses riwayat pesan terenkripsi Ekspor Audit @@ -2091,13 +2091,13 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Kirim gambar dengan ukuran asli - Apakah Anda mau mengirim lampiran ini ke %1$s\? + Apakah Anda ingin mengirim lampiran ini ke %1$s\? Hapus… Tidak dapat menemukan rahasia di penyimpanan Jika Anda tidak dapat mengakses sesi yang sudah ada Peringatan tingkat kepercayaan Level kepercayaan peringatan - Level kepercayaan default + Level kepercayaan bawaan Dipilih Video mempunyai draf yang belum dikirim @@ -2109,7 +2109,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Buka widget Tangkap layar Gagal mengotentikasi - ${app_name} meminta Anda untuk memasukkan kredential untuk melakukan aksi ini. + ${app_name} meminta Anda untuk memasukkan kredensial untuk melakukan tindakan ini. Otentikasi Ulang Dibutuhkan Geser untuk mengakhirkan panggilan Orang tak dikenal @@ -2155,7 +2155,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Koneksi ke server telah hilang Tidak Ya - Hampir selesai! Apakah %s menampilkan centang yang sama\? + Hampir selesai! Apakah %s menampilkan sebuah centang\? Kode QR Atur Ulang Kunci Memulai Tanda Tangan Silang @@ -2186,9 +2186,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Izin space Menghapus cekalan akan mengizinkan pengguna untuk bergabung ke space lagi. Mencekal pengguna akan mengeluarkan pengguna dari space ini dan mencegah pengguna untuk bergabung lagi. - mengeluarkan pengguna akan mengeluarkannya dari space ini. + Pengguna akan dikeluarkan dari space ini. \n -\nUntuk mencegah pengguna untuk bergabung lagi, Anda seharusnya cekal pengguna itu saja. +\nUntuk mencegah mereka untuk bergabung lagi, Anda seharusnya mencekalnya. Berhenti Merekam Menambahkan ( ͡° ͜ʖ ͡°) ke pesan teks biasa Tidak ada kebijakan yang disediakan oleh server identitasnya @@ -2552,4 +2552,40 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %1$s dan %2$s Email belum diverifikasi, periksa kotak masuk Anda Semua Obrolan + Tampilkan Semua Sesi (V2, Dalam Pengembangan) + Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi. + Sesi lainnya + Sesi + Buka daftar space + Buat percakapan atau ruangan baru + Orang + Favorit + Belum dibaca + Semua + A - Z + Aktivitas + Urutkan berdasarkan + Tampilkan terkini + Tampilkan saringan + Preferensi tata letak + Jelajahi Ruangan + Buat Ruangan + Mulai Obrolan + Maaf, ruangan ini tidak ditemukan. +\nMohon coba lagi nanti.%s + Belum diverifikasi · Aktivitas terakhir %1$s + Terverifikasi · Aktivitas terakhir %1$s + Tampilkan Semua (%1$d) + Sesi Saat Ini + Tampilkan Detail + Verifikasi Sesi + Verifikasi sesi Anda saat ini untuk perpesanan yang aman. + Sesi Anda saat ini siap untuk perpesanan yang aman. + Sesi belum diverifikasi + Sesi terverifikasi + Tipe perangkat tidak diketahui + Desktop + Web + Ponsel + Undangan diff --git a/vector/src/main/res/values-in/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-in/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-in/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-in/strings_no_weblate.xml diff --git a/vector/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml similarity index 100% rename from vector/src/main/res/values-is/strings.xml rename to library/ui-strings/src/main/res/values-is/strings.xml diff --git a/vector/src/main/res/values-is/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-is/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-is/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-is/strings_no_weblate.xml diff --git a/vector/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml similarity index 98% rename from vector/src/main/res/values-it/strings.xml rename to library/ui-strings/src/main/res/values-it/strings.xml index b14b9d8fbd..ecb29d1586 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2591,4 +2591,40 @@ \nQuesto homeserver potrebbe non essere configurato per mostrare mappe. Apri le impostazioni Tutte le chat + Mostra tutte le sessioni (V2, WIP) + Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più. + Altre sessioni + Sessioni + Apri elenco spazi + Crea una nuova conversazione o stanza + Persone + Preferiti + Non lette + Tutte + A - Z + Attività + Ordina per + Mostra recenti + Mostra filtri + Preferenze disposizione + Esplora le stanze + Crea una stanza + Inizia una chat + Non verificata · Ultima attività %1$s + Verificata · Ultima attività %1$s + Vedi tutte (%1$d) + Sessione attuale + Vedi dettagli + Verifica la sessione + Verifica la tua sessione attuale per messaggi più sicuri. + La tua sessione attuale è pronta per i messaggi sicuri. + Sessione non verificata + Sessione verificata + Tipo di dispositivo sconosciuto + Desktop + Web + Mobile + Spiacenti, questa stanza non è stata trovata. +\nRiprova più tardi.%s + Inviti diff --git a/vector/src/main/res/values-it/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-it/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-it/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-it/strings_no_weblate.xml diff --git a/vector/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml similarity index 100% rename from vector/src/main/res/values-iw/strings.xml rename to library/ui-strings/src/main/res/values-iw/strings.xml diff --git a/vector/src/main/res/values-iw/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-iw/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-iw/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-iw/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml similarity index 100% rename from vector/src/main/res/values-ja/strings.xml rename to library/ui-strings/src/main/res/values-ja/strings.xml diff --git a/vector/src/main/res/values-ja/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ja/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ja/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ja/strings_no_weblate.xml diff --git a/vector/src/main/res/values-kaa/strings.xml b/library/ui-strings/src/main/res/values-kaa/strings.xml similarity index 100% rename from vector/src/main/res/values-kaa/strings.xml rename to library/ui-strings/src/main/res/values-kaa/strings.xml diff --git a/vector/src/main/res/values-kab/strings.xml b/library/ui-strings/src/main/res/values-kab/strings.xml similarity index 100% rename from vector/src/main/res/values-kab/strings.xml rename to library/ui-strings/src/main/res/values-kab/strings.xml diff --git a/vector/src/main/res/values-kab/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-kab/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-kab/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-kab/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ko/strings.xml b/library/ui-strings/src/main/res/values-ko/strings.xml similarity index 100% rename from vector/src/main/res/values-ko/strings.xml rename to library/ui-strings/src/main/res/values-ko/strings.xml diff --git a/vector/src/main/res/values-ko/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ko/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ko/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ko/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ku/strings.xml b/library/ui-strings/src/main/res/values-ku/strings.xml similarity index 100% rename from vector/src/main/res/values-ku/strings.xml rename to library/ui-strings/src/main/res/values-ku/strings.xml diff --git a/vector/src/main/res/values-ku/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ku/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ku/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ku/strings_no_weblate.xml diff --git a/vector/src/main/res/values-lo/strings.xml b/library/ui-strings/src/main/res/values-lo/strings.xml similarity index 100% rename from vector/src/main/res/values-lo/strings.xml rename to library/ui-strings/src/main/res/values-lo/strings.xml diff --git a/vector/src/main/res/values-lo/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-lo/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-lo/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-lo/strings_no_weblate.xml diff --git a/vector/src/main/res/values-lt/strings.xml b/library/ui-strings/src/main/res/values-lt/strings.xml similarity index 66% rename from vector/src/main/res/values-lt/strings.xml rename to library/ui-strings/src/main/res/values-lt/strings.xml index 454bf8a4db..c33f8257c6 100644 --- a/vector/src/main/res/values-lt/strings.xml +++ b/library/ui-strings/src/main/res/values-lt/strings.xml @@ -447,4 +447,238 @@ %d praleistų balso skambučių Skambutis baigtas - + ${app_name} reikia leidimo prieiti prie jūsų mikrofono, kad galėtumėte atlikti garso skambučius. + Baigiamas skambutis… + Nuotolinio ryšio pusėje nepavyko prisijungti. + Jokio atsakymo + Naudotojas, kuriam skambinote, yra užimtas. + Naudotojas užimtas + Sulaikėte skambutį + %s sulaikė skambutį + Sulaikyti + Tęsti + Balso skambutis su %s + Vaizdo skambutis su %s + Vaizdo skambutis vyksta… + Skambutis vyksta… + Įeinantis balso skambutis + Įeinantis vaizdo skambutis + + Praleistas vaizdo skambutis + %d praleisti vaizdo skambučiai + %d praleistų vaizdo skambučių + + Skambutis skamba… + Jungiamasi prie skambučio… + Pasirinkite skambučių melodiją: + Įeinančio skambučio melodija + Įeinantiems skambučiams naudoti numatytąją ${app_name} melodiją + Prieš pradedant skambutį prašyti patvirtinimo + Užkirsti kelią atsitiktiniam skambučiui + Mažas + Vidutinis + Didelis + Originalas + + %d narystės pokytis + %d narystės pokyčiai + %d narystės pokyčių + + Prašome paleisti ${app_name} kitame įrenginyje, kuris gali iššifruoti žinutę, kad galėtų išsiųsti raktus į šią sesiją. + Pakartotinai paprašykite šifravimo raktų iš kitų seansų. + Išsiųsta per daug užklausų + Nebuvo tinkamo JSON + Klaidingas JSON + Neautorizuotas, trūksta galiojančių tapatumo duomenų + SSL klaida. + SSL klaida: bendrakeleivio tapatybė nepatvirtinta. + Pasirinkti namų serverį + Nepavyko pasiekti namų serverio URL adresu %s. Patikrinkite nuorodą arba pasirinkite namų serverį rankiniu būdu. + Nepavyko pasiekti namų serverio šiuo URL adresu, prašome jį patikrinti + Tai nėra galiojantis \"Matrix\" serverio adresas + Prašome įvesti tinkamą URL adresą + Peržiūrėkite ir sutikite su šio namų serverio taisyklėmis: + %1$s išėjo iš kambario. Priežastis: %2$s + Prisijungėte. Priežastis: %1$s + %1$s prisijungė. Priežastis: %2$s + Prisijungėte prie kambario. Priežastis: %1$s + %1$s prisijungė prie kambario. Priežastis: %2$s + Siunčiama žinutė… + Žinutė išsiųsta + - Kai kurie naudotojai nebeignoruojami + ${app_name} turi išvalyti talpyklą, kad ji būtų atnaujinta dėl šios priežasties: +\n%s +\n +\nAtkreipkite dėmesį, kad atlikus šį veiksmą programa bus paleista iš naujo ir tai gali šiek tiek užtrukti. + Pradinio sinchronizavimo užklausa + Pradinė sinchronizacija: +\nImportuojame paskyros duomenis + Pradinė sinchronizacija: +\nImportuojame išeitus kambarius + Pradinė sinchronizacija: +\nImportuojame kambarių kvietimus + Pradinė sinchronizacija: +\nĮkeliame jūsų pokalbius +\nJei prisijungėte prie daugybės kambarių, tai gali užtrukti + Pradinė sinchronizacija: +\nImportuojame kambarius + Pradinė sinchronizacija: +\nImportuojame kriptografija + Pradinė sinchronizacija: +\nImportuojame paskyrą… + Pradinė sinchronizacija: +\nLaukiame serverio atsakymo… + Pradinė sinchronizacija: +\nAtsisiunčiame duomenis… + Tuščias kambarys (buvo %s) + Tuščias kambarys + + %1$s, %2$s, %3$s ir %4$d kitas + %1$s, %2$s, %3$s ir %4$d kiti + %1$s, %2$s, %3$s ir %4$d kitų + + %1$s, %2$s, %3$s ir %4$s + %1$s, %2$s ir %3$s + %1$s ir %2$s + Kvietimas į kambarį + Telefono numeris + El. pašto adresas + Jums neleidžiama prisijungti prie šio kambario + Sukurti kambarį + Naršyti kambarius + Jūs atnaujinote čia. + El. paštas nepatvirtintas, patikrinkite savo pašto dėžutę + Nepavyko patvirtinti el. pašto adreso: įsitikinkite, kad paspaudėte el. laiške esančią nuorodą + Šis namų serveris norėtų įsitikinti, kad nesate robotas + Pamiršote slaptažodį\? + Šis telefono numeris jau yra įrašytas. + Šis el. pašto adresas jau yra įrašytas. + Tai neatrodo kaip tinkamas el. pašto adresas + Neteisingas naudotojo vardas ir (arba) slaptažodis + Pateikti + Prisijungimas vienkartiniu prisijungimu + Prisijungti + Atsiprašome, nerastos jokios išorinės programos šiam veiksmui atlikti. + Šiuo metu nėra įjungti jokie lipdukų paketai. +\n +\nAr norite pridėti keletą dabar\? + Naudoti kaip numatytąjį ir daugiau neklausti + Filmuoti + Fotografuoti + Fotografuoti arba filmuoti + Siųsti lipduką + Siųsti failus + Sustabdyti ekrano bendrinimą + Bendrinti ekraną + Įjungti HD + Išjungti HD + Atgalinė + Priekinė + Perjungti kamerą + Pasirinkite garso įrenginį + Kvietimai + A - Z + Veikla + Rikiuoti pagal + Rodyti naujausius + Rodyti filtrus + Išdėstymo parinktys + Kitas + min + val + %1$s pakeitė alternatyvius šio kambario adresus. + + Pašalinote alternatyvų šio kambario adresą %1$s. + Pašalinote alternatyvius šio kambario adresus %1$s. + Pašalinote alternatyvius šio kambario adresus %1$s. + + + %1$s pašalino alternatyvų šio kambario adresą %2$s. + %1$s pašalino alternatyvius šio kambario adresus %2$s. + %1$s pašalino alternatyvius šio kambario adresus %2$s. + + + Pridėjote alternatyvų šio kambario adresą %1$s. + Pridėjote alternatyvius šio kambario adresus %1$s. + Pridėjote alternatyvius šio kambario adresus %1$s. + + + %1$s pridėjo alternatyvų šio kambario adresą %2$s. + %1$s pridėjo alternatyvius šio kambario adresus %2$s. + %1$s pridėjo alternatyvius šio kambario adresus %2$s. + + Pašalinote pagrindinį šio kambario adresą. + %1$s pašalino pagrindinį šio kambario adresą. + Nustatėte pagrindinį šio kambario adresą į %1$s. + %1$s nustatė pagrindinį šio kambario adresą į %2$s. + Pridėjote %1$s ir pašalinote %2$s kaip šio kambario adresus. + %1$s pridėjo %2$s ir pašalino %3$s kaip šio kambario adresus. + + Pašalinote %1$s kaip šio kambario adresą. + Pašalinote %1$s iš šio kambario adresų. + Pašalinote %1$s iš šio kambario adresų. + + + %1$s pašalino %2$s kaip šio kambario adresą. + %1$s pašalino %2$s iš šio kambario adresų. + %1$s pašalino %2$s iš šio kambario adresų. + + + Pridėjote %1$s kaip šio kambario adresą. + Pridėjote %1$s kaip šio kambario adresus. + Pridėjote %1$s kaip šio kambario adresus. + + + %1$s pridėjo %2$s kaip šio kambario adresą. + %1$s pridėjo %2$s kaip šio kambario adresus. + %1$s pridėjo %2$s kaip šio kambario adresus. + + Atšaukėte %1$s kvietimą. Priežastis: %2$s + %1$s atšaukė %2$s kvietimą. Priežastis: %3$s + Priėmėte %1$s kvietimą. Priežastis: %2$s + %1$s priėmė %2$s kvietimą. Priežastis: %3$s + Jūs užblokavote %1$s. Priežastis: %2$s + %1$s užblokavo %2$s. Priežastis: %3$s + Atblokavote %1$s. Priežastis: %2$s + %1$s atblokavo %2$s. Priežastis: %3$s + Pašalinote %1$s. Priežastis: %2$s + %1$s pašalino %2$s. Priežastis: %3$s + Kvietimą atmetėte. Priežastis: %1$s + %1$s atmetė kvietimą. Priežastis: %2$s + Išėjote. Priežastis: %1$s + %1$s išėjo. Priežastis: %2$s + Išėjote iš kambario. Priežastis: %1$s + Visi pokalbiai + Matricos klaida + Nepavyksta išsiųsti žinutės + Siuntėjo įrenginys neatsiuntė mums šios žinutės raktų. + ** Nepavyksta iššifruoti: %s ** + %1$s nuo %2$s iki %3$s + %1$s pakeitė %2$s galios lygį. + Pakeitėte %1$s galios lygį. + Pasirinktinis + Pasirinktinis (%1$d) + Standartinis + Moderatorius + Adminas + Pakeitėte %1$s valdiklį + %1$s pakeitė %2$s valdiklį + Pašalinote %1$s valdiklį + %1$s pašalino %2$s valdiklį + Pridėjote %1$s valdiklį + %1$s pridėjo %2$s valdiklį + %1$s išsiuntė kvietimą %2$s prisijungti prie kambario + %1$s atšaukė %2$s kvietimą prisijungti prie kambario + Priėmėte kvietimą į %1$s + %1$s priėmė kvietimą į %2$s + Atšaukėte kvietimą %1$s + %1$s atšaukė %2$s kvietimą + Atšaukėte %1$s kvietimą prisijungti prie kambario + Pakeitimų nėra. + • Serveriai atitinkantys %s buvo pašalinti iš leidžiamų sąrašo. + • Serveriai atitinkantys %s dabar yra leidžiami. + • Serveriai atitinkantys %s buvo pašalinti iš draudimų sąrašo. + • Serveriai atitinkantys %s dabar yra uždrausti. + • Serveriai atitinkantys %s yra leidžiami. + • Serveriai atitinkantys %s yra uždrausti. + \ No newline at end of file diff --git a/vector/src/main/res/values-lt/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-lt/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-lt/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-lt/strings_no_weblate.xml diff --git a/vector/src/main/res/values-lv/strings.xml b/library/ui-strings/src/main/res/values-lv/strings.xml similarity index 100% rename from vector/src/main/res/values-lv/strings.xml rename to library/ui-strings/src/main/res/values-lv/strings.xml diff --git a/vector/src/main/res/values-lv/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-lv/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-lv/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-lv/strings_no_weblate.xml diff --git a/vector/src/main/res/values-mk/strings.xml b/library/ui-strings/src/main/res/values-mk/strings.xml similarity index 100% rename from vector/src/main/res/values-mk/strings.xml rename to library/ui-strings/src/main/res/values-mk/strings.xml diff --git a/vector/src/main/res/values-ml/strings.xml b/library/ui-strings/src/main/res/values-ml/strings.xml similarity index 100% rename from vector/src/main/res/values-ml/strings.xml rename to library/ui-strings/src/main/res/values-ml/strings.xml diff --git a/vector/src/main/res/values-ml/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ml/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ml/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ml/strings_no_weblate.xml diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml similarity index 100% rename from vector/src/main/res/values-nb-rNO/strings.xml rename to library/ui-strings/src/main/res/values-nb-rNO/strings.xml diff --git a/vector/src/main/res/values-nb-rNO/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-nb-rNO/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-nb-rNO/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-nb-rNO/strings_no_weblate.xml diff --git a/vector/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml similarity index 99% rename from vector/src/main/res/values-nl/strings.xml rename to library/ui-strings/src/main/res/values-nl/strings.xml index 513fb42fa1..b1d239963e 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -2596,4 +2596,27 @@ %1$s en %2$s E-mailadres niet geverifieerd, controleer je inbox + Toon alle sessies (V2, WIP) + Kan kaart niet laden +\nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven. + Open instellingen + Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt. + Andere sessies + Sessies + Lijst met publieke spaces + Maak een nieuw gesprek of een nieuwe kamer + Personen + Favorieten + Ongelezen + Alles + A - Z + Activiteit + Sorteer op + Recente tonen + Toon filters + Lay-outvoorkeuren + Ontdek kamers + Kamer creëren + Start gesprek + Alle gesprekken diff --git a/vector/src/main/res/values-nl/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-nl/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-nl/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-nl/strings_no_weblate.xml diff --git a/vector/src/main/res/values-nn/strings.xml b/library/ui-strings/src/main/res/values-nn/strings.xml similarity index 100% rename from vector/src/main/res/values-nn/strings.xml rename to library/ui-strings/src/main/res/values-nn/strings.xml diff --git a/vector/src/main/res/values-nn/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-nn/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-nn/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-nn/strings_no_weblate.xml diff --git a/vector/src/main/res/values-pa/strings.xml b/library/ui-strings/src/main/res/values-pa/strings.xml similarity index 100% rename from vector/src/main/res/values-pa/strings.xml rename to library/ui-strings/src/main/res/values-pa/strings.xml diff --git a/vector/src/main/res/values-pa/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-pa/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-pa/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-pa/strings_no_weblate.xml diff --git a/vector/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml similarity index 97% rename from vector/src/main/res/values-pl/strings.xml rename to library/ui-strings/src/main/res/values-pl/strings.xml index ecc9316084..18b0de078c 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -735,7 +735,7 @@ Ustal jak inni mogą odnaleść twoje konto. Media Domyślne źródło mediów - Odzyskiwanie zaszyforwanych wiadomości + Odzyskiwanie zaszyfrowanych wiadomości %1$s: %2$d wiadomość %1$s: %2$d wiadomości @@ -1058,12 +1058,12 @@ Zaakceptowałeś(-łaś) Żądanie weryfikacji wysłane Żądanie weryfikacji - Zweryfikuj tą sesję + Zweryfikuj tę sesję Zeskanuj kod z urządzenia innego użytkownika aby bezpiecznie zweryfikować siebie nawzajem Zeskanuj ich kod Nie można zeskanować Jeżeli nie jesteś z tą osobą, zamiast tego porównaj emoji - Zweryfikuj porównując emoji + Zweryfikuj porównując emotikony Zweryfikuj %s Zweryfikowano %s Oczekiwanie na %s… @@ -1099,7 +1099,7 @@ Aktywować szyfrowanie\? Raz włączone szyfrowanie w pokoju nie może zostać wyłączone. Wiadomości wysłane w zaszyfrowanym pokoju nie są widziane przez serwer, a jedynie przez uczestników w pokoju. Aktywowanie szyfrowania może uniemożliwić wielu botom i mostkom prawidłowe działanie. Aktywuj szyfrowanie - Aby być bezpiecznym, zweryfikuj %s poprzez sprawdzenie jednorazowego kodu. + Aby zachować bezpieczeństwo, zweryfikuj %s poprzez sprawdzenie jednorazowego kodu. Aby być bezpiecznym, zrób to osobiście lub użyj innej metody komunikacji. Porównaj unikalny ciąg emoji, upewniając się, że pojawiają się w identycznym porządku. Porównaj kod wyświetlany na ekranie innego użytkownika. @@ -1120,7 +1120,7 @@ Wyloguj z tej sesji Brak dostępnej informacji o kryptografii Ta sesja jest zaufana dla bezpiecznej wymiany wiadomości, ponieważ ją zweryfikowałeś(-łaś): - Zweryfikuj tą sesję aby oznaczyć ją jako zaufaną i przyznać jej dostęp do zaszyfrowanych wiadomości. Jeżeli nie logowałeś(-łaś) się do tej sesji, twoje konto mogło zostać naruszone: + Zweryfikuj tę sesję aby oznaczyć ją jako zaufaną i przyznać jej dostęp do zaszyfrowanych wiadomości. Jeżeli nie logowałeś(-łaś) się do tej sesji, twoje konto mogło zostać zaatakowane: %d aktywna sesja %d aktywne sesje @@ -1330,12 +1330,12 @@ Hasło odzyskiwania Weryfikacja anulowana Weryfikacja anulowana. Możesz rozpocząć jej proces ponownie. - Jedno z poniższych mogło zostać skompromitowane: + Jedno z poniższych może być zagrożone: \n -\n-Twoje hasło -\n-Twój serwer domowy -\n-To urządzenie albo inne urządzenie -\n-Połączenie internetowe używane przez urządzenie +\n- Twoje hasło +\n- Twój serwer domowy +\n- To lub drugie urządzenie +\n- Połączenie internetowe używane przez dowolne z urządzeń \n \nZalecamy natychmiastową zmianę Twojego hasła oraz klucza odzyskiwania w Ustawieniach. Jeżeli anulujesz, nie będziesz w stanie czytać zaszyfrowanych wiadomości na nowym urządzeniu, a inni użytkownicy nie będą mu ufali @@ -1388,7 +1388,7 @@ Zablokuj wszystkich nie będących członkami %s przed dołączeniem do tego pokoju Ukryj zaawansowane Pokaż zaawansowane - Nie można znaleźć właściwego serwera domowego. Zweryfikuj swój identyfikator + Nie można znaleźć właściwego serwera domowego. Sprawdź swój identyfikator To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \"@user:homeserver.org\" Jeżeli nie pamiętasz hasła, cofnij się aby je zresetować. Matrix ID @@ -1492,7 +1492,7 @@ Włącz \"Zezwalaj na integracje\" w Ustawieniach żeby to zrobić. Integracje są zablokowane To zastąpi obecny Klucz bądź Hasło. - Wygeneruj nowy Klucz Bezpieczeństwa albo Hasło dla istniejącej kopii zapasowej. + Wygeneruj nowy klucz bezpieczeństwa albo hasło dla istniejącej kopii zapasowej. Zabezpiecza przeciwko utracie dostępu do zaszyfrowanych wiadomości oraz danych poprzez zapisanie zaszyfrowanych kluczy na Twoim serwerze. Powiadomienie zostało kliknięte! Proszę kliknąć na powiadomieniu, Jeżeli nie widzisz powiadomienia, sprawdź ustawienia systemowe. @@ -1649,7 +1649,7 @@ Mój kod Udostępnij mój kod Zeskanuj kod QR - Nie możemy zaprosić użytkowników. Zweryfikuj osoby, które chcesz zaprosić i spróbuj ponownie. + Nie udało się zaprosić użytkowników. Sprawdź osoby, które chcesz zaprosić i spróbuj ponownie. Zaproszenia wysłane do %1$s i jeszcze jednej osoby Zaproszenia wysłane do %1$s i %2$d innych osób @@ -1985,7 +1985,7 @@ Udostępnij link Zaproś przez nazwę użytkownika lub email Zaproś przez email - Aktualnie jesteś tylko Ty. %s będzie jeszcze lepsza kiedy dołączą inni. + Aktualnie jesteś tu tylko ty. %s będzie jeszcze lepszą przestrzenią, gdy dołączą do niej inni. Zaproś do %s Zaproś osoby Zaproś osoby do Twojej przestrzeni @@ -2127,7 +2127,7 @@ Limit wielkości pliku na serwerze Wersja serwera Nazwa serwera - Zweryfikuj zgodność wyświetlonych emotikon + Zamiast tego, zweryfikuj porównując emotikony Zeskanuj za pomocą tego urządzenia Zeskanuj kod Twoim drugim urządzeniem lub przełącz się i zeskanuj za pomocą tego urządzenia Głos @@ -2311,8 +2311,8 @@ Zastąp kolor wyświetlanej nazwy Posiadam już konto - Połącz się z każdym. - Ty jesteś w kontroli. + Bezpieczna komunikacja. + Masz wszystko pod kontrolą. Przejmij swoje konwersacje. By odkryć istniejące kontakty, musisz najpierw przesłać swoje dane kontaktowe (adresy e-mail i numer telefonu) do serwera tożsamości. Przed wysłaniem Twoje dane zostaną zaszyfrowane w celu zachowania prywatności. Uzyskaj pomoc w korzystaniu z ${app_name} @@ -2392,12 +2392,12 @@ Społeczności Zespoły Przyjaciele i rodzina - Pomożemy Ci się połączyć + Pomożemy Ci nawiązać kontakt Z kim będziesz najczęściej rozmawiać\? - Szyfrowane od-końca-do-końca i nie wymaga numeru telefonu. Brak reklam i dataminingu. + Szyfrowane od-końca-do-końca, bez konieczności podawania numeru telefonu. Zero reklam i dataminingu. Wybierz, gdzie prowadzone są Twoje rozmowy, dając Ci kontrolę i niezależność. Połączenie przez sieć Matrix. Bezpieczna i niezależna komunikacja, która zapewnia ten sam poziom prywatności, co rozmowa twarzą w twarz we własnym domu. - Wiadomości dla Twojego zespołu. + Komunikacja dla Twojego zespołu. Położenie Zagadnienia prawne Już przeglądasz ten wątek! @@ -2521,7 +2521,7 @@ Każdy w przestrzeni nadrzędnej będzie mógł znaleźć ten pokój i dołączyć do niego — nie ma potrzeby ręcznego zapraszania wszystkich. W każdej chwili możesz to zmienić w ustawieniach pokoju. Automatycznie aktualizuj nadrzędną przestrzeń Twój system automatycznie wyśle dzienniki, gdy wystąpi błąd niemożności odszyfrowania - Błędy automatycznego deszyfrowania raportów. + Automatycznie zgłaszaj błędy deszyfrowania. Opuść pokój o podanym identyfikatorze (lub aktualny pokój, jeśli null) Udostępnili swoją lokalizację Powiadom cały pokój @@ -2686,4 +2686,52 @@ %1$s i %2$s Email nie został zweryfikowany, sprawdź swoją skrzynkę - + Nie udało się zarejestrować tokena punktu końcowego na serwerze domowym: +\n%1$s + Wyświetl wszystkie sesje (V2, WIP) + Bieżąca brama: %s + Wejście + Nie można znaleźć punktu końcowego. + Bieżący punkt końcowy: %s + Punkt końcowy + Nie można wczytać mapy. +\nTen serwer macierzysty może nie być skonfigurowany do wyświetlania map. + Otwórz ustawienia + Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz. + Inne sesje + Sesje + Lista otwartych przestrzeni + Utwórz nową rozmowę lub pokój + Ludzie + Ulubione + Nieprzeczytane + Wszystkie + Punkt końcowy został pomyślnie zarejestrowany na serwerze domowym. + Rejestracja punktu końcowego + A - Z + Aktywności + Sortuj według + Wyświetl ostatnie + Wyświetl filtry + Preferencje interfejsu + Przeglądaj pokoje + Utwórz pokój + Zacznij rozmawiać + Wszystkie rozmowy + Nie zweryfikowano · Ostatnia aktywność %1$s + Zweryfikowano · Ostatnia aktywność %1$s + Pokaż wszystkie (%1$d) + Obecna sesja + Pokaż szczegóły + Zweryfikuj sesję + Twoja obecna sesja jest przygotowana do bezpiecznej komunikacji. + Niezweryfikowana sesja + Zweryfikowana sesja + Nieznany typ urządzenia + Komputer + Przeglądarka + Urządzenie przenośne + Niestety, ten pokój nie został znaleziony. +\nSpróbuj ponownie później.%s + Zaproszenia + \ No newline at end of file diff --git a/vector/src/main/res/values-pl/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-pl/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-pl/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-pl/strings_no_weblate.xml diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml similarity index 98% rename from vector/src/main/res/values-pt-rBR/strings.xml rename to library/ui-strings/src/main/res/values-pt-rBR/strings.xml index a148ea90cb..08c41db365 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2600,4 +2600,40 @@ \nEste servidor casa pode não estar configurado para exibir mapas. Abrir configurações Todos os Chats + Mostrar Todas Sessões (V2, WIP) + Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais. + Outras sessões + Sessões + Abrir lista de espaços + Criar uma nova conversa ou sala + Todas + Pessoas + Favoritas + Não-lidas + A - Z + Atividade + Ordenar por + Mostrar recentes + Mostrar filtros + Preferências de layout + Explorar Salas + Criar Sala + Começar Chat + Não-verificada · Última atividade %1$s + Verificada · Última atividade %1$s + Ver Todas (%1$d) + Sessão Atual + Visualizar Detalhes + Verificar Sessão + Verifique sua sessão atual para mensageria segura melhorada. + Sua sessão atual está pronta para mensageria segura. + Sessão não-verificada + Sessão verificada + Tipo de dispositivo desconhecido + Desktop + Mobile + Web + Desculpe, esta sala não tem sido encontrada. +\nPor favor retente mais tarde.%s + Convites diff --git a/vector/src/main/res/values-pt-rBR/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-pt-rBR/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-pt-rBR/strings_no_weblate.xml diff --git a/vector/src/main/res/values-pt/strings.xml b/library/ui-strings/src/main/res/values-pt/strings.xml similarity index 100% rename from vector/src/main/res/values-pt/strings.xml rename to library/ui-strings/src/main/res/values-pt/strings.xml diff --git a/vector/src/main/res/values-pt/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-pt/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-pt/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-pt/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ro/strings.xml b/library/ui-strings/src/main/res/values-ro/strings.xml similarity index 100% rename from vector/src/main/res/values-ro/strings.xml rename to library/ui-strings/src/main/res/values-ro/strings.xml diff --git a/vector/src/main/res/values-ro/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ro/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ro/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ro/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml similarity index 99% rename from vector/src/main/res/values-ru/strings.xml rename to library/ui-strings/src/main/res/values-ru/strings.xml index 01ce2f1bf6..4852be1f82 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -2659,5 +2659,23 @@ Открыть настройки Не удалось загрузить карту \nВозможно, этот домашний сервер не настроен для отображения карт. - Все Беседы - + Все беседы + Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете. + Другие сессии + Сессии + Создать беседу или комнату + Показать все сессии (V2, в разработке) + Люди + Настройки макета + Фильтры + Недавние + Избранные + Непрочитанные + Все + А - Я + Активности + Сортировать по + Обзор комнат + Начать беседу + Создать комнату + \ No newline at end of file diff --git a/vector/src/main/res/values-ru/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-ru/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-ru/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-ru/strings_no_weblate.xml diff --git a/vector/src/main/res/values-rue/strings.xml b/library/ui-strings/src/main/res/values-rue/strings.xml similarity index 100% rename from vector/src/main/res/values-rue/strings.xml rename to library/ui-strings/src/main/res/values-rue/strings.xml diff --git a/vector/src/main/res/values-si/strings.xml b/library/ui-strings/src/main/res/values-si/strings.xml similarity index 100% rename from vector/src/main/res/values-si/strings.xml rename to library/ui-strings/src/main/res/values-si/strings.xml diff --git a/vector/src/main/res/values-si/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-si/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-si/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-si/strings_no_weblate.xml diff --git a/vector/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml similarity index 98% rename from vector/src/main/res/values-sk/strings.xml rename to library/ui-strings/src/main/res/values-sk/strings.xml index cb2d371ccc..2cc2d0280e 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2650,4 +2650,40 @@ \nTento domovský server nemusí byť nakonfigurovaný na zobrazovanie máp. Otvoriť nastavenia Všetky konverzácie + Zobraziť všetky relácie (V2, WIP) + V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate. + Iné relácie + Relácie + Otvoriť zoznam priestorov + Vytvoriť novú konverzáciu alebo miestnosť + Ľudia + Obľúbené + Neprečítané + Všetky + A - Z + Aktivity + Zoradiť podľa + Zobraziť posledné + Zobraziť filtre + Predvoľby rozmiestnenia + Preskúmať miestnosti + Vytvoriť miestnosť + Začať konverzáciu + Neoverené - Posledná aktivita %1$s + Overené - Posledná aktivita %1$s + Zobraziť všetky (%1$d) + Aktuálna relácia + Zobraziť podrobnosti + Overiť reláciu + Overte svoju aktuálnu reláciu pre vylepšené bezpečné zasielanie správ. + Vaša aktuálna relácia je pripravená na bezpečné zasielanie správ. + Neoverená relácia + Overená relácia + Neznámy typ zariadenia + Stolný počítač + Web + Mobil + Je nám ľúto, táto miestnosť nebola nájdená. +\nProsím, skúste to neskôr.%s + Pozvánky diff --git a/vector/src/main/res/values-sk/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-sk/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-sk/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-sk/strings_no_weblate.xml diff --git a/vector/src/main/res/values-sl/strings.xml b/library/ui-strings/src/main/res/values-sl/strings.xml similarity index 100% rename from vector/src/main/res/values-sl/strings.xml rename to library/ui-strings/src/main/res/values-sl/strings.xml diff --git a/vector/src/main/res/values-sl/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-sl/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-sl/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-sl/strings_no_weblate.xml diff --git a/vector/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml similarity index 100% rename from vector/src/main/res/values-sq/strings.xml rename to library/ui-strings/src/main/res/values-sq/strings.xml diff --git a/vector/src/main/res/values-sq/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-sq/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-sq/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-sq/strings_no_weblate.xml diff --git a/vector/src/main/res/values-sr/strings.xml b/library/ui-strings/src/main/res/values-sr/strings.xml similarity index 100% rename from vector/src/main/res/values-sr/strings.xml rename to library/ui-strings/src/main/res/values-sr/strings.xml diff --git a/vector/src/main/res/values-sr/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-sr/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-sr/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-sr/strings_no_weblate.xml diff --git a/vector/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml similarity index 100% rename from vector/src/main/res/values-sv/strings.xml rename to library/ui-strings/src/main/res/values-sv/strings.xml diff --git a/vector/src/main/res/values-sv/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-sv/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-sv/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-sv/strings_no_weblate.xml diff --git a/vector/src/main/res/values-szl/strings.xml b/library/ui-strings/src/main/res/values-szl/strings.xml similarity index 100% rename from vector/src/main/res/values-szl/strings.xml rename to library/ui-strings/src/main/res/values-szl/strings.xml diff --git a/vector/src/main/res/values-szl/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-szl/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-szl/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-szl/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ta/strings.xml b/library/ui-strings/src/main/res/values-ta/strings.xml similarity index 100% rename from vector/src/main/res/values-ta/strings.xml rename to library/ui-strings/src/main/res/values-ta/strings.xml diff --git a/vector/src/main/res/values-te/strings.xml b/library/ui-strings/src/main/res/values-te/strings.xml similarity index 100% rename from vector/src/main/res/values-te/strings.xml rename to library/ui-strings/src/main/res/values-te/strings.xml diff --git a/vector/src/main/res/values-te/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-te/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-te/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-te/strings_no_weblate.xml diff --git a/vector/src/main/res/values-th/strings.xml b/library/ui-strings/src/main/res/values-th/strings.xml similarity index 100% rename from vector/src/main/res/values-th/strings.xml rename to library/ui-strings/src/main/res/values-th/strings.xml diff --git a/vector/src/main/res/values-th/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-th/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-th/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-th/strings_no_weblate.xml diff --git a/vector/src/main/res/values-tlh/strings.xml b/library/ui-strings/src/main/res/values-tlh/strings.xml similarity index 100% rename from vector/src/main/res/values-tlh/strings.xml rename to library/ui-strings/src/main/res/values-tlh/strings.xml diff --git a/vector/src/main/res/values-tlh/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-tlh/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-tlh/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-tlh/strings_no_weblate.xml diff --git a/vector/src/main/res/values-tr/strings.xml b/library/ui-strings/src/main/res/values-tr/strings.xml similarity index 100% rename from vector/src/main/res/values-tr/strings.xml rename to library/ui-strings/src/main/res/values-tr/strings.xml diff --git a/vector/src/main/res/values-tr/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-tr/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-tr/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-tr/strings_no_weblate.xml diff --git a/vector/src/main/res/values-tzm/strings.xml b/library/ui-strings/src/main/res/values-tzm/strings.xml similarity index 100% rename from vector/src/main/res/values-tzm/strings.xml rename to library/ui-strings/src/main/res/values-tzm/strings.xml diff --git a/vector/src/main/res/values-tzm/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-tzm/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-tzm/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-tzm/strings_no_weblate.xml diff --git a/vector/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml similarity index 98% rename from vector/src/main/res/values-uk/strings.xml rename to library/ui-strings/src/main/res/values-uk/strings.xml index bf786be0c4..1c809fff3e 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2700,4 +2700,40 @@ \nМожливо, цей домашній сервер не налаштовано для показу карт. Відкрити налаштування Усі бесіди + Показати всі сеанси (V2, WIP) + Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте. + Інші сеанси + Сеанси + Відкрити список кімнат + Створити нову розмову або кімнату + Люди + Обрані + Непрочитані + Усі + А - Я + Діяльністю + Упорядкувати за + Показувати останні + Показати фільтри + Налаштування макета + Знайти кімнати + Створити кімнату + Розпочати бесіду + Не звірений · Остання активність %1$s + Звірений · Остання активність %1$s + Переглянути всі (%1$d) + Поточний сеанс + Переглянути подробиці + Звірити сеанс + Звірте свій поточний сеанс для безпечнішого обміну повідомленнями. + Ваш поточний сеанс готовий для безпечного обміну повідомленнями. + Не звірений сеанс + Звірений сеанс + Невідомий тип пристрою + Комп\'ютер + Браузер + Мобільний + Перепрошуємо, цю кімнату не знайдено. +\nСпробуйте пізніше.%s + Запрошення diff --git a/vector/src/main/res/values-uk/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-uk/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-uk/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-uk/strings_no_weblate.xml diff --git a/vector/src/main/res/values-ur/strings.xml b/library/ui-strings/src/main/res/values-ur/strings.xml similarity index 100% rename from vector/src/main/res/values-ur/strings.xml rename to library/ui-strings/src/main/res/values-ur/strings.xml diff --git a/vector/src/main/res/values-uz/strings.xml b/library/ui-strings/src/main/res/values-uz/strings.xml similarity index 100% rename from vector/src/main/res/values-uz/strings.xml rename to library/ui-strings/src/main/res/values-uz/strings.xml diff --git a/vector/src/main/res/values-uz/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-uz/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-uz/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-uz/strings_no_weblate.xml diff --git a/vector/src/main/res/values-vi/strings.xml b/library/ui-strings/src/main/res/values-vi/strings.xml similarity index 100% rename from vector/src/main/res/values-vi/strings.xml rename to library/ui-strings/src/main/res/values-vi/strings.xml diff --git a/vector/src/main/res/values-vi/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-vi/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-vi/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-vi/strings_no_weblate.xml diff --git a/vector/src/main/res/values-vls/strings.xml b/library/ui-strings/src/main/res/values-vls/strings.xml similarity index 100% rename from vector/src/main/res/values-vls/strings.xml rename to library/ui-strings/src/main/res/values-vls/strings.xml diff --git a/vector/src/main/res/values-vls/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-vls/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-vls/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-vls/strings_no_weblate.xml diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml similarity index 98% rename from vector/src/main/res/values-zh-rCN/strings.xml rename to library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 3083978734..4e1c8e61c8 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -1309,7 +1309,7 @@ 管理会话 登出此会话 加密信息不可用 - 此会话对安全消息可信因为你已验证它: + 因为你已验证此会话,所以其在安全地收发消息上可受信任: 验证此会话以将其标记为可信,并授予其访问加密消息的权限。如果你未登录此会话,则你的账户可能已被盗: %d 个活动会话 @@ -1323,7 +1323,7 @@ 会话 可信任 未信任 - 此会话可信任,可以用于收发加密消息,因为 %1$s(%2$s)已验证了它: + 可信任此会话用于安全地收发消息,因为%1$s(%2$s)已验证了它: %1$s (%2$s) 使用新会话登录: 在此用户信任此会话之前,发送到该会话和从该会话发送的消息均标有警告。或者,你可以手动进行验证。 初始化交叉签名 @@ -1682,7 +1682,7 @@ "话题: " 添加一个话题 %s让人们知道此房间是关于什么的。 - 这是你和 %s 的私聊消息历史记录的开始。 + 这是你和%s的私聊消息历史的开始。 这是此对话的开始。 这是 %s 的开始。 导出审计 @@ -2550,4 +2550,37 @@ \n此主服务器可能没有设置好显示地图。 打开设置 全部聊天 - + 显示全部会话(V2, WIP) + 为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。 + 其他会话 + 会话 + 打开空间列表 + 创建新对话或房间 + + 收藏 + 未读 + 全部 + A—Z + 活动 + 排序方式 + 显示最近的 + 显示过滤条件 + 布局偏好 + 探索房间 + 创建房间 + 开始聊天 + 抱歉,未发现此房间。 +\n请晚些重试。%s + 未验证 · 上次活跃 %1$s + 已验证 · 上次活跃 %1$s + 查看全部(%1$d) + 当前会话 + 查看详情 + 验证会话 + 为了获得增强的安全的消息传送,请验证你当前的会话。 + 你的当前会话已准备好安全地收发消息。 + 未验证的会话 + 已验证的会话 + 未知的设备类型 + 邀请 + \ No newline at end of file diff --git a/vector/src/main/res/values-zh-rCN/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-zh-rCN/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-zh-rCN/strings_no_weblate.xml diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml similarity index 98% rename from vector/src/main/res/values-zh-rTW/strings.xml rename to library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 91388ed561..0f5208bcde 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2550,4 +2550,40 @@ \n此家伺服器可能未設定好顯示地圖。 開啟設定 所有聊天 + 顯示所有工作階段 (V2, WIP) + 為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。 + 其他工作階段 + 工作階段 + 開啟空間清單 + 建立新的對話或聊天室 + 聯絡人 + 最愛 + 未讀 + 全部 + A - Z + 排序由 + 活動 + 顯示最近的 + 顯示過濾條件 + 佈局偏好設定 + 探索聊天室 + 建立聊天室 + 開始聊天 + 未驗證 · 最後活動 %1$s + 已驗證 · 最後活動 %1$s + 檢視全部 (%1$d) + 目前工作階段 + 檢視詳細資訊 + 驗證工作階段 + 驗證您目前的工作階段以強化安全通訊。 + 您目前的工作階段已準備好進行安全通訊。 + 未驗證的工作階段 + 已驗證的工作階段 + 未知的裝置類型 + 桌面 + 網頁 + 行動裝置 + 抱歉,找不到此聊天室。 +\n請稍後再試。%s + 邀請 diff --git a/vector/src/main/res/values-zh-rTW/strings_no_weblate.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values-zh-rTW/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values-zh-rTW/strings_no_weblate.xml diff --git a/vector/src/main/res/values/array.xml b/library/ui-strings/src/main/res/values/array.xml similarity index 100% rename from vector/src/main/res/values/array.xml rename to library/ui-strings/src/main/res/values/array.xml diff --git a/vector/src/main/res/values/donottranslate.xml b/library/ui-strings/src/main/res/values/donottranslate.xml similarity index 100% rename from vector/src/main/res/values/donottranslate.xml rename to library/ui-strings/src/main/res/values/donottranslate.xml diff --git a/vector/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml similarity index 96% rename from vector/src/main/res/values/strings.xml rename to library/ui-strings/src/main/res/values/strings.xml index 980524dee8..6a87ce82f4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -141,6 +141,10 @@ Create Room Change Space Explore Rooms + + Expand %s children + + Collapse %s children @@ -435,12 +439,25 @@ Activity A - Z + Enable new layout + A simplified Element with optional tabs + Invites Low priority "System Alerts" Suggested Rooms + + No spaces yet. + Spaces are a new way to group rooms and people. Create a space to get started. + + + Invites + + Nothing new. + This is where your new requests and invites will be. + Conversations Matrix contacts only @@ -1345,6 +1362,9 @@ Start the system camera instead of the custom camera screen. To continue you need to accept the Terms of this service. + + Sorry, this room has not been found.\nPlease retry later.%s + You added a new session \'%s\', which is requesting encryption keys. A new session is requesting encryption keys.\nSession name: %1$s\nLast seen: %2$s\nIf you didn’t log in on another session, ignore this request. @@ -3204,4 +3224,67 @@ Show All Sessions (V2, WIP) + Mobile + Web + Desktop + Unknown device type + Verified session + Unverified session + + Your current session is ready for secure messaging. + + Verify your current session for enhanced secure messaging. + Your current session is ready for secure messaging. + This session is ready for secure messaging. + Verify your current session for enhanced secure messaging. + Verify or sign out from this session for best security and reliability. + Verify Session + View Details + + Current Session + View All (%1$d) + + Verified · Last activity %1$s + + Unverified · Last activity %1$s + + + Inactive for %1$d+ day (%2$s) + Inactive for %1$d+ days (%2$s) + + Security recommendations + Improve your account security by following these recommendations. + Unverified sessions + Verify or sign out from unverified sessions. + Inactive sessions + + Consider signing out from old sessions (%1$d day or more) that you don’t use anymore. + Consider signing out from old sessions (%1$d days or more) that you don’t use anymore. + + Current Session + Session + + Last activity %1$s + + + %s\nis looking a little empty. + + Spaces are a new way to group rooms and people. Add an existing room, or create a new one, using the bottom-right button. + + Welcome to ${app_name},\n%s. + The all-in-one secure chat app for teams, friends and organisations. Create a chat, or join an existing room, to get started. + Nothing to report. + This is where your unread messages will show up, when you have some. + + Welcome to a new view! + + To simplify your ${app_name}, tabs are now optional. Manage them using the top-right menu. + Access Spaces + + Access your Spaces (bottom-right) faster and easier than ever before. + Give Feedback + + Tap top right to see the option to feedback. + Try it out + diff --git a/vector/src/main/res/values/strings_no_weblate.xml b/library/ui-strings/src/main/res/values/strings_no_weblate.xml similarity index 100% rename from vector/src/main/res/values/strings_no_weblate.xml rename to library/ui-strings/src/main/res/values/strings_no_weblate.xml diff --git a/library/ui-styles/src/main/res/values-h720dp/dimens.xml b/library/ui-styles/src/main/res/values-h720dp/dimens.xml index 1a7791720d..2a7b12cf2f 100644 --- a/library/ui-styles/src/main/res/values-h720dp/dimens.xml +++ b/library/ui-styles/src/main/res/values-h720dp/dimens.xml @@ -2,4 +2,8 @@ 0.05 0.40 - \ No newline at end of file + + 16dp + 40dp + 46dp + diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index e72d02f51e..01af740d43 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -143,6 +143,7 @@ #0DBD8B #17191C #FF4B55 + #0FFF4B55 diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 53f1044a12..758dd6e978 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -74,4 +74,9 @@ 112dp + + + 8dp + 16dp + 28dp diff --git a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml index f0807f89c6..97e0290815 100644 --- a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml @@ -1,7 +1,7 @@ - + diff --git a/library/ui-styles/src/main/res/values/stylable_security_recommendation_view.xml b/library/ui-styles/src/main/res/values/stylable_security_recommendation_view.xml new file mode 100644 index 0000000000..4283c8da8a --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_security_recommendation_view.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/library/ui-styles/src/main/res/values/styles_devices_management.xml b/library/ui-styles/src/main/res/values/styles_devices_management.xml index 2a63c2ed36..6fb236d3e6 100644 --- a/library/ui-styles/src/main/res/values/styles_devices_management.xml +++ b/library/ui-styles/src/main/res/values/styles_devices_management.xml @@ -7,6 +7,7 @@ diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt index f22cfa369a..80ed311901 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -72,7 +72,7 @@ class FlowSession(private val session: Session) { } fun liveMyDevicesInfo(): Flow> { - return session.cryptoService().getLiveMyDevicesInfo().asFlow() + return session.cryptoService().getMyDevicesInfoLive().asFlow() .startWith(session.coroutineDispatchers.io) { session.cryptoService().getMyDevicesInfo() } diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index aa8731da2a..7b06edb530 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -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" @@ -198,8 +199,7 @@ dependencies { // Exif data handling implementation libs.apache.commonsImaging - // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54' + implementation libs.google.phonenumber 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/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt index ba1afd4758..48cfbebe5b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper +import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.time.DefaultClock import kotlin.random.Random @@ -37,6 +38,7 @@ internal class CryptoStoreHelper { userId = "userId_" + Random.nextInt(), deviceId = "deviceId_sample", clock = DefaultClock(), + myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper() ) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 251c13ccbf..f883295495 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -676,8 +676,8 @@ class E2eeSanityTests : InstrumentedTest { assertEquals("Decimal code should have matched", oldCode, newCode) // Assert that devices are verified - val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) - val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) + val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) + val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified) Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index 8cb38ddc87..ef3fdfeeda 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -193,7 +193,7 @@ class XSigningTest : InstrumentedTest { fail("Bob should see the new device") } - val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId) + val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) // Manually mark it as trusted from first session diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index c2e74abc59..1bffbeeeaa 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -521,9 +521,9 @@ class SASTest : InstrumentedTest { testHelper.await(bobSASLatch) // Assert that devices are verified - val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId) + val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId) val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = - bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) + bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index a5e05f69e0..e0e662c789 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic 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.content.RoomKeyWithHeldContent +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.SessionInfo interface CryptoService { @@ -113,7 +114,19 @@ interface CryptoService { fun setRoomBlacklistUnverifiedDevices(roomId: String) - fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? + fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? + + fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback) + + fun getCryptoDeviceInfo(userId: String): List + + fun getLiveCryptoDeviceInfo(): LiveData> + + fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData> + + fun getLiveCryptoDeviceInfo(userId: String): LiveData> + + fun getLiveCryptoDeviceInfo(userIds: List): LiveData> fun requestRoomKeyForEvent(event: Event) @@ -127,9 +140,9 @@ interface CryptoService { fun getMyDevicesInfo(): List - fun getLiveMyDevicesInfo(): LiveData> + fun getMyDevicesInfoLive(): LiveData> - fun getDeviceInfo(deviceId: String, callback: MatrixCallback) + fun getMyDevicesInfoLive(deviceId: String): LiveData> fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int @@ -156,14 +169,6 @@ interface CryptoService { fun downloadKeys(userIds: List, forceDownload: Boolean, callback: MatrixCallback>) - fun getCryptoDeviceInfo(userId: String): List - - fun getLiveCryptoDeviceInfo(): LiveData> - - fun getLiveCryptoDeviceInfo(userId: String): LiveData> - - fun getLiveCryptoDeviceInfo(userIds: List): LiveData> - fun addNewSessionListener(newSessionListener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 8fdbba21c5..84c25776e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -70,6 +70,9 @@ object EventType { const val STATE_ROOM_ENCRYPTION = "m.room.encryption" const val STATE_ROOM_SERVER_ACL = "m.room.server_acl" + // This type is for local purposes, it should never be processed by the server + const val LOCAL_STATE_ROOM_THIRD_PARTY_INVITE = "local.room.third_party_invite" + // Call Events const val CALL_INVITE = "m.call.invite" const val CALL_CANDIDATES = "m.call.candidates" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt index 6bcf576824..24748f88e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt @@ -18,10 +18,14 @@ package org.matrix.android.sdk.api.session.identity import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier sealed class ThreePid(open val value: String) { + @JsonClass(generateAdapter = true) data class Email(val email: String) : ThreePid(email) + + @JsonClass(generateAdapter = true) data class Msisdn(val msisdn: String) : ThreePid(msisdn) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index b7b0cc890b..d6eb7b30d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -17,13 +17,16 @@ package org.matrix.android.sdk.api.session.room.model.create import android.net.Uri +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.internal.di.MoshiProvider +@JsonClass(generateAdapter = true) open class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. @@ -61,12 +64,12 @@ open class CreateRoomParams { * A list of user IDs to invite to the room. * This will tell the server to invite everyone in the list to the newly created room. */ - val invitedUserIds = mutableListOf() + var invitedUserIds = mutableListOf() /** * A list of objects representing third party IDs to invite into the room. */ - val invite3pids = mutableListOf() + var invite3pids = mutableListOf() /** * Initial Guest Access. @@ -99,14 +102,14 @@ open class CreateRoomParams { * The server will clobber the following keys: creator. * Future versions of the specification may allow the server to clobber other keys. */ - val creationContent = mutableMapOf() + var creationContent = mutableMapOf() /** * A list of state events to set in the new room. This allows the user to override the default state events * set in the new room. The expected format of the state events are an object with type, state_key and content keys set. * Takes precedence over events set by preset, but gets overridden by name and topic keys. */ - val initialStates = mutableListOf() + var initialStates = mutableListOf() /** * Set to true to disable federation of this room. @@ -151,7 +154,7 @@ open class CreateRoomParams { * Supported value: MXCRYPTO_ALGORITHM_MEGOLM. */ var algorithm: String? = null - private set + internal set var historyVisibility: RoomHistoryVisibility? = null @@ -161,10 +164,18 @@ open class CreateRoomParams { var roomVersion: String? = null - var featurePreset: RoomFeaturePreset? = null + @Transient var featurePreset: RoomFeaturePreset? = null companion object { - private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" - private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + internal const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" + internal const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + + fun fromJson(json: String?): CreateRoomParams? { + return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).fromJson(it) } + } } } + +internal fun CreateRoomParams.toJSONString(): String { + return MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).toJson(this) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt index fcfdc3e333..d89c72c513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.api.session.room.model.create +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content +@JsonClass(generateAdapter = true) data class CreateRoomStateEvent( /** * Required. The type of event to send. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 35c066dea8..8dd7c309c6 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -73,6 +73,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityConten import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.sync.model.SyncResponse +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting @@ -273,23 +274,18 @@ internal class DefaultCryptoService @Inject constructor( .executeBy(taskExecutor) } - override fun getLiveMyDevicesInfo(): LiveData> { + override fun getMyDevicesInfoLive(): LiveData> { return cryptoStore.getLiveMyDevicesInfo() } + override fun getMyDevicesInfoLive(deviceId: String): LiveData> { + return cryptoStore.getLiveMyDevicesInfo(deviceId) + } + override fun getMyDevicesInfo(): List { return cryptoStore.getMyDevicesInfo() } - override fun getDeviceInfo(deviceId: String, callback: MatrixCallback) { - getDeviceInfoTask - .configureWith(GetDeviceInfoTask.Params(deviceId)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - } - .executeBy(taskExecutor) - } - override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) } @@ -513,7 +509,7 @@ internal class DefaultCryptoService @Inject constructor( * @param userId the user id * @param deviceId the device id */ - override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { + override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { cryptoStore.getUserDevice(userId, deviceId) } else { @@ -521,6 +517,15 @@ internal class DefaultCryptoService @Inject constructor( } } + override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback) { + getDeviceInfoTask + .configureWith(GetDeviceInfoTask.Params(deviceId)) { + this.executionThread = TaskThread.CRYPTO + this.callback = callback + } + .executeBy(taskExecutor) + } + override fun getCryptoDeviceInfo(userId: String): List { return cryptoStore.getUserDeviceList(userId).orEmpty() } @@ -529,6 +534,10 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getLiveDeviceList() } + override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData> { + return cryptoStore.getLiveDeviceWithId(deviceId) + } + override fun getLiveCryptoDeviceInfo(userId: String): LiveData> { return cryptoStore.getLiveDeviceList(userId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index e466def1a1..d405bdce27 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -779,6 +779,11 @@ internal class DefaultCrossSigningService @Inject constructor( override fun onUsersDeviceUpdate(userIds: List) { Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") + checkTrustAndAffectedRoomShields(userIds) + } + + fun checkTrustAndAffectedRoomShields(userIds: List) { + Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}") val workerParams = UpdateTrustWorker.Params( sessionId = sessionId, filename = updateTrustWorkerDataRepository.createParam(userIds) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index f1dc060e10..6d845ec59e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -207,6 +207,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses private suspend fun updateTrustStep2(userList: List, myCrossSigningInfo: MXCrossSigningInfo?) { Timber.d("## CrossSigning - Updating shields for impacted rooms...") awaitTransaction(sessionRealmConfiguration) { sessionRealm -> + Timber.d("## CrossSigning - Updating shields for impacted rooms - in transaction") Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> sessionRealm.where(RoomMemberSummaryEntity::class.java) .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) @@ -239,6 +240,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses } } } + Timber.d("## CrossSigning - Updating shields for impacted rooms - END") } private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 0413fc730c..56eba25249 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -238,10 +238,14 @@ internal interface IMXCryptoStore { // TODO temp fun getLiveDeviceList(): LiveData> + fun getLiveDeviceWithId(deviceId: String): LiveData> + fun getMyDevicesInfo(): List fun getLiveMyDevicesInfo(): LiveData> + fun getLiveMyDevicesInfo(deviceId: String): LiveData> + fun saveMyDevicesInfo(info: List) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index f5468634cb..3b8fa4cacd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper +import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailMapper @@ -68,6 +69,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity @@ -112,6 +114,7 @@ internal class RealmCryptoStore @Inject constructor( @UserId private val userId: String, @DeviceId private val deviceId: String?, private val clock: Clock, + private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper, ) : IMXCryptoStore { /* ========================================================================================== @@ -578,6 +581,12 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getLiveDeviceWithId(deviceId: String): LiveData> { + return Transformations.map(getLiveDeviceList()) { devices -> + devices.firstOrNull { it.deviceId == deviceId }.toOptional() + } + } + override fun getMyDevicesInfo(): List { return monarchy.fetchAllCopiedSync { it.where() @@ -596,17 +605,24 @@ internal class RealmCryptoStore @Inject constructor( { realm: Realm -> realm.where() }, - { entity -> - DeviceInfo( - deviceId = entity.deviceId, - lastSeenIp = entity.lastSeenIp, - lastSeenTs = entity.lastSeenTs, - displayName = entity.displayName - ) - } + { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) } ) } + override fun getLiveMyDevicesInfo(deviceId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where() + .equalTo(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, deviceId) + }, + { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) } + ) + + return Transformations.map(liveData) { + it.firstOrNull().toOptional() + } + } + override fun saveMyDevicesInfo(info: List) { val entities = info.map { MyDeviceLastSeenInfoEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt new file mode 100644 index 0000000000..38a7569aab --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.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.crypto.store.db.mapper + +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity +import javax.inject.Inject + +internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() { + + fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo { + return DeviceInfo( + deviceId = entity.deviceId, + lastSeenIp = entity.lastSeenIp, + lastSeenTs = entity.lastSeenTs, + displayName = entity.displayName + ) + } +} 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/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/migration/MigrateSessionTo036.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt new file mode 100644 index 0000000000..efcb181ecb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo036(realm: DynamicRealm) : RealmMigrator(realm, 36) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.create("LocalRoomSummaryEntity") + .addField(LocalRoomSummaryEntityFields.ROOM_ID, String::class.java) + .addPrimaryKey(LocalRoomSummaryEntityFields.ROOM_ID) + .setRequired(LocalRoomSummaryEntityFields.ROOM_ID, true) + .addField(LocalRoomSummaryEntityFields.CREATE_ROOM_PARAMS_STR, String::class.java) + .addRealmObjectField(LocalRoomSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt new file mode 100644 index 0000000000..fd8331e986 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.toJSONString + +internal open class LocalRoomSummaryEntity( + @PrimaryKey var roomId: String = "", + var roomSummaryEntity: RoomSummaryEntity? = null, + private var createRoomParamsStr: String? = null +) : RealmObject() { + + var createRoomParams: CreateRoomParams? + get() { + return CreateRoomParams.fromJson(createRoomParamsStr) + } + set(value) { + createRoomParamsStr = value?.toJSONString() + } + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index d131589dd1..b222bcb710 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit ReadReceiptEntity::class, RoomEntity::class, RoomSummaryEntity::class, + LocalRoomSummaryEntity::class, RoomTagEntity::class, SyncEntity::class, PendingThreePidEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt new file mode 100644 index 0000000000..527350bedc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields + +internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { + val query = realm.where() + if (roomId != null) { + query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId) + } + return query +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/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/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index 113e780e5c..cb2088a145 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -42,7 +42,7 @@ internal object NetworkModule { @Provides @JvmStatic fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor { - val logger = FormattedJsonHttpLogger() + val logger = FormattedJsonHttpLogger(BuildConfig.OKHTTP_LOGGING_LEVEL) val interceptor = HttpLoggingInterceptor(logger) interceptor.level = BuildConfig.OKHTTP_LOGGING_LEVEL return interceptor diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt similarity index 84% rename from matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 2661bd1f70..4e0525536c 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -23,15 +23,17 @@ import org.json.JSONException import org.json.JSONObject import timber.log.Timber -internal class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { +internal class FormattedJsonHttpLogger( + private val level: HttpLoggingInterceptor.Level +) : HttpLoggingInterceptor.Logger { companion object { private const val INDENT_SPACE = 2 } /** - * Log the message and try to log it again as a JSON formatted string - * Note: it can consume a lot of memory but it is only in DEBUG mode + * Log the message and try to log it again as a JSON formatted string. + * Note: it can consume a lot of memory but it is only in DEBUG mode. * * @param message */ @@ -39,6 +41,10 @@ internal class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { override fun log(@NonNull message: String) { Timber.v(message) + // Try to log formatted Json only if there is a chance that [message] contains Json. + // It can be only the case if we log the bodies of Http requests. + if (level != HttpLoggingInterceptor.Level.BODY) return + if (message.startsWith("{")) { // JSON Detected try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index a79f35bcb6..a7572035df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.session.space.SpaceModule import org.matrix.android.sdk.internal.session.sync.SyncModule import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.session.sync.SyncTokenStore +import org.matrix.android.sdk.internal.session.sync.handler.UpdateUserWorker import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import org.matrix.android.sdk.internal.session.terms.TermsModule import org.matrix.android.sdk.internal.session.thirdparty.ThirdPartyModule @@ -128,6 +129,8 @@ internal interface SessionComponent { fun inject(worker: UpdateTrustWorker) + fun inject(worker: UpdateUserWorker) + fun inject(worker: DeactivateLiveLocationShareWorker) @Component.Factory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index d01324a35f..1475b67276 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -43,9 +43,13 @@ import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAli import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomStateEventsTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask import org.matrix.android.sdk.internal.session.room.delete.DefaultDeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask @@ -213,6 +217,12 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + @Binds + abstract fun bindCreateLocalRoomStateEventsTask(task: DefaultCreateLocalRoomStateEventsTask): CreateLocalRoomStateEventsTask + + @Binds + abstract fun bindCreateRoomFromLocalRoomTask(task: DefaultCreateRoomFromLocalRoomTask): CreateRoomFromLocalRoomTask + @Binds abstract fun bindDeleteLocalRoomTask(task: DefaultDeleteLocalRoomTask): DeleteLocalRoomTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt new file mode 100644 index 0000000000..a9ff4970fe --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -0,0 +1,299 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import org.matrix.android.sdk.api.MatrixPatterns.getServerName +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.banOrDefault +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.inviteOrDefault +import org.matrix.android.sdk.api.session.room.model.kickOrDefault +import org.matrix.android.sdk.api.session.room.model.redactOrDefault +import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask.Params +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock +import javax.inject.Inject + +/** + * Generate a list of local state events from the given [CreateRoomBody]. + * The states events are generated according to the given configuration and following the matrix specification. + * This list reflects as much as possible a list of state events related to a real room configured and got from the server. + * + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ +internal interface CreateLocalRoomStateEventsTask : Task> { + data class Params(val createRoomBody: CreateRoomBody) +} + +internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( + @UserId private val myUserId: String, + private val userService: UserService, + private val clock: Clock, +) : CreateLocalRoomStateEventsTask { + + private lateinit var createRoomBody: CreateRoomBody + + override suspend fun execute(params: Params): List { + createRoomBody = params.createRoomBody + + // Build the list of the state events following the priorities from the matrix specification + // Changing the order of the events might break the correct display of the room on the client side + return buildList { + createRoomCreateEvent() + createRoomMemberEvents(listOf(myUserId)) + createRoomPowerLevelsEvent() + createRoomAliasEvent() + createRoomPresetEvents() + createRoomInitialStateEvents() + createRoomNameAndTopicStateEvents() + createRoomMemberEvents(createRoomBody.invitedUserIds.orEmpty()) + createRoomThreePidEvents() + createRoomDefaultEvents() + } + } + + /** + * Generate the create state event related to this room. + */ + private fun MutableList.createRoomCreateEvent() { + val roomCreateEvent = createLocalStateEvent( + type = EventType.STATE_ROOM_CREATE, + content = RoomCreateContent( + creator = myUserId, + roomVersion = createRoomBody.roomVersion, + type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String + + ).toContent(), + ) + add(roomCreateEvent) + } + + /** + * Generate the create state event related to the power levels using the given overridden values or the default values according to the specification. + * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels + */ + private fun MutableList.createRoomPowerLevelsEvent() { + val powerLevelsContent = createLocalStateEvent( + type = EventType.STATE_ROOM_POWER_LEVELS, + content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { + it.copy( + ban = it.banOrDefault(), + eventsDefault = it.eventsDefaultOrDefault(), + invite = it.inviteOrDefault(), + kick = it.kickOrDefault(), + redact = it.redactOrDefault(), + stateDefault = it.stateDefaultOrDefault(), + usersDefault = it.usersDefaultOrDefault(), + ) + }.toContent(), + ) + add(powerLevelsContent) + } + + /** + * Generate the local room member state events related to the given user ids, if any. + */ + private suspend fun MutableList.createRoomMemberEvents(userIds: List) { + val memberEvents = userIds + .mapNotNull { tryOrNull { userService.resolveUser(it) } } + .map { user -> + createLocalStateEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + isDirect = createRoomBody.isDirect.takeUnless { user.userId == myUserId }.orFalse(), + membership = if (user.userId == myUserId) Membership.JOIN else Membership.INVITE, + displayName = user.displayName, + avatarUrl = user.avatarUrl + ).toContent(), + stateKey = user.userId + ) + } + addAll(memberEvents) + } + + /** + * Generate the local state events related to the given third party invites, if any. + */ + private fun MutableList.createRoomThreePidEvents() { + createRoomBody.invite3pids.orEmpty().forEach { body -> + val localThirdPartyInviteEvent = createLocalStateEvent( + type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + content = LocalRoomThirdPartyInviteContent( + isDirect = createRoomBody.isDirect.orFalse(), + membership = Membership.INVITE, + displayName = body.address, + thirdPartyInvite = body.toThreePid() + ).toContent(), + ) + val thirdPartyInviteEvent = createLocalStateEvent( + type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, + content = RoomThirdPartyInviteContent( + displayName = body.address, + keyValidityUrl = null, + publicKey = null, + publicKeys = null + ).toContent(), + ) + add(localThirdPartyInviteEvent) + add(thirdPartyInviteEvent) + } + } + + /** + * Generate the local state event related to the given alias, if any. + */ + fun MutableList.createRoomAliasEvent() { + if (createRoomBody.roomAliasName != null) { + val canonicalAliasContent = createLocalStateEvent( + type = EventType.STATE_ROOM_CANONICAL_ALIAS, + content = RoomCanonicalAliasContent( + canonicalAlias = "${createRoomBody.roomAliasName}:${myUserId.getServerName()}" + ).toContent(), + ) + add(canonicalAliasContent) + } + } + + /** + * Generate the local state events related to the given [CreateRoomPreset]. + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ + private fun MutableList.createRoomPresetEvents() { + val preset = createRoomBody.preset ?: return + + var joinRules: RoomJoinRules? = null + var historyVisibility: RoomHistoryVisibility? = null + var guestAccess: GuestAccess? = null + when (preset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT, + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> { + joinRules = RoomJoinRules.INVITE + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.CanJoin + } + CreateRoomPreset.PRESET_PUBLIC_CHAT -> { + joinRules = RoomJoinRules.PUBLIC + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.Forbidden + } + } + + add(createLocalStateEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent())) + add(createLocalStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent())) + add(createLocalStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent())) + } + + /** + * Generate the local state events related to the given initial states, if any. + * The given initial state events override the potential existing ones of the same type. + */ + private fun MutableList.createRoomInitialStateEvents() { + val initialStates = createRoomBody.initialStates ?: return + + val initialStateEvents = initialStates.map { createLocalStateEvent(it.type, it.content, it.stateKey) } + // Erase existing events of the same type + removeAll { event -> event.type in initialStateEvents.map { it.type } } + // Add the initial state events to the list + addAll(initialStateEvents) + } + + /** + * Generate the local events related to the given room name and topic, if any. + */ + private fun MutableList.createRoomNameAndTopicStateEvents() { + if (createRoomBody.name != null) { + add(createLocalStateEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent())) + } + if (createRoomBody.topic != null) { + add(createLocalStateEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent())) + } + } + + /** + * Generate the local events which have not been set and are in that case provided by the server with default values. + * Default events: + * - m.room.history_visibility (https://spec.matrix.org/latest/client-server-api/#server-behaviour-5) + * - m.room.guest_access (https://spec.matrix.org/latest/client-server-api/#mroomguest_access) + */ + private fun MutableList.createRoomDefaultEvents() { + // HistoryVisibility + if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { + add( + createLocalStateEvent( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + content = RoomHistoryVisibilityContent(RoomHistoryVisibility.SHARED.value).toContent(), + ) + ) + } + // GuestAccess + if (none { it.type == EventType.STATE_ROOM_GUEST_ACCESS }) { + add( + createLocalStateEvent( + type = EventType.STATE_ROOM_GUEST_ACCESS, + content = RoomGuestAccessContent(GuestAccess.Forbidden.value).toContent(), + ) + ) + } + } + + /** + * Generate a local state event from the given parameters. + * + * @param type the event type, see [EventType] + * @param content the content of the event + * @param stateKey the stateKey, if any + * + * @return a local state event + */ + private fun createLocalStateEvent(type: String?, content: Content?, stateKey: String? = ""): Event { + return Event( + type = type, + senderId = myUserId, + stateKey = stateKey, + content = content, + originServerTs = clock.epochMillis(), + eventId = LocalEcho.createLocalEchoId() + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index d57491a4c8..03c2b2a47e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -21,26 +21,15 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho -import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure -import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary -import org.matrix.android.sdk.api.session.user.UserService -import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -48,6 +37,7 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -56,7 +46,6 @@ import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater @@ -70,22 +59,22 @@ import javax.inject.Inject internal interface CreateLocalRoomTask : Task internal class DefaultCreateLocalRoomTask @Inject constructor( - @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomSummaryUpdater: RoomSummaryUpdater, @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, - private val userService: UserService, + private val cryptoService: DefaultCryptoService, private val clock: Clock, + private val createLocalRoomStateEventsTask: CreateLocalRoomStateEventsTask, ) : CreateLocalRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomBody = createRoomBodyBuilder.build(params.withDefault()) + val createRoomBody = createRoomBodyBuilder.build(params) val roomId = RoomLocalEcho.createLocalEchoId() monarchy.awaitTransaction { realm -> createLocalRoomEntity(realm, roomId, createRoomBody) - createLocalRoomSummaryEntity(realm, roomId, createRoomBody) + createLocalRoomSummaryEntity(realm, roomId, params, createRoomBody) } // Wait for room to be created in DB @@ -114,14 +103,29 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } } - private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { - val otherUserId = createRoomBody.getDirectUserId() - if (otherUserId != null) { - RoomSummaryEntity.getOrCreate(realm, roomId).apply { + private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomParams: CreateRoomParams, createRoomBody: CreateRoomBody) { + // Create the room summary entity + val roomSummaryEntity = realm.createObject(roomId).apply { + val otherUserId = createRoomBody.getDirectUserId() + if (otherUserId != null) { isDirect = true directUserId = otherUserId } } + + // Update the createRoomParams from the potential feature preset before saving + createRoomParams.featurePreset?.let { featurePreset -> + featurePreset.updateRoomParams(createRoomParams) + createRoomParams.initialStates.addAll(featurePreset.setupInitialStates().orEmpty()) + } + + // Create a LocalRoomSummaryEntity decorated by the related RoomSummaryEntity and the updated CreateRoomParams + realm.createObject(roomId).also { + it.roomSummaryEntity = roomSummaryEntity + it.createRoomParams = createRoomParams + } + + // Update the RoomSummaryEntity by simulating a fake sync response roomSummaryUpdater.update( realm = realm, roomId = roomId, @@ -150,7 +154,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( isLastForward = true } - val eventList = createLocalRoomEvents(createRoomBody) + val eventList = createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(createRoomBody)) val roomMemberContentsByUser = HashMap() for (event in eventList) { @@ -169,6 +173,9 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent() roomMemberEventHandler.handle(realm, roomId, event, false) } + + // Give info to crypto module + cryptoService.onStateEvent(roomId, event) } roomMemberContentsByUser.getOrPut(event.senderId) { @@ -187,81 +194,4 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( return chunkEntity } - - /** - * Build the list of the events related to the room creation params. - * - * @param createRoomBody the room creation params - * - * @return the list of events - */ - private suspend fun createLocalRoomEvents(createRoomBody: CreateRoomBody): List { - val myUser = userService.getUser(userId) ?: User(userId) - val invitedUsers = createRoomBody.invitedUserIds.orEmpty() - .mapNotNull { tryOrNull { userService.resolveUser(it) } } - - val createRoomEvent = createLocalEvent( - type = EventType.STATE_ROOM_CREATE, - content = RoomCreateContent( - creator = userId - ).toContent() - ) - val myRoomMemberEvent = createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - membership = Membership.JOIN, - displayName = myUser.displayName, - avatarUrl = myUser.avatarUrl - ).toContent(), - stateKey = userId - ) - val roomMemberEvents = invitedUsers.map { - createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - isDirect = createRoomBody.isDirect.orFalse(), - membership = Membership.INVITE, - displayName = it.displayName, - avatarUrl = it.avatarUrl - ).toContent(), - stateKey = it.userId - ) - } - - return buildList { - add(createRoomEvent) - add(myRoomMemberEvent) - addAll(createRoomBody.initialStates.orEmpty().map { createLocalEvent(it.type, it.content, it.stateKey) }) - addAll(roomMemberEvents) - } - } - - /** - * Generate a local event from the given parameters. - * - * @param type the event type, see [EventType] - * @param content the content of the Event - * @param stateKey the stateKey, if any - * - * @return a fake event - */ - private fun createLocalEvent(type: String?, content: Content?, stateKey: String? = ""): Event { - return Event( - type = type, - senderId = userId, - stateKey = stateKey, - content = content, - originServerTs = clock.epochMillis(), - eventId = LocalEcho.createLocalEchoId() - ) - } - - /** - * Setup default values to the CreateRoomParams as the room is created locally (the default values will not be defined by the server). - */ - private fun CreateRoomParams.withDefault() = this.apply { - if (visibility == null) visibility = RoomDirectoryVisibility.PRIVATE - if (historyVisibility == null) historyVisibility = RoomHistoryVisibility.SHARED - if (guestAccess == null) guestAccess = GuestAccess.Forbidden - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index b326c3618c..17e1aba6f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody /** @@ -119,7 +120,13 @@ internal data class CreateRoomBody( */ @Json(name = "room_version") val roomVersion: String? -) +) { + companion object { + fun fromJson(json: String?): CreateRoomBody? { + return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomBody::class.java).fromJson(it) } + } + } +} /** * Tells if the created room can be a direct chat one. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt new file mode 100644 index 0000000000..02538a5cc3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import com.zhuinden.monarchy.Monarchy +import io.realm.kotlin.where +import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock +import java.util.UUID +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Create a room on the server from a local room. + * The configuration of the local room will be use to configure the new room. + * The potential local room members will also be invited to this new room. + * + * A local tombstone event will be created to indicate that the local room has been replacing by the new one. + */ +internal interface CreateRoomFromLocalRoomTask : Task { + data class Params(val localRoomId: String) +} + +internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( + @UserId private val userId: String, + @SessionDatabase private val monarchy: Monarchy, + private val createRoomTask: CreateRoomTask, + private val stateEventDataSource: StateEventDataSource, + private val clock: Clock, +) : CreateRoomFromLocalRoomTask { + + private val realmConfiguration + get() = monarchy.realmConfiguration + + override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { + val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + ?.content.toModel() + ?.replacementRoomId + + if (replacementRoomId != null) { + return replacementRoomId + } + + var createRoomParams: CreateRoomParams? = null + var isEncrypted = false + monarchy.doWithRealm { realm -> + realm.where() + .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId) + .findFirst() + ?.let { + createRoomParams = it.createRoomParams + isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse() + } + } + val roomId = createRoomTask.execute(createRoomParams!!) + + try { + // Wait for all the room events before triggering the replacement room + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0) + } + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + EventEntity.whereRoomId(realm, roomId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY) + } + if (isEncrypted) { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + EventEntity.whereRoomId(realm, roomId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION) + } + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(roomId) + } + + createTombstoneEvent(params, roomId) + return roomId + } + + /** + * Create a Tombstone event to indicate that the local room has been replaced by a new one. + */ + private suspend fun createTombstoneEvent(params: CreateRoomFromLocalRoomTask.Params, roomId: String) { + val now = clock.epochMillis() + val event = Event( + type = EventType.STATE_ROOM_TOMBSTONE, + senderId = userId, + originServerTs = now, + stateKey = "", + eventId = UUID.randomUUID().toString(), + content = RoomTombstoneContent( + replacementRoomId = roomId + ).toContent() + ) + monarchy.awaitTransaction { realm -> + val eventEntity = event.toEntity(params.localRoomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) + if (event.stateKey != null && event.type != null && event.eventId != null) { + CurrentStateEventEntity.getOrCreate(realm, params.localRoomId, event.stateKey, event.type).apply { + eventId = event.eventId + root = eventEntity + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index d76640573f..e558d34ff9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -54,8 +54,7 @@ internal class DefaultCreateRoomTask @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration, + @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val globalErrorReceiver: GlobalErrorReceiver, private val clock: Clock, @@ -71,7 +70,6 @@ internal class DefaultCreateRoomTask @Inject constructor( } val createRoomBody = createRoomBodyBuilder.build(params) - val createRoomResponse = try { executeRequest(globalErrorReceiver) { roomAPI.createRoom(createRoomBody) @@ -90,6 +88,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } throw throwable } + val roomId = createRoomResponse.roomId // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt new file mode 100644 index 0000000000..617ed35326 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.model.Membership + +/** + * Class representing the EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE state event content + * This class is only used to store the third party invite data of a local room. + */ +@JsonClass(generateAdapter = true) +internal data class LocalRoomThirdPartyInviteContent( + @Json(name = "membership") val membership: Membership, + @Json(name = "displayname") val displayName: String? = null, + @Json(name = "is_direct") val isDirect: Boolean = false, + @Json(name = "third_party_invite") val thirdPartyInvite: ThreePid? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt index 936c94e520..49951d2da0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -70,6 +71,9 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( RoomEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - RoomEntity - delete ${it.size} entries") } ?.deleteAllFromRealm() + LocalRoomSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - LocalRoomSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() } } else { Timber.i("## DeleteLocalRoomTask - Failed to remove room with id $roomId: not a local room") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index fd6552525e..cb7bbf07fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -140,7 +140,8 @@ internal class RoomMemberEventHandler @Inject constructor( val previousDisplayName = prevContent?.get("displayname") as? String val previousAvatar = prevContent?.get("avatar_url") as? String - if (previousDisplayName != roomMember.displayName || previousAvatar != roomMember.avatarUrl) { + if ((previousDisplayName != null && previousDisplayName != roomMember.displayName) || + (previousAvatar != null && previousAvatar != roomMember.avatarUrl)) { aggregator.userIdsToFetch.add(eventUserId) } } 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/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 59c9de2932..ecc452edb3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -16,10 +16,12 @@ package org.matrix.android.sdk.internal.session.room.state +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.task.Task import timber.log.Timber import javax.inject.Inject @@ -35,28 +37,40 @@ internal interface SendStateTask : Task { internal class DefaultSendStateTask @Inject constructor( private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask, ) : SendStateTask { override suspend fun execute(params: SendStateTask.Params): String { return executeRequest(globalErrorReceiver) { - val response = if (params.stateKey.isEmpty()) { - roomAPI.sendStateEvent( - roomId = params.roomId, - stateEventType = params.eventType, - params = params.body - ) + if (RoomLocalEcho.isLocalEchoId(params.roomId)) { + // Room is local, so create a real one and send the event to this new room + createRoomAndSendEvent(params) } else { - roomAPI.sendStateEvent( - roomId = params.roomId, - stateEventType = params.eventType, - stateKey = params.stateKey, - params = params.body - ) - } - response.eventId.also { - Timber.d("State event: $it just sent in room ${params.roomId}") + val response = if (params.stateKey.isEmpty()) { + roomAPI.sendStateEvent( + roomId = params.roomId, + stateEventType = params.eventType, + params = params.body + ) + } else { + roomAPI.sendStateEvent( + roomId = params.roomId, + stateEventType = params.eventType, + stateKey = params.stateKey, + params = params.body + ) + } + response.eventId.also { + Timber.d("State event: $it just sent in room ${params.roomId}") + } } } } + + private suspend fun createRoomAndSendEvent(params: SendStateTask.Params): String { + val roomId = createRoomFromLocalRoomTask.execute(CreateRoomFromLocalRoomTask.Params(params.roomId)) + Timber.d("State event: convert local room (${params.roomId}) to existing room ($roomId) before sending the event.") + return execute(params.copy(roomId = roomId)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 7e064a84ec..6979d42827 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -63,6 +63,7 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import timber.log.Timber import javax.inject.Inject import kotlin.system.measureTimeMillis @@ -91,7 +92,8 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummary: RoomSyncSummary? = null, unreadNotifications: RoomSyncUnreadNotifications? = null, updateMembers: Boolean = false, - inviterId: String? = null + inviterId: String? = null, + aggregator: SyncResponsePostTreatmentAggregator? = null ) { val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) if (roomSummary != null) { @@ -180,8 +182,14 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) { - // mmm maybe we could only refresh shield instead of checking trust also? - crossSigningService.onUsersDeviceUpdate(otherRoomMembers) + if (aggregator == null) { + // Do it now + // mmm maybe we could only refresh shield instead of checking trust also? + crossSigningService.checkTrustAndAffectedRoomShields(otherRoomMembers) + } else { + // Schedule it + aggregator.userIdsForCheckingTrustAndAffectedRoomShields.addAll(otherRoomMembers) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 392c73bd83..05216d1de1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -126,21 +126,33 @@ internal class SyncResponseHandler @Inject constructor( } // Everything else we need to do outside the transaction - aggregatorHandler.handle(aggregator) - - syncResponse.rooms?.let { - checkPushRules(it, isInitialSync) - userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) - dispatchInvitedRoom(it) + measureTimeMillis { + aggregatorHandler.handle(aggregator) + }.also { + Timber.v("Aggregator management took $it ms") } - Timber.v("On sync completed") - cryptoSyncHandler.onSyncCompleted(syncResponse) + measureTimeMillis { + syncResponse.rooms?.let { + checkPushRules(it, isInitialSync) + userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) + dispatchInvitedRoom(it) + } + }.also { + Timber.v("SyncResponse.rooms post treatment took $it ms") + } + + measureTimeMillis { + cryptoSyncHandler.onSyncCompleted(syncResponse) + }.also { + Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") + } // post sync stuffs monarchy.writeAsync { roomSyncHandler.postSyncSpaceHierarchyHandle(it) } + Timber.v("On sync completed") } private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index e9452c59fc..2b7f936fa8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -23,6 +23,9 @@ internal class SyncResponsePostTreatmentAggregator { // Map of roomId to directUserId val directChatsToCheck = mutableMapOf() - // List of userIds to fetch and update at the end of incremental syncs - val userIdsToFetch = mutableListOf() + // Set of userIds to fetch and update at the end of incremental syncs + val userIdsToFetch = mutableSetOf() + + // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync + val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index c638ed4f80..c749f77fff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -16,32 +16,39 @@ package org.matrix.android.sdk.internal.session.sync.handler -import com.zhuinden.monarchy.Monarchy +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask +import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository +import org.matrix.android.sdk.internal.di.SessionId +import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable -import org.matrix.android.sdk.internal.session.user.UserEntityFactory import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask -import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.logLimit +import org.matrix.android.sdk.internal.worker.WorkerParamsFactory +import timber.log.Timber +import java.util.concurrent.TimeUnit import javax.inject.Inject internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val getProfileInfoTask: GetProfileInfoTask, - @SessionDatabase private val monarchy: Monarchy, + private val crossSigningService: DefaultCrossSigningService, + private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository, + private val workManagerProvider: WorkManagerProvider, + @SessionId private val sessionId: String, ) { suspend fun handle(aggregator: SyncResponsePostTreatmentAggregator) { cleanupEphemeralFiles(aggregator.ephemeralFilesToDelete) updateDirectUserIds(aggregator.directChatsToCheck) fetchAndUpdateUsers(aggregator.userIdsToFetch) + handleUserIdsForCheckingTrustAndAffectedRoomShields(aggregator.userIdsForCheckingTrustAndAffectedRoomShields) } private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List) { @@ -79,23 +86,26 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( } } - private suspend fun fetchAndUpdateUsers(userIdsToFetch: List) { - fetchUsers(userIdsToFetch) - .takeIf { it.isNotEmpty() } - ?.saveLocally() + private fun fetchAndUpdateUsers(userIdsToFetch: Collection) { + if (userIdsToFetch.isEmpty()) return + Timber.d("## Configure Worker to update users: ${userIdsToFetch.logLimit()}") + val workerParams = UpdateTrustWorker.Params( + sessionId = sessionId, + filename = updateTrustWorkerDataRepository.createParam(userIdsToFetch.toList()) + ) + val workerData = WorkerParamsFactory.toData(workerParams) + + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setInputData(workerData) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) + .build() + + workManagerProvider.workManager + .beginUniqueWork("USER_UPDATE_QUEUE", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) + .enqueue() } - private suspend fun fetchUsers(userIdsToFetch: List) = userIdsToFetch.mapNotNull { - tryOrNull { - val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it)) - User.fromJson(it, profileJson) - } - } - - private suspend fun List.saveLocally() { - val userEntities = map { user -> UserEntityFactory.create(user) } - monarchy.awaitTransaction { - it.insertOrUpdate(userEntities) - } + private fun handleUserIdsForCheckingTrustAndAffectedRoomShields(userIdsWithDeviceUpdate: Iterable) { + crossSigningService.checkTrustAndAffectedRoomShields(userIdsWithDeviceUpdate.toList()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt new file mode 100644 index 0000000000..1f840a82d5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt @@ -0,0 +1,99 @@ +/* + * 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.sync.handler + +import android.content.Context +import androidx.work.WorkerParameters +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker +import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorkerDataRepository +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.SessionComponent +import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask +import org.matrix.android.sdk.internal.session.user.UserEntityFactory +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.logLimit +import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker +import timber.log.Timber +import javax.inject.Inject + +/** + * Note: We reuse the same type [UpdateTrustWorker.Params], since the input data are the same. + */ +internal class UpdateUserWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : + SessionSafeCoroutineWorker(context, params, sessionManager, UpdateTrustWorker.Params::class.java) { + + @SessionDatabase + @Inject lateinit var monarchy: Monarchy + @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository + @Inject lateinit var getProfileInfoTask: GetProfileInfoTask + + override fun injectWith(injector: SessionComponent) { + injector.inject(this) + } + + override suspend fun doSafeWork(params: UpdateTrustWorker.Params): Result { + val userList = params.filename + ?.let { updateTrustWorkerDataRepository.getParam(it) } + ?.userIds + ?: params.updatedUserIds.orEmpty() + + // List should not be empty, but let's avoid go further in case of empty list + if (userList.isNotEmpty()) { + Timber.v("## UpdateUserWorker - updating users: ${userList.logLimit()}") + fetchAndUpdateUsers(userList) + } + + cleanup(params) + return Result.success() + } + + private suspend fun fetchAndUpdateUsers(userIdsToFetch: Collection) { + fetchUsers(userIdsToFetch) + .takeIf { it.isNotEmpty() } + ?.saveLocally() + } + + private suspend fun fetchUsers(userIdsToFetch: Collection) = userIdsToFetch.mapNotNull { + tryOrNull { + val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it)) + User.fromJson(it, profileJson) + } + } + + private suspend fun List.saveLocally() { + val userEntities = map { user -> UserEntityFactory.create(user) } + Timber.d("## saveLocally()") + monarchy.awaitTransaction { + Timber.d("## saveLocally() - in transaction") + it.insertOrUpdate(userEntities) + } + Timber.d("## saveLocally() - END") + } + + private fun cleanup(params: UpdateTrustWorker.Params) { + params.filename + ?.let { updateTrustWorkerDataRepository.delete(it) } + } + + override fun buildErrorParams(params: UpdateTrustWorker.Params, message: String): UpdateTrustWorker.Params { + return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 30e1ec6679..bc91ca205d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -154,12 +154,12 @@ internal class RoomSyncHandler @Inject constructor( } is HandlingStrategy.INVITED -> handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountInvitedRooms, 0.1f) { - handleInvitedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) + handleInvitedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator) } is HandlingStrategy.LEFT -> { handlingStrategy.data.mapWithProgress(reporter, InitialSyncStep.ImportingAccountLeftRooms, 0.3f) { - handleLeftRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) + handleLeftRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator) } } } @@ -285,7 +285,8 @@ internal class RoomSyncHandler @Inject constructor( Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, - updateMembers = hasRoomMember + updateMembers = hasRoomMember, + aggregator = aggregator ) return roomEntity } @@ -295,7 +296,8 @@ internal class RoomSyncHandler @Inject constructor( roomId: String, roomSync: InvitedRoomSync, insertType: EventInsertType, - syncLocalTimestampMillis: Long + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator ): RoomEntity { Timber.v("Handle invited sync for room $roomId") val isInitialSync = insertType == EventInsertType.INITIAL_SYNC @@ -319,7 +321,7 @@ internal class RoomSyncHandler @Inject constructor( it.type == EventType.STATE_ROOM_MEMBER } roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.INVITE) - roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = true, inviterId = inviterEvent?.senderId) + roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = true, inviterId = inviterEvent?.senderId, aggregator = aggregator) return roomEntity } @@ -328,7 +330,8 @@ internal class RoomSyncHandler @Inject constructor( roomId: String, roomSync: RoomSync, insertType: EventInsertType, - syncLocalTimestampMillis: Long + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator ): RoomEntity { val isInitialSync = insertType == EventInsertType.INITIAL_SYNC val roomEntity = RoomEntity.getOrCreate(realm, roomId) @@ -366,7 +369,7 @@ internal class RoomSyncHandler @Inject constructor( roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomTypingUsersHandler.handle(realm, roomId, null) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) - roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications) + roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications, aggregator = aggregator) return roomEntity } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt index 83f9532870..80bbbb7938 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.Dea import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker import org.matrix.android.sdk.internal.session.room.send.SendEventWorker +import org.matrix.android.sdk.internal.session.sync.handler.UpdateUserWorker import org.matrix.android.sdk.internal.session.sync.job.SyncWorker import timber.log.Timber import javax.inject.Inject @@ -62,6 +63,8 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage SyncWorker(appContext, workerParameters, sessionManager) UpdateTrustWorker::class.java.name -> UpdateTrustWorker(appContext, workerParameters, sessionManager) + UpdateUserWorker::class.java.name -> + UpdateUserWorker(appContext, workerParameters, sessionManager) UploadContentWorker::class.java.name -> UploadContentWorker(appContext, workerParameters, sessionManager) DeactivateLiveLocationShareWorker::class.java.name -> diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt new file mode 100644 index 0000000000..a27f430edc --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt @@ -0,0 +1,52 @@ +/* + * 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.crypto.store.db.mapper + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity + +private const val A_DEVICE_ID = "device-id" +private const val AN_IP_ADDRESS = "ip-address" +private const val A_TIMESTAMP = 123L +private const val A_DISPLAY_NAME = "display-name" + +class MyDeviceLastSeenInfoEntityMapperTest { + + private val myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper() + + @Test + fun `given an entity when mapping to model then all fields are correctly mapped`() { + val entity = MyDeviceLastSeenInfoEntity( + deviceId = A_DEVICE_ID, + lastSeenIp = AN_IP_ADDRESS, + lastSeenTs = A_TIMESTAMP, + displayName = A_DISPLAY_NAME + ) + val expectedDeviceInfo = DeviceInfo( + deviceId = A_DEVICE_ID, + lastSeenIp = AN_IP_ADDRESS, + lastSeenTs = A_TIMESTAMP, + displayName = A_DISPLAY_NAME + ) + + val deviceInfo = myDeviceLastSeenInfoEntityMapper.map(entity) + + deviceInfo shouldBeEqualTo expectedDeviceInfo + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt new file mode 100644 index 0000000000..1c2cf293b6 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotBeNull +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns.getServerName +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_EMAIL +import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_MSISDN +import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid +import org.matrix.android.sdk.internal.util.time.DefaultClock + +private const val MY_USER_ID = "my-user-id" +private const val MY_USER_DISPLAY_NAME = "my-user-display-name" +private const val MY_USER_AVATAR = "my-user-avatar" + +@ExperimentalCoroutinesApi +internal class DefaultCreateLocalRoomStateEventsTaskTest { + + private val clock = DefaultClock() + private val userService = mockk() + + private val defaultCreateLocalRoomStateEventsTask = DefaultCreateLocalRoomStateEventsTask( + myUserId = MY_USER_ID, + userService = userService, + clock = clock + ) + + lateinit var createRoomBody: CreateRoomBody + + @Before + fun setup() { + createRoomBody = mockk { + every { roomVersion } returns null + every { creationContent } returns null + every { roomAliasName } returns null + every { topic } returns null + every { name } returns null + every { powerLevelContentOverride } returns null + every { initialStates } returns null + every { invite3pids } returns null + every { preset } returns null + every { isDirect } returns null + every { invitedUserIds } returns null + } + coEvery { userService.resolveUser(any()) } answers { User(firstArg()) } + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room create state event`() = runTest { + // Given + val aRoomVersion = "a_room_version" + + every { createRoomBody.roomVersion } returns aRoomVersion + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomCreateEvent = result.find { it.type == EventType.STATE_ROOM_CREATE } + val roomCreateContent = roomCreateEvent?.content.toModel() + + roomCreateContent?.creator shouldBeEqualTo MY_USER_ID + roomCreateContent?.roomVersion shouldBeEqualTo aRoomVersion + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct name and topic state events`() = runTest { + // Given + val aRoomName = "a_room_name" + val aRoomTopic = "a_room_topic" + + every { createRoomBody.name } returns aRoomName + every { createRoomBody.topic } returns aRoomTopic + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomNameEvent = result.find { it.type == EventType.STATE_ROOM_NAME } + val roomTopicEvent = result.find { it.type == EventType.STATE_ROOM_TOPIC } + + roomNameEvent?.content.toModel()?.name shouldBeEqualTo aRoomName + roomTopicEvent?.content.toModel()?.topic shouldBeEqualTo aRoomTopic + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room member events`() = runTest { + // Given + data class RoomMember(val user: User, val membership: Membership) + + val aRoomMemberList: List = listOf( + RoomMember(User(MY_USER_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR), Membership.JOIN), + RoomMember(User("userA_id", "userA_display_name", "userA_avatar"), Membership.INVITE), + RoomMember(User("userB_id", "userB_display_name", "userB_avatar"), Membership.INVITE) + ) + + every { createRoomBody.invitedUserIds } returns aRoomMemberList.filter { it.membership == Membership.INVITE }.map { it.user.userId } + coEvery { userService.resolveUser(any()) } answers { + aRoomMemberList.map { it.user }.find { it.userId == firstArg() } ?: User(firstArg()) + } + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomMemberEvents = result.filter { it.type == EventType.STATE_ROOM_MEMBER } + + roomMemberEvents.map { it.stateKey } shouldBeEqualTo aRoomMemberList.map { it.user.userId } + roomMemberEvents.forEach { event -> + val roomMemberContent = event.content.toModel() + val roomMember = aRoomMemberList.find { it.user.userId == event.stateKey } + + roomMember.shouldNotBeNull() + roomMemberContent?.avatarUrl shouldBeEqualTo roomMember.user.avatarUrl + roomMemberContent?.displayName shouldBeEqualTo roomMember.user.displayName + roomMemberContent?.membership shouldBeEqualTo roomMember.membership + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct power levels event`() = runTest { + // Given + val aPowerLevelsContent = PowerLevelsContent( + ban = 1, + kick = 2, + invite = 3, + redact = 4, + eventsDefault = 5, + events = null, + usersDefault = 6, + users = null, + stateDefault = 7, + notifications = null + ) + + every { createRoomBody.powerLevelContentOverride } returns aPowerLevelsContent + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomPowerLevelsEvent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS } + roomPowerLevelsEvent?.content.toModel() shouldBeEqualTo aPowerLevelsContent + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct canonical alias event`() = runTest { + // Given + val aRoomAlias = "a_room_alias" + val expectedCanonicalAlias = "$aRoomAlias:${MY_USER_ID.getServerName()}" + + every { createRoomBody.roomAliasName } returns aRoomAlias + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomPowerLevelsEvent = result.find { it.type == EventType.STATE_ROOM_CANONICAL_ALIAS } + roomPowerLevelsEvent?.content.toModel()?.canonicalAlias shouldBeEqualTo expectedCanonicalAlias + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct preset related events`() = runTest { + data class ExpectedResult(val joinRules: RoomJoinRules, val historyVisibility: RoomHistoryVisibility, val guestAccess: GuestAccess) + data class Case(val preset: CreateRoomPreset, val expectedResult: ExpectedResult) + + CreateRoomPreset.values().forEach { aRoomPreset -> + // Given + val case = when (aRoomPreset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT -> Case( + CreateRoomPreset.PRESET_PRIVATE_CHAT, + ExpectedResult(RoomJoinRules.INVITE, RoomHistoryVisibility.SHARED, GuestAccess.CanJoin) + ) + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> Case( + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT, + ExpectedResult(RoomJoinRules.INVITE, RoomHistoryVisibility.SHARED, GuestAccess.CanJoin) + ) + CreateRoomPreset.PRESET_PUBLIC_CHAT -> Case( + CreateRoomPreset.PRESET_PUBLIC_CHAT, + ExpectedResult(RoomJoinRules.PUBLIC, RoomHistoryVisibility.SHARED, GuestAccess.Forbidden) + ) + } + every { createRoomBody.preset } returns case.preset + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.find { it.type == EventType.STATE_ROOM_JOIN_RULES } + ?.content.toModel() + ?.joinRules shouldBeEqualTo case.expectedResult.joinRules + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel() + ?.historyVisibility shouldBeEqualTo case.expectedResult.historyVisibility + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel() + ?.guestAccess shouldBeEqualTo case.expectedResult.guestAccess + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the initial state events`() = runTest { + // Given + val aListOfInitialStateEvents = listOf( + Event( + type = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + content = EncryptionEventContent(MXCRYPTO_ALGORITHM_MEGOLM).toContent() + ), + Event( + type = "a_custom_type", + content = mapOf("a_custom_map_to_integer" to 42), + stateKey = "a_state_key" + ), + Event( + type = "another_custom_type", + content = mapOf("a_custom_map_to_boolean" to false), + stateKey = "another_state_key" + ) + ) + + every { createRoomBody.initialStates } returns aListOfInitialStateEvents + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + aListOfInitialStateEvents.forEach { expected -> + val found = result.find { it.type == expected.type } + found.shouldNotBeNull() + found.content shouldBeEqualTo expected.content + found.stateKey shouldBeEqualTo expected.stateKey + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct third party invite events`() = runTest { + // Given + val aListOfThreePids = listOf( + ThreePid.Email("bob@matrix.org"), + ThreePid.Msisdn("+11111111111"), + ThreePid.Email("alice@matrix.org"), + ThreePid.Msisdn("+22222222222"), + ) + val aListOf3pids = aListOfThreePids.mapIndexed { index, threePid -> + ThreePidInviteBody( + idServer = "an_id_server_$index", + idAccessToken = "an_id_access_token_$index", + medium = when (threePid) { + is ThreePid.Email -> MEDIUM_EMAIL + is ThreePid.Msisdn -> MEDIUM_MSISDN + }, + address = threePid.value + ) + } + every { createRoomBody.invite3pids } returns aListOf3pids + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val thirdPartyInviteEvents = result.filter { it.type == EventType.STATE_ROOM_THIRD_PARTY_INVITE } + val thirdPartyInviteContents = thirdPartyInviteEvents.map { it.content.toModel() } + val localThirdPartyInviteEvents = result.filter { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + val localThirdPartyInviteContents = localThirdPartyInviteEvents.map { it.content.toModel() } + + thirdPartyInviteEvents.size shouldBeEqualTo aListOf3pids.size + localThirdPartyInviteEvents.size shouldBeEqualTo aListOf3pids.size + + aListOf3pids.forEach { expected -> + thirdPartyInviteContents.find { it?.displayName == expected.address }.shouldNotBeNull() + + val localThirdPartyInviteContent = localThirdPartyInviteContents.find { it?.thirdPartyInvite == expected.toThreePid() } + localThirdPartyInviteContent.shouldNotBeNull() + localThirdPartyInviteContent.membership shouldBeEqualTo Membership.INVITE + localThirdPartyInviteContent.isDirect shouldBeEqualTo createRoomBody.isDirect.orFalse() + localThirdPartyInviteContent.displayName shouldBeEqualTo expected.address + } + } + + @Test + fun `given a CreateRoomBody with default values when execute then the resulting list of events is correct`() = runTest { + // Given + // map of expected event types to occurrences + val expectedEventTypes = mapOf( + EventType.STATE_ROOM_CREATE to 1, + EventType.STATE_ROOM_POWER_LEVELS to 1, + EventType.STATE_ROOM_MEMBER to 1, + EventType.STATE_ROOM_GUEST_ACCESS to 1, + EventType.STATE_ROOM_HISTORY_VISIBILITY to 1, + ) + coEvery { userService.resolveUser(any()) } answers { + if (firstArg() == MY_USER_ID) User(MY_USER_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR) else User(firstArg()) + } + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.size shouldBeEqualTo expectedEventTypes.values.sum() + result.map { it.type }.toSet() shouldBeEqualTo expectedEventTypes.keys + + // Room create + result.find { it.type == EventType.STATE_ROOM_CREATE }.shouldNotBeNull() + // Room member + result.singleOrNull { it.type == EventType.STATE_ROOM_MEMBER }?.stateKey shouldBeEqualTo MY_USER_ID + // Power levels + val powerLevelsContent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS }?.content.toModel() + powerLevelsContent.shouldNotBeNull() + powerLevelsContent.ban shouldBeEqualTo Role.Moderator.value + powerLevelsContent.kick shouldBeEqualTo Role.Moderator.value + powerLevelsContent.invite shouldBeEqualTo Role.Moderator.value + powerLevelsContent.redact shouldBeEqualTo Role.Moderator.value + powerLevelsContent.eventsDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.usersDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.stateDefault shouldBeEqualTo Role.Moderator.value + // Guest access + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel()?.guestAccess shouldBeEqualTo GuestAccess.Forbidden + // History visibility + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel()?.historyVisibility shouldBeEqualTo RoomHistoryVisibility.SHARED + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events is correctly ordered with the right values`() = runTest { + // Given + val expectedIsDirect = true + val expectedHistoryVisibility = RoomHistoryVisibility.WORLD_READABLE + + every { createRoomBody.roomVersion } returns "a_room_version" + every { createRoomBody.roomAliasName } returns "a_room_alias_name" + every { createRoomBody.name } returns "a_name" + every { createRoomBody.topic } returns "a_topic" + every { createRoomBody.powerLevelContentOverride } returns PowerLevelsContent( + ban = 1, + kick = 2, + invite = 3, + redact = 4, + eventsDefault = 5, + events = null, + usersDefault = 6, + users = null, + stateDefault = 7, + notifications = null + ) + every { createRoomBody.invite3pids } returns listOf( + ThreePidInviteBody( + idServer = "an_id_server", + idAccessToken = "an_id_access_token", + medium = MEDIUM_EMAIL, + address = "an_email@example.org" + ) + ) + every { createRoomBody.preset } returns CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + every { createRoomBody.initialStates } returns listOf( + Event(type = "a_custom_type", stateKey = ""), + // override the value from the preset + Event( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + content = RoomHistoryVisibilityContent(expectedHistoryVisibility.value).toContent() + ) + ) + every { createRoomBody.isDirect } returns expectedIsDirect + every { createRoomBody.invitedUserIds } returns listOf("a_user_id") + + val orderedExpectedEventType = listOf( + EventType.STATE_ROOM_CREATE, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_POWER_LEVELS, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_JOIN_RULES, + EventType.STATE_ROOM_GUEST_ACCESS, + "a_custom_type", + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, + ) + + // When + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.map { it.type } shouldBeEqualTo orderedExpectedEventType + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel()?.historyVisibility shouldBeEqualTo expectedHistoryVisibility + result.lastOrNull { it.type == EventType.STATE_ROOM_MEMBER } + ?.content.toModel()?.isDirect shouldBeEqualTo expectedIsDirect + result.lastOrNull { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + ?.content.toModel()?.isDirect shouldBeEqualTo expectedIsDirect + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt new file mode 100644 index 0000000000..d3732363b5 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.create + +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.realm.kotlin.where +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.util.time.DefaultClock +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource + +private const val A_LOCAL_ROOM_ID = "local.a-local-room-id" +private const val AN_EXISTING_ROOM_ID = "an-existing-room-id" +private const val A_ROOM_ID = "a-room-id" +private const val MY_USER_ID = "my-user-id" + +@ExperimentalCoroutinesApi +internal class DefaultCreateRoomFromLocalRoomTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val clock = DefaultClock() + private val createRoomTask = mockk() + private val fakeStateEventDataSource = FakeStateEventDataSource() + + private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask( + userId = MY_USER_ID, + monarchy = fakeMonarchy.instance, + createRoomTask = createRoomTask, + stateEventDataSource = fakeStateEventDataSource.instance, + clock = clock + ) + + @Before + fun setup() { + mockkStatic("org.matrix.android.sdk.internal.database.RealmQueryLatchKt") + coJustRun { awaitNotEmptyResult(realmConfiguration = any(), timeoutMillis = any(), builder = any()) } + + mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") + coEvery { any().copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, any()) } answers { firstArg() } + + mockkStatic("org.matrix.android.sdk.internal.database.query.CurrentStateEventEntityQueriesKt") + every { CurrentStateEventEntity.getOrCreate(fakeMonarchy.fakeRealm.instance, any(), any(), any()) } answers { + CurrentStateEventEntity(roomId = arg(2), stateKey = arg(3), type = arg(4)) + } + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a local room id when execute then the existing room id is kept`() = runTest { + // Given + givenATombstoneEvent( + Event( + roomId = A_LOCAL_ROOM_ID, + type = EventType.STATE_ROOM_TOMBSTONE, + stateKey = "", + content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent() + ) + ) + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + val result = defaultCreateRoomFromLocalRoomTask.execute(params) + + // Then + verifyTombstoneEvent(AN_EXISTING_ROOM_ID) + result shouldBeEqualTo AN_EXISTING_ROOM_ID + } + + @Test + fun `given a local room id when execute then it is correctly executed`() = runTest { + // Given + val aCreateRoomParams = mockk() + val aLocalRoomSummaryEntity = mockk { + every { roomSummaryEntity } returns mockk(relaxed = true) + every { createRoomParams } returns aCreateRoomParams + } + givenATombstoneEvent(null) + givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity) + + coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + val result = defaultCreateRoomFromLocalRoomTask.execute(params) + + // Then + verifyTombstoneEvent(null) + // CreateRoomTask has been called with the initial CreateRoomParams + coVerify { createRoomTask.execute(aCreateRoomParams) } + // The resulting roomId matches the roomId returned by the createRoomTask + result shouldBeEqualTo A_ROOM_ID + // A tombstone state event has been created + coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) } + } + + private fun givenATombstoneEvent(event: Event?) { + fakeStateEventDataSource.givenGetStateEventReturns(event) + } + + private fun givenALocalRoomSummaryEntity(localRoomSummaryEntity: LocalRoomSummaryEntity) { + every { + fakeMonarchy.fakeRealm.instance + .where() + .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID) + .findFirst() + } returns localRoomSummaryEntity + } + + private fun verifyTombstoneEvent(expectedRoomId: String?) { + fakeStateEventDataSource.verifyGetStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + fakeStateEventDataSource.instance.getStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + ?.content.toModel() + ?.replacementRoomId shouldBeEqualTo expectedRoomId + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt index 588bfaa979..d51ed77399 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent @@ -69,7 +70,7 @@ class DefaultGetActiveBeaconInfoForUserTaskTest { fakeStateEventDataSource.verifyGetStateEvent( roomId = params.roomId, eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - stateKey = A_USER_ID + stateKey = QueryStringValue.Equals(A_USER_ID) ) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt index d77084fe3b..2d501f12af 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction internal class FakeMonarchy { val instance = mockk() - private val fakeRealm = FakeRealm() + val fakeRealm = FakeRealm() init { mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") @@ -42,6 +42,12 @@ internal class FakeMonarchy { } coAnswers { secondArg Any>().invoke(fakeRealm.instance) } + coEvery { + instance.doWithRealm(any()) + } coAnswers { + firstArg().doWithRealm(fakeRealm.instance) + } + every { instance.realmConfiguration } returns mockk() } inline fun givenWhere(): RealmQuery { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt index ca03316fa7..ebb2a1d7a0 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.test.fakes import io.mockk.every import io.mockk.mockk import io.mockk.verify -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -37,12 +37,12 @@ internal class FakeStateEventDataSource { } returns event } - fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: String) { + fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: QueryStateEventValue) { verify { instance.getStateEvent( roomId = roomId, eventType = eventType, - stateKey = QueryStringValue.Equals(stateKey) + stateKey = stateKey ) } } diff --git a/settings.gradle b/settings.gradle index 782d2caf4a..e5b5511b94 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,9 @@ +include ':vector-app' include ':vector' include ':vector-config' include ':matrix-sdk-android' include ':library:core-utils' +include ':library:ui-strings' include ':library:ui-styles' include ':library:jsonviewer' include ':library:attachment-viewer' diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh index 910616176c..fc46fca758 100755 --- a/tools/check/check_code_quality.sh +++ b/tools/check/check_code_quality.sh @@ -74,7 +74,9 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code.txt \ ./vector/src/debug/java \ ./vector/src/release/java \ ./vector/src/fdroid/java \ - ./vector/src/gplay/java + ./vector/src/gplay/java \ + ./vector-app/src/gplay/java \ + ./vector-app/src/main/java resultForbiddenStringInCode=$? @@ -95,7 +97,9 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_app.txt ./vector/src/debug/java \ ./vector/src/release/java \ ./vector/src/fdroid/java \ - ./vector/src/gplay/java + ./vector/src/gplay/java \ + ./vector-app/src/gplay/java \ + ./vector-app/src/main/java resultForbiddenStringInCodeApp=$? @@ -107,7 +111,8 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_resources.txt ./vector/src/main/res/color \ ./vector/src/main/res/layout \ ./vector/src/main/res/values \ - ./vector/src/main/res/xml + ./vector/src/main/res/xml \ + ./vector-app/src/main/res/values resultForbiddenStringInResource=$? @@ -115,7 +120,8 @@ echo echo "Search for forbidden patterns in layouts..." ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_layout.txt \ - ./vector/src/main/res/layout + ./vector/src/main/res/layout \ + ./vector-app/src/main/res/layout resultForbiddenStringInLayout=$? @@ -155,7 +161,11 @@ ${checkLongFilesScript} ${maxLines} \ ./vector/src/main/java \ ./vector/src/release/java \ ./vector/src/sharedTest/java \ - ./vector/src/test/java + ./vector/src/test/java \ + ./vector/src/androidTest/java \ + ./vector/src/gplay/java \ + ./vector/src/main/java + resultLongFiles=$? diff --git a/vector/lint.xml b/tools/lint/lint.xml similarity index 100% rename from vector/lint.xml rename to tools/lint/lint.xml diff --git a/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl index 0f01b347c0..133faa6821 100644 --- a/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/Fragment.kt.ftl @@ -18,10 +18,10 @@ import javax.inject.Inject data class ${fragmentArgsClass}() : Parcelable -//TODO add this fragment into FragmentModule -class ${fragmentClass} @Inject constructor( - private val viewModelFactory: ${viewModelClass}.Factory -) : VectorBaseFragment(), ${viewModelClass}.Factory by viewModelFactory { +@AndroidEntryPoint +class ${fragmentClass}() : + VectorBaseFragment(), + ${viewModelClass}.Factory by viewModelFactory { <#if createFragmentArgs> private val fragmentArgs: ${fragmentArgsClass} by args() diff --git a/vector-app/build.gradle b/vector-app/build.gradle new file mode 100644 index 0000000000..a8262fde40 --- /dev/null +++ b/vector-app/build.gradle @@ -0,0 +1,378 @@ +import com.android.build.OutputFile + +apply plugin: 'com.android.application' +apply plugin: 'com.google.firebase.appdistribution' +apply plugin: 'com.google.android.gms.oss-licenses-plugin' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' +apply plugin: 'dagger.hilt.android.plugin' +apply plugin: 'kotlinx-knit' +apply plugin: 'com.likethesalad.stem' + +if (project.hasProperty("coverage")) { + apply plugin: 'jacoco' +} + +kapt { + correctErrorTypes = true +} + +knit { + files = fileTree(project.rootDir) { + include '**/*.md' + include '**/*.kt' + include '**/*.kts' + exclude '**/build/**' + exclude '**/.gradle/**' + exclude '**/towncrier/template.md' + exclude '**/CHANGES.md' + } +} + +// Note: 2 digits max for each value +ext.versionMajor = 1 +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 = 36 + +static def getGitTimestamp() { + def cmd = 'git show -s --format=%ct' + return cmd.execute().text.trim() as Long +} + +static def generateVersionCodeFromTimestamp() { + // It's unix timestamp, minus timestamp of October 3rd 2018 (first commit date) divided by 100: It's incremented by one every 100 seconds. + // plus 20_000_000 for compatibility reason with the previous way the Version Code was computed + // Note that the result will be multiplied by 10 when adding the digit for the arch + return ((getGitTimestamp() - 1_538_524_800) / 100).toInteger() + 20_000_000 +} + +def generateVersionCodeFromVersionName() { + // plus 4_000_000 for compatibility reason with the previous way the Version Code was computed + // Note that the result will be multiplied by 10 when adding the digit for the arch + return (versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch) + 4_000_000 +} + +def getVersionCode() { + if (gitBranchName() == "develop") { + return generateVersionCodeFromTimestamp() + } else { + return generateVersionCodeFromVersionName() + } +} + +static def gitRevision() { + def cmd = "git rev-parse --short=8 HEAD" + return cmd.execute().text.trim() +} + +static def gitRevisionDate() { + def cmd = "git show -s --format=%ci HEAD^{commit}" + return cmd.execute().text.trim() +} + +static def gitBranchName() { + def fromEnv = System.env.BUILDKITE_BRANCH as String ?: "" + + if (!fromEnv.isEmpty()) { + return fromEnv + } else { + // Note: this command return "HEAD" on Buildkite, so use the system env 'BUILDKITE_BRANCH' content first + def cmd = "git rev-parse --abbrev-ref HEAD" + return cmd.execute().text.trim() + } +} + +// For Google Play build, build on any other branch than main will have a "-dev" suffix +static def getGplayVersionSuffix() { + if (gitBranchName() == "main") { + return "" + } else { + return "-dev" + } +} + +static def gitTag() { + def cmd = "git describe --exact-match --tags" + return cmd.execute().text.trim() +} + +// For F-Droid build, build on a not tagged commit will have a "-dev" suffix +static def getFdroidVersionSuffix() { + if (gitTag() == "") { + return "-dev" + } else { + return "" + } +} + +project.android.buildTypes.all { buildType -> + buildType.javaCompileOptions.annotationProcessorOptions.arguments = + [ + validateEpoxyModelUsage: String.valueOf(buildType.name == 'debug') + ] +} + +// map for the version codes last digit +// x86 must have greater values than arm +// 64 bits have greater value than 32 bits +ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].withDefault { 0 } + +def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0 + +android { + // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use + // Ref: https://issuetracker.google.com/issues/144111441 + ndkVersion "21.3.6528147" + + compileSdk versions.compileSdk + + defaultConfig { + applicationId "im.vector.app" + // Set to API 21: see #405 + minSdk versions.minSdk + targetSdk versions.targetSdk + multiDexEnabled true + + renderscriptTargetApi 24 + renderscriptSupportModeEnabled true + + // `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode. + // Other branches (main, features, etc.) will have version code based on application version. + versionCode project.getVersionCode() + + // Required for sonar analysis + versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar" + + // Generate a random app task affinity + manifestPlaceholders = [appTaskAffinitySuffix: "H_${gitRevision()}"] + + buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" + buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\"" + buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\"" + buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\"" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + // Keep abiFilter for the universalApk + ndk { + abiFilters "armeabi-v7a", "x86", 'arm64-v8a', 'x86_64' + } + + // Ref: https://developer.android.com/studio/build/configure-apk-splits.html + splits { + // Configures multiple APKs based on ABI. + abi { + // Enables building multiple APKs per ABI. + enable true + + // By default all ABIs are included, so use reset() and include to specify that we only + // want APKs for armeabi-v7a, x86, arm64-v8a and x86_64. + + // Resets the list of ABIs that Gradle should create APKs for to none. + reset() + + // Specifies a list of ABIs that Gradle should create APKs for. + include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" + + // Generate a universal APK that includes all ABIs, so user who install from CI tool can use this one by default. + universalApk true + } + } + + applicationVariants.all { variant -> + // assign different version code for each output + def baseVariantVersion = variant.versionCode * 10 + variant.outputs.each { output -> + def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI)) + // Known limitation: it does not modify the value in the BuildConfig.java generated file + // See https://issuetracker.google.com/issues/171133218 + output.versionCodeOverride = baseVariantVersion + baseAbiVersionCode + print "ABI " + output.getFilter(OutputFile.ABI) + " \t-> VersionCode = " + output.versionCodeOverride + "\n" + output.outputFileName = output.outputFileName.replace("vector-app", "vector") + } + } + + // The following argument makes the Android Test Orchestrator run its + // "pm clear" command after each test invocation. This command ensures + // that the app's state is completely cleared between tests. + testInstrumentationRunnerArguments clearPackageData: 'true' + } + + testOptions { + // Disables animations during instrumented tests you run from the command line… + // This property does not affect tests that you run using Android Studio.” + animationsDisabled = true + + // Comment to run on Android 12 +// execution 'ANDROIDX_TEST_ORCHESTRATOR' + } + + signingConfigs { + debug { + keyAlias 'androiddebugkey' + keyPassword 'android' + storeFile file('./signature/debug.keystore') + storePassword 'android' + } + nightly { + keyAlias System.env.ELEMENT_ANDROID_NIGHTLY_KEYID ?: project.property("signing.element.nightly.keyId") + keyPassword System.env.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD ?: project.property("signing.element.nightly.keyPassword") + storeFile file('./signature/nightly.keystore') + storePassword System.env.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD ?: project.property("signing.element.nightly.storePassword") + } + release { + keyAlias project.property("signing.element.keyId") + keyPassword project.property("signing.element.keyPassword") + storeFile file(project.property("signing.element.storePath")) + storePassword project.property("signing.element.storePassword") + } + } + + buildTypes { + debug { + applicationIdSuffix ".debug" + signingConfig signingConfigs.debug + resValue "string", "app_name", "Element dbg" + resValue "color", "launcher_background", "#0DBD8B" + + if (project.hasProperty("coverage")) { + testCoverageEnabled = coverage.enableTestCoverage + } + } + + release { + resValue "string", "app_name", "Element" + resValue "color", "launcher_background", "#0DBD8B" + postprocessing { + removeUnusedCode true + removeUnusedResources true + // We do not activate obfuscation as it makes it hard then to read crash reports, and it's a bit useless on an open source project :) + obfuscate false + optimizeCode true + proguardFiles 'proguard-rules.pro' + } + // signingConfig signingConfigs.release + } + + nightly { + initWith release + applicationIdSuffix ".nightly" + versionNameSuffix "-nightly" + // Just override the background color of the launcher icon for the nightly build. + resValue "color", "launcher_background", "#07007E" + // We need to copy paste this block, this is not done automatically by `initWith release` + postprocessing { + removeUnusedCode true + removeUnusedResources true + // We do not activate obfuscation as it makes it hard then to read crash reports, and it's a bit useless on an open source project :) + obfuscate false + optimizeCode true + proguardFiles 'proguard-rules.pro' + } + matchingFallbacks = ['release'] + signingConfig signingConfigs.nightly + firebaseAppDistribution { + artifactType = "APK" + // We upload the universal APK to fix this error: + // "App Distribution found more than 1 output file for this variant. + // Please contact firebase-support@google.com for help using APK splits with App Distribution." + artifactPath = "$rootDir/vector-app/build/outputs/apk/gplay/nightly/vector-gplay-universal-nightly.apk" + // This file will be generated by the GitHub action + releaseNotesFile = "CHANGES_NIGHTLY.md" + groups = "external-testers" + // This should not be required, but if I do not add the appId, I get this error: + // "App Distribution halted because it had a problem uploading the APK: [404] Requested entity was not found." + appId = "1:912726360885:android:efd8545af52a9f9300427c" + } + } + } + + flavorDimensions "store" + + productFlavors { + gplay { + apply plugin: 'com.google.gms.google-services' + afterEvaluate { + tasks.matching { it.name.contains("GoogleServices") && !it.name.contains("Gplay") }*.enabled = false + } + + dimension "store" + isDefault = true + versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" + buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"" + buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\"" + } + + fdroid { + dimension "store" + versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}" + buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"" + buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\"" + } + } + + lintOptions { + lintConfig file("../tools/lint/lint.xml") + + checkDependencies true + abortOnError true + } + + compileOptions { + sourceCompatibility versions.sourceCompat + targetCompatibility versions.targetCompat + } + + kotlinOptions { + jvmTarget = "11" + freeCompilerArgs += [ + "-opt-in=kotlin.RequiresOptIn", + // Fixes false positive "This is an internal Mavericks API. It is not intended for external use." + // of MvRx `by viewModel()` calls. Maybe due to the inlining of code... This is a temporary fix... + "-opt-in=com.airbnb.mvrx.InternalMavericksApi", + // Opt in for kotlinx.coroutines.FlowPreview too + "-opt-in=kotlinx.coroutines.FlowPreview", + // Opt in for kotlinx.coroutines.ExperimentalCoroutinesApi too + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + ] + } +} + +dependencies { + implementation project(':vector') + implementation project(':vector-config') + implementation libs.dagger.hilt + implementation 'androidx.multidex:multidex:2.0.1' + implementation "androidx.sharetarget:sharetarget:1.1.0" + + kapt libs.dagger.hiltCompiler + + androidTestImplementation libs.androidx.testCore + androidTestImplementation libs.androidx.testRunner + androidTestImplementation libs.androidx.testRules + androidTestImplementation libs.androidx.junit + androidTestImplementation libs.androidx.espressoCore + androidTestImplementation libs.androidx.espressoContrib + androidTestImplementation libs.androidx.espressoIntents + androidTestImplementation libs.tests.kluent + androidTestImplementation libs.androidx.coreTesting + androidTestImplementation(libs.jetbrains.coroutinesTest) { + exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" + } + // Plant Timber tree for test + androidTestImplementation libs.tests.timberJunitRule + // "The one who serves a great Espresso" + androidTestImplementation('com.adevinta.android:barista:4.2.0') { + exclude group: 'org.jetbrains.kotlin' + } + androidTestImplementation libs.mockk.mockkAndroid + androidTestUtil libs.androidx.orchestrator + androidTestImplementation libs.androidx.fragmentTesting + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10" + debugImplementation libs.androidx.fragmentTesting +} + diff --git a/vector/proguard-rules.pro b/vector-app/proguard-rules.pro similarity index 100% rename from vector/proguard-rules.pro rename to vector-app/proguard-rules.pro diff --git a/vector/signature/README.md b/vector-app/signature/README.md similarity index 100% rename from vector/signature/README.md rename to vector-app/signature/README.md diff --git a/vector/signature/debug.keystore b/vector-app/signature/debug.keystore similarity index 100% rename from vector/signature/debug.keystore rename to vector-app/signature/debug.keystore diff --git a/vector/signature/nightly.keystore b/vector-app/signature/nightly.keystore similarity index 100% rename from vector/signature/nightly.keystore rename to vector-app/signature/nightly.keystore diff --git a/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt b/vector-app/src/androidTest/java/im/vector/app/CantVerifyTest.kt similarity index 96% rename from vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt rename to vector-app/src/androidTest/java/im/vector/app/CantVerifyTest.kt index e6b17c1e9e..6f9d6cdde9 100644 --- a/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/CantVerifyTest.kt @@ -23,6 +23,7 @@ import androidx.test.espresso.matcher.ViewMatchers import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest +import com.adevinta.android.barista.internal.viewaction.SleepViewAction import im.vector.app.features.MainActivity import im.vector.app.ui.robot.ElementRobot import org.junit.Rule @@ -33,7 +34,7 @@ import java.util.UUID @RunWith(AndroidJUnit4::class) @LargeTest -class CantVerifyTest : VerificationTestBase() { +class CantVerifyTest { @get:Rule val testRule = RuleChain diff --git a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt b/vector-app/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt similarity index 83% rename from vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt rename to vector-app/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt index 735e96c1e0..f09e522932 100644 --- a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt @@ -21,6 +21,8 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.test.platform.app.InstrumentationRegistry +import dagger.hilt.EntryPoints +import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.features.analytics.store.AnalyticsStore import kotlinx.coroutines.runBlocking import org.junit.rules.TestWatcher @@ -39,10 +41,11 @@ class ClearCurrentSessionRule : TestWatcher() { runBlocking { reflectAnalyticDatastore(context).edit { it.clear() } runCatching { - val holder = (context.applicationContext as VectorApplication).activeSessionHolder - holder.getSafeActiveSession()?.signOutService()?.signOut(true) - (context.applicationContext as VectorApplication).vectorPreferences.clearPreferences() - holder.clearActiveSession() + val entryPoint = EntryPoints.get(context.applicationContext, SingletonEntryPoint::class.java) + val sessionHolder = entryPoint.activeSessionHolder() + sessionHolder.getSafeActiveSession()?.signOutService()?.signOut(true) + entryPoint.vectorPreferences().clearPreferences() + sessionHolder.clearActiveSession() } } return super.apply(base, description) diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/EspressoExt.kt rename to vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt diff --git a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt b/vector-app/src/androidTest/java/im/vector/app/RegistrationTest.kt similarity index 99% rename from vector/src/androidTest/java/im/vector/app/RegistrationTest.kt rename to vector-app/src/androidTest/java/im/vector/app/RegistrationTest.kt index 7920e8e0d8..68a4d27deb 100644 --- a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/RegistrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector-app/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt similarity index 99% rename from vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt rename to vector-app/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index 7dc20178f2..1243758b2f 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. diff --git a/vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt b/vector-app/src/androidTest/java/im/vector/app/TestMatrixCallback.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/TestMatrixCallback.kt rename to vector-app/src/androidTest/java/im/vector/app/TestMatrixCallback.kt diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector-app/src/androidTest/java/im/vector/app/VerificationTestBase.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt rename to vector-app/src/androidTest/java/im/vector/app/VerificationTestBase.kt diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt similarity index 99% rename from vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt rename to vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index 8c9faee336..da13e49e84 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. @@ -33,6 +33,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest +import com.adevinta.android.barista.internal.viewaction.SleepViewAction import im.vector.app.core.utils.getMatrixInstance import im.vector.app.features.MainActivity import im.vector.app.features.home.HomeActivity diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector-app/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt similarity index 98% rename from vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt rename to vector-app/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index 23a662dcc8..53e088118b 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 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. @@ -33,6 +33,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.platform.app.InstrumentationRegistry +import com.adevinta.android.barista.internal.viewaction.SleepViewAction import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.getMatrixInstance import im.vector.app.features.MainActivity diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt b/vector-app/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt rename to vector-app/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt rename to vector-app/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt rename to vector-app/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt rename to vector-app/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt similarity index 91% rename from vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index 9434006060..d4878b8dcc 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -21,6 +21,7 @@ import androidx.test.espresso.IdlingPolicies import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import im.vector.app.R import im.vector.app.espresso.tools.ScreenshotFailureRule @@ -28,6 +29,7 @@ import im.vector.app.features.MainActivity import im.vector.app.getString import im.vector.app.ui.robot.ElementRobot import im.vector.app.ui.robot.settings.labs.LabFeature +import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences import im.vector.app.ui.robot.withDeveloperMode import org.junit.Rule import org.junit.Test @@ -49,7 +51,14 @@ class UiAllScreensSanityTest { .around(GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)) .around(ScreenshotFailureRule()) - private val elementRobot = ElementRobot() + private val elementRobot = ElementRobot( + LabFeaturesPreferences( + InstrumentationRegistry.getInstrumentation() + .targetContext + .resources + .getBoolean(R.bool.settings_labs_new_app_layout_default) + ) + ) // Last passing: // 2020-11-09 @@ -101,11 +110,11 @@ class UiAllScreensSanityTest { val spaceName = UUID.randomUUID().toString() elementRobot.space { - createSpace { + createSpace(true) { createAndCrawl(spaceName) } val publicSpaceName = UUID.randomUUID().toString() - createSpace { + createSpace(false) { createPublicSpace(publicSpaceName) } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/AnalyticsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/AnalyticsRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/AnalyticsRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/AnalyticsRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/CreateNewRoomRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/CreateNewRoomRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/CreateNewRoomRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/CreateNewRoomRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/DialogRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/DialogRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/DialogRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/DialogRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt similarity index 76% rename from vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt index b6fbfc23ab..b70fcfec25 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt @@ -33,21 +33,25 @@ import com.adevinta.android.barista.interaction.BaristaDialogInteractions.clickD import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDrawer import im.vector.app.EspressoHelper import im.vector.app.R +import im.vector.app.espresso.tools.clickOnPreference import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.espresso.tools.waitUntilDialogVisible import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.features.createdirect.CreateDirectRoomActivity import im.vector.app.features.home.HomeActivity import im.vector.app.features.onboarding.OnboardingActivity +import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.initialSyncIdlingResource import im.vector.app.ui.robot.settings.SettingsRobot import im.vector.app.ui.robot.settings.labs.LabFeature +import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences import im.vector.app.ui.robot.space.SpaceRobot import im.vector.app.withIdlingResource import timber.log.Timber -class ElementRobot { - +class ElementRobot( + private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(false) +) { fun onboarding(block: OnboardingRobot.() -> Unit) { block(OnboardingRobot()) } @@ -73,40 +77,63 @@ class ElementRobot { val activity = EspressoHelper.getCurrentActivity()!! val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession() withIdlingResource(initialSyncIdlingResource(uiSession)) { - waitUntilViewVisible(withId(R.id.bottomNavigationView)) + waitUntilViewVisible(withId(R.id.roomListContainer)) } } fun settings(shouldGoBack: Boolean = true, block: SettingsRobot.() -> Unit) { - openDrawer() - clickOn(R.id.homeDrawerHeaderSettingsView) + if (labsPreferences.isNewAppLayoutEnabled) { + onView(withId((R.id.avatar))).perform(click()) + } else { + openDrawer() + clickOn(R.id.homeDrawerHeaderSettingsView) + } + block(SettingsRobot()) if (shouldGoBack) pressBack() - waitUntilViewVisible(withId(R.id.bottomNavigationView)) + waitUntilViewVisible(withId(R.id.roomListContainer)) } fun newDirectMessage(block: NewDirectMessageRobot.() -> Unit) { - clickOn(R.id.bottom_action_people) - clickOn(R.id.createChatRoomButton) + if (labsPreferences.isNewAppLayoutEnabled) { + clickOn(R.id.newLayoutCreateChatButton) + waitUntilDialogVisible(withId(R.id.start_chat)) + clickOn(R.id.start_chat) + } else { + clickOn(R.id.bottom_action_people) + clickOn(R.id.createChatRoomButton) + } + waitUntilActivityVisible { waitUntilViewVisible(withId(R.id.userListSearch)) } closeSoftKeyboard() block(NewDirectMessageRobot()) pressBack() - waitUntilViewVisible(withId(R.id.bottomNavigationView)) + if (labsPreferences.isNewAppLayoutEnabled) { + pressBack() // close create dialog + } + waitUntilViewVisible(withId(R.id.roomListContainer)) } fun newRoom(block: NewRoomRobot.() -> Unit) { - clickOn(R.id.bottom_action_rooms) - RoomListRobot().newRoom { block() } - waitUntilViewVisible(withId(R.id.bottomNavigationView)) + if (!labsPreferences.isNewAppLayoutEnabled) { + clickOn(R.id.bottom_action_rooms) + } + RoomListRobot(labsPreferences).newRoom { block() } + if (labsPreferences.isNewAppLayoutEnabled) { + pressBack() // close create dialog + } + waitUntilViewVisible(withId(R.id.roomListContainer)) } fun roomList(block: RoomListRobot.() -> Unit) { - clickOn(R.id.bottom_action_rooms) - block(RoomListRobot()) - waitUntilViewVisible(withId(R.id.bottomNavigationView)) + if (!labsPreferences.isNewAppLayoutEnabled) { + clickOn(R.id.bottom_action_rooms) + } + + block(RoomListRobot(labsPreferences)) + waitUntilViewVisible(withId(R.id.roomListContainer)) } fun toggleLabFeature(labFeature: LabFeature) { @@ -146,8 +173,17 @@ class ElementRobot { } fun signout(expectSignOutWarning: Boolean) { - clickOn(R.id.groupToolbarAvatarImageView) - clickOn(R.id.homeDrawerHeaderSignoutView) + if (labsPreferences.isNewAppLayoutEnabled) { + onView(withId((R.id.avatar))) + .perform(click()) + waitUntilActivityVisible { + clickOn(R.string.settings_general_title) + } + clickOnPreference(R.string.action_sign_out) + } else { + clickOn(R.id.groupToolbarAvatarImageView) + clickOn(R.id.homeDrawerHeaderSignoutView) + } val isShowingSignOutWarning = kotlin.runCatching { waitUntilViewVisible(withId(R.id.exitAnywayButton)) @@ -187,7 +223,7 @@ class ElementRobot { } fun space(block: SpaceRobot.() -> Unit) { - block(SpaceRobot()) + block(SpaceRobot(labsPreferences)) } } diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/NewDirectMessageRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/NewDirectMessageRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/NewDirectMessageRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/NewDirectMessageRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/NewRoomRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/NewRoomRobot.kt similarity index 79% rename from vector/src/androidTest/java/im/vector/app/ui/robot/NewRoomRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/NewRoomRobot.kt index 09ff1162c0..0cea26d5cc 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/NewRoomRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/NewRoomRobot.kt @@ -21,10 +21,15 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import im.vector.app.R import im.vector.app.espresso.tools.waitUntilViewVisible +import im.vector.app.features.DefaultVectorFeatures +import im.vector.app.features.VectorFeatures +import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences class NewRoomRobot( - var createdRoom: Boolean = false + var createdRoom: Boolean = false, + private val labsPreferences: LabFeaturesPreferences ) { + private val features: VectorFeatures = DefaultVectorFeatures() fun createNewRoom(block: CreateNewRoomRobot.() -> Unit) { clickOn(R.string.create_new_room) diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt similarity index 99% rename from vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt index e72535c116..1f1a799db3 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt @@ -33,7 +33,6 @@ import im.vector.app.features.DefaultVectorFeatures import im.vector.app.waitForView class OnboardingRobot { - private val defaultVectorFeatures = DefaultVectorFeatures() fun crawl() { diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingServersRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/OnboardingServersRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingServersRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/OnboardingServersRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt similarity index 74% rename from vector/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt index dc07f06202..e4984aeed0 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt @@ -27,9 +27,11 @@ import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import im.vector.app.R import im.vector.app.espresso.tools.waitUntilActivityVisible +import im.vector.app.espresso.tools.waitUntilDialogVisible import im.vector.app.features.roomdirectory.RoomDirectoryActivity +import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences -class RoomListRobot { +class RoomListRobot(private val labsPreferences: LabFeaturesPreferences) { fun openRoom(roomName: String, block: RoomDetailRobot.() -> Unit) { clickOn(roomName) @@ -49,11 +51,17 @@ class RoomListRobot { } fun newRoom(block: NewRoomRobot.() -> Unit) { - clickOn(R.id.createGroupRoomButton) - waitUntilActivityVisible { - BaristaVisibilityAssertions.assertDisplayed(R.id.publicRoomsList) + if (labsPreferences.isNewAppLayoutEnabled) { + clickOn(R.id.newLayoutCreateChatButton) + waitUntilDialogVisible(ViewMatchers.withId(R.id.create_room)) + clickOn(R.id.create_room) + } else { + clickOn(R.id.createGroupRoomButton) + waitUntilActivityVisible { + BaristaVisibilityAssertions.assertDisplayed(R.id.publicRoomsList) + } } - val newRoomRobot = NewRoomRobot() + val newRoomRobot = NewRoomRobot(false, labsPreferences) block(newRoomRobot) if (!newRoomRobot.createdRoom) { pressBack() diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsAdvancedRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsAdvancedRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsAdvancedRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsAdvancedRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsGeneralRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsGeneralRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsGeneralRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsGeneralRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsHelpRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsHelpRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsHelpRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsHelpRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsLegalsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsLegalsRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsLegalsRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsLegalsRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsNotificationsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsNotificationsRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsNotificationsRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsNotificationsRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeature.kt diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeaturesPreferences.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeaturesPreferences.kt new file mode 100644 index 0000000000..8f36e7ae23 --- /dev/null +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/settings/labs/LabFeaturesPreferences.kt @@ -0,0 +1,19 @@ +/* + * 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.ui.robot.settings.labs + +data class LabFeaturesPreferences(val isNewAppLayoutEnabled: Boolean) diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt similarity index 94% rename from vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt index 018f3097ba..e5147c2085 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt @@ -31,6 +31,7 @@ import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.espresso.tools.waitUntilDialogVisible import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.features.home.HomeActivity +import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.spaces.manage.SpaceManageActivity class SpaceCreateRobot { @@ -85,7 +86,9 @@ class SpaceCreateRobot { clickOn(R.id.nextButton) waitUntilViewVisible(withId(R.id.recyclerView)) clickOn(R.id.nextButton) - waitUntilDialogVisible(withId(R.id.inviteByMxidButton)) + waitUntilActivityVisible { + waitUntilDialogVisible(withId(R.id.inviteByMxidButton)) + } // close invite dialog pressBack() waitUntilViewVisible(withId(R.id.timelineRecyclerView)) diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt new file mode 100644 index 0000000000..e8ff58ba6a --- /dev/null +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt @@ -0,0 +1,105 @@ +/* + * 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.ui.robot.space + +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.longClick +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers +import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn +import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDrawer +import com.adevinta.android.barista.internal.viewaction.ClickChildAction +import im.vector.app.R +import im.vector.app.espresso.tools.waitUntilDialogVisible +import im.vector.app.espresso.tools.waitUntilViewVisible +import im.vector.app.features.DefaultVectorFeatures +import im.vector.app.features.VectorFeatures +import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences +import org.hamcrest.Matchers + +class SpaceRobot(private val labsPreferences: LabFeaturesPreferences) { + private val features: VectorFeatures = DefaultVectorFeatures() + + fun createSpace(isFirstSpace: Boolean, block: SpaceCreateRobot.() -> Unit) { + if (labsPreferences.isNewAppLayoutEnabled) { + clickOn(R.id.newLayoutOpenSpacesButton) + if (isFirstSpace) { + waitUntilDialogVisible(ViewMatchers.withId(R.id.spaces_empty_group)) + clickOn(R.id.spaces_empty_button) + } else { + waitUntilDialogVisible(ViewMatchers.withId(R.id.groupListView)) + Espresso.onView(ViewMatchers.withId(R.id.groupListView)) + .perform( + RecyclerViewActions.actionOnItem( + ViewMatchers.hasDescendant(ViewMatchers.withId(R.id.plus)), + click() + ).atPosition(0) + ) + } + } else { + openDrawer() + clickOn(R.string.create_space) + } + block(SpaceCreateRobot()) + } + + fun spaceMenu(spaceName: String, block: SpaceMenuRobot.() -> Unit) { + if (labsPreferences.isNewAppLayoutEnabled) { + clickOn(R.id.newLayoutOpenSpacesButton) + waitUntilDialogVisible(ViewMatchers.withId(R.id.groupListView)) + } else { + openDrawer() + } + with(SpaceMenuRobot()) { + openMenu(spaceName) + block() + } + } + + fun openMenu(spaceName: String) { + waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) + if (labsPreferences.isNewAppLayoutEnabled) { + Espresso.onView(ViewMatchers.withId(R.id.groupListView)) + .perform( + RecyclerViewActions.actionOnItem( + ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.name), ViewMatchers.withText(spaceName))), + longClick() + ).atPosition(0) + ) + } else { + Espresso.onView(ViewMatchers.withId(R.id.groupListView)) + .perform( + RecyclerViewActions.actionOnItem( + ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.groupNameView), ViewMatchers.withText(spaceName))), + ClickChildAction.clickChildWithId(R.id.groupTmpLeave) + ).atPosition(0) + ) + } + + waitUntilDialogVisible(ViewMatchers.withId(R.id.spaceNameView)) + } + + fun selectSpace(spaceName: String) { + if (!labsPreferences.isNewAppLayoutEnabled) { + openDrawer() + waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) + } + clickOn(spaceName) + } +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt similarity index 100% rename from vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt rename to vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt diff --git a/vector-app/src/gplay/AndroidManifest.xml b/vector-app/src/gplay/AndroidManifest.xml new file mode 100755 index 0000000000..0ac14f9cd3 --- /dev/null +++ b/vector-app/src/gplay/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/vector/src/gplay/debug/google-services.json b/vector-app/src/gplay/debug/google-services.json similarity index 100% rename from vector/src/gplay/debug/google-services.json rename to vector-app/src/gplay/debug/google-services.json diff --git a/vector/src/gplay/nightly/google-services.json b/vector-app/src/gplay/nightly/google-services.json similarity index 100% rename from vector/src/gplay/nightly/google-services.json rename to vector-app/src/gplay/nightly/google-services.json diff --git a/vector/src/gplay/release/google-services.json b/vector-app/src/gplay/release/google-services.json similarity index 100% rename from vector/src/gplay/release/google-services.json rename to vector-app/src/gplay/release/google-services.json diff --git a/vector-app/src/main/AndroidManifest.xml b/vector-app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..84607cf3d7 --- /dev/null +++ b/vector-app/src/main/AndroidManifest.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector-app/src/main/java/im/vector/app/VectorApplication.kt similarity index 98% rename from vector/src/main/java/im/vector/app/VectorApplication.kt rename to vector-app/src/main/java/im/vector/app/VectorApplication.kt index 46cb6ec79b..ee04d908e8 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector-app/src/main/java/im/vector/app/VectorApplication.kt @@ -1,11 +1,11 @@ /* - * 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. * 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, @@ -66,6 +66,7 @@ import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.version.VersionProvider +import im.vector.application.R import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.auth.AuthenticationService diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt similarity index 98% rename from vector/src/main/java/im/vector/app/core/di/SingletonModule.kt rename to vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt index a060ebe731..384c584e0c 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -1,11 +1,11 @@ /* - * 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. * 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, @@ -28,7 +28,6 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import im.vector.app.BuildConfig import im.vector.app.EmojiCompatWrapper import im.vector.app.EmojiSpanify import im.vector.app.SpaceStateHandler @@ -58,6 +57,7 @@ import im.vector.app.features.settings.FontScalePreferencesImpl import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository +import im.vector.application.BuildConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers diff --git a/vector/src/main/res/drawable-anydpi-v26/ic_launcher_foreground.xml b/vector-app/src/main/res/drawable-anydpi-v26/ic_launcher_foreground.xml similarity index 100% rename from vector/src/main/res/drawable-anydpi-v26/ic_launcher_foreground.xml rename to vector-app/src/main/res/drawable-anydpi-v26/ic_launcher_foreground.xml diff --git a/vector/src/main/res/drawable/ic_launcher_background.xml b/vector-app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from vector/src/main/res/drawable/ic_launcher_background.xml rename to vector-app/src/main/res/drawable/ic_launcher_background.xml diff --git a/vector/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/vector-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from vector/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to vector-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/vector/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/vector-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from vector/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to vector-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/vector/src/main/res/mipmap-hdpi/ic_launcher.png b/vector-app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from vector/src/main/res/mipmap-hdpi/ic_launcher.png rename to vector-app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/vector/src/main/res/mipmap-hdpi/ic_launcher_round.png b/vector-app/src/main/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from vector/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to vector-app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/vector/src/main/res/mipmap-mdpi/ic_launcher.png b/vector-app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from vector/src/main/res/mipmap-mdpi/ic_launcher.png rename to vector-app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/vector/src/main/res/mipmap-mdpi/ic_launcher_round.png b/vector-app/src/main/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from vector/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to vector-app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/vector/src/main/res/mipmap-xhdpi/ic_launcher.png b/vector-app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from vector/src/main/res/mipmap-xhdpi/ic_launcher.png rename to vector-app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/vector/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/vector-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from vector/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to vector-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/vector/src/main/res/mipmap-xxhdpi/ic_launcher.png b/vector-app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from vector/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to vector-app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/vector/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/vector-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from vector/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to vector-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/vector/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/vector-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from vector/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to vector-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/vector/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/vector-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from vector/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to vector-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/vector/src/main/res/values/font_certs.xml b/vector-app/src/main/res/values/font_certs.xml similarity index 100% rename from vector/src/main/res/values/font_certs.xml rename to vector-app/src/main/res/values/font_certs.xml diff --git a/vector-app/src/main/res/values/strings.xml b/vector-app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..84dde58d40 --- /dev/null +++ b/vector-app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + + ignored + diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index b2cd21c3de..1701fd45b0 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -38,6 +38,7 @@ false + false true false diff --git a/vector/build.gradle b/vector/build.gradle index 7296262019..a5538053fc 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -1,14 +1,8 @@ -import com.android.build.OutputFile - -apply plugin: 'com.android.application' -apply plugin: 'com.google.firebase.appdistribution' -apply plugin: 'com.google.android.gms.oss-licenses-plugin' +apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' -apply plugin: 'com.likethesalad.stem' apply plugin: 'dagger.hilt.android.plugin' -apply plugin: 'kotlinx-knit' if (project.hasProperty("coverage")) { apply plugin: 'jacoco' @@ -18,98 +12,11 @@ kapt { correctErrorTypes = true } -knit { - files = fileTree(project.rootDir) { - include '**/*.md' - include '**/*.kt' - include '**/*.kts' - exclude '**/build/**' - exclude '**/.gradle/**' - exclude '**/towncrier/template.md' - exclude '**/CHANGES.md' - exclude '/node_modules' - } -} - -// Note: 2 digits max for each value -ext.versionMajor = 1 -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 - -static def getGitTimestamp() { - def cmd = 'git show -s --format=%ct' - return cmd.execute().text.trim() as Long -} - -static def generateVersionCodeFromTimestamp() { - // It's unix timestamp, minus timestamp of October 3rd 2018 (first commit date) divided by 100: It's incremented by one every 100 seconds. - // plus 20_000_000 for compatibility reason with the previous way the Version Code was computed - // Note that the result will be multiplied by 10 when adding the digit for the arch - return ((getGitTimestamp() - 1_538_524_800) / 100).toInteger() + 20_000_000 -} - -def generateVersionCodeFromVersionName() { - // plus 4_000_000 for compatibility reason with the previous way the Version Code was computed - // Note that the result will be multiplied by 10 when adding the digit for the arch - return (versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch) + 4_000_000 -} - -def getVersionCode() { - if (gitBranchName() == "develop") { - return generateVersionCodeFromTimestamp() - } else { - return generateVersionCodeFromVersionName() - } -} - static def gitRevision() { def cmd = "git rev-parse --short=8 HEAD" return cmd.execute().text.trim() } -static def gitRevisionDate() { - def cmd = "git show -s --format=%ci HEAD^{commit}" - return cmd.execute().text.trim() -} - -static def gitBranchName() { - def fromEnv = System.env.BUILDKITE_BRANCH as String ?: "" - - if (!fromEnv.isEmpty()) { - return fromEnv - } else { - // Note: this command return "HEAD" on Buildkite, so use the system env 'BUILDKITE_BRANCH' content first - def cmd = "git rev-parse --abbrev-ref HEAD" - return cmd.execute().text.trim() - } -} - -// For Google Play build, build on any other branch than main will have a "-dev" suffix -static def getGplayVersionSuffix() { - if (gitBranchName() == "main") { - return "" - } else { - return "-dev" - } -} - -static def gitTag() { - def cmd = "git describe --exact-match --tags" - return cmd.execute().text.trim() -} - -// For F-Droid build, build on a not tagged commit will have a "-dev" suffix -static def getFdroidVersionSuffix() { - if (gitTag() == "") { - return "-dev" - } else { - return "" - } -} - project.android.buildTypes.all { buildType -> buildType.javaCompileOptions.annotationProcessorOptions.arguments = [ @@ -117,13 +24,6 @@ project.android.buildTypes.all { buildType -> ] } -// map for the version codes last digit -// x86 must have greater values than arm -// 64 bits have greater value than 32 bits -ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].withDefault { 0 } - -def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0 - android { // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use // Ref: https://issuetracker.google.com/issues/144111441 @@ -132,29 +32,9 @@ android { compileSdk versions.compileSdk defaultConfig { - applicationId "im.vector.app" // Set to API 21: see #405 minSdk versions.minSdk targetSdk versions.targetSdk - multiDexEnabled true - - renderscriptTargetApi 24 - renderscriptSupportModeEnabled true - - // `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode. - // Other branches (main, features, etc.) will have version code based on application version. - versionCode project.getVersionCode() - - // Required for sonar analysis - versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar" - - // Generate a random app task affinity - manifestPlaceholders = [appTaskAffinitySuffix: "H_${gitRevision()}"] - - buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" - buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\"" - buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\"" - buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -163,38 +43,8 @@ android { abiFilters "armeabi-v7a", "x86", 'arm64-v8a', 'x86_64' } - // Ref: https://developer.android.com/studio/build/configure-apk-splits.html - splits { - // Configures multiple APKs based on ABI. - abi { - // Enables building multiple APKs per ABI. - enable true - - // By default all ABIs are included, so use reset() and include to specify that we only - // want APKs for armeabi-v7a, x86, arm64-v8a and x86_64. - - // Resets the list of ABIs that Gradle should create APKs for to none. - reset() - - // Specifies a list of ABIs that Gradle should create APKs for. - include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" - - // Generate a universal APK that includes all ABIs, so user who install from CI tool can use this one by default. - universalApk true - } - } - - applicationVariants.all { variant -> - // assign different version code for each output - def baseVariantVersion = variant.versionCode * 10 - variant.outputs.each { output -> - def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI)) - // Known limitation: it does not modify the value in the BuildConfig.java generated file - // See https://issuetracker.google.com/issues/171133218 - output.versionCodeOverride = baseVariantVersion + baseAbiVersionCode - print "ABI " + output.getFilter(OutputFile.ABI) + " \t-> VersionCode = " + output.versionCodeOverride + "\n" - } - } + // Generate a random app task affinity + manifestPlaceholders = [appTaskAffinitySuffix: "H_${gitRevision()}"] // The following argument makes the Android Test Orchestrator run its // "pm clear" command after each test invocation. This command ensures @@ -210,124 +60,31 @@ android { // Comment to run on Android 12 // execution 'ANDROIDX_TEST_ORCHESTRATOR' } - signingConfigs { - debug { - keyAlias 'androiddebugkey' - keyPassword 'android' - storeFile file('./signature/debug.keystore') - storePassword 'android' - } - nightly { - keyAlias System.env.ELEMENT_ANDROID_NIGHTLY_KEYID ?: project.property("signing.element.nightly.keyId") - keyPassword System.env.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD ?: project.property("signing.element.nightly.keyPassword") - storeFile file('./signature/nightly.keystore') - storePassword System.env.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD ?: project.property("signing.element.nightly.storePassword") - } - release { - keyAlias project.property("signing.element.keyId") - keyPassword project.property("signing.element.keyPassword") - storeFile file(project.property("signing.element.storePath")) - storePassword project.property("signing.element.storePassword") - } - } - buildTypes { debug { - applicationIdSuffix ".debug" - resValue "string", "app_name", "Element dbg" - resValue "color", "launcher_background", "#0DBD8B" - - signingConfig signingConfigs.debug - if (project.hasProperty("coverage")) { testCoverageEnabled = coverage.enableTestCoverage } } - - release { - resValue "string", "app_name", "Element" - resValue "color", "launcher_background", "#0DBD8B" - - postprocessing { - removeUnusedCode true - removeUnusedResources true - // We do not activate obfuscation as it makes it hard then to read crash reports, and it's a bit useless on an open source project :) - obfuscate false - optimizeCode true - proguardFiles 'proguard-rules.pro' - } - // signingConfig signingConfigs.release - } - nightly { initWith release - applicationIdSuffix ".nightly" - versionNameSuffix "-nightly" - - // Just override the background color of the launcher icon for the nightly build. - resValue "color", "launcher_background", "#07007E" - - // We need to copy paste this block, this is not done automatically by `initWith release` - postprocessing { - removeUnusedCode true - removeUnusedResources true - // We do not activate obfuscation as it makes it hard then to read crash reports, and it's a bit useless on an open source project :) - obfuscate false - optimizeCode true - proguardFiles 'proguard-rules.pro' - } matchingFallbacks = ['release'] - signingConfig signingConfigs.nightly - firebaseAppDistribution { - artifactType = "APK" - // We upload the universal APK to fix this error: - // "App Distribution found more than 1 output file for this variant. - // Please contact firebase-support@google.com for help using APK splits with App Distribution." - artifactPath = "$rootDir/vector/build/outputs/apk/gplay/nightly/vector-gplay-universal-nightly.apk" - // This file will be generated by the GitHub action - releaseNotesFile = "CHANGES_NIGHTLY.md" - groups = "external-testers" - // This should not be required, but if I do not add the appId, I get this error: - // "App Distribution halted because it had a problem uploading the APK: [404] Requested entity was not found." - appId = "1:912726360885:android:efd8545af52a9f9300427c" - } } + release } flavorDimensions "store" productFlavors { gplay { - apply plugin: 'com.google.gms.google-services' - afterEvaluate { - tasks.matching { it.name.contains("GoogleServices") && !it.name.contains("Gplay") }*.enabled = false - } - dimension "store" - isDefault = true - versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" - - buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"" - buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\"" } fdroid { dimension "store" - - versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}" - - buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"" - buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\"" } } - lintOptions { - lintConfig file("lint.xml") - - checkDependencies true - abortOnError true - } - compileOptions { sourceCompatibility versions.sourceCompat targetCompatibility versions.targetCompat @@ -367,15 +124,15 @@ android { dependencies { implementation project(":vector-config") - implementation project(":matrix-sdk-android") + api project(":matrix-sdk-android") implementation project(":matrix-sdk-android-flow") implementation project(":library:jsonviewer") + implementation project(":library:ui-strings") implementation project(":library:ui-styles") implementation project(":library:core-utils") implementation project(":library:attachment-viewer") implementation project(":library:diff-match-patch") implementation project(":library:multipicker") - implementation 'androidx.multidex:multidex:2.0.1' implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid @@ -384,49 +141,47 @@ dependencies { implementation libs.androidx.appCompat implementation libs.androidx.fragmentKtx implementation libs.androidx.constraintLayout - implementation "androidx.sharetarget:sharetarget:1.1.0" implementation libs.androidx.core implementation "androidx.media:media:1.6.0" implementation "androidx.transition:transition:1.4.1" implementation libs.androidx.biometric - implementation "org.threeten:threetenbp:1.4.0:no-tzdb" - implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.11.0" + api "org.threeten:threetenbp:1.4.0:no-tzdb" + api "com.gabrielittner.threetenbp:lazythreetenbp:0.11.0" implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin // Lifecycle implementation libs.androidx.lifecycleLivedata - implementation libs.androidx.lifecycleProcess + api libs.androidx.lifecycleProcess implementation libs.androidx.lifecycleRuntimeKtx - implementation libs.androidx.datastorepreferences + api libs.androidx.datastorepreferences // Opus Encoder implementation libs.element.opusencoder // Log - implementation libs.jakewharton.timber + api libs.jakewharton.timber // Debug - implementation 'com.facebook.stetho:stetho:1.6.0' + api 'com.facebook.stetho:stetho:1.6.0' - // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.54' + api libs.google.phonenumber // FlowBinding implementation libs.github.flowBinding implementation libs.github.flowBindingAppcompat - implementation libs.airbnb.epoxy + api libs.airbnb.epoxy implementation libs.airbnb.epoxyGlide kapt libs.airbnb.epoxyProcessor implementation libs.airbnb.epoxyPaging - implementation libs.airbnb.mavericks + api libs.airbnb.mavericks // Snap Helper https://github.com/rubensousa/GravitySnapHelper - implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2' + api 'com.github.rubensousa:gravitysnaphelper:2.2.2' // Nightly // API-only library @@ -435,7 +190,7 @@ dependencies { gplayImplementation libs.google.appdistribution // Work - implementation libs.androidx.work + api libs.androidx.work // Paging implementation libs.androidx.pagingRuntimeKtx @@ -444,7 +199,7 @@ dependencies { implementation libs.arrow.core // Pref - implementation libs.androidx.preferenceKtx + api libs.androidx.preferenceKtx // UI implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' @@ -502,7 +257,7 @@ dependencies { // UnifiedPush implementation 'com.github.UnifiedPush:android-connector:2.0.1' // UnifiedPush gplay flavor only - gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.1') { + gplayImplementation('com.google.firebase:firebase-messaging:23.0.8') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -519,7 +274,7 @@ dependencies { implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10227332@aar') // Jitsi - implementation('org.jitsi.react:jitsi-meet-sdk:5.0.2') { + api('org.jitsi.react:jitsi-meet-sdk:5.0.2') { exclude group: 'com.google.firebase' exclude group: 'com.google.android.gms' exclude group: 'com.android.installreferrer' @@ -531,8 +286,8 @@ dependencies { implementation 'me.dm7.barcodescanner:zxing:1.9.13' // Emoji Keyboard - implementation libs.vanniktech.emojiMaterial - implementation libs.vanniktech.emojiGoogle + api libs.vanniktech.emojiMaterial + api libs.vanniktech.emojiGoogle implementation 'im.dlg:android-dialer:1.2.5' @@ -545,14 +300,14 @@ dependencies { implementation 'commons-codec:commons-codec:1.15' // MapTiler - fdroidImplementation(libs.maplibre.androidSdk) { + fdroidApi(libs.maplibre.androidSdk) { exclude group: 'com.google.android.gms', module: 'play-services-location' } - fdroidImplementation(libs.maplibre.pluginAnnotation) { + fdroidApi(libs.maplibre.pluginAnnotation) { exclude group: 'com.google.android.gms', module: 'play-services-location' } - gplayImplementation libs.maplibre.androidSdk - gplayImplementation libs.maplibre.pluginAnnotation + gplayApi libs.maplibre.androidSdk + gplayApi libs.maplibre.pluginAnnotation // TESTS testImplementation libs.tests.junit diff --git a/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt b/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt index 61f745178c..65f81b145b 100644 --- a/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/voice/VoiceRecorderProviderTests.kt @@ -20,6 +20,8 @@ import android.os.Build import androidx.test.platform.app.InstrumentationRegistry import im.vector.app.AndroidVersionTestOverrider import im.vector.app.features.DefaultVectorFeatures +import io.mockk.every +import io.mockk.spyk import org.amshove.kluent.shouldBeInstanceOf import org.junit.After import org.junit.Test @@ -27,7 +29,7 @@ import org.junit.Test class VoiceRecorderProviderTests { private val context = InstrumentationRegistry.getInstrumentation().targetContext - private val provider = VoiceRecorderProvider(context, DefaultVectorFeatures()) + private val provider = spyk(VoiceRecorderProvider(context, DefaultVectorFeatures())) @After fun tearDown() { @@ -35,11 +37,19 @@ class VoiceRecorderProviderTests { } @Test - fun provideVoiceRecorderOnAndroidQReturnsQRecorder() { + fun provideVoiceRecorderOnAndroidQAndCodecReturnsQRecorder() { AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q) + every { provider.hasOpusEncoder() } returns true provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderQ::class) } + @Test + fun provideVoiceRecorderOnAndroidQButNoCodecReturnsLRecorder() { + AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q) + every { provider.hasOpusEncoder() } returns false + provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderL::class) + } + @Test fun provideVoiceRecorderOnOlderAndroidVersionReturnsLRecorder() { AndroidVersionTestOverrider.override(Build.VERSION_CODES.LOLLIPOP) diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt deleted file mode 100644 index b8a2f4313b..0000000000 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.ui.robot.space - -import androidx.recyclerview.widget.RecyclerView -import androidx.test.espresso.Espresso -import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.matcher.ViewMatchers -import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn -import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDrawer -import com.adevinta.android.barista.internal.viewaction.ClickChildAction -import im.vector.app.R -import im.vector.app.espresso.tools.waitUntilDialogVisible -import im.vector.app.espresso.tools.waitUntilViewVisible -import org.hamcrest.Matchers - -class SpaceRobot { - - fun createSpace(block: SpaceCreateRobot.() -> Unit) { - openDrawer() - clickOn(R.string.create_space) - block(SpaceCreateRobot()) - } - - fun spaceMenu(spaceName: String, block: SpaceMenuRobot.() -> Unit) { - openDrawer() - with(SpaceMenuRobot()) { - openMenu(spaceName) - block() - } - } - - fun openMenu(spaceName: String) { - waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) - Espresso.onView(ViewMatchers.withId(R.id.groupListView)) - .perform( - RecyclerViewActions.actionOnItem( - ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.groupNameView), ViewMatchers.withText(spaceName))), - ClickChildAction.clickChildWithId(R.id.groupTmpLeave) - ).atPosition(0) - ) - waitUntilDialogVisible(ViewMatchers.withId(R.id.spaceNameView)) - } - - fun selectSpace(spaceName: String) { - openDrawer() - waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView)) - clickOn(spaceName) - } -} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index c127e3aed6..9b2711a8c3 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -88,7 +88,7 @@ class DebugFeaturesStateFactory @Inject constructor( createBooleanFeature( label = "Enable New App Layout", key = DebugFeatureKeys.newAppLayoutEnabled, - factory = VectorFeatures::isNewAppLayoutEnabled + factory = VectorFeatures::isNewAppLayoutFeatureEnabled ), createBooleanFeature( label = "Enable New Device Management", diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 003b9b8084..bb4cae3201 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -76,8 +76,8 @@ class DebugVectorFeatures( override fun shouldStartDmOnFirstMessage(): Boolean = read(DebugFeatureKeys.startDmOnFirstMsg) ?: vectorFeatures.shouldStartDmOnFirstMessage() - override fun isNewAppLayoutEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled) - ?: vectorFeatures.isNewAppLayoutEnabled() + override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled) + ?: vectorFeatures.isNewAppLayoutFeatureEnabled() override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled) ?: vectorFeatures.isNewDeviceManagementEnabled() diff --git a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt index d3e70e26e6..2abf6487e2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksFragment.kt @@ -28,7 +28,8 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentDebugMemoryLeaksBinding @AndroidEntryPoint -class DebugMemoryLeaksFragment : VectorBaseFragment() { +class DebugMemoryLeaksFragment : + VectorBaseFragment() { private val viewModel: DebugMemoryLeaksViewModel by fragmentViewModel() diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index 38253fe7c2..be3d41e0e1 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -16,6 +16,8 @@ package im.vector.app.features.debug.settings +import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -24,6 +26,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentDebugPrivateSettingsBinding +import im.vector.app.features.home.room.list.home.release.ReleaseNotesActivity class DebugPrivateSettingsFragment : VectorBaseFragment() { @@ -35,7 +38,6 @@ class DebugPrivateSettingsFragment : VectorBaseFragment viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked)) } + views.releaseNotesActivityHasBeenDisplayedReset.setOnClickListener { + viewModel.handle(DebugPrivateSettingsViewActions.ResetReleaseNotesActivityHasBeenDisplayed) + } + views.showReleaseNotesActivity.setOnClickListener { + startActivity(Intent(requireActivity(), ReleaseNotesActivity::class.java)) + } } override fun invalidate() = withState(viewModel) { @@ -57,5 +65,7 @@ class DebugPrivateSettingsFragment : VectorBaseFragment(initialState) { @AssistedFactory @@ -43,6 +45,15 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( init { observeVectorOverrides() + observeReleaseNotesPreferencesStore() + } + + private fun observeReleaseNotesPreferencesStore() { + releaseNotesPreferencesStore.appLayoutOnboardingShown.setOnEach { + copy( + releaseNotesActivityHasBeenDisplayed = it + ) + } } private fun observeVectorOverrides() { @@ -72,6 +83,13 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) is SetDisplayNameCapabilityOverride -> handleSetDisplayNameCapabilityOverride(action) is SetAvatarCapabilityOverride -> handleSetAvatarCapabilityOverride(action) + DebugPrivateSettingsViewActions.ResetReleaseNotesActivityHasBeenDisplayed -> handleResetReleaseNotesActivityHasBeenDisplayed() + } + } + + private fun handleResetReleaseNotesActivityHasBeenDisplayed() { + viewModelScope.launch { + releaseNotesPreferencesStore.setAppLayoutOnboardingShown(false) } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 749b11a744..a390c94942 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -22,7 +22,8 @@ import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdo data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, - val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides(), + val releaseNotesActivityHasBeenDisplayed: Boolean = false, ) : MavericksState data class HomeserverCapabilityOverrides( diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index c42ad68dce..55824930bc 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -49,6 +49,27 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="4dp" /> + + +