diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 2dd968951f..0e51d5155e 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -5,7 +5,7 @@ body: - type: markdown attributes: value: | - Thank you for taking the time to propose a new feature or make a suggestion. + Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/vector-im/element-meta/discussions/new?category=ideas). - type: textarea id: usecase attributes: diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index b28dbbde69..0c3542997a 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -20,34 +20,11 @@ body: - [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores. - [ ] While Weblate is locked, and after the PR from Weblate has been merged, handle all the TODOs in the main `strings.xml` file - [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect. - - [ ] Ensure all [the required PRs](https://github.com/vector-im/element-android/pulls?q=is%3Aopen+is%3Apr+label%3AZ-NextRelease) have been merged ### Do the release - - [ ] Make sure `develop` and `main` are up to date (git pull) - - [ ] Checkout develop and create a release with gitflow, branch name `release/1.2.3` - - [ ] Check the crashes from the PlayStore - - [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.2.3-dev - - [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()` - - [ ] Create an account on matrix.org and do some smoke tests that the sanity test does not cover like: 1-1 call, 1-1 video call, Jitsi call for instance - - [ ] Run towncrier: `towncrier build --version v1.2.3 --draft` (remove `--draft` do write the file CHANGES.md) - - [ ] Check that the folder `changelog.d` is empty. It can happen that some remaining files stay here - - [ ] Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things - - [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs - - [ ] (optional) Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes. - - [ ] Finish release with gitflow, delete the draft PR (if created) - - [ ] Push `main` and the new tag `v1.2.3` to origin - - [ ] Checkout `develop` - - [ ] Increase version (versionPatch + 2) in `./vector/build.gradle` - - [ ] Change the value of SDK_VERSION in the file `./matrix-sdk-android/build.gradle` - - [ ] Commit and push `develop` - - [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch. - - [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them. - - [ ] Install the APK on your phone to check that the upgrade went well (no init sync, etc.) - - [ ] Create the release on gitHub [from the tag](https://github.com/vector-im/element-android/tags), copy paste the block from the file CHANGES.md - - [ ] Add the 4 signed APKs to the GitHub release - - [ ] Ping the Android Internal room + - [ ] Run the script ./tools/release/releaseScript.sh and follow the steps. ### Once tested and validated internally @@ -84,29 +61,9 @@ body: The SDK2 and the sample app are released only when Element has been pushed to production. - - [ ] Checkout the `main` branch on Element Android project + - [ ] On the [SDK2 project](https://github.com/matrix-org/matrix-android-sdk2), run the script ./tools/releaseScript.sh and follow the instructions. - #### On the SDK2 project - - https://github.com/matrix-org/matrix-android-sdk2 - - - [ ] Create a release with GitFlow - - [ ] Update the value of VERSION_NAME in the file gradle.properties - - [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project. - - [ ] Run the script `./tools/import_from_element.sh` - - [ ] Check the diff in the file `./matrix-sdk-android/build.gradle` and restore what may have been erased (in particular the line `apply plugin: "com.vanniktech.maven.publish"` and the line about the version) - - [ ] Let the script finish to build the library - - [ ] Update the file `CHANGES.md` - - [ ] Finish the release using GitFlow - - [ ] Push the branch `main`, the new tag and the branch `develop` to origin - - ##### Release on MavenCentral - - - [ ] Checkout the branch `main` - - [ ] Run the command `./gradlew publish --no-daemon --no-parallel`. You'll need some non-public element to do so - - [ ] Run the command `./gradlew closeAndReleaseRepository`. If it is working well, you can jump directly to the final step of this section. - - If `./gradlew closeAndReleaseRepository` fails (for instance, several repositories are waiting to be handled), you have to close and release the repository manually. Do the following steps: + Note: if the step `./gradlew closeAndReleaseRepository` fails (for instance, several repositories are waiting to be handled), you have to close and release the repository manually. Do the following steps: - [ ] Connect to https://s01.oss.sonatype.org - [ ] Click on Staging Repositories and check the the files have been uploaded @@ -114,15 +71,6 @@ body: - [ ] Wait (check Activity tab until step "Repository closed" is displayed) - [ ] Click on release. The staging repository will disappear - Final step - - - [ ] Check that the release is available in https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/ (it can take a few minutes) - - ##### Release on GitHub - - - [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags) - - [ ] Upload the AAR on the GitHub release - ### Android SDK2 sample https://github.com/matrix-org/matrix-android-sdk2-sample diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 5698a696b6..30b6600c94 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.3 + uses: danger/danger-js@11.1.4 with: args: "--dangerfile tools/danger/dangerfile.js" env: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b6333c5940..a44872e0ef 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build docs run: ./gradlew dokkaHtml diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index bf948064ed..af854bf371 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -16,7 +16,7 @@ env: jobs: # More info on should-i-run: - # If this fails to run (the IF doesn't complete) then the needs will not be satisfied for any of the + # If this fails to run (the IF doesn't complete) then the needs will not be satisfied for any of the # other jobs below, so none will run. # except for the notification job at the bottom which will run all the time, unless should-i-run isn't # successful, or all the other jobs have succeeded @@ -27,11 +27,12 @@ jobs: if: github.event.pull_request.merged # Additionally require PR to have been completely merged. steps: - run: echo "Run those tests!" # no-op success - + ui-tests: name: UI Tests (Synapse) needs: should-i-run runs-on: buildjet-4vcpu-ubuntu-2204 + timeout-minutes: 90 # We might need to increase it if the time for tests grows strategy: fail-fast: false matrix: @@ -52,7 +53,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v1.0.3 + uses: michaelkaye/setup-matrix-synapse@v1.0.4 with: uploadLogs: true httpPort: 8080 @@ -94,7 +95,7 @@ jobs: needs: - should-i-run - ui-tests - if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success')) + if: always() && (needs.should-i-run.result == 'success' ) && (needs.ui-tests.result != 'success') # No concurrency required, runs every time on a schedule. steps: - uses: michaelkaye/matrix-hookshot-action@v1.0.0 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 1692e2e281..9d9e8e76e8 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -66,7 +66,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@11.1.3 + uses: danger/danger-js@11.1.4 with: args: "--dangerfile tools/danger/dangerfile-lint.js" env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1816fe3a78..931ec2da45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,6 +14,7 @@ jobs: tests: name: Runs all tests runs-on: buildjet-4vcpu-ubuntu-2204 + timeout-minutes: 90 # We might need to increase it if the time for tests grows strategy: matrix: api-level: [28] @@ -50,7 +51,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.8 - - uses: michaelkaye/setup-matrix-synapse@v1.0.3 + - uses: michaelkaye/setup-matrix-synapse@v1.0.4 with: uploadLogs: true httpPort: 8080 @@ -126,26 +127,26 @@ jobs: # Unneeded as part of the test suite above, kept around in case we want to re-enable them. # # # Build Android Tests -# build-android-tests: -# name: Build Android Tests -# runs-on: ubuntu-latest +# build-android-tests: +# name: Build Android Tests +# runs-on: ubuntu-latest # concurrency: # group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }} # cancel-in-progress: true -# steps: -# - uses: actions/checkout@v3 -# - uses: actions/setup-java@v3 -# with: -# distribution: 'adopt' -# java-version: 11 -# - uses: actions/cache@v3 -# with: -# path: | -# ~/.gradle/caches -# ~/.gradle/wrapper -# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} -# restore-keys: | -# ${{ runner.os }}-gradle- -# - name: Build Android Tests +# steps: +# - uses: actions/checkout@v3 +# - uses: actions/setup-java@v3 +# with: +# distribution: 'adopt' +# java-version: 11 +# - uses: actions/cache@v3 +# with: +# path: | +# ~/.gradle/caches +# ~/.gradle/wrapper +# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} +# restore-keys: | +# ${{ runner.os }}-gradle- +# - name: Build Android Tests # run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml index 6a22bf5223..4dadc25ab4 100644 --- a/.github/workflows/triage-incoming.yml +++ b/.github/workflows/triage-incoming.yml @@ -10,7 +10,7 @@ jobs: # Skip in forks if: github.repository == 'vector-im/element-android' steps: - - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 + - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d with: project: Issue triage column: Incoming diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 174e3c54c0..f1458a1d11 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -29,6 +29,23 @@ jobs: labels: ['Z-Labs'] }) + apply_Help-Wanted_label: + name: Add "Help Wanted" label to all "good first issue" and Hacktoberfest + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'good first issue') || + contains(github.event.issue.labels.*.name, 'Hacktoberfest') + steps: + - uses: actions/github-script@v5 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['Help Wanted'] + }) + move_needs_info_issues: name: X-Needs-Info issues to Need info column on triage board runs-on: ubuntu-latest @@ -48,7 +65,13 @@ jobs: # Skip in forks if: > github.repository == 'vector-im/element-android' && - contains(github.event.issue.labels.*.name, 'X-Needs-Design') + contains(github.event.issue.labels.*.name, 'X-Needs-Design') && + (contains(github.event.issue.labels.*.name, 'S-Critical') && + (contains(github.event.issue.labels.*.name, 'O-Frequent') || + contains(github.event.issue.labels.*.name, 'O-Occasional')) || + (contains(github.event.issue.labels.*.name, 'S-Major') && + contains(github.event.issue.labels.*.name, 'O-Frequent')) || + contains(github.event.issue.labels.*.name, 'A11y')) steps: - uses: octokit/graphql-action@v2.x id: add_to_project @@ -56,8 +79,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -80,8 +103,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -106,8 +129,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -131,8 +154,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -155,8 +178,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -180,8 +203,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -205,8 +228,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -235,8 +258,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -246,3 +269,105 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc4ABTXY" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features1: + name: Add labelled issues to PS features team 1 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Polls') || + contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || + (contains(github.event.issue.labels.*.name, 'A-Voice-Messages') && + !contains(github.event.issue.labels.*.name, 'A-Broadcast')) || + (contains(github.event.issue.labels.*.name, 'A-Session-Mgmt') && + contains(github.event.issue.labels.*.name, 'A-User-Settings')) + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKF" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features2: + name: Add labelled issues to PS features team 2 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-DM-Start') || + contains(github.event.issue.labels.*.name, 'A-Broadcast') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKd" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + ps_features3: + name: Add labelled issues to PS features team 3 + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4AHJKW" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + voip: + name: Add labelled issues to VoIP project board + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'Team: VoIP') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4ABMIk" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml index 61f1f114dd..6aeba66ccc 100644 --- a/.github/workflows/triage-move-review-requests.yml +++ b/.github/workflows/triage-move-review-requests.yml @@ -60,8 +60,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!, $contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -129,8 +129,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!, $contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index e762102226..07e73fe805 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -24,7 +24,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A11y') && contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 + - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d with: project: Android App Team column: Important Issues & Topics (P1) @@ -50,7 +50,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A11y') && contains(github.event.issue.labels.*.name, 'O-Frequent'))) steps: - - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 + - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d with: project: Crypto Team column: Ready diff --git a/.github/workflows/triage-unlabelled.yml b/.github/workflows/triage-unlabelled.yml index 06df286d09..98d6579958 100644 --- a/.github/workflows/triage-unlabelled.yml +++ b/.github/workflows/triage-unlabelled.yml @@ -28,7 +28,7 @@ jobs: echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV fi - name: Move issue - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 + uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d if: ${{ env.ALREADY_IN_BOARD == 'true' }} with: project: Issue triage diff --git a/CHANGES.md b/CHANGES.md index d1e4834988..442d3641dd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,142 @@ +Changes in Element v1.5.8 (2022-11-17) +====================================== + +Features ✨ +---------- + - [Session manager] Multi-session signout ([#7418](https://github.com/vector-im/element-android/issues/7418)) + - Rich text editor: add full screen mode. ([#7436](https://github.com/vector-im/element-android/issues/7436)) + - [Rich text editor] Add plain text mode ([#7452](https://github.com/vector-im/element-android/issues/7452)) + - Move TypingView inside the timeline items. ([#7496](https://github.com/vector-im/element-android/issues/7496)) + - Push notifications toggle: align implementation for current session ([#7512](https://github.com/vector-im/element-android/issues/7512)) + - Voice messages - Persist the playback position across different screens ([#7582](https://github.com/vector-im/element-android/issues/7582)) + +Bugfixes 🐛 +---------- + - [Voice Broadcast] Do not display the recorder view for a live broadcast started from another session ([#7431](https://github.com/vector-im/element-android/issues/7431)) + - [Session manager] Hide push notification toggle when there is no server support ([#7457](https://github.com/vector-im/element-android/issues/7457)) + - Fix rich text editor textfield not growing to fill parent on full screen. ([#7491](https://github.com/vector-im/element-android/issues/7491)) + - Fix duplicated mention pills in some cases ([#7501](https://github.com/vector-im/element-android/issues/7501)) + - Voice Broadcast - Fix duplicated voice messages in the internal playlist ([#7502](https://github.com/vector-im/element-android/issues/7502)) + - When joining a room, the message composer is displayed once the room is loaded. ([#7509](https://github.com/vector-im/element-android/issues/7509)) + - Voice Broadcast - Fix error on voice messages in unencrypted rooms ([#7519](https://github.com/vector-im/element-android/issues/7519)) + - Fix description of verified sessions ([#7533](https://github.com/vector-im/element-android/issues/7533)) + +In development 🚧 +---------------- + - [Voice Broadcast] Improve timeline items factory and handle bad recording state display ([#7448](https://github.com/vector-im/element-android/issues/7448)) + - [Voice Broadcast] Stop recording when opening the room after an app restart ([#7450](https://github.com/vector-im/element-android/issues/7450)) + - [Voice Broadcast] Improve playlist fetching and player codebase ([#7478](https://github.com/vector-im/element-android/issues/7478)) + - [Voice Broadcast] Display an error dialog if the user fails to start a voice broadcast ([#7485](https://github.com/vector-im/element-android/issues/7485)) + - [Voice Broadcast] Add seekbar in listening tile ([#7496](https://github.com/vector-im/element-android/issues/7496)) + - [Voice Broadcast] Improve the live indicator icon rendering in the timeline ([#7579](https://github.com/vector-im/element-android/issues/7579)) + - Voice Broadcast - Add maximum length ([#7588](https://github.com/vector-im/element-android/issues/7588)) + +SDK API changes ⚠️ +------------------ + - [Metrics] Add `SpannableMetricPlugin` to support spans within transactions. ([#7514](https://github.com/vector-im/element-android/issues/7514)) + - Fix a bug that caused messages with no formatted text to be quoted as "null". ([#7530](https://github.com/vector-im/element-android/issues/7530)) + - If message content has no `formattedBody`, default to `body` when editing. ([#7574](https://github.com/vector-im/element-android/issues/7574)) + + +Changes in Element v1.5.7 (2022-11-07) +====================================== + +Bugfixes 🐛 +---------- +- Fix regression when syncing with homeserver < 1.4. ([#7534](https://github.com/vector-im/element-android/issues/7534)) + +Changes in Element v1.5.6 (2022-11-02) +====================================== + +Features ✨ +---------- + - Add new UI for selecting an attachment ([#7429](https://github.com/vector-im/element-android/issues/7429)) + - Multi selection in sessions list ([#7396](https://github.com/vector-im/element-android/issues/7396)) + +Bugfixes 🐛 +---------- + - New line and Enter hardware key presses deleting existing text in some keyboards. ([#7357](https://github.com/vector-im/element-android/issues/7357)) + - Fix share actions using share dialog. ([#7400](https://github.com/vector-im/element-android/issues/7400)) + - Fix crash by disabling Flipper on Android API 22 and below - only affects debug version of the application. ([#7428](https://github.com/vector-im/element-android/issues/7428)) + +In development 🚧 +---------------- + - [Voice Broadcast] Live listening support ([#7419](https://github.com/vector-im/element-android/issues/7419)) + - [Voice Broadcast] Improve rendering in the timeline ([#7421](https://github.com/vector-im/element-android/issues/7421)) + - Add logic for sign in with QR code ([#7369](https://github.com/vector-im/element-android/issues/7369)) + +SDK API changes ⚠️ +------------------ + - Add MetricPlugin interface to implement metrics in SDK clients. ([#7438](https://github.com/vector-im/element-android/issues/7438)) + +Other changes +------------- + - Upgrade Jitsi SDK to 6.2.2 and WebRtc to 1.106.1-jitsi-12039821. ([#6195](https://github.com/vector-im/element-android/issues/6195)) + - Gets thread notifications from sync response ([#7424](https://github.com/vector-im/element-android/issues/7424)) + - Replace org.apache.sanselan:sanselan by org.apache.commons:commons-imaging ([#7454](https://github.com/vector-im/element-android/issues/7454)) + + +Changes in Element v1.5.4 (2022-10-19) +====================================== + +Features ✨ +---------- + - Add WYSIWYG editor, under a lab flag. ([#7288](https://github.com/vector-im/element-android/issues/7288)) + - New Device management, can be enabled in the labs settings. + - Voice broadcast can be enabled in the labs settings (recording is possible only on Android 10 and up). + +Bugfixes 🐛 +---------- + - Fix wrong mic button direction to cancel on RTL languages ([#5968](https://github.com/vector-im/element-android/issues/5968)) + - Handle properly when getUser returns null - prefer using getUserOrDefault ([#7372](https://github.com/vector-im/element-android/issues/7372)) + - [Device Management] Long session names not handled well ([#7310](https://github.com/vector-im/element-android/issues/7310)) + - Fix editing formatted messages with plain text editor ([#7359](https://github.com/vector-im/element-android/issues/7359)) + +In development 🚧 +---------------- + - [Device Management] Save "matrix_client_information" events on login/registration ([#7257](https://github.com/vector-im/element-android/issues/7257)) + - [Device management] Add lab flag for the feature ([#7336](https://github.com/vector-im/element-android/issues/7336)) + - [Device management] Add lab flag for matrix client info account data event ([#7344](https://github.com/vector-im/element-android/issues/7344)) + - [Device Management] Redirect to the new screen everywhere when lab flag is on ([#7374](https://github.com/vector-im/element-android/issues/7374)) + - [Device Management] Show correct device type icons ([#7277](https://github.com/vector-im/element-android/issues/7277)) + - [Device Management] Render extended device info ([#7294](https://github.com/vector-im/element-android/issues/7294)) + - [Device management] Improve the parsing for OS of Desktop/Web sessions ([#7321](https://github.com/vector-im/element-android/issues/7321)) + - [Device management] Hide the IP address and last activity date on current session ([#7324](https://github.com/vector-im/element-android/issues/7324)) + - [Device management] Update the unknown verification status icon ([#7327](https://github.com/vector-im/element-android/issues/7327)) + - [Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget ([#7273](https://github.com/vector-im/element-android/issues/7273)) + - [Voice Broadcast] Aggregate state events in the timeline ([#7283](https://github.com/vector-im/element-android/issues/7283)) + - [Voice Broadcast] Record and send non aggregated voice messages to the room ([#7363](https://github.com/vector-im/element-android/issues/7363)) + - [Voice Broadcast] Start listening to a voice broadcast ([#7387](https://github.com/vector-im/element-android/issues/7387)) + - [Voice Broadcast] Enable the feature (behind a lab flag and only for Android 10 and up) ([#7393](https://github.com/vector-im/element-android/issues/7393)) + - [Voice Broadcast] Add additional data in events ([#7397](https://github.com/vector-im/element-android/issues/7397)) + - Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API ([#7217](https://github.com/vector-im/element-android/issues/7217)) + - Adds pusher toggle setting to device manager v2 ([#7261](https://github.com/vector-im/element-android/issues/7261)) + - Implement QR Code Login UI ([#7338](https://github.com/vector-im/element-android/issues/7338)) + - Implements client-side of local notification settings event ([#7300](https://github.com/vector-im/element-android/issues/7300)) + - Links "Enable Notifications for this session" setting to enabled value in pusher ([#7281](https://github.com/vector-im/element-android/issues/7281)) + +SDK API changes ⚠️ +------------------ + - Stop using `original_event` field from `/relations` endpoint ([#7282](https://github.com/vector-im/element-android/issues/7282)) + - Add `formattedText` or similar optional parameters in several methods: + * RelationService: + * editTextMessage + * editReply + * replyToMessage + * SendService: + * sendQuotedTextMessage + This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible. ([#7288](https://github.com/vector-im/element-android/issues/7288)) + - Add support for `m.login.token` auth during QR code based sign in ([#7358](https://github.com/vector-im/element-android/issues/7358)) + - Allow getting the formatted or plain text body of a message for the fun `TimelineEvent.getTextEditableContent()`. ([#7359](https://github.com/vector-im/element-android/issues/7359)) + +Other changes +------------- + - Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment. ([#7285](https://github.com/vector-im/element-android/issues/7285)) + - Dependency to arrow has been removed. Please use `org.matrix.android.sdk.api.util.Optional` instead. ([#7335](https://github.com/vector-im/element-android/issues/7335)) + - Update WYSIWYG editor designs. ([#7354](https://github.com/vector-im/element-android/issues/7354)) + - Update WYSIWYG library to v0.2.1. ([#7384](https://github.com/vector-im/element-android/issues/7384)) + + Changes in Element v1.5.2 (2022-10-05) ====================================== diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e3c784dac..40ae848415 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ * [Code quality](#code-quality) * [Internal tool](#internal-tool) * [ktlint](#ktlint) + * [knit](#knit) * [lint](#lint) * [Unit tests](#unit-tests) * [Tests](#tests) @@ -126,6 +127,23 @@ Note that you can run For ktlint to fix some detected errors for you (you still have to check and commit the fix of course) +#### knit + +[knit](https://github.com/Kotlin/kotlinx-knit) is a tool which checks markdown files on the project. Also it generates/updates the table of content (toc) of the markdown files. + +So everytime the toc should be updated, just run +
+./gradlew knit ++ +and commit the changes. + +The CI will check that markdown files are up to date by running + +
+./gradlew knitCheck ++ #### lint
diff --git a/README.md b/README.md index e351b64927..e8fceb2eb2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) +[![Latest build](https://github.com/vector-im/element-android/actions/workflows/build.yml/badge.svg?query=branch%3Adevelop)](https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Adevelop) [![Weblate](https://translate.element.io/widgets/element-android/-/svg-badge.svg)](https://translate.element.io/engage/element-android/?utm_source=widget) [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android) @@ -14,7 +14,7 @@ It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-androi [](https://play.google.com/store/apps/details?id=im.vector.app) [](https://f-droid.org/app/im.vector.app) -Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nightly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml) +Build of develop branch: [![GitHub Action](https://github.com/vector-im/element-android/actions/workflows/build.yml/badge.svg?query=branch%3Adevelop)](https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Adevelop) Nightly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml) # New Android SDK @@ -40,7 +40,7 @@ If you would like to receive releases more quickly (bearing in mind that they ma 1. [Sign up to receive beta releases](https://play.google.com/apps/testing/im.vector.app) via the Google Play Store. 2. Install a [release APK](https://github.com/vector-im/element-android/releases) directly - download the relevant .apk file and allow installing from untrusted sources in your device settings. Note: these releases are the Google Play version, which depend on some Google services. If you prefer to avoid that, try the latest dev builds, and choose the F-Droid version. -3. If you're really brave, install the [very latest dev build](https://buildkite.com/matrix-dot-org/element-android/builds/latest?branch=develop&state=passed) - click on *Assemble (GPlay or FDroid) Debug version* then on *Artifacts*. +3. If you're really brave, install the [very latest dev build](https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Adevelop) - pick a build, then click on `Summary` to download the APKs from there: `vector-Fdroid-debug` and `vector-Gplay-debug` contains the APK for the desired store. Each file contains 5 APKs. 4 APKs for every supported specific architecture of device. In doubt you can install the `universal` APK. ## Contributing diff --git a/build.gradle b/build.gradle index be1f48a3db..d9b9715daa 100644 --- a/build.gradle +++ b/build.gradle @@ -24,16 +24,16 @@ buildscript { classpath libs.gradle.gradlePlugin classpath libs.gradle.kotlinPlugin classpath libs.gradle.hiltPlugin - classpath 'com.google.firebase:firebase-appdistribution-gradle:3.0.3' + classpath 'com.google.firebase:firebase-appdistribution-gradle:3.1.1' classpath 'com.google.gms:google-services:4.3.14' - classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' + classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' - classpath "com.likethesalad.android:stem-plugin:2.2.2" - classpath 'org.owasp:dependency-check-gradle:7.2.1' - classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" + classpath "com.likethesalad.android:stem-plugin:2.2.3" + classpath 'org.owasp:dependency-check-gradle:7.3.0' + classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' - classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:1.0.0' + classpath libs.squareup.paparazziPlugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -43,12 +43,12 @@ plugins { // ktlint Plugin id "org.jlleitschuh.gradle.ktlint" version "11.0.0" // Detekt - id "io.gitlab.arturbosch.detekt" version "1.21.0" + id "io.gitlab.arturbosch.detekt" version "1.22.0" // Ksp - id "com.google.devtools.ksp" version "1.7.20-1.0.6" + id "com.google.devtools.ksp" version "1.7.21-1.0.8" // Dependency Analysis - id 'com.autonomousapps.dependency-analysis' version "1.13.1" + id 'com.autonomousapps.dependency-analysis' version "1.16.0" // Gradle doctor id "com.osacky.doctor" version "0.8.1" } @@ -96,9 +96,9 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.2" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-6.2.2" // Note: to test Jitsi release you can use a local file like this: - // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" + // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-6.2.2" content { groups.jitsi.regex.each { includeGroupByRegex it } groups.jitsi.group.each { includeGroup it } @@ -157,6 +157,9 @@ allprojects { // To have XML report for Danger reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) } + filter { + exclude { element -> element.file.path.contains("$buildDir/generated/") } + } disabledRules = [ // TODO Re-enable these 4 rules after reformatting project "indent", @@ -328,7 +331,7 @@ ext.initScreenshotTests = { project -> if (hasScreenshots) { project.apply plugin: 'app.cash.paparazzi' } - project.dependencies { testCompileOnly "app.cash.paparazzi:paparazzi:1.0.0" } + project.dependencies { testCompileOnly libs.squareup.paparazzi } project.android.testOptions.unitTests.all { def screenshotTestCapture = "**/*ScreenshotTest*" if (hasScreenshots) { @@ -348,17 +351,21 @@ subprojects { project -> if (it instanceof com.android.build.gradle.LibraryExtension) { libraryVariants.all { variant -> def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin") - variant.addJavaSourceFoldersToModel(outputFolder) - android.sourceSets.getAt(variant.name).java { - srcDir(outputFolder) + if (outputFolder.exists()) { + variant.addJavaSourceFoldersToModel(outputFolder) + android.sourceSets.getAt(variant.name).java { + srcDir(outputFolder) + } } } } else if (it instanceof com.android.build.gradle.AppExtension) { applicationVariants.all { variant -> def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin") - variant.addJavaSourceFoldersToModel(outputFolder) - android.sourceSets.getAt(variant.name).java { - srcDir(outputFolder) + if (outputFolder.exists()) { + variant.addJavaSourceFoldersToModel(outputFolder) + android.sourceSets.getAt(variant.name).java { + srcDir(outputFolder) + } } } } diff --git a/changelog.d/2725.feature b/changelog.d/2725.feature new file mode 100644 index 0000000000..eb3fcaed57 --- /dev/null +++ b/changelog.d/2725.feature @@ -0,0 +1 @@ +Add setting to allow disabling direct share diff --git a/changelog.d/5679.bugfix b/changelog.d/5679.bugfix new file mode 100644 index 0000000000..0394bc3e5d --- /dev/null +++ b/changelog.d/5679.bugfix @@ -0,0 +1 @@ +Fix italic text is truncated when bubble mode and markdown is enabled diff --git a/changelog.d/6996.sdk b/changelog.d/6996.sdk new file mode 100644 index 0000000000..588ec160d7 --- /dev/null +++ b/changelog.d/6996.sdk @@ -0,0 +1 @@ +Added support for read receipts in threads. Now user in a room can have multiple read receipts (one per thread + one in main thread + one without threadId) diff --git a/changelog.d/7277.wip b/changelog.d/7277.wip deleted file mode 100644 index 168d10b809..0000000000 --- a/changelog.d/7277.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Show correct device type icons diff --git a/changelog.d/7285.misc b/changelog.d/7285.misc deleted file mode 100644 index ce94383146..0000000000 --- a/changelog.d/7285.misc +++ /dev/null @@ -1 +0,0 @@ -Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment. diff --git a/changelog.d/7546.feature b/changelog.d/7546.feature new file mode 100644 index 0000000000..94450082c9 --- /dev/null +++ b/changelog.d/7546.feature @@ -0,0 +1 @@ +[Device Manager] Toggle IP address visibility diff --git a/changelog.d/7555.bugfix b/changelog.d/7555.bugfix new file mode 100644 index 0000000000..064b21a9e5 --- /dev/null +++ b/changelog.d/7555.bugfix @@ -0,0 +1 @@ +Missing translations on "replyTo" messages diff --git a/changelog.d/7577.feature b/changelog.d/7577.feature new file mode 100644 index 0000000000..e21ccb13c0 --- /dev/null +++ b/changelog.d/7577.feature @@ -0,0 +1 @@ +New implementation of the full screen mode for the Rich Text Editor. diff --git a/changelog.d/7583.misc b/changelog.d/7583.misc new file mode 100644 index 0000000000..3c63aeaadf --- /dev/null +++ b/changelog.d/7583.misc @@ -0,0 +1 @@ +Remove usage of Buildkite. diff --git a/changelog.d/7594.misc b/changelog.d/7594.misc new file mode 100644 index 0000000000..5c5771d8d0 --- /dev/null +++ b/changelog.d/7594.misc @@ -0,0 +1 @@ +Better validation of edits diff --git a/changelog.d/7604.bugfix b/changelog.d/7604.bugfix new file mode 100644 index 0000000000..0fbee55bce --- /dev/null +++ b/changelog.d/7604.bugfix @@ -0,0 +1 @@ +ANR on session start when sending client info is enabled diff --git a/changelog.d/7620.bugfix b/changelog.d/7620.bugfix new file mode 100644 index 0000000000..55c0e423ad --- /dev/null +++ b/changelog.d/7620.bugfix @@ -0,0 +1 @@ +Make the plain text mode layout of the RTE more compact. diff --git a/changelog.d/7626.sdk b/changelog.d/7626.sdk new file mode 100644 index 0000000000..4d9f28183a --- /dev/null +++ b/changelog.d/7626.sdk @@ -0,0 +1,2 @@ +Sync Filter now taking in account homeserver capabilities to not pass unsupported parameters. +Sync Filter is now configured by providing SyncFilterBuilder class instance, instead of Filter to identify Filter changes related to homeserver capabilities diff --git a/changelog.d/7629.wip b/changelog.d/7629.wip new file mode 100644 index 0000000000..ecc4449b6f --- /dev/null +++ b/changelog.d/7629.wip @@ -0,0 +1 @@ +Voice Broadcast - Handle redaction of the state events on the listener and recorder sides diff --git a/changelog.d/7634.bugfix b/changelog.d/7634.bugfix new file mode 100644 index 0000000000..a3c829840a --- /dev/null +++ b/changelog.d/7634.bugfix @@ -0,0 +1 @@ +Push notification for thread message is now shown correctly when user observes rooms main timeline diff --git a/changelog.d/7646.bugfix b/changelog.d/7646.bugfix new file mode 100644 index 0000000000..7f771bc6f7 --- /dev/null +++ b/changelog.d/7646.bugfix @@ -0,0 +1 @@ +Voice Broadcast - Fix playback stuck in buffering mode diff --git a/changelog.d/7655.wip b/changelog.d/7655.wip new file mode 100644 index 0000000000..24358007a9 --- /dev/null +++ b/changelog.d/7655.wip @@ -0,0 +1 @@ +Voice Broadcast - Update the buffering display in the timeline diff --git a/changelog.d/7656.wip b/changelog.d/7656.wip new file mode 100644 index 0000000000..ab0e47289f --- /dev/null +++ b/changelog.d/7656.wip @@ -0,0 +1 @@ +Voice Broadcast - Remove voice messages related to a VB from the room attachments diff --git a/dependencies.gradle b/dependencies.gradle index 359cf577ef..31c32bb26b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,49 +1,45 @@ ext.versions = [ - 'minSdk' : 21, - 'compileSdk' : 32, - 'targetSdk' : 32, + 'compileSdk' : 33, + 'targetSdk' : 33, 'sourceCompat' : JavaVersion.VERSION_11, 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.2.2" +def gradle = "7.3.1" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.7.20" +def kotlin = "1.7.21" def kotlinCoroutines = "1.6.4" -def dagger = "2.44" -def appDistribution = "16.0.0-beta04" +def dagger = "2.44.2" +def appDistribution = "16.0.0-beta05" def retrofit = "2.9.0" -def arrow = "0.8.2" def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.164.0" +def flipper = "0.174.0" def epoxy = "5.0.0" def mavericks = "3.0.1" -def glide = "4.14.1" +def glide = "4.14.2" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" - -def sentry = "6.4.3" - -def fragment = "1.5.3" - +def sentry = "6.7.0" +def fragment = "1.5.4" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def espresso = "3.4.0" def androidxTest = "1.4.0" -def androidxOrchestrator = "1.4.1" +def androidxOrchestrator = "1.4.2" +def paparazzi = "1.1.0" + ext.libs = [ gradle : [ 'gradlePlugin' : "com.android.tools.build:gradle:$gradle", 'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin", 'hiltPlugin' : "com.google.dagger:hilt-android-gradle-plugin:$dagger" - ], jetbrains : [ 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", @@ -51,12 +47,12 @@ ext.libs = [ 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ - 'activity' : "androidx.activity:activity:1.5.1", + 'activity' : "androidx.activity:activity-ktx:1.6.1", 'appCompat' : "androidx.appcompat:appcompat:1.5.1", 'biometric' : "androidx.biometric:biometric:1.1.0", - 'core' : "androidx.core:core-ktx:1.8.0", + 'core' : "androidx.core:core-ktx:1.9.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", - 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.4", + 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.5", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", 'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", @@ -83,11 +79,11 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.6.1", + 'material' : "com.google.android.material:material:1.7.0", '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.56" + 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.1" ], dagger : [ 'dagger' : "com.google.dagger:dagger:$dagger", @@ -102,22 +98,21 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", + 'wysiwyg' : "io.element.android:wysiwyg:0.7.0.1" ], squareup : [ '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", + 'paparazzi' : "app.cash.paparazzi:paparazzi:$paparazzi", + 'paparazziPlugin' : "app.cash.paparazzi:paparazzi-gradle-plugin:$paparazzi", 'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit", 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" ], rx : [ 'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0" ], - arrow : [ - 'core' : "io.arrow-kt:arrow-core:$arrow", - 'instances' : "io.arrow-kt:arrow-instances-core:$arrow" - ], markwon : [ 'core' : "io.noties.markwon:core:$markwon", 'extLatex' : "io.noties.markwon:ext-latex:$markwon", @@ -165,13 +160,13 @@ ext.libs = [ 'emojiGoogle' : "com.vanniktech:emoji-google:$vanniktechEmoji" ], apache : [ - 'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator" + 'commonsImaging' : "org.apache.commons:commons-imaging:1.0-alpha3" ], sentry: [ 'sentryAndroid' : "io.sentry:sentry-android:$sentry" ], tests : [ - 'kluent' : "org.amshove.kluent:kluent-android:1.68", + 'kluent' : "org.amshove.kluent:kluent-android:1.72", 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", 'junit' : "junit:junit:4.13.2", ] diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index cb40a9fac2..55b1db6392 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -141,7 +141,6 @@ ext.groups = [ 'commons-io', 'commons-logging', 'info.picocli', - 'io.arrow-kt', 'io.element.android', 'io.github.davidburstrom.contester', 'io.github.detekt.sarif4k', @@ -155,6 +154,7 @@ ext.groups = [ 'io.netty', 'io.noties.markwon', 'io.opencensus', + 'io.perfmark', 'io.reactivex.rxjava2', 'io.realm', 'io.sentry', @@ -183,8 +183,8 @@ ext.groups = [ 'org.apache.ant', 'org.apache.commons', 'org.apache.httpcomponents', - 'org.apache.sanselan', 'org.bouncycastle', + 'org.ccil.cowan.tagsoup', 'org.checkerframework', 'org.codehaus', 'org.codehaus.groovy', diff --git a/docs/installing_from_ci.md b/docs/installing_from_ci.md new file mode 100644 index 0000000000..01fb4afef2 --- /dev/null +++ b/docs/installing_from_ci.md @@ -0,0 +1,52 @@ +## Installing from CI + + + + * [Installing from Buildkite](#installing-from-buildkite) + * [Installing from GitHub](#installing-from-github) + * [Create a GitHub token](#create-a-github-token) + * [Provide artifact URL](#provide-artifact-url) + * [Next steps](#next-steps) + * [Future improvement](#future-improvement) + + + +Installing APK build by the CI is possible + +### Installing from Buildkite + +The script `./tools/install/installFromBuildkite.sh` can be used, but Builkite will be removed soon. See next section. + +### Installing from GitHub + +To install an APK built by a GitHub action, run the script `./tools/install/installFromGitHub.sh`. You will need to pass a GitHub token to do so. + +#### Create a GitHub token + +You can create a GitHub token going to your Github account, at this page: [https://github.com/settings/tokens](https://github.com/settings/tokens). + +You need to create a token (classic) with the scope `repo/public_repo`. So just check the corresponding checkbox. +Validity can be long since the scope of this token is limited. You will still be able to delete the token and generate a new one. +Click on Generate token and save the token locally. + +### Provide artifact URL + +The script will ask for an artifact URL. You can get this artifact URL by following these steps: + +- open the pull request +- in the check at the bottom, click on `APK Build / Build debug APKs` +- click on `Summary` +- scroll to the bottom of the page +- copy the link `vector-Fdroid-debug` if you want the F-Droid variant or `vector-Gplay-debug` if you want the Gplay variant. + +The copied link can be provided to the script. + +### Next steps + +The script will download the artifact, unzip it and install the correct version (regarding arch) on your device. + +Files will be added to the folder `./tmp/DebugApks`. Feel free to cleanup this folder from time to time, the script will not delete files. + +### Future improvement + +The script could ask the user for a Pull Request number and Gplay/Fdroid choice like it was done with Buildkite script. Using GitHub API may be possible to do that. diff --git a/docs/jitsi.md b/docs/jitsi.md index 4dd06effdb..d6c93c49aa 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -93,4 +93,4 @@ url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.10. - Build the project and perform the sanity tests again. -- Update the file `/CHANGES.md` to notify about the library upgrade, and create a regular PR for project Element Android. +- Create a PR for project Element Android and add a changelog file `.misc` to notify about the library upgrade. diff --git a/fastlane/metadata/android/az/short_description.txt b/fastlane/metadata/android/az/short_description.txt new file mode 100644 index 0000000000..ecf3d5008c --- /dev/null +++ b/fastlane/metadata/android/az/short_description.txt @@ -0,0 +1 @@ +Qrup mesajlaşma - şifrəli mesajlaşma, qrup söhbəti və video zənglər diff --git a/fastlane/metadata/android/az/title.txt b/fastlane/metadata/android/az/title.txt new file mode 100644 index 0000000000..4ca0ffb55b --- /dev/null +++ b/fastlane/metadata/android/az/title.txt @@ -0,0 +1 @@ +Element - Təhlükəsiz Mesajlaşma diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105000.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105000.txt new file mode 100644 index 0000000000..032fa68e48 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Odložené přímé zprávy jsou ve výchozím nastavení povoleny. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105020.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105020.txt new file mode 100644 index 0000000000..b2b7ca7675 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Nové rozvržení aplikace je povoleno ve výchozím nastavení! +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105040.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105040.txt new file mode 100644 index 0000000000..c1bf4fd59a --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Nové funkce v Experimentálních funkcích: Rozšířený editor zpráv, nová správa zařízení, hlasové vysílání. Stále v aktivním vývoji! +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105060.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105060.txt new file mode 100644 index 0000000000..e966dbbd92 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: nové uživatelské rozhraní pro výběr přílohy. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105070.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105070.txt new file mode 100644 index 0000000000..e966dbbd92 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: nové uživatelské rozhraní pro výběr přílohy. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105080.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105080.txt new file mode 100644 index 0000000000..90210199a1 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: opravy různých chyb a vylepšení. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105000.txt b/fastlane/metadata/android/de-DE/changelogs/40105000.txt new file mode 100644 index 0000000000..254c0fe0d8 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Die wichtigste Änderung in dieser Version: Verzögerte Direktnachrichten standardmäßig aktiviert! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105020.txt b/fastlane/metadata/android/de-DE/changelogs/40105020.txt new file mode 100644 index 0000000000..af7a8d7cce --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Die wichtigste Änderung in dieser Version: Neues App-Layout standardmäßig aktiviert! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105040.txt b/fastlane/metadata/android/de-DE/changelogs/40105040.txt new file mode 100644 index 0000000000..017e23cd9e --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Die wichtigste Änderung in dieser Version: Neue Funktionen in den Labor-Einstellungen: Textverarbeitungs-Editor, neue Geräteverwaltung, Sprachübertragung. Noch in aktiver Entwicklung! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105060.txt b/fastlane/metadata/android/de-DE/changelogs/40105060.txt new file mode 100644 index 0000000000..0b36faff1e --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Die wichtigste Änderung in dieser Version: Neues Anhangauswahl-UI. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105070.txt b/fastlane/metadata/android/de-DE/changelogs/40105070.txt new file mode 100644 index 0000000000..3141cea7cb --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Die wichtigste Änderung in dieser Version: Neue Anhangauswahl-Oberfläche. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40105080.txt b/fastlane/metadata/android/de-DE/changelogs/40105080.txt new file mode 100644 index 0000000000..0422f9cd4f --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: Fehlerbehebungen und Verbesserungen. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40105040.txt b/fastlane/metadata/android/en-US/changelogs/40105040.txt new file mode 100644 index 0000000000..1073dc57e0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Main changes in this version: New features under the labs settings: Rich text composer, new device management, voice broadcast. Still under active development! +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40105060.txt b/fastlane/metadata/android/en-US/changelogs/40105060.txt new file mode 100644 index 0000000000..8269f7145c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Main changes in this version: new UI for selecting an attachment. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40105070.txt b/fastlane/metadata/android/en-US/changelogs/40105070.txt new file mode 100644 index 0000000000..8269f7145c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Main changes in this version: new UI for selecting an attachment. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40105080.txt b/fastlane/metadata/android/en-US/changelogs/40105080.txt new file mode 100644 index 0000000000..f9ca8cdd7c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and improvements. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105000.txt b/fastlane/metadata/android/et/changelogs/40105000.txt new file mode 100644 index 0000000000..2a031d88ac --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: ajastatud otsesõnumite saatmine on nüüd vaikimisi kasutusel. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105020.txt b/fastlane/metadata/android/et/changelogs/40105020.txt new file mode 100644 index 0000000000..7a4a6ca253 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: rakenduse uus kujundus on nüüd vaikimisi kasutusel. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105040.txt b/fastlane/metadata/android/et/changelogs/40105040.txt new file mode 100644 index 0000000000..b1c84cad47 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Uued võimalused katsete all: vormindatud teksti põhine toimeti, uus seadmehaldus, ringhäälingukõned (kõik on hetkel aktiivsel arendamisel). +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105060.txt b/fastlane/metadata/android/et/changelogs/40105060.txt new file mode 100644 index 0000000000..d5606e24b3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: uus liides manuste lisamiseks. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105070.txt b/fastlane/metadata/android/et/changelogs/40105070.txt new file mode 100644 index 0000000000..061e09814d --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: uus liides manuste valimiseks. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/et/changelogs/40105080.txt b/fastlane/metadata/android/et/changelogs/40105080.txt new file mode 100644 index 0000000000..37b9a2cfe5 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: erinevate vigade parandused ja kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105000.txt b/fastlane/metadata/android/fa/changelogs/40105000.txt new file mode 100644 index 0000000000..605efd76f1 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105000.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: پیامهای مستقیم تعویقی به کار افتاده به صورت پیشگزیده! +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105020.txt b/fastlane/metadata/android/fa/changelogs/40105020.txt new file mode 100644 index 0000000000..f6e193ede5 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105020.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: چینش کارهٔ جدید به کار افتاده به صورت پیشگزیده! +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105040.txt b/fastlane/metadata/android/fa/changelogs/40105040.txt new file mode 100644 index 0000000000..a33ad09422 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105040.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: قابلیتهای جدید در تنظیمات آزمایشگاهها: نگارندهٔ متن غنی، مدیریت افزارهٔ جدید، پخش صدا. هنوز زیر توسعهٔ فعّال! +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105060.txt b/fastlane/metadata/android/fa/changelogs/40105060.txt new file mode 100644 index 0000000000..b677c05c89 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105060.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رابط کاربری جدید برای گزینش پیوست. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105070.txt b/fastlane/metadata/android/fa/changelogs/40105070.txt new file mode 100644 index 0000000000..b677c05c89 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105070.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رابط کاربری جدید برای گزینش پیوست. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40105080.txt b/fastlane/metadata/android/fa/changelogs/40105080.txt new file mode 100644 index 0000000000..91385addde --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105080.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: رفع اشکالها و بهبود. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105000.txt b/fastlane/metadata/android/fr-FR/changelogs/40105000.txt new file mode 100644 index 0000000000..707cc20fd3 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Création des conversations privées différée activée par défaut. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105020.txt b/fastlane/metadata/android/fr-FR/changelogs/40105020.txt new file mode 100644 index 0000000000..fec750141c --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Nouvelle disposition de l’application activée par défaut ! +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105040.txt b/fastlane/metadata/android/fr-FR/changelogs/40105040.txt new file mode 100644 index 0000000000..027e2b0252 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Nouvelles fonctionnalités expérimentales : éditeur de texte formaté, nouveau gestionnaire d’appareils, diffusion audio. C’est toujours en cours de développement ! +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105060.txt b/fastlane/metadata/android/fr-FR/changelogs/40105060.txt new file mode 100644 index 0000000000..b33f290d0d --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : nouvelle interface de sélection d’une pièce jointe. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105070.txt b/fastlane/metadata/android/fr-FR/changelogs/40105070.txt new file mode 100644 index 0000000000..b33f290d0d --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : nouvelle interface de sélection d’une pièce jointe. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105080.txt b/fastlane/metadata/android/fr-FR/changelogs/40105080.txt new file mode 100644 index 0000000000..d33197c270 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : corrections de bugs et améliorations. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105000.txt b/fastlane/metadata/android/id/changelogs/40105000.txt new file mode 100644 index 0000000000..08e3e78300 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Pesan langsung yang ditangguhkan diaktifkan secara bawaan. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105020.txt b/fastlane/metadata/android/id/changelogs/40105020.txt new file mode 100644 index 0000000000..736d2c48a6 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Tata letak aplikasi baru diaktifkan secara bawaan! +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105040.txt b/fastlane/metadata/android/id/changelogs/40105040.txt new file mode 100644 index 0000000000..810af47607 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Fitur baru di belakang pengaturan uji coba: Komposer teks kaya, pengelolaan perangkat baru, siaran suara. Masih dalam pengembangan aktif! +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105060.txt b/fastlane/metadata/android/id/changelogs/40105060.txt new file mode 100644 index 0000000000..32fb87563e --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Antarmuka baru untuk memilih sebuah lampiran. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105070.txt b/fastlane/metadata/android/id/changelogs/40105070.txt new file mode 100644 index 0000000000..32fb87563e --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Antarmuka baru untuk memilih sebuah lampiran. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/changelogs/40105080.txt b/fastlane/metadata/android/id/changelogs/40105080.txt new file mode 100644 index 0000000000..8384716bbc --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: perbaikan kutu dan fitur +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt index 20d805c582..79baa1f9ac 100644 --- a/fastlane/metadata/android/id/full_description.txt +++ b/fastlane/metadata/android/id/full_description.txt @@ -1,4 +1,4 @@ -Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung-ke-ujung untuk menyediakan konferensi video, pembagian berkas, dan panggilan suara yang aman. +Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung ke ujung untuk menyediakan konferensi video, pembagian berkas, dan panggilan suara yang aman. Fitur Element termasuk: - Alat komunikasi daring yang canggih @@ -11,7 +11,7 @@ Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas ya Element benar-benar berbeda dari aplikasi perpesanan dan aplikasi kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi yang terdesentralisasi. Perpesanan dengan privasi dan enkripsi -Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data, dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung-ke-ujung, dan verifikasi perangkat menggunakan penandatanganan silang. +Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data, dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung ke ujung, dan verifikasi perangkat menggunakan penandatanganan silang. Element memberikan Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan siapa saja secara aman di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan aplikasi seperti Slack. @@ -30,7 +30,7 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda: Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain, atau bahkan menggunakan aplikasi perpesanan yang berbeda. Sangat aman -Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang di dalam obrolan dapat mendekripsikan pesan), dan verifikasi perangkat menggunakan penandatanganan silang. +Enkripsi ujung ke ujung yang nyata (hanya mereka yang di dalam obrolan dapat mendekripsikan pesan), dan verifikasi perangkat menggunakan penandatanganan silang. Komunikasi dan integrasi lengkap Perpesanan, panggilan suara dan video, pembagian berkas, pembagian layar dan banyak integrasi bot dan widget. Buat ruangan dan komunitas, tetap terhubung, dan selesaikan hal-hal penting. diff --git a/fastlane/metadata/android/it-IT/changelogs/40105000.txt b/fastlane/metadata/android/it-IT/changelogs/40105000.txt new file mode 100644 index 0000000000..9132fea7b9 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: messaggi diretti differiti attivati in modo predefinito. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105020.txt b/fastlane/metadata/android/it-IT/changelogs/40105020.txt new file mode 100644 index 0000000000..accee9e36d --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: nuova interfaccia dell'app attivata in modo predefinito! +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105040.txt b/fastlane/metadata/android/it-IT/changelogs/40105040.txt new file mode 100644 index 0000000000..1e69c7436e --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: nuove funzioni nelle impostazioni Laboratori: compositore in rich text, nuova gestione dispositivi, trasmissione voce. Ancora in sviluppo attivo! +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105060.txt b/fastlane/metadata/android/it-IT/changelogs/40105060.txt new file mode 100644 index 0000000000..34d299b774 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: nuova interfaccia utente per selezionare un allegato! +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105070.txt b/fastlane/metadata/android/it-IT/changelogs/40105070.txt new file mode 100644 index 0000000000..ec4d944d72 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: nuova interfaccia utente per selezionare un allegato. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40105080.txt b/fastlane/metadata/android/it-IT/changelogs/40105080.txt new file mode 100644 index 0000000000..a3d49ca1b7 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: correzione di errori e miglioramenti. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105000.txt b/fastlane/metadata/android/pt-BR/changelogs/40105000.txt new file mode 100644 index 0000000000..d8f3a953a3 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: DM diferida habilitada por default. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105020.txt b/fastlane/metadata/android/pt-BR/changelogs/40105020.txt new file mode 100644 index 0000000000..d7cca674fa --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Novo layout de app habilitado por default! +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105040.txt b/fastlane/metadata/android/pt-BR/changelogs/40105040.txt new file mode 100644 index 0000000000..99d6971bf3 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Novas funcionalidades sob as configurações de labs: Compositor de texto rico, novo gerenciador de dispositivo, broadcast de voz. Ainda sob desenvolvimento ativo! +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105060.txt b/fastlane/metadata/android/pt-BR/changelogs/40105060.txt new file mode 100644 index 0000000000..108a8a88b4 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: novo UI para selecionar um anexo. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105070.txt b/fastlane/metadata/android/pt-BR/changelogs/40105070.txt new file mode 100644 index 0000000000..108a8a88b4 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: novo UI para selecionar um anexo. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105080.txt b/fastlane/metadata/android/pt-BR/changelogs/40105080.txt new file mode 100644 index 0000000000..6e85b9ba6a --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: consertos de bugs e melhorias. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104260.txt b/fastlane/metadata/android/ru-RU/changelogs/40104260.txt new file mode 100644 index 0000000000..b023e07b3d --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104260.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Использование UnifiedPush и разрешение пользователям получать push-оповещения без FCM. +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104270.txt b/fastlane/metadata/android/ru-RU/changelogs/40104270.txt new file mode 100644 index 0000000000..ff4e5cdf15 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104270.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Исправления различных багов и улучшения стабильности работы. +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104280.txt b/fastlane/metadata/android/ru-RU/changelogs/40104280.txt new file mode 100644 index 0000000000..ff4e5cdf15 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104280.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Исправления различных багов и улучшения стабильности работы. +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104300.txt b/fastlane/metadata/android/ru-RU/changelogs/40104300.txt new file mode 100644 index 0000000000..aec45e0348 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Улучшены вход и регистрация +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104310.txt b/fastlane/metadata/android/ru-RU/changelogs/40104310.txt new file mode 100644 index 0000000000..aec45e0348 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Улучшены вход и регистрация +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104320.txt b/fastlane/metadata/android/ru-RU/changelogs/40104320.txt new file mode 100644 index 0000000000..d6c614f22b --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Исправления различных багов и улучшения стабильности работы +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104340.txt b/fastlane/metadata/android/ru-RU/changelogs/40104340.txt new file mode 100644 index 0000000000..63da187fe9 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: различные исправления ошибок и улучшения стабильности. +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104360.txt b/fastlane/metadata/android/ru-RU/changelogs/40104360.txt new file mode 100644 index 0000000000..208233d92e --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Новый вид приложения можно включить в настройках лаборатории. Пожалуйста, попробуйте! +Исправлены проблемы, связанные с отсутствием уведомлений и длительной инкрементной синхронизацией. +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105000.txt b/fastlane/metadata/android/ru-RU/changelogs/40105000.txt new file mode 100644 index 0000000000..93ea0aff68 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: отложённые личные сообщения включены по умолчанию +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105020.txt b/fastlane/metadata/android/ru-RU/changelogs/40105020.txt new file mode 100644 index 0000000000..83bf3c747b --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: новый вид приложения включён по умолчанию! +Весь список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105040.txt b/fastlane/metadata/android/ru-RU/changelogs/40105040.txt new file mode 100644 index 0000000000..c923750bc4 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии — новые возможности в настройках лаборатории: наглядный текстовый редактор, новое управление устройствами, голосовая трансляция. Всё это ещё находится в активной разработке! +Весь список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105060.txt b/fastlane/metadata/android/ru-RU/changelogs/40105060.txt new file mode 100644 index 0000000000..234d265dd8 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: новый интерфейс для выбора прикреплённых файлов +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105070.txt b/fastlane/metadata/android/ru-RU/changelogs/40105070.txt new file mode 100644 index 0000000000..234d265dd8 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: новый интерфейс для выбора прикреплённых файлов +Полный список изменений: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105000.txt b/fastlane/metadata/android/sk/changelogs/40105000.txt new file mode 100644 index 0000000000..9f48366456 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Oneskorené priame správy sú zapnuté ako predvolené. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105020.txt b/fastlane/metadata/android/sk/changelogs/40105020.txt new file mode 100644 index 0000000000..ceec087a30 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Nové rozvrhnutie aplikácie je zapnuté ako predvolené! +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105040.txt b/fastlane/metadata/android/sk/changelogs/40105040.txt new file mode 100644 index 0000000000..75d4196fbf --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Nové funkcie v rámci laboratórnych nastavení: Rozšírený textový editor, nová správa zariadení, hlasové vysielanie. Stále prebieha aktívny vývoj! +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105060.txt b/fastlane/metadata/android/sk/changelogs/40105060.txt new file mode 100644 index 0000000000..0d1d4965ca --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: nové používateľské rozhranie na výber príloh. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105070.txt b/fastlane/metadata/android/sk/changelogs/40105070.txt new file mode 100644 index 0000000000..0d1d4965ca --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: nové používateľské rozhranie na výber príloh. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sk/changelogs/40105080.txt b/fastlane/metadata/android/sk/changelogs/40105080.txt new file mode 100644 index 0000000000..56daa3b4b7 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: opravy chýb a vylepšenia. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104120.txt b/fastlane/metadata/android/sq/changelogs/40104120.txt new file mode 100644 index 0000000000..f93220235b --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: U lejon përdoruesve të shfaqen si jo në linjë dhe shton një lojtës audio për bashkëngjitje audio +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104130.txt b/fastlane/metadata/android/sq/changelogs/40104130.txt new file mode 100644 index 0000000000..f93220235b --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: U lejon përdoruesve të shfaqen si jo në linjë dhe shton një lojtës audio për bashkëngjitje audio +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104140.txt b/fastlane/metadata/android/sq/changelogs/40104140.txt new file mode 100644 index 0000000000..c8b2eb09ab --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104140.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Përmirësim i administrimit të përdoruesve të shpërfillur. Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104160.txt b/fastlane/metadata/android/sq/changelogs/40104160.txt new file mode 100644 index 0000000000..987197f0f6 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104160.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Administrim më i mirë i mesazheve të fshehtëzuar. Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104180.txt b/fastlane/metadata/android/sq/changelogs/40104180.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104180.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104190.txt b/fastlane/metadata/android/sq/changelogs/40104190.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104190.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104200.txt b/fastlane/metadata/android/sq/changelogs/40104200.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104200.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104220.txt b/fastlane/metadata/android/sq/changelogs/40104220.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104220.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104230.txt b/fastlane/metadata/android/sq/changelogs/40104230.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104230.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104240.txt b/fastlane/metadata/android/sq/changelogs/40104240.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104240.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104250.txt b/fastlane/metadata/android/sq/changelogs/40104250.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104250.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104260.txt b/fastlane/metadata/android/sq/changelogs/40104260.txt new file mode 100644 index 0000000000..c5ffad38c9 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104260.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Përdorim i UnifiedPush dhe lejim i përdoruesve të kenë push pa FCM. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104270.txt b/fastlane/metadata/android/sq/changelogs/40104270.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104270.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104280.txt b/fastlane/metadata/android/sq/changelogs/40104280.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104280.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104300.txt b/fastlane/metadata/android/sq/changelogs/40104300.txt new file mode 100644 index 0000000000..6c1be8f556 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Bërje e mundur hapash të përmirësuar hyrje dhe dalje nga llogaria. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104310.txt b/fastlane/metadata/android/sq/changelogs/40104310.txt new file mode 100644 index 0000000000..6c1be8f556 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Bërje e mundur hapash të përmirësuar hyrje dhe dalje nga llogaria. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104320.txt b/fastlane/metadata/android/sq/changelogs/40104320.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104340.txt b/fastlane/metadata/android/sq/changelogs/40104340.txt new file mode 100644 index 0000000000..87f801d1f4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash dhe përmirësime të ndryshme qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104360.txt b/fastlane/metadata/android/sq/changelogs/40104360.txt new file mode 100644 index 0000000000..ef9251a497 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Skema e re e Aplikacionit mund të aktivizohet që nga rregullimet Labs. Ju lutemi, provojeni! +Ndreqje problemesh me njoftim që mungon dhe njëkohësim i gjatë shtues. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105000.txt b/fastlane/metadata/android/sq/changelogs/40105000.txt new file mode 100644 index 0000000000..2ee2ded823 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Hedhje poshtë MD e aktivizuar, si parazgjedhje. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105020.txt b/fastlane/metadata/android/sq/changelogs/40105020.txt new file mode 100644 index 0000000000..26647d519f --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Skema e re e aplikacionit e aktivizuar, si parazgjedhje! +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105040.txt b/fastlane/metadata/android/sq/changelogs/40105040.txt new file mode 100644 index 0000000000..4e38434f89 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Veçori të reja nën rregullimet Labs: hartues teksti të pasur, administrim i ri pajisjesh, transmetim zanor. Ende nën zhvillim aktivt! +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105060.txt b/fastlane/metadata/android/sq/changelogs/40105060.txt new file mode 100644 index 0000000000..eb300bafed --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: ndërfaqe e re UI për përzgjedhjen e një bashkëngjitjeje! +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40105070.txt b/fastlane/metadata/android/sq/changelogs/40105070.txt new file mode 100644 index 0000000000..f4beb912a5 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë verson: ndërfaqe UI e re për përzgjedhje të një bashkëngjitjeje. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104300.txt b/fastlane/metadata/android/sv-SE/changelogs/40104300.txt new file mode 100644 index 0000000000..dcf7fd896c --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104300.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Aktiverar förbättrad inloggnings- och registreringsprocedur. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104310.txt b/fastlane/metadata/android/sv-SE/changelogs/40104310.txt new file mode 100644 index 0000000000..dcf7fd896c --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104310.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Aktiverar förbättrad inloggnings- och registreringsprocedur. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104320.txt b/fastlane/metadata/android/sv-SE/changelogs/40104320.txt new file mode 100644 index 0000000000..d8db452b51 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104320.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104340.txt b/fastlane/metadata/android/sv-SE/changelogs/40104340.txt new file mode 100644 index 0000000000..d8db452b51 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104340.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104360.txt b/fastlane/metadata/android/sv-SE/changelogs/40104360.txt new file mode 100644 index 0000000000..1f2c984748 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Ny applayout kan aktiveras in experimentinställningarna. Ge det ett försök! +Fixa problem med missade aviseringar, och long inkrementell synk. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105000.txt b/fastlane/metadata/android/sv-SE/changelogs/40105000.txt new file mode 100644 index 0000000000..f425ec24e8 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Fördröjda DM:er aktiverad som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105020.txt b/fastlane/metadata/android/sv-SE/changelogs/40105020.txt new file mode 100644 index 0000000000..66af38ec60 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Ny applayout aktiv som förval. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105040.txt b/fastlane/metadata/android/sv-SE/changelogs/40105040.txt new file mode 100644 index 0000000000..66b2b47e7d --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Nya funktioner under experimentinställningarna: Rik-text-redigerare, ny enhetshantering, röstsändning. Fortfarande under aktiv utveckling! +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105060.txt b/fastlane/metadata/android/sv-SE/changelogs/40105060.txt new file mode 100644 index 0000000000..d64984fcfb --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: nytt gränssnitt för val av bilaga. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105070.txt b/fastlane/metadata/android/sv-SE/changelogs/40105070.txt new file mode 100644 index 0000000000..d64984fcfb --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: nytt gränssnitt för val av bilaga. +Full ändringslogg: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105000.txt b/fastlane/metadata/android/uk/changelogs/40105000.txt new file mode 100644 index 0000000000..ab8964b99a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105000.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: відкладені особисті повідомлення увімкнено типово. +Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.2.0 diff --git a/fastlane/metadata/android/uk/changelogs/40105020.txt b/fastlane/metadata/android/uk/changelogs/40105020.txt new file mode 100644 index 0000000000..90a62209dc --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: новий вигляд застосунку типово увімкнено! +Журнал усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105040.txt b/fastlane/metadata/android/uk/changelogs/40105040.txt new file mode 100644 index 0000000000..b3327f68ab --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105040.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Нові можливості в налаштуваннях лабораторії: Текстовий редактор, нове керування пристроями, голосові трансляції. Досі в активній розробці! +Список усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105060.txt b/fastlane/metadata/android/uk/changelogs/40105060.txt new file mode 100644 index 0000000000..4be635901f --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105060.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: новий інтерфейс для вибору вкладення. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105070.txt b/fastlane/metadata/android/uk/changelogs/40105070.txt new file mode 100644 index 0000000000..65254059c5 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: новий інтерфейс для вибору вкладень. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/uk/changelogs/40105080.txt b/fastlane/metadata/android/uk/changelogs/40105080.txt new file mode 100644 index 0000000000..e6f6384a5f --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105080.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: усування вад і вдосконалення. +Перелік усіх змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105000.txt b/fastlane/metadata/android/zh-TW/changelogs/40105000.txt new file mode 100644 index 0000000000..7ab6a7a7bf --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105000.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:預設啟用延遲直接訊息。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105020.txt b/fastlane/metadata/android/zh-TW/changelogs/40105020.txt new file mode 100644 index 0000000000..d83fd08a53 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105020.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:預設啟用新的應用程式佈局! +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105040.txt b/fastlane/metadata/android/zh-TW/changelogs/40105040.txt new file mode 100644 index 0000000000..b35b1185b9 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105040.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:實驗室設定下有新功能:格式化文字編輯器、新裝置管理、語音廣播。仍在積極開發中! +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105060.txt b/fastlane/metadata/android/zh-TW/changelogs/40105060.txt new file mode 100644 index 0000000000..56667ccfc0 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105060.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:選取附件的新使用者介面。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105070.txt b/fastlane/metadata/android/zh-TW/changelogs/40105070.txt new file mode 100644 index 0000000000..56667ccfc0 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105070.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:選取附件的新使用者介面。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105080.txt b/fastlane/metadata/android/zh-TW/changelogs/40105080.txt new file mode 100644 index 0000000000..2a368ec8be --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105080.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:臭蟲修復與改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/library/attachment-viewer/build.gradle b/library/attachment-viewer/build.gradle index 8bbafd3387..fc9495b113 100644 --- a/library/attachment-viewer/build.gradle +++ b/library/attachment-viewer/build.gradle @@ -18,6 +18,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { + namespace "im.vector.lib.attachmentviewer" compileSdk versions.compileSdk diff --git a/library/attachment-viewer/src/main/AndroidManifest.xml b/library/attachment-viewer/src/main/AndroidManifest.xml index 8970b47178..8072ee00db 100644 --- a/library/attachment-viewer/src/main/AndroidManifest.xml +++ b/library/attachment-viewer/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt index 98398760d1..21d96afb77 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt @@ -316,10 +316,6 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi } return false } - - override fun onDoubleTap(e: MotionEvent?): Boolean { - return super.onDoubleTap(e) - } }) override fun onEvent(event: AttachmentEvents) { diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt index 7a83ee28d4..2f840cebee 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt @@ -96,31 +96,27 @@ class SwipeToDismissHandler( .setDuration(ANIMATION_DURATION) .setInterpolator(AccelerateInterpolator()) .setUpdateListener { onSwipeViewMove(swipeView.translationY, translationLimit) } - .setAnimatorListener(onAnimationEnd = { + .setAnimatorEndListener { if (translationTo != 0f) { onDismiss() } // remove the update listener, otherwise it will be saved on the next animation execution: swipeView.animate().setUpdateListener(null) - }) + } .start() } } -internal fun ViewPropertyAnimator.setAnimatorListener( - onAnimationEnd: ((Animator?) -> Unit)? = null, - onAnimationStart: ((Animator?) -> Unit)? = null -) = this.setListener( +private fun ViewPropertyAnimator.setAnimatorEndListener( + onAnimationEnd: () -> Unit, +) = setListener( object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - onAnimationEnd?.invoke(animation) + override fun onAnimationEnd(animation: Animator) { + onAnimationEnd() } + } +) - override fun onAnimationStart(animation: Animator?) { - onAnimationStart?.invoke(animation) - } - }) - -internal val View?.hitRect: Rect - get() = Rect().also { this?.getHitRect(it) } +private val View.hitRect: Rect + get() = Rect().also { getHitRect(it) } diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt index 92d28d26c9..07c7b4588f 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt @@ -103,14 +103,12 @@ class VideoViewHolder constructor(itemView: View) : views.videoView.setOnPreparedListener { stopTimer() countUpTimer = CountUpTimer(100).also { - it.tickListener = object : CountUpTimer.TickListener { - override fun onTick(milliseconds: Long) { - val duration = views.videoView.duration - val progress = views.videoView.currentPosition - val isPlaying = views.videoView.isPlaying -// Log.v("FOO", "isPlaying $isPlaying $progress/$duration") - eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) - } + it.tickListener = CountUpTimer.TickListener { + val duration = views.videoView.duration + val progress = views.videoView.currentPosition + val isPlaying = views.videoView.isPlaying + // Log.v("FOO", "isPlaying $isPlaying $progress/$duration") + eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) } it.resume() } diff --git a/library/core-utils/build.gradle b/library/core-utils/build.gradle index 0f7789a2a8..b985127ec6 100644 --- a/library/core-utils/build.gradle +++ b/library/core-utils/build.gradle @@ -20,6 +20,8 @@ plugins { } android { + namespace "im.vector.lib.core.utils" + compileSdk versions.compileSdk defaultConfig { minSdk versions.minSdk diff --git a/library/core-utils/src/main/AndroidManifest.xml b/library/core-utils/src/main/AndroidManifest.xml index 20a9414519..8072ee00db 100644 --- a/library/core-utils/src/main/AndroidManifest.xml +++ b/library/core-utils/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/compat/Compat.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/compat/Compat.kt new file mode 100644 index 0000000000..8b0ad7767b --- /dev/null +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/compat/Compat.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.lib.core.utils.compat + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.os.Build +import android.os.Bundle +import android.os.Parcelable +import java.io.Serializable + +inline fun Intent.getParcelableExtraCompat(key: String): T? = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableExtra(key, T::class.java) + else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T? +} + +inline fun Intent.getParcelableArrayListExtraCompat(key: String): ArrayList ? = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableArrayListExtra(key, T::class.java) + else -> @Suppress("DEPRECATION") getParcelableArrayListExtra (key) +} + +inline fun Bundle.getParcelableCompat(key: String): T? = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelable(key, T::class.java) + else -> @Suppress("DEPRECATION") getParcelable(key) as? T? +} + +inline fun Bundle.getSerializableCompat(key: String): T? = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java) + else -> @Suppress("DEPRECATION") getSerializable(key) as? T? +} + +inline fun Intent.getSerializableExtraCompat(key: String): T? = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java) + else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T? +} + +fun PackageManager.queryIntentActivitiesCompat(data: Intent, flags: Int): List { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> queryIntentActivities( + data, + PackageManager.ResolveInfoFlags.of(flags.toLong()) + ) + else -> @Suppress("DEPRECATION") queryIntentActivities(data, flags) + } +} + +fun PackageManager.resolveActivityCompat(data: Intent, flags: Int): ResolveInfo? { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> resolveActivity( + data, + PackageManager.ResolveInfoFlags.of(flags.toLong()) + ) + else -> @Suppress("DEPRECATION") resolveActivity(data, flags) + } +} diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index e9d311fe03..a4fd8bb4e1 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -66,7 +66,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) { coroutineScope.cancel() } - interface TickListener { + fun interface TickListener { fun onTick(milliseconds: Long) } } diff --git a/library/external/dialpad/build.gradle b/library/external/dialpad/build.gradle index fade8ddf30..e6f249f535 100644 --- a/library/external/dialpad/build.gradle +++ b/library/external/dialpad/build.gradle @@ -2,6 +2,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { + namespace "com.android.dialer.dialpadview" + compileSdk versions.compileSdk defaultConfig { diff --git a/library/external/dialpad/src/main/AndroidManifest.xml b/library/external/dialpad/src/main/AndroidManifest.xml index 1d412d0ae5..8072ee00db 100644 --- a/library/external/dialpad/src/main/AndroidManifest.xml +++ b/library/external/dialpad/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java b/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java index 5c6ce46257..09079235af 100644 --- a/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java +++ b/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java @@ -103,6 +103,7 @@ public class DialpadView extends LinearLayout { @Override protected void onFinishInflate() { + super.onFinishInflate(); setupKeypad(); mDigits = (EditText) findViewById(R.id.digits); mDelete = (ImageButton) findViewById(R.id.deleteButton); @@ -201,14 +202,6 @@ public class DialpadView extends LinearLayout { zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus)); } - private Drawable getDrawableCompat(Context context, int id) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return context.getDrawable(id); - } else { - return context.getResources().getDrawable(id); - } - } - public void setShowVoicemailButton(boolean show) { View view = findViewById(R.id.dialpad_key_voicemail); if (view != null) { diff --git a/library/external/jsonviewer/build.gradle b/library/external/jsonviewer/build.gradle index 50bb635e8e..a5d297b860 100644 --- a/library/external/jsonviewer/build.gradle +++ b/library/external/jsonviewer/build.gradle @@ -18,6 +18,8 @@ buildscript { } android { + namespace "org.billcarsonfr.jsonviewer" + compileSdk versions.compileSdk defaultConfig { diff --git a/library/external/jsonviewer/src/main/AndroidManifest.xml b/library/external/jsonviewer/src/main/AndroidManifest.xml index 73322c2fdb..cc947c5679 100644 --- a/library/external/jsonviewer/src/main/AndroidManifest.xml +++ b/library/external/jsonviewer/src/main/AndroidManifest.xml @@ -1 +1 @@ - + diff --git a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt index 0ebf539d4d..696655a19f 100644 --- a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt +++ b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.view.WindowManager import androidx.fragment.app.DialogFragment import com.airbnb.mvrx.Mavericks +import im.vector.lib.core.utils.compat.getParcelableCompat class JSonViewerDialog : DialogFragment() { @@ -36,16 +37,17 @@ class JSonViewerDialog : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val args: JSonViewerFragmentArgs = arguments?.getParcelable(Mavericks.KEY_ARG) ?: return + val args: JSonViewerFragmentArgs = arguments?.getParcelableCompat(Mavericks.KEY_ARG) ?: return if (savedInstanceState == null) { childFragmentManager.beginTransaction() .replace( - R.id.fragmentContainer, JSonViewerFragment.newInstance( - args.jsonString, - args.defaultOpenDepth, - true, - args.styleProvider - ) + R.id.fragmentContainer, + JSonViewerFragment.newInstance( + args.jsonString, + args.defaultOpenDepth, + true, + args.styleProvider + ) ) .commitNow() } diff --git a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt index 719ce29045..f7c7f4d7bc 100644 --- a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt +++ b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt @@ -28,6 +28,7 @@ import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import im.vector.lib.core.utils.compat.getParcelableCompat import kotlinx.parcelize.Parcelize @Parcelize @@ -53,7 +54,7 @@ class JSonViewerFragment : Fragment(), MavericksView { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - val args: JSonViewerFragmentArgs? = arguments?.getParcelable(Mavericks.KEY_ARG) + val args: JSonViewerFragmentArgs? = arguments?.getParcelableCompat(Mavericks.KEY_ARG) val inflate = if (args?.wrap == true) { inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false) diff --git a/library/multipicker/build.gradle b/library/multipicker/build.gradle index 2de99d5c20..c77a86a764 100644 --- a/library/multipicker/build.gradle +++ b/library/multipicker/build.gradle @@ -19,6 +19,8 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' android { + namespace "im.vector.lib.multipicker" + compileSdk versions.compileSdk defaultConfig { @@ -35,9 +37,19 @@ android { } } + compileOptions { + sourceCompatibility versions.sourceCompat + targetCompatibility versions.targetCompat + } + + kotlinOptions { + jvmTarget = "11" + } } dependencies { + implementation project(":library:core-utils") + api libs.androidx.activity implementation libs.androidx.exifinterface implementation libs.androidx.core diff --git a/library/multipicker/src/main/AndroidManifest.xml b/library/multipicker/src/main/AndroidManifest.xml index c02a22d1d9..2b4ef0e884 100644 --- a/library/multipicker/src/main/AndroidManifest.xml +++ b/library/multipicker/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + () { * Call this function from onActivityResult(int, int, Intent). * Returns selected contact or empty list if user did not select any contacts. */ + @SuppressLint("Recycle") override fun getSelectedFiles(context: Context, data: Intent?): List { val contactList = mutableListOf () diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt index 8960f3228b..1cfcba505f 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt @@ -22,6 +22,9 @@ import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.net.Uri import androidx.activity.result.ActivityResultLauncher +import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat +import im.vector.lib.core.utils.compat.getParcelableExtraCompat +import im.vector.lib.core.utils.compat.queryIntentActivitiesCompat /** * Abstract class to provide all types of Pickers. @@ -45,13 +48,13 @@ abstract class Picker { val uriList = mutableListOf () if (data.action == Intent.ACTION_SEND) { - (data.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { uriList.add(it) } + data.getParcelableExtraCompat (Intent.EXTRA_STREAM)?.let { uriList.add(it) } } else if (data.action == Intent.ACTION_SEND_MULTIPLE) { - val extraUriList: List ? = data.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + val extraUriList: List ? = data.getParcelableArrayListExtraCompat(Intent.EXTRA_STREAM) extraUriList?.let { uriList.addAll(it) } } - val resInfoList: List = context.packageManager.queryIntentActivities(data, PackageManager.MATCH_DEFAULT_ONLY) + val resInfoList: List = context.packageManager.queryIntentActivitiesCompat(data, PackageManager.MATCH_DEFAULT_ONLY) uriList.forEach { for (resolveInfo in resInfoList) { val packageName: String = resolveInfo.activityInfo.packageName @@ -91,6 +94,7 @@ abstract class Picker { } else if (dataUri != null) { selectedUriList.add(dataUri) } else { + @Suppress("DEPRECATION") data?.extras?.get(Intent.EXTRA_STREAM)?.let { (it as? List<*>)?.filterIsInstance ()?.let { uriList -> selectedUriList.addAll(uriList) diff --git a/library/ui-strings/build.gradle b/library/ui-strings/build.gradle index 6a31f24c9b..b6e6de5c22 100644 --- a/library/ui-strings/build.gradle +++ b/library/ui-strings/build.gradle @@ -5,6 +5,8 @@ plugins { } android { + namespace "im.vector.lib.strings" + compileSdk versions.compileSdk defaultConfig { minSdk versions.minSdk diff --git a/library/ui-strings/src/main/AndroidManifest.xml b/library/ui-strings/src/main/AndroidManifest.xml index deff03ee0a..8072ee00db 100644 --- a/library/ui-strings/src/main/AndroidManifest.xml +++ b/library/ui-strings/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/library/ui-strings/src/main/res/values-ar/strings.xml b/library/ui-strings/src/main/res/values-ar/strings.xml index 70b9a33ab5..a49ecc3d08 100644 --- a/library/ui-strings/src/main/res/values-ar/strings.xml +++ b/library/ui-strings/src/main/res/values-ar/strings.xml @@ -1167,4 +1167,12 @@ البريد الإلكتروني كلمة السر الجديدة التالي - ++ + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-az/strings.xml b/library/ui-strings/src/main/res/values-az/strings.xml index 84f2772950..6fe322bdd0 100644 --- a/library/ui-strings/src/main/res/values-az/strings.xml +++ b/library/ui-strings/src/main/res/values-az/strings.xml @@ -1,6 +1,5 @@- صفر
+- واحد
+- اثنان
+- قليلة
+- كثيرة
+- اخرى
+- +%s-nin dəvəti %1$s dəvət etdi %2$s %1$s sizi dəvət etdi @@ -21,60 +20,44 @@%s səsli zəng etdi. %s zəngə cavab verdi. %s zəng başa çatdı. -"%1$s gələcək otaq tarixçəsini %2$s-ə görünən etdi" +%1$s gələcək otaq tarixçəsini %2$s-ə görünən etdi bütün otaq üzvləri, dəvət olunduğu andan. bütün otaq üzvləri, qoşulduğu andan. bütün otaq üzvləri. hər kəs. %s bu otağı təkmilləşdirdi. - -(avatar da dəyişdirilib) %1$s otaq adını sildi %1$s otaq mövzusunu sildi %1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi %1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi %1$s %2$s üçün dəvəti qəbul etdi -** Şifrəni aça bilmir: %s ** Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib. -Mesaj göndərmək olmur - -Matris xətası - -Şifrəli mesaj -Elektron poçt ünvanı Telefon nömrəsi -Otağa dəvət -%1$s və %2$s - - -Boş otaq -İlkin sinxronizasiya: \nHesab idxal olunur… İlkin sinxronizasiya: \nKriptografiyanın idxalı İlkin sinxronizasiya: \nOtaqlar idxalı -İlkin sinxronizasiya: -\nOtaqlara daxil olmaq +İlkin sinxronizasiya: +\nSöhbətləriniz yüklənilir +\nƏgər çoxlu otaqlara qoşulmusunuzsa, bu, bir az vaxt apara bilər İlkin sinxronizasiya: \nDəvət olunmuş otaqların idxalı İlkin sinxronizasiya: \nTərk olunmuş otaqların idxalı İlkin sinxronizasiya: \nHesab məlumatlarının idxalı -Mesaj göndərilir… -%1$s-nin dəvəti. Səbəb: %2$s %1$s dəvət olunmuş %2$s. Səbəb: %3$s %1$s sizi dəvət etdi. Səbəb: %2$s @@ -86,4 +69,71 @@%1$s blokladı %2$s. Səbəb: %3$s %1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s %1$s %2$s dəvətini geri götürdü. Səbəb: %3$s -Otağ yaratdınız +Bu əməliyyatı yerinə yetirmək üçün sistem tənzimləmələrindən Kameraya icazə verin. +Tənzimləmələr +%1$s qoşuldu +Otağa qoşuldunuz +%1$s-ı dəvət etdiniz +Müzakirə yaratdınız +%1$s otağı yaratdı +Səsli bildirişlər +Bildirişləri dinləmək +Ümumi +Ümumi +Bu istifadəçiyə məhəl qoymamaq onun mesajlarını paylaşdığınız otaqlardan siləcək. +\n +\nBu əməliyyatı istənilən vaxt ümumi tənzimləmələrdə geri qaytara bilərsiniz. +Bu tələbi yerinə yetirmək üçün bəzi icazələr yoxdur, lütfən, sistem tənzimləmələrindən icazələr verin. +Otaq avatarını sildiniz +%1$s otaq avatarını sildi +Otaq mövzusunu sildiniz +Otağın adını sildiniz +🎉 Bütün serverlərin iştirakı qadağandır! Bu otaq artıq istifadə edilə bilməz. +Dəyişiklik yoxdur. +Bu otaq üçün server ACL-lərini dəyişdirdiniz. +• %s ilə uyğunlaşan serverlər qadağan edilib. +Bu otaq üçün server ACL-lərini təyin etdiniz. +%s bu otaq üçün server ACL-lərini təyin etdi. +Burada təkmilləşdirdiniz. +%s burada təkmilləşdi. +Bu otağı təkmilləşdirdiniz. +Gələcək mesajları %1$s üçün görünən etdiniz +%1$s gələcək mesajları %2$s üçün görünən etdi +Gələcək otaq tarixçəsini %1$s üçün görünən etdiniz +Zəngi bitirdiniz. +Zəngə cavab verdiniz. +Zəngi qurmaq üçün məlumat göndərdiniz. +%s zəngi qurmaq üçün məlumat göndərdi. +Səsli zəng etdiniz. +Video zəng etdiniz. +Otağın adını buna dəyişdiniz: %1$s +Otaq avatarını dəyişdiniz +%1$s otaq avatarını dəyişdi +Mövzunu buna dəyişdiniz: %1$s +Ekran adınızı sildiniz (bu, %1$s idi) +%1$s ekran adınızı %2$s olaraq dəyişdiniz +Ekran adınızı %1$s qoydunuz +Avatarınızı dəyişdirmisiniz +%1$s dəvətini geri götürdünüz +%1$s adlı istifadəçini qadağan etdiniz +%1$s adlı istifadəçini qadağan etdiniz +%1$s adlı istifadəçini qovdunuz +Dəvəti rədd etdiniz +Otağı tərk etdiniz +%1$s otaqdan çıxdı +Otağı tərk etdiniz +Qoşuldunuz +%1$s müzakirə yaratdı +Sizin dəvətiniz ++ +- %1$d seçildi
+- %1$d seçildi
+%1$s, %2$s-ı dəvət etdi +Otağa qoşulmaq üçün %1$s-a dəvət göndərdiniz +%s, bu otaq üçün server ACL-lərini dəyişdi. +• %s ilə uyğunlaşan serverlərə icazə verildi. +Siz %1$s üçün otağa qoşulmaq dəvətin ləğv etdiniz +%1$s-ı dəvət etdiniz + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index 25c490807e..7e3e019ee5 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -381,7 +381,7 @@Versió Versió d\'OLM Termes i condicions -Avisos de terceres parts +Avisos de tercers Copyright Política de privacitat Esborra la memòria cau @@ -891,7 +891,7 @@Motiu de l\'expulsió Expulsa usuari Continua amb SSO -Per a la teva pròpia privadesa, ${app_name} només admet l\'enviament del \"hash\" de correus electrònics i números de telèfon. +Per a la teva pròpia privadesa, ${app_name} només admet l\'enviament del \'hash\' d\'adreces de correu electrònic i números de telèfon. Només admès en sales xifrades El xifrat que utilitza aquesta sala no és compatible L\'aplicació no ha pogut crear un compte en aquest servidor. @@ -978,7 +978,7 @@ Error SSL. La trucada d\'${app_name} ha fallat Restableix la contrasenya a %1$s -Aquest correu electrònic no està associat amb cap compte. +Aquesta adreça de correu electrònic no està associada a cap compte. Ho sentim, aquest servidor no accepta comptes nous. S\'ha produït un error en carregar la pàgina: %1$s (%2$d) Introdueix l\'adreça del servidor que vulguis utilitzar @@ -1002,10 +1002,10 @@Dóna consentiment Revoca el meu consentiment Envia correus i números de telèfon -T\'hem enviat un correu de confirmació a %s, primer revisa el correu i fes clic a l\'enllaç de confirmació -T\'hem enviat un correu de confirmació a %s, revisa\'l i fes clic a l\'enllaç de confirmació +Hem enviat un correu electrònic a %s, primer revisa el correu i fes clic a l\'enllaç de confirmació +Hem enviat un correu electrònic a %s, revisa\'l i fes clic a l\'enllaç de confirmació Les opcions de descobriment apareixeran quan hagis afegit un número de telèfon. -Les opcions de descobriment apareixeran quan hagis afegit un correu. +Les opcions de descobriment apareixeran quan hagis afegit una adreça de correu electrònic. Desconnecta el servidor d\'identitat Configura el servidor d\'identitat Canvia el servidor d\'identitat @@ -1100,7 +1100,7 @@Revisa i gestiona les adreces d\'aquesta sala i la seva visibilitat al directori de sales. Adreces de la sala Accés a la sala -Gestiona els correus i els números de telèfon vinculats amb el teu compte de Matrix +Gestiona les adreces de correu electrònic i els números de telèfon vinculats amb el teu compte de Matrix Correus i números de telèfon Activa \'Permet integracions\' a la configuració per poder fer això. Les integracions estan desactivades @@ -1154,7 +1154,7 @@Assegura\'t de que has clicat a l\'enllaç del correu que t\'hem enviat. Elimina %s\? Números de telèfon -No s\'ha afegit cap correu electrònic al teu compte +No s\'ha afegit cap adreça de correu electrònic al teu compte No s\'ha afegit cap número de telèfon al teu compte Filtra usuaris vetats Tema @@ -1198,9 +1198,9 @@Se t\'ha desconnectat de totes les teves sessions i no rebràs més notificacions. Per reactivar les notificacions, torna a iniciar sessió a cada dispositiu. Format: Url: -session_name: -push_key: -app_id: +Àlies de la sessió: +Clau \'push\': +ID d\'aplicació: Revisa la configuració per activar les notificacions Estàs veient la notificació! Clica\'m! Vetat per %1$s @@ -1249,7 +1249,7 @@Configuració Ignora usuari Gira i retalla -Estableix un correu per a la recuperació del compte. Posterior i opcionalment, pots permetre que els usuaris que coneixes et puguin trobar a partir del correu electrònic. +Estableix una adreça de correu electrònic per recuperar el teu compte. Posterior i opcionalment, pots permetre que els usuaris que coneixes et puguin trobar a partir d\'aquesta adreça. Següent Número de telèfon (opcional) Número de telèfon @@ -1262,7 +1262,7 @@No s\'ha pogut trobar un servidor local vàlid. Comprova l\'identificador Accepta els termes de servei del servidor d\'identitat (%s) per poder ser trobat mitjançant l\'adreça de correu electrònic o el número de telèfon. Introdueix l\'URL d\'un servidor d\'identitat -Has donat el teu consentiment per poder enviar correus electrònics i números de telèfon a aquest servidor d\'identitat per trobar altres usuaris dels teus contactes. +Has donat el teu consentiment per poder enviar adreces de correu electrònic i números de telèfon a aquest servidor d\'identitat per trobar altres usuaris dels teus contactes. Números de telèfon perquè et trobin Si et desconnectes del servidor d\'identitat no podràs ser trobat per altres usuaris ni convidar-los mitjançant el correu electrònic o el número de telèfon. Correus electrònics perquè et puguin trobar @@ -1376,7 +1376,7 @@Si treus el vet a un usuari podrà tornar a unir-se a la sala. No s\'ha pogut treure el vet a l\'usuari Treu el vet a l\'usuari -app_display_name: +Àlies de l\'aplicació: El teu àlies Estableix la foto URL de la teva foto @@ -1611,7 +1611,7 @@Fes clic a l\'enllaç per confirmar la nova contrasenya. Quan hagis anat a l\'enllaç que conté, fes clic a sota. S\'ha enviat un correu de verificació a %1$s. Revisa la teva safata d\'entrada -Aquest correu no està vinculat amb cap compte +Aquesta adreça de correu electrònic no està vinculada a cap compte Continua Atenció! Nova contrasenya @@ -1630,7 +1630,7 @@ha enviat una nevada ❄️ El meu codi Comparteix el meu codi -Escaneja un codi QR +Escaneja codi QR No és un codi QR de Matrix vàlid No hi ha informació criptogràfica disponible La nova sessió s\'ha verificat. Tindrà accés als teus missatges xifrats i es mostrarà de confiança per als altres usuaris. @@ -2120,8 +2120,8 @@Assegura\'t que les persones adequades tinguin accés a %s. Pots convidar-ne més després. Assegura\'t que les persones adequades tinguin accés a %s. Envia multimèdia a mida real -Per descobrir contactes existents, s\'ha d\'enviar informació de contacte (correus i números de telèfon) al servidor d\'identitat utilitzat. Es fa un \'hash\' de les dades abans d\'enviar-les per privacitat. -Envia correus i números de telèfon a %s +Per descobrir contactes existents, s\'ha d\'enviar informació de contacte (adreces de correu electrònic i números de telèfon) al servidor d\'identitat utilitzat. Es fa un \'hash\' de les dades abans d\'enviar-les per privacitat. +Envia adreces de correu electrònic i números de telèfon a %s Els teus contactes són privats. Per descobrir els usuaris dels teus contactes, necessitem permís per enviar informació dels contactes al servidor d\'identitat que estiguis utilitzant. Estàs utilitzant una versió beta dels espais. Els teus comentaris ajudaran a les properes versions. S\'anotaran la teva plataforma i nom d\'usuari per poder utilitzar els teus comentaris tant bé com puguem. Qualsevol a un espai amb aquesta sala podrà trobar-la i unir-s\'hi. Només els administradors d\'aquesta sala poden afegir-la a un espai. @@ -2150,7 +2150,7 @@Comparteix ubicació Reinicia l\'aplicació per aplicar els canvis. Activa format matemàtic amb LaTeX -Enllaça aquest correu amb el teu compte +Enllaça aquesta adreça de correu electrònic al teu compte (%1$s) %1$s (%2$s) No s\'ha pogut reproduir %1$s @@ -2438,7 +2438,7 @@Altres Mencions i paraules clau Notificacions per defecte -Per rebre notificacions per correu, has d\'associar un correu electrònic amb el teu compte de Matrix +Per rebre notificacions per correu, has d\'associar una adreça de correu electrònic al teu compte de Matrix S\'ha tancat la sessió! La sala ha estat abandonada! Cap @@ -2616,7 +2616,6 @@Ordena per Mostra recents Mostra filtres -Mostra totes les sessions (V2, WIP) Crea sala Inicia xat Verificada · Última activitat %1$s @@ -2710,4 +2709,134 @@Sessió actual Element simplificat amb pestanyes opcionals Activa la nova visualització +Les sessions inactives son sessions que no has utilitzat durant un temps, però continuen rebent claus de xifrat. +\n +\nL\'eliminació de sessions inactives millora la seguretat i el rendiment, i et pot ajudar a identificar sessions noves sospitoses. +Tanca aquesta sessió +Obre la pantalla d\'eines per a desenvolupadors +Els usuaris dels xats directes i sales al les quals t\'hagis unit poden veure la llista completa de les teves sessions. +\n +\nAixò els pot proporcionar més confiança de que realment parlen amb tu però, poden veure el nom de sessió que introdueixis. +Les sessions verificades son sessions en què has iniciat sessió amb les teves credencials i s\'han verificat utilitzant una frase de seguretat o mitjançant la verificació creuada. +\n +\nAixò vol dir que contenen claus de xifrat dels teus missatges anteriors i confirmen als altres usuaris amb qui parles, que aquestes sessions son realment teves. +Les sessions no verificades son sessions en què has iniciat sessió amb les teves credencials però s\'hi ha fet una verificació creuada. +\n +\nAssegura\'t que reconeixes aquestes sessions especialment, ja que podrien representar un ús no autoritzat del teu compte. +Canvi de nom de sessions +Sessions verificades +Sessions no verificades +Sessions inactives +Tingues en compte que els noms de sessió son visibles per les persones amb qui parlis. +Els noms de sessió personalitzats et permeten identificar els teus dispositius més fàcilment. +Nom de la sessió +Canvia el nom de la sessió +No verificada · Sessió actual +L\'autenticitat d\'aquest missatge xifrat no ha pogut ser garantida en aquest dispositiu. +Afegeix (╯°□°)╯︵ ┻━┻ abans d\'un missatge de text +⚠ Hi ha dispositius no verificats en aquesta sala, no podran desxifrat els missatges que enviïs. +No enviïs mai missatges xifrats a sessions no verificades d\'aquesta sala. +D\'acord +Transmissió de veu +Inicia una transmissió de veu +Sol·licita que no es desi cap dada personalitzada del teclat en funció del que escrius a les converses (per exemple l\'historial d\'escriptura o el diccionari). Tingues en compte que alguns teclats poden no respectar aquesta configuració. +Teclat incògnit +🔒 Has activat el xifrat a només en sessions verificades a totes les sales, a Configuració > Seguretat. +Estat de verificació desconegut +Escaneja codi QR +ID de sessió: +${app_name} necessita permís per mostrar notificacions. Les notificacions poden mostrar missatges, invitacions, etc. +\n +\nConcedeix els permisos a les següents finestres emergents per poder veure les notificacions correctament. +S\'ha detectat un problema de seguretat en configurar la missatgeria segura. Algun dels següents elements pot estar compromès: el servidor que utilitzes; alguna de les teves connexions a Internet; algun dels teus dispositius; +Comprova el dispositiu amb la sessió iniciada, s\'hauria de mostrar el codi de sota. Verifica que el codi de sota coincideix amb el del dispositiu: +Comença a la pantalla d\'inici de sessió +Comença a la pantalla d\'inici de sessió +Iniciant sessió a un dispositiu mòbil\? +Mostra codi QR +Selecciona \'Escaneja codi QR\' +Selecciona \'Mostra codi QR\' +Ves a Configuració -> Seguretat i privadesa +Obre l\'aplicació en l\'altre dispositiu +Inici de sessió cancel·lat per l\'altre dispositiu. +El codi QR és invàlid. +L\'altre dispositiu ha d\'haver iniciat sessió. +L\'altre dispositiu ja ha iniciat sessió. +Ha fallat la petició. +Petició denegada per l\'altre dispositiu. +La vinculació no s\'ha completat en el temps permès. +La vinculació amb aquest dispositiu no està admesa. +Connexió sense èxit +Connexió segura establerta +Escaneja el codi QR de sota amb el dispositiu amb la sessió tancada. +Utilitza el dispositiu amb la sessió iniciada per escanejar el codi QR següent: +Torna-ho a provar +No coincideix\? +Iniciant sessió +Connectant al dispositiu +Escaneja codi QR +Confirma +Assegura\'t que coneixes l\'origen d\'aquest codi. L\'enllaç de dispositius, proporciona a algú accés complet al teu compte. +Aplica subratllat +Ratlla-ho +Aplica negreta +Aplica cursiva +Selecciona \'Inicia sessió amb codi QR\' +El servidor no és compatible amb l\'inici de sessió mitjançant codi QR. +Inicia sessió amb codi QR +Utilitza la càmera d\'aquest dispositiu per escanejar el codi QR que es mostra a l\'altre dispositiu: +Escaneja codi QR +3 +2 +1 +Permet enregistrar i enviar emissions de veu dins una sala. +Activa l\'emissió de veu (en desenvolupament) +Activa la gravació d\'informació de client +Desa el nom de client, la versió i l\'URL per reconèixer les sessions més fàcilment dins el gestor de sessions. +Obté un millor control i visibilitat de totes les teves sessions. +Activa el nou gestor de sessions +Pots utilitzar aquest dispositiu per iniciar la sessió amb un codi QR a un dispositiu mòbil o web. Ho pots fer de dues maneres: +Inicia sessió amb codi QR +Sistema operatiu +Model +Navegador +URL +Versió +Nom +Aplicació +Verifica la teva sessió actual per mostrar l\'estat de verificació d\'aquesta sessió. +Activat: +Alguna cosa ha anat malament. Comprova la teva connexió i torna-ho a provar. +Concedir permís +${app_name} necessita permís per mostrar notificacions. +\nSi us plau, concedeix-li el permís. +Prova l\'editor de text enriquit (el mode text pla arribarà aviat) +Activa l\'editor de text enriquit +Rep notificacions en aquesta sessió. +Notificacions +Carregant +Pausa l\'emissió de veu +Reprodueix o reprèn l\'emissió de veu +Atura l\'enregistrament d\'emissió de veu +Pausa l\'enregistrament d\'emissió de veu +Reprèn l\'enregistrament d\'emissió de veu +En directe +Deselecciona-ho tot +Selecciona-ho tot ++ +- %1$d seleccionat
+- %1$d seleccionats
+Enquestes +Selecciona sessions +Contacte +Càmera +Ubicació +Emissió de veu +Adjunts +Adhesius +Galeria +Format de text +Enrere 30 segons +Avança 30 segons \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index 1983036271..f260a129fc 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -351,7 +351,7 @@Hovor probíhá… Protější strana hovor nepřijala. Informace -${app_name} potřebuje oprávnění pro přístup k Vašemu mikrofonu pro uskutečnění hlasových hovorů. +${app_name} potřebuje oprávnění pro přístup k vašemu mikrofonu pro uskutečnění hlasových hovorů. ANO NE Pokračovat @@ -411,12 +411,12 @@Hotovo Opravdu se chcete odhlásit\? Video hovor probíhá… -${app_name} potřebuje oprávnění pro přístup k Vaší kameře a mikrofonu pro uskutečnění video hovoru. + ${app_name} potřebuje oprávnění pro přístup k vaší kameře a mikrofonu pro uskutečnění video hovoru. \n \nProsím, povolte přístup na následující hlášce abyste mohli uskutečnit hovor. Tuto změnu nelze zvrátit, protože povyšujete uživatele na stejnou úroveň, jakou máte vy. \nOpravdu to chcete udělat\? -Toto by mohlo znamenat, že někdo škodlivě zachytává Vaši komunikaci nebo že Váš telefon nedůvěřuje certifikátu poskytnutému vzdáleným serverem. +Toto by mohlo znamenat, že někdo škodlivě zachytává vaši komunikaci nebo že Váš telefon nedůvěřuje certifikátu poskytnutému vzdáleným serverem. Pokud administrátor serveru řekl, že toto je předpokládané, ujistěte se, že otisk níže se shoduje s otiskem který Vám poskytl. Certifikát se změnil z toho, kterému Váš telefon důvěřoval. Toto je VELMI NEOBVYKLÉ. Je doporučeno, abyste NEPŘIJALI tento nový certifikát. Certifikát se změnil z původně důvěryhodného na nyní nedůvěryhodný. Server patrně obnovil svůj certifikát. Kontaktujte administrátora kvůli očekávanému otisku. @@ -578,7 +578,7 @@Deaktivace účtu Deaktivovat můj účet Objevování -Správa Vašich nastavení pro objevování. +Správa vašich nastavení pro objevování. Analýza Odeslat analytická data ${app_name} sbírá anonymní analytická data pro vylepšení aplikace. @@ -648,8 +648,8 @@Import klíčů místností Import klíčů z místního souboru Import -Šifruj pouze do ověřených relací -Nikdy neposílejte šifrované zprávy do neověřených relací z této relace. +Šifrovat pouze do ověřených relací +Nikdy neposílat šifrované zprávy do neověřených relací z této relace. Není ověřeno Potvrzeno neznámá ip @@ -848,7 +848,7 @@Začít používat zálohu klíčů (Pokročilé) Zabezpečit zálohu přístupovou frází. -Uložíme zašifrovanou kopii Vašich klíčů na Vašem domovském serveru. Chraňte svoji zálohu přístupovou frází, abyste ji udrželi v bezpečí. + Uložíme zašifrovanou kopii vašich klíčů na Vašem domovském serveru. Chraňte svoji zálohu přístupovou frází, abyste ji udrželi v bezpečí. \n \nZ důvodu nejvyšší bezpečnosti by se měla lišit od hesla účtu. Nastavit přístupovou frází @@ -856,7 +856,7 @@Nebo zabezpečte svoji zálohu pomocí klíče obnovy, uloženého někde v bezpečí. (Pokročilé) Nastavit s klíčem obnovy Podařilo se! -Váš klíč obnovy je záchranná síť - lze jej použít pro obnovu Vašich šifrovaných zpráv, pokud zapomenete svou přístupovou frázi. + Váš klíč obnovy je záchranná síť - lze jej použít pro obnovu vašich šifrovaných zpráv, pokud zapomenete svou přístupovou frázi. \nUchovávejte svůj klíč obnovy velmi bezpečně, např. ve správci hesel (nebo trezoru) Uchovávejte svůj klíč obnovy velmi bezpečně, např. ve správci hesel (nebo trezoru) Hotovo @@ -962,16 +962,16 @@Push pravidla Žádná push pravidla nejsou definována Žádné push brány nejsou registrovány -app_id: -push_key: -app_display_name: -session_name: +ID aplikace: +Klíč push: +Zobrazovaný název aplikace: +Název relace: Url: Formát: Hlas a video Nápověda a O aplikaci Registrovat token -Učinit návrh +Poslat návrh Prosím, zapište svůj návrh níže. Popište svůj návrh tady Děkujeme, návrh byl úspěšně odeslán @@ -1011,7 +1011,7 @@Volby pro nalezení se ukážou, jakmile doplníte telefonní číslo. Odpojení od serveru identit bude znamenat, že Vás jiní uživatelé nebudou moci najít a Vy nebudete moci pozvat druhé pomocí emailu nebo telefonního čísla. Telefonní čísla pro nalezení -Poslali jsme Vám potvrzovací email na %s, podívejte se do emailu a klikněte na protvrzovací odkaz +Poslali jsme vám email na %s, podívejte se do něj a klikněte na protvrzovací odkaz Zadejte URL serveru pro identity Nemohl jsem se spojit se serverem pro identity Prosím, zadejte url serveru pro identity @@ -1023,7 +1023,7 @@Zapnout podrobné záznamy. Podrobné záznamy pomohou vývojářům mnoha podrobnostmi, odešlete-li RageShake. I když jsou zapnuty, aplikace nezaznamenává obsah zpráv nebo jakákoli soukromá data. Prosím, opakujte, jakmile jste přijali všeobecné podmínky svého domovského serveru. -Vypadá to, že serveru dlouho trvá odpovědět, to může být způsobeno buď slabým spojením nebo chybou na serveru. Prosím, opakujte za chvíli. +Vypadá to, že serveru trvá příliš dlouho, než odpoví, což může být způsobeno buď špatným připojením, nebo chybou serveru. Zkuste to prosím za chvíli znovu. Poslat přílohu Otevřít navigační zásuvku Otevřít menu založení místnosti @@ -1079,12 +1079,12 @@Neignorujete žádné uživatele Více možností po dlouhém stisku na místnost %1$s učinil místnost veřejnou pro kohokoli znalého odkazu. -%1$s nastavil místnost jen pro pozvané. +%1$s nastavil místnost jako pouze pro pozvané. Nepřečtené zprávy Je to Vaše konverzace. Vlastněte ji. Chatujte s lidmi přímo nebo ve skupinách Udržujte konverzace soukromé pomocí šifrování -Rozšiřte a upravte si svůj zážitek +Rozšiřte a upravte si možnosti Můžeme začít Vybrat server Jako email, účty mají jeden domov, ačkoli můžete mluvit s kýmkoli @@ -1106,7 +1106,7 @@Prémiový hosting pro organizace Zadejte adresu Modular Element nebo serveru, který chcete použít Při načítání stránky došlo k chybě: %1$s (%2$d) -Aplikace se nemůže přihlásit k tomuto homeserveru. Homeserver podporuje následující typy přihlášení: %1$s. + Aplikace se nemůže přihlásit k tomuto domovskému serveru. Domovský server podporuje následující typy přihlášení: %1$s. \n \nChcete se přihlásit webovým klientem\? Omlouváme se, tento server již nepřijímá nové účty. @@ -1136,7 +1136,7 @@ \n \nZastavit proces změny hesla\?Nastavit emailovou adresu -Nastavte emailovou adresu pro obnově svého účtu. Později můžete volitelně dovolit lidem, které znáte, aby Vás podle emailu nalezli. +Nastavte e-mailovou adresu pro obnovení účtu. Později můžete volitelně povolit svým známým, aby vás podle této adresy nalezli. Email (volitelné) Dále @@ -1218,7 +1218,7 @@${app_name} může spadnout častěji, když se objeví neočekávané chyby Předsune ¯\\_(ツ)_/¯ do textové zprávy Zapnout šifrování -Jakmile zapnuto, šifrování nelze vypnout. +Po zapnutí šifrování ho není možné vypnout. Vaše emailová doména není autorizována registrovat se na tomto serveru Nedůvěryhodné přihlášení Shodují se @@ -1279,7 +1279,7 @@${app_name} neobstarává události typu \'%1$s\' ${app_name} narazil na chybu při převádění obsahu události s id \'%1$s\' Odignorovat -Tato relace nemůže sdílet toto ověření s jinými z Vašich relací. + Tato relace nemůže sdílet toto ověření s jinými z vašich relací. \nToto ověření bude uloženo místně a sdíleno v budoucí verzi aplikace. Odešle danou zprávu zabarvenou jako duha Odešle daný emote zabarvený jako duha @@ -1297,15 +1297,15 @@Vaše nová relace je nyní ověřena. Má přístup k Vašim zašifrovaným zprávám a ostatní uživatelé ji uvidi jako důvěryhodnou. Křížové podepisování Křížové podpisování je zapnuto. -\nPrivátní klíče v zařízení. +\nSoukromé klíče v zařízení.Křížové podpisování je zapnuto \nKlíče jsou důvěryhodné. -\nPrivátní klíče nejsou známy +\nSoukromé klíče nejsou známyKřížové podpisování je zapnuto. \nKlíče nejsou důvěryhodné Křížové podpisování není zapnuto Aktivní relace -Ukázat všechny relace +Zobrazit všechny relace Správa relací Odhlásit se z této relace Žádná kryptografická informace není k dispozici @@ -1440,7 +1440,7 @@Zpráva smazána Ukázat odstraněné zprávy Zobrazit zástupný symbol za odstraněné zprávy -Poslali jsme Vám potvrzovací email na %s, podívejte se nejdříve do emailu a klikněte na protvrzovací odkaz +Poslali jsme vám email na %s, podívejte se nejdříve do emailu a klikněte na protvrzovací odkaz Ověřovací kód není správný. MÉDIA V této místnosti nejsou žádná média @@ -1475,7 +1475,7 @@Manuálně ověřit textem Ověřit přihlášení Interaktivně ověřit pomocí Emoji -Potvrďte svou identitu ověřením tohoto přihlášení v některé z Vašich dalších relacích a udělte přístup k zašifrovaným zprávám. +Potvrďte svou identitu ověřením tohoto přihlášení v některé z vašich dalších relacích a udělte přístup k zašifrovaným zprávám. Zvolte si, prosím, uživatelské jméno. Prosím, zvolte heslo. Překontrolovat tento odkaz @@ -1517,7 +1517,7 @@Nemáte povolení zahájit konferenční hovor v této místnosti Zahájit video schůzku Zahájit hlasovou schůzku -Schůzky používají pravidla zabezpečení a přístupu Jitsi. Všichni lidé nyní v místnosti uvidí pozvánku k připojení, zatímco Vaše schůzka probíhá. +Schůzky používají pravidla zabezpečení a přístupu Jitsi. Všichni lidé nyní v místnosti uvidí pozvánku k připojení, zatímco vaše schůzka probíhá. Nemůžete zahájit hovor se sebou Nemůžete zahájit hovor se sebou, počkejte, až účastníci přijmou pozvánku Přidání widgetu se nezdařilo @@ -1567,9 +1567,9 @@Důvod k vykázání Zrušit vykázání uživatele Zrušení vykázání uživatele jim opět umožní vstoupit do místnosti. -Žádné telefonní číslo nebylo zadáno do Vašeho účtu +Žádné telefonní číslo nebylo zadáno do vašeho účtu Emailová adresa -Žádná emailová adresa nebyla zadána do Vašeho účtu +Žádná emailová adresa nebyla zadána do vašeho účtu Telefonní čísla Ostranit %s\? Ujistěte se, že kliknete na odkaz v e-mailu, který jsme Vám poslali. @@ -1577,7 +1577,7 @@Vytvořit bezpečnou zálohu Resetovat bezpečnou zálohu Nastavit na tomto zařízení -Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na Vašem serveru. +Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na vašem serveru. Generovat nový bezpečnostní klíč nebo nastavit novou bezpečnostní frázi pro existující zálohu. To nahradí Váš nynější klíč nebo frázi. Integrace jsou vypnuty @@ -1641,7 +1641,7 @@Zastavit fotoaparát Spustit fotoaparát Bezpečná záloha -Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na Vašem serveru. +Ochrana před ztrátou přístupu k šifrovaným zprávám a datům pomocí zálohy šifrovacích klíčů na vašem serveru. Nastavit Použít bezpečnostní klíč Generovat bezpečnostní klíč k uložení na bezpečném místě např. správci hesel nebo sejfu. @@ -2019,20 +2019,20 @@Dejte prostoru jméno a pokračujte. Doplňte nějaké podrobnosti, aby zaujal. Můžete je kdykoli změnit. Vytvořit prostor -Pouze na pozvání, nejlepší pro Vás nebo týmy -Privátní +Pouze na pozvání, nejlepší pro vás nebo týmy +Soukromý Otevřený pro všechny, nejlepší pro komunity Veřejný -Privátní prostor pro Vás a Vaše kolegy +Soukromý prostor pro vás a vaše kolegy Já a kolegové -Privátní prostor k organizaci Vašich místností +Soukromý prostor pro uspořádání vašich místností Jen já Ujistěte se, že ti správní lidé mají přístup do %s. S kým pracujete\? Ke vstupu do existujícího prostoru potřebujete pozvání. Můžete změnit později Jaký typ prostoru chcete vytvořit\? -Váš privátní prostor +Váš soukromý prostor Váš veřejný prostor Přidat prostor Opustit místnost s daným id (nebo nynější místnost pokud prázdné) @@ -2044,7 +2044,7 @@Kdokoliv může místnost najít a připojit se do ní Veřejný Pouze pozvaní mohou místnost najít a vstoupit do ní -Privátní +Soukromý Neznámé nastavení přístupu (%s) Každý může na místnost zaklepat, členové pak mohou přijmout či odmítnout Dovolit hostům vstoupit @@ -2104,13 +2104,13 @@Prohlédnout a spravovat adresy tohoto prostoru. Adresy prostorů Aktualizujte na doporučenou verzi místnosti -Tato místnost používá místnost verze %s, kterou homeserver označil za nestabilní. +Tato místnost používá verzi místnosti %s, kterou domovský server označil za nestabilní. K aktualizaci místnosti potřebujete oprávnění Automaticky aktualizovat mateřský prostor Automaticky pozvat uživatele Budete aktualizovat tuto místnost z %1$s na %2$s. -Aktualizace místnosti je pokročilá akce a obvykle se doporučuje tehdy, je-li místnost nestabilní kvůli chybám, chybějícím funkcím nebo slabým místům v zabezpečení. -\nObvykle má vliv pouze na to, jak server místnost zpracovává. +Aktualizace místnosti je pokročilá akce a obvykle se doporučuje, pokud je místnost nestabilní kvůli chybám, chybějícím funkcím nebo bezpečnostním zranitelnostem. +\nObvykle ovlivňuje pouze způsob zpracování místnosti na serveru. Aktualizovat soukromou místnost Aktualizovat veřejnou místnost Aktualizace @@ -2124,7 +2124,7 @@Raději ověřit porovnáním emoji Oskenovat tímto zařízením Oskenujte kód svým dalším zařízením nebo přepněte a oskenujte tímto zařízením -URL API Homeserveru +URL API domovského serveru Chybějící oprávnění Pro provedení této akce udělte, prosím, oprávnění Fotoaparát v systémových nastaveních. Některá z oprávnění potřebných k provedení akce chybí, prosím, udělte oprávnění v systémových nastaveních. @@ -2165,7 +2165,7 @@Prostory, které mají přístup Umožněte členům prostoru ho najít a zpřístupnit. Členové prostoru %s mohou vyhledávat, prohlížet a připojovat se. -Soukromý (pouze pro pozvané) +Soukromý (jen pro pozvané) Chcete-li odesílat hlasové zprávy, povolte oprávnění mikrofonu. Aktualizace místnosti Zprávy od bota @@ -2650,7 +2650,6 @@ \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 @@ -2763,5 +2762,164 @@Vytvořit přímou zprávu pouze při první zprávě Povolit odložené přímé zprávy Zjednodušený Element s volitelnými kartami -Povolit nový vzhled +Zapnout nové uspořádání +Ostatní uživatelé v přímých zprávách a místnostech, ke kterým se připojíte, si mohou prohlédnout úplný seznam vašich relací. +\n +\nTo jim poskytuje jistotu, že s vámi skutečně mluví, ale také to znamená, že mohou vidět název relace, který zde zadáte. +Přejmenování relací +Ověřené relace se přihlásily pomocí vašich přihlašovacích údajů a poté byly ověřeny buď pomocí vaší zabezpečené přístupové fráze, nebo křížovým ověřením. +\n +\nTo znamená, že uchovávají šifrovací klíče pro vaše předchozí zprávy a potvrzují ostatním uživatelům, se kterými komunikujete, že tyto relace jste skutečně vy. +Ověřené relace +Neověřené relace jsou relace, které se přihlásily pomocí vašich přihlašovacích údajů, ale nebyly křížově ověřeny. +\n +\nMěli byste si být obzvláště jisti, že tyto relace rozpoznáte, protože by mohly představovat neoprávněné použití vašeho účtu. +Neověřené relace +Neaktivní relace jsou relace, které jste po určitou dobu nepoužili, ale nadále dostávají šifrovací klíče. +\n +\nOdstranění neaktivních relací zvyšuje zabezpečení a výkon a usnadňuje identifikaci podezřelé nové relace. +Neaktivní relace +Uvědomte si, že jména relací jsou viditelná i pro osoby, se kterými komunikujete. +Vlastní názvy relací vám pomohou snadněji rozpoznat vaše zařízení. +Název relace +Přejmenovat relaci +Odhlásit se z této relace +Neověřeno · Vaše aktuální relace +Zahájit hlasové vysílání +Pravost této šifrované zprávy nelze v tomto zařízení zaručit. +Požadujte, aby klávesnice neaktualizovala žádné personalizované údaje, jako je historie psaní a slovník, na základě toho, co jste napsali v konverzacích. Upozorňujeme, že některé klávesnice nemusí toto nastavení respektovat. +Inkognito klávesnice +Přidá znaky (╯°□°)╯︵ ┻━┻ před zprávy ve formátu obyčejného textu +Hlasové vysílání +Otevřít nástroje pro vývojáře +🔒 V nastavení zabezpečení jste povolili šifrování pouze do ověřených relací pro všechny místnosti. +⚠ V této místnosti jsou neověřená zařízení, která nebudou schopna dešifrovat odeslané zprávy. +Nikdy neodesílat šifrované zprávy do neověřených relací v této místnosti. +Rozumím +Použít podtržení +Použít přeškrtnutí +Použít tučný text +Použít kurzívu +Zaznamenávat název, verzi a url pro snadnější rozpoznání relací ve správci relací. +Povolit záznamenávání informací o klientu +Získejte lepší přehled a kontrolu nad všemi relacemi. +Použít nový správce relací +Operační systém +Model +Prohlížeč +URL +Verze +Název +Aplikace +Přijímat push oznámení v této relaci. +Push oznámení +Ověřením aktuální relace zjistíte stav ověření této relace. +Neznámý stav ověření +Zapnuto: +ID relace: +Něco se pokazilo. Zkontrolujte prosím síťové připojení a zkuste to znovu. +Udělit oprávnění +${app_name} potřebuje oprávnění k zobrazování oznámení. +\nUdělte prosím toto oprávnění. +${app_name} potřebuje oprávnění k zobrazování oznámení. Oznámení mohou zobrazovat vaše zprávy, pozvánky atd. +\n +\nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech. +Vyzkoušejte rozšířený textový editor (textový režim již brzy) +Povolit rozšířený textový editor +Ujistěte se, že znáte původ tohoto kódu. Propojením zařízení poskytnete někomu plný přístup ke svému účtu. +Potvrdit +Zkuste to znovu +Neshoduje se\? +Probíhá přihlašování +Připojování k zařízení +Naskenujte QR kód +Přihlašování na mobilním zařízením\? +Zobrazit QR kód na tomto zařízení +Vyberte možnost \"Naskenovat QR kód\" +Začněte na přihlašovací obrazovce +Vyberte možnost \"Přihlásit se pomocí QR kódu\" +Začněte na přihlašovací obrazovce +Vyberte možnost \"Zobrazit QR kód\" +Přejděte do Nastavení -> Zabezpečení a soukromí +Otevřete aplikaci na vašem druhém zařízení +Žádost byla na druhém zařízení zamítnuta. +Propojení nebylo dokončeno v požadovaném čase. +Propojení s tímto zařízením není podporováno. +Neúspěšné připojení +Zkontrolujte vaše přihlášené zařízení, měl by se zobrazit níže uvedený kód. Zkontrolujte, zda níže uvedený kód odpovídá danému zařízení: +Zabezpečené připojení navázáno +Pomocí odhlášeného zařízení naskenujte níže uvedený QR kód. +Pomocí přihlášeného zařízení naskenujte níže uvedený QR kód: +Přihlásit se pomocí QR kódu +Pomocí fotoaparátu na tomto zařízení naskenujte QR kód zobrazený na druhém zařízení: +Naskenovat QR kód +3 +2 +1 +Pomocí tohoto zařízení se můžete přihlásit do mobilního nebo webového zařízení pomocí QR kódu. Můžete to provést dvěma způsoby: +Přihlásit se pomocí QR kódu +Naskenovat QR kód +Možnost nahrávat a odesílat hlasové vysílání na časové ose místnosti. +Povolit hlasové vysílání (v aktivním vývoji) +Domovský server nepodporuje přihlášení pomocí QR kódu. +Přihlášení bylo na druhém zařízení zrušeno. +Tento QR kód je neplatný. +Druhé zařízení musí být přihlášeno. +Druhé zařízení je již přihlášeno. +Při nastavování zabezpečeného zasílání zpráv se vyskytl problém se zabezpečením. Může být napadena jedna z následujících věcí: váš domovský server; vaše internetové připojení; vaše zařízení; +Žádost se nezdařila. +Ukládání do vyrovnávací paměti +Pozastavit hlasové vysílání +Přehrát nebo obnovit hlasové vysílání +Ukončit záznam hlasového vysílání +Pozastavit záznam hlasového vysílání +Obnovit záznam hlasového vysílání +Živě +Vybrat relace +Kontakt +Fotoaparát +Poloha +Hlasování +Hlasové vysílání +Přílohy +Nálepky +Knihovna fotografií +Zrušit výběr všech +Vybrat všechny ++ +- %1$d vybraný
+- %1$d vybrané
+- %1$d vybraných
+Přepnutí režimu celé obrazovky +Formátování textu +Již nahráváte hlasové vysílání. Ukončete prosím aktuální hlasové vysílání a zahajte nové. +Hlasové vysílání už nahrává někdo jiný. Počkejte, až jeho hlasové vysílání skončí, a zahajte nové. +Nemáte potřebná oprávnění k zahájení hlasového vysílání v této místnosti. Obraťte se na správce místnosti, aby vám zvýšil oprávnění. +Nelze zahájit nové hlasové vysílání +Přetočení o 30 sekund zpět +Přetočení o 30 sekund dopředu +Ověřené relace jsou všude tam, kde tento účet používáte po zadání přístupové fráze nebo po potvrzení své totožnosti jinou ověřenou relací. +\n +\nTo znamená, že máte všechny klíče potřebné k odemknutí zašifrovaných zpráv a potvrzení ostatním uživatelům, že této relaci důvěřujete. ++ +- Odhlásit se z %1$d relace
+- Odhlásit se ze %1$d relací
+- Odhlásit se z %1$d relací
+Odhlásit se +zbývá %1$s +vytvořil hlasování. +poslal nálepku. +poslal video. +poslal obrázek. +poslal hlasovou zprávu. +poslal zvukový soubor. +odeslal soubor. +V odpovědi na +Skrýt IP adresu +Zobrazit IP adresu +Citace +Odpovídám na %s +Úpravy \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 27f46160bc..be53c15026 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -40,9 +40,9 @@%1$s und %2$s Leerer Raum %s hat diesen Raum aufgewertet. -Sende eine Nachricht… +Sende eine Nachricht … Erste Synchronisation: -\nImportiere Benutzerkonto… +\nImportiere Konto …Erste Synchronisation: \nImportiere Kryptoschlüssel Erste Synchronisation: @@ -57,7 +57,7 @@ Erste Synchronisation: \nImportiere Benutzerdaten %1$s hat die Einladung an %2$s, den Raum zu betreten, zurückgezogen -%1$s\'s Einladung. Grund: %2$s +Einladung von %1$s. Grund: %2$s %1$s hat %2$s eingeladen. Grund: %3$s %1$s hat dich eingeladen. Grund: %2$s %1$s ist dem Raum beigetreten. Grund: %2$s @@ -67,7 +67,7 @@%1$s hat Sperre von %2$s aufgehoben. Grund: %3$s %1$s hat %2$s verbannt. Grund: %3$s %1$s hat die Einladung für %2$s angenommen. Grund: %3$s -%1$s hat Einladung für %2$s verworfen. Grund: %3$s +%1$s hat die Einladung für %2$s zurückgezogen. Grund: %3$s - %1$s fügt %2$s als eine Adresse für diesen Raum hinzu.
- %1$s fügt %2$s als Adressen für diesen Raum hinzu.
@@ -263,13 +263,13 @@Nur Matrix-Kontakte Keine Ergebnisse Räume -Logdateien übermitteln +Sende Protokolle Absturzberichte übermitteln Bildschirmfoto übermitteln Problem melden Bitte beschreibe das Problem. Was hast du genau gemacht\? Was sollte passieren\? Was ist tatsächlich passiert\? Problembeschreibung -Um Probleme diagnostizieren zu können, werden Protokolle der Anwendung zusammen mit dem Fehlerbericht übermittelt. Dieser Fehlerbericht wird, wie die Protokolle und das Bildschirmfoto, nicht öffentlich sichtbar sein. Wenn du nur den oben eingegebenen Text senden möchtest, die nachfolgenden Haken entsprechend entfernen: +Um Probleme diagnostizieren zu können, werden Protokolle der Anwendung zusammen mit dem Fehlerbericht übermittelt. Dieser Fehlerbericht wird, inklusive der Protokolle und des Bildschirmfotos, nicht öffentlich sichtbar sein. Wenn du nur den oben eingegebenen Text senden möchtest, entferne die Häkchen: Du scheinst dein Telefon frustriert zu schütteln. Möchtest du das Fenster zum Senden eines Fehlerberichts öffnen\? Dein Fehlerbericht wurde erfolgreich übermittelt Der Fehlerbericht konnte nicht übermittelt werden (%s) @@ -300,11 +300,11 @@Groß Mittel Klein -Verbindungsaufbau… +Verbindungsaufbau … Anruf beendet Eingehender Videoanruf Eingehender Sprachanruf -Anruf aktiv… +Anruf aktiv … Die Gegenseite hat den Anruf nicht angenommen. Information ${app_name} benötigt die Berechtigung, auf dein Mikrofon zugreifen zu können, um (Sprach-)Anrufe tätigen zu können. @@ -329,9 +329,9 @@Erwähnen Du wirst diese Änderung nicht rückgängig machen können, da die Person dieselbe Berechtigungsstufe wie du erhalten wird. \nBist du sicher\? -%s schreibt… -%1$s und %2$s schreiben… -%1$s, %2$s und andere schreiben… +%s tippt … +%1$s und %2$s tippen … +%1$s, %2$s und andere tippen … Du bist nicht berechtigt, in diesen Raum zu schreiben. Vertrauen Nicht vertrauen @@ -353,7 +353,7 @@Telefonnummer hinzufügen Anwendungsinformationen in den Systemeinstellungen anzeigen. Anwendungsinformationen -Benachrichtigungen für diesen Account +Benachrichtigungen für dieses Konto Benachrichtigungen für diese Sitzung Direktnachrichten Gruppenunterhaltungen @@ -408,7 +408,7 @@Alle Nur Mitglieder Nur Mitglieder (ab Einladung) -Nur Mitglieder (ab Beitreten) +Nur Mitglieder (ab Betreten) Verbannte Benutzer Erweitert Interne ID dieses Raumes @@ -431,15 +431,15 @@Schlüssel aus lokaler Datei importieren Importieren Nur zu verifizierten Sitzungen verschlüsseln -Von dieser Sitzung aus keine verschlüsselten Nachrichten an nicht-verifizierte Sitzungen senden. +Niemals verschlüsselte Nachrichten von dieser Sitzung zu unverifizierten Sitzungen senden. Nicht verifiziert Verifiziert Bestätigen Vergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des anderen Nutzers und bestätige: Falls sie nicht übereinstimmen, wurde die Kommunikation vielleicht kompromittiert. Raumverzeichnis auswählen -Server-Name -Alle Räume auf dem %s-Server +Name des Servers +Alle Räume auf %s Alle nativen %s-Räume Bedienoberfläche Sprache @@ -467,20 +467,20 @@Berechtigungslevel muss eine positive ganze Zahl sein. Du bist nicht Mitglied in diesem Raum. Du hast keine Berechtigung, diese Aktion in diesem Raum auszuführen. -Anfrage beinhaltet keine Raum-ID. +room_id fehlt in der Anfrage. Raum %s ist nicht sichtbar. Integrationen hinzufügen Benachrichtigungston Anfrage konnte nicht gesendet werden. -Anfrage enthält keine user_id. +user_id fehlt in der Anfrage. Helles Design Dunkles Design Schwarzes Design Auf Ereignisse lauschen Nachrichten mit meinem Anzeigenamen Nachrichten mit meinen Benutzernamen -Du hast die neue Sitzung \'%s\' hinzugefügt, die jetzt Verschlüsselungs-Schlüssel anfordert. -Deine bislang nicht verifiziertes Sitzung \'%s\' fordert Verschlüsselungs-Schlüssel an. +Du hast die neue Sitzung „%s“ hinzugefügt, die jetzt Verschlüsselungs-Schlüssel anfordert. +Deine bislang nicht verifizierte Sitzung „%s“ fordert Verschlüsselungs-Schlüssel an. Verifizierung beginnen Anruf Laute Benachrichtigungen @@ -495,7 +495,7 @@Aus Laut Verschlüsselte Nachricht -Lädt… +Lädt … Sicher, dass du einen Sprachanruf starten möchtest\? Sicher, dass du einen Videoanruf starten möchtest\? Die Verbannung einer Person entfernt sie aus diesem Raum und hindert sie am erneuten Beitritt. @@ -572,7 +572,7 @@Bestimmt das Berechtigungslevel des Benutzers Setzt Berechtigungen des Benutzers zurück Lädt Benutzer mit angegebener Kennung in den aktuellen Raum ein -Raum mit angegebener Adresse beitreten +Raum mit angegebener Adresse betreten Verlasse Raum Raumthema ändern Entfernt die Person angegebener ID @@ -609,7 +609,7 @@Schreibbenachrichtigungen senden Lasse andere Benutzer wissen, dass du tippst. Markdown-Formatierung -Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen (*), um kursiven Text anzuzeigen. +Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen, um kursiven Text anzuzeigen. Lesebestätigungen zeigen Klicke auf die Lesebestätigungen für eine detailliertere Liste. Einladungen, Entfernungen und Verbannungen bleiben sichtbar. @@ -626,9 +626,9 @@Klingelton für eingehende Anrufe Wähle Klingelton für Anrufe: Akzeptieren -Bitte lese und akzeptiere die Richtlinien dieses Homeservers: +Bitte lese und akzeptiere die Richtlinien dieses Heim-Servers: Tests ausführen -Läuft… (%1$d von %2$d) +Läuft … (%1$d von %2$d) Einer oder mehrere Tests sind fehlgeschlagen. Versuche vorgeschlagene Lösung(en). Einer oder mehrere Tests sind fehlgeschlagen. Bitte sende einen Fehlerbericht, damit dies untersucht werden kann. Systemeinstellungen. @@ -637,7 +637,7 @@ \nBitte überprüfe die Systemeinstellungen.Öffne Einstellungen Kontoeinstellungen. -Benachrichtigungen sind für dein Konto eingeschaltet. +Benachrichtigungen sind für dein Konto aktiviert. Benachrichtigungen sind für dein Konto deaktiviert. \nBitte überprüfe die Kontoeinstellungen. Aktiviere @@ -646,7 +646,7 @@Benachrichtigungen sind für diese Sitzung nicht aktiviert. \nBitte überprüfe die Einstellungen für ${app_name}. Aktiviere -${app_name} benutzt Google-Play-Dienste um Push-Nachrichten zu übermitteln, doch scheinen sie nicht korrekt konfiguriert zu sein: + ${app_name} benutzt Google-Play-Dienste, um Push-Nachrichten zu übermitteln, allerdings scheint dies nicht korrekt konfiguriert zu sein: \n%1$s Repariere Play-Dienste Firebase-Token @@ -679,20 +679,20 @@Wenn ein Benutzer ein abgestecktes Gerät mit ausgeschaltetem Bildschirm eine Weile nicht bewegt, wechselt es in den Bereitschaftsmodus. Dies hindert Apps daran, auf das Netzwerk zuzugreifen und verzögert die Ausführung von Aufgaben, Synchronisierungen und Standard-Alarmen. Ignoriere Optimierungen Keine validen Google-Play-Dienste gefunden. Benachrichtigungen könnten nicht richtig funktionieren. -Videogespräch aktiv… +Videogespräch aktiv … Schlüsselsicherung Schlüsselsicherung verwenden Überspringen Fertig Erweiterte Benachrichtigungseinstellungen Angepasste Einstellungen. -Beachte, dass einige Nachrichtentypen leise sind (erzeugen eine Benachrichtigung aber keinen Ton). +Beachte, dass einige Nachrichtentypen leise sind (erzeugen eine Benachrichtigung, aber keinen Ton). Einige Benachrichtigungen sind in deinen erweiterten Einstellungen deaktiviert. Konto hinzufügen Laute Benachrichtigungen einstellen Anrufbenachrichtigung einstellen Stumme Benachrichtigungen einstellen -LED-Farbe, Vibration, Ton usw. wählen +Wähle LED-Farbe, Vibration, Ton … Stumm Bitte eine Passphrase eingeben Passphrase ist zu schwach @@ -703,16 +703,16 @@Wiederherstellungsschlüssel speichern Sichere als Datei Bitte mache eine Kopie -Wiederherstellungsschlüssel teilen mit… +Wiederherstellungsschlüssel teilen mit … Wiederherstellungsschlüssel Unerwarteter Fehler Bist du sicher\? Wiederherstellungsschlüssel eingeben -Stelle Backup wieder her: +Stelle Sicherung wieder her: Historie entschlüsseln Von Sicherung wiederherstellen -Sicherung löschen -Lösche Sicherung… +Lösche Sicherung +Lösche Sicherung … Lösche Sicherung Präferenz der Benachrichtigungen nach Ereignis [%1$s] @@ -721,14 +721,14 @@ \nDieser Fehler liegt nicht unter der Kontrolle von ${app_name}. Er kann aus verschiedenen Gründen auftreten. Vielleicht wird es funktionieren, wenn du es später noch einmal probierst. Außerdem kannst Du prüfen, ob die Datennutzung der Google-Play-Dienste unbeschränkt ist und die Geräteuhr richtig eingestellt ist. Der Fehler kann aber auch unter Custom-ROMs auftreten. [%1$s] \nDieser Fehler ist außerhalb von ${app_name} passiert. Es gibt kein Google-Konto auf dem Gerät. Bitte füge ein Google-Konto hinzu. -Verwaltung der Kryptoschlüssel -Schlüssel-Sicherung verwalten +Verwaltung der Verschlüsselungs-Schlüssel +Schlüsselsicherung verwalten Nachrichten in verschlüsselten Räumen sind mit Ende-zu-Ende-Verschlüsselung gesichert. Nur du und der Empfänger haben die Schlüssel um diese Nachrichten zu lesen. \n \nSichere deine Schlüssel, um sie nicht zu verlieren. Wiederherstellungsschlüssel aus Passphrase generieren. Dies kann mehrere Sekunden brauchen. Du verlierst möglicherweise den Zugang zu deinen Nachrichten, wenn du dich abmeldest oder das Gerät verlierst. -Rufe Backup-Version ab… +Rufe Sicherungsversion ab … Nutze deine Wiederherstellungs-Passphrase, um deinen verschlüsselten Nachrichtenverlauf lesen zu können nutze deinen Wiederherstellungsschlüssel Wenn du deine Wiederherstellungspassphrase nicht weist, kannst du %s. @@ -759,14 +759,14 @@Um die Schlüsselsicherung für diese Sitzung zu verwenden, stelle sie jetzt mit deiner Passphrase oder deinem Wiederherstellungsschlüssel wieder her. Deine gesicherten Schlüssel vom Server löschen\? Du wirst deinen Wiederherstellungsschlüssel nicht mehr nutzen können, um deinen verschlüsselten Nachrichtenverlauf zu lesen. Beim Abmelden gehen deine verschlüsselten Nachrichten verloren -Schlüssel-Sicherung wird durchgeführt. Wenn du dich jetzt abmeldest, gehen deine verschlüsselten Nachrichten verloren. +Schlüsselsicherung läuft. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten. Schlüsselsicherung sollte bei allen Sitzungen aktiviert sein, um den Verlust verschlüsselter Nachrichten zu verhindern. Ich möchte meine verschlüsselten Nachrichten nicht -Sichere Schlüssel… -Sicher\? +Sichere Schlüssel … +Bist du sicher\? Sicherung Alle verschlüsselten Nachrichten gehen verloren, wenn Du dich abmeldest ohne die Schlüssel gesichert zu haben. -Wirklich abmelden\? +Bist du sicher, dass du dich abmelden möchtest\? Wiederherstellung verschlüsselter Nachrichten Bitte gib einen Benutzernamen ein. Richte Schlüsselsicherung ein @@ -781,27 +781,27 @@(Erweitert) Wiederherstellungsschlüssel einrichten Erfolg! Deine Schlüssel wurden gesichert. -Dein Wiederherstellungsschlüssel ist ein Sicherungsnetz - du kannst es benutzen um den Zugriff auf deine verschlüsselten Nachrichten wiederherzustellen, falls du deine Passphrase vergisst. + Dein Wiederherstellungsschlüssel ist ein Sicherungsnetz – du kannst es benutzen, um den Zugriff auf deine verschlüsselten Nachrichten wiederherzustellen, falls du deine Passphrase vergisst. \nVerwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) -Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort auf, wie z.B. einem Passwortmanager (oder Tresor) auf +Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) auf Ich habe eine Kopie angefertigt Teilen Verliere nie wieder verschlüsselte Nachrichten Benutze Schlüsselsicherung Neue sichere Schlüssel für Nachrichten Verwalte Schlüsselsicherung -Sichere Schlüssel… +Sichere deine Schlüssel. Dies könnte einige Minuten dauern … Alle Schlüssel sind gesichert - - Sichere %d Schlüssel…
-- Sichere %d Schlüssel…
+- Sichere einen Schlüssel …
+- Sichere %d Schlüssel …
Version Algorithmus Signatur -Berechne Wiederherstellungsschlüssel… -Lade Schlüssel herunter… -Importiere Schlüssel… +Berechne Wiederherstellungsschlüssel … +Lade Schlüssel herunter … +Importiere Schlüssel … Ignorieren Mit Single-Sign-On anmelden Nachricht mit Eingabetaste senden @@ -870,14 +870,14 @@Kein Netzwerk. Bitte überprüfe deine Internetverbindung. Ändern Netzwerk wechseln -Bitte warten… +Bitte warten … Für diesen Raum kann keine Vorschau angezeigt werden Räume Direktnachrichten ERSTELLEN Name Öffentlich -Jeder wird diesem Raum beitreten können +Jeder wird diesen Raum betreten können Integrationsmanager Schlüsselaustausch anfragen Es sieht so aus, als hättest du bereits ein Setup-Schlüssel-Backup von einer anderen Sitzung. Möchtest du es durch das, was du gerade erstellt hast, ersetzen\? @@ -902,9 +902,9 @@Beschreibe hier deine Anmerkung Versteckte Ereignisse in der Zeitleiste anzeigen Direktnachrichten -Warten… -Miniaturbild wird verschlüsselt… -Datei wird verschlüsselt… +Warten … +Vorschaubild wird verschlüsselt … +Verschlüssle Datei … (bearbeitet) Nachrichtenbearbeitung Keine Änderungen gefunden @@ -912,7 +912,7 @@Sende eine neue Direktnachricht Das Raumverzeichnis anzeigen Link in die Zwischenablage kopiert -Raum erstellen… +Erstelle Raum … Bearbeitungsverlauf anzeigen E2E-Schlüssel aus der Datei \"%1$s\" importieren. Vielen Dank, der Vorschlag wurde erfolgreich gesendet @@ -947,7 +947,7 @@Identitäts-Server konfigurieren Identitäts-Server ändern Auffindbare E-Mail-Adressen -Erkennungsoptionen werden angezeigt, sobald du eine E-Mail hinzugefügt hast. +Entdeckungsoptionen werden angezeigt, sobald du eine E-Mail-Adresse hinzugefügt hast. Gib eine Identitäts-Server-Adresse ein Konnte keine Verbindung zum Homeserver herstellen Dies ist keine Adresse eines Matrixservers @@ -997,13 +997,13 @@Bitte erneut versuchen, nachdem du die Nutzungsbedingungen deines Heim-Servers akzeptiert hast. Bei Benutzung könnten Cookies gesetzt werden und es könnten Daten mit %s geteilt werden: Bei Benutzung könnten Daten mit %s geteilt werden: -Optionen zum Finden werden erscheinen, sobald du eine Telefonnummer hinzugefügt hast. -Wir haben dir eine Bestätigungsmail an %s gesendet. Prüfe dein Postfach und klicke auf den Bestätigungslink +Entdeckungsoptionen werden angezeigt, sobald du eine Telefonnummer hinzugefügt hast. +Wir haben eine E-Mail an %s gesendet. Prüfe deine E-Mails und klicke auf den Bestätigungslink Es sieht aus, als würde der Server zu viel Zeit benötigen, um zu antworten. Der Grund kann eine schlechte Verbindung oder ein Fehler mit dem Server sein. Bitte versuche es später erneut. Anhang senden Navigationsmenü öffnen Raumerstellungsmenü öffnen -Schließe das Raumerstellungsmenü… +Schließe das Raumerstellungsmenü … Erstelle eine neue Direktnachricht Erstelle einen neuen Raum Schließe Key-Backup-Einblendung @@ -1022,23 +1022,23 @@Sticker Es ist Spam Es ist unangebracht -Benutzerdefinierte Meldung… +Benutzerdefinierte Meldung … Diesen Inhalt melden -Meldegrund +Grund für Meldung des Inhalts MELDEN NUTZER IGNORIEREN Inhalt gemeldet Dieser Inhalt wurde gemeldet. \n -\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden. +\nWenn du keine weiteren Inhalte dieser Person sehen möchtest, kannst sie ignorieren, um ihre Nachrichten auszublenden.Als Spam gemeldet Dieser Inhalt wurde als Spam gemeldet. \n -\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden. +\nWenn du keine weiteren Inhalte dieser Person sehen möchtest, kannst sie ignorieren, um ihre Nachrichten auszublenden.Als unangebracht gemeldet Dieser Inhalt wurde als unangebracht gemeldet. \n -\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden. +\nWenn du keine weiteren Inhalte dieser Person sehen möchtest, kannst sie ignorieren, um ihre Nachrichten auszublenden.Nutzer ignorieren Alle Nachrichten (laut) Alle Nachrichten @@ -1066,8 +1066,8 @@Eine Trennung von deinem Identitäts-Server würde bedeuten, dass du weder von anderen gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst. Du teilst deine E-Mail-Adressen oder Telefonnummern momentan auf dem Identitäts-Server %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören. Stimme den Nutzungsbedingungen des Identitäts-Servers (%s) zu, um per E-Mail-Adresse oder Telefonnummer auffindbar zu sein zu können. -Zu teilende Daten nicht verarbeitbar -Erweitere und individualisiere dein Benutzererlebnis +Konnte zu teilende Daten nicht verarbeiten +Erweitere und personalisiere deine Erfahrung Mit %1$s verbinden Mit Element Matrix Services verbinden Mit einem anderen Server verbinden @@ -1082,17 +1082,17 @@Die Anwendung kann kein neues Benutzerkonto auf diesem Server erstellen. \n \nMöchtest du dich mit einer Web-Anwendung anmelden\? -Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft. +Diese E-Mail-Adresse ist mit keinem Konto verknüpft. Passwort auf %1$s zurücksetzen Neues Passwort Achtung! Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssel zurücksetzen. Dein verschlüsselter Verlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt. Fortfahren -Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft +Diese E-Mail-Adresse ist mit keinem Konto verknüpft Prüfe deinen Posteingang Eine Bestätigungsmail wurde an %1$s versendet. -Klicke auf den Link um dein neues Passwort zu bestätigen. Sobald du dem enthaltenen Link gefolgt bist, klicke unten. +Tippe auf den Link um dein neues Passwort zu bestätigen. Sobald du dem enthaltenen Link gefolgt bist, klicke unten. Ich habe meine E-Mail-Adresse bestätigt Erfolgreich! Dein Passwort wurde zurückgesetzt. @@ -1133,14 +1133,14 @@Weiter Du wurdest von allen Sitzungen abgemeldet und erhältst keine Push-Benachrichtigungen mehr. Um Benachrichtigungen wieder zu aktivieren, melde dich auf jedem Gerät erneut an. Warnung -Lege eine E-Mail-Adresse fest, um dein Konto wiederherzustellen. Später kannst du optional zulassen, dass Personen dich anhand dieser E-Mail-Adresse entdecken. +Lege eine E-Mail-Adresse fest, um dein Konto wiederherzustellen. Später kannst du optional zulassen, dass Personen dich anhand dieser E-Mail-Adresse entdecken können. Weiter Lege Telefonnummer fest Lege eine Telefonnummer fest, damit Personen dich anhand dieser entdecken können. Bitte verwende das internationale Format. Weiter Weiter -Internationale Telefonnummern müssen mit \'+\' beginnen +Internationale Telefonnummern müssen mit „+“ beginnen Die Telefonnummer scheint ungültig zu sein. Bitte prüfen Registrieren bei %1$s Benutzername @@ -1149,8 +1149,8 @@Bitte löse das Captcha Veralteter Homeserver - - Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunde…
-- Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunden…
+- Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunde …
+- Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunden …
Gesehen von Du bist abgemeldet @@ -1181,7 +1181,7 @@ \nBitte zuerst die Daten löschen und dann erneut anmelden.matrix.to-Link fehlerhaft Die Beschreibung ist zu kurz -Initiale Synchronisierung… +Initiale Synchronisierung … Erweiterte Einstellungen Entwicklermodus Der Entwicklermodus aktiviert versteckte Funktionen und kann die Anwendung weniger stabil machen. Nur für Entwickler! @@ -1192,7 +1192,7 @@Einstellungen Aktuelle Sitzung Andere Sitzungen -Zeigt nur die ersten Ergebnisse, gib mehr Buchstaben ein… +Zeigt nur die ersten Ergebnisse, gib weitere Zeichen ein … Ausfallsicher ${app_name} kann häufiger abstürzen, wenn ein unerwarteter Fehler auftritt Stellt einer Klartextnachricht ¯\\_(ツ)_/¯ voran @@ -1213,22 +1213,22 @@Bild. Audio Datei -Warten… +Warte … %s brach ab -Du hast abgebrochen +Du brachst ab %s hat akzeptiert Du hast akzeptiert Verifizierung gesendet Verifizierung angefragt Verifiziere diese Sitzung -Scanne den Code mit dem Gerät des Gegenüber für eine gegenseitige Überprüfung +Lasse den Code mit dem Gerät deines Gegenüber für eine gegenseitige Verifizierung einlesen Scanne Code des Anderen -Kann nicht scannen -Wenn ihr nicht am selben Ort seid, vergleicht Emoji stattdessen +Kann nicht einlesen +Wenn ihr nicht am selben Ort seid, vergleicht stattdessen Emoji Verifizieren via Emoji-Vergleich %s verifizieren %s verifiziert -Warte auf %s… +Warte auf %s … Nachrichten in diesem Raum sind nicht Ende-zu-Ende-verschlüsselt. Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. \n @@ -1244,7 +1244,7 @@ Hochgeladene Dateien Raum verlassen -Verlasse den Raum… +Verlasse den Raum … Administratoren Moderatoren Benutzerdefiniert @@ -1253,8 +1253,8 @@Administrator in %1$s Moderator in %1$s Springen und als gelesen markieren -${app_name} kann keine Ereignisse vom Typ \'%1$s\' -${app_name} ist beim Verarbeiten des Ereignisinhalts mit der ID \'%1$s\' auf ein Problem gestoßen +${app_name} unterstützt keine Ereignisse vom Typ „%1$s“ +${app_name} ist beim Verarbeiten des Ereignisinhalts mit der ID „%1$s“ auf ein Problem gestoßen Nicht ignorieren Diese Sitzung kann diese Verifizierung nicht mit deinen anderen Sitzungen teilen. \nDie Überprüfung wird lokal gespeichert und in einer zukünftigen Version der App freigegeben. @@ -1262,15 +1262,15 @@Sendet das angegebene Emote in Regenbogenfarben Zeitleiste Nachrichteneditor -Ende-zu-Ende-Verschlüsselung aktivieren… +Aktiviere Ende-zu-Ende-Verschlüsselung … Verschlüsselung aktivieren\? Nach der Aktivierung kann die Verschlüsselung für den Raum nicht deaktiviert werden. Nachrichten können nicht vom Server gesehen werden, nur von den Teilnehmenden des Raums. Möglicherweise funktionieren danach einige Bots und Bridges nicht mehr ordnungsgemäß. Verschlüsselung aktivieren Um sicher zu gehen, verifiziere %s, indem ein einmaliger Code überprüft wird. Um sicher zu sein, tut dies persönlich oder verwendet einen anderen Kommunikationsweg. Vergleiche die einzigartigen Emoji und stell sicher, dass sie in derselben Reihenfolge angezeigt werden. -Vergleiche den Code mit dem Code auf dem Bildschirm deines Gegenübers. -Nachrichten mit diesem Gegenüber sind Ende-zu-Ende-verschlüsselt und können nicht von Dritten gelesen werden. +Vergleiche den Code mit dem Code auf dem Bildschirm deines Gegenüber. +Nachrichten mit dieser Person sind Ende-zu-Ende-verschlüsselt und können nicht von Dritten gelesen werden. Deine neue Sitzung ist jetzt verifiziert. Sie hat Zugriff auf deine verschlüsselten Nachrichten, und andere Benutzer sehen sie als vertrauenswürdig an. Quersignierung Quersignierung ist aktiviert, @@ -1284,7 +1284,7 @@ Aktive Sitzungen Alle Sitzungen anzeigen Sitzungen verwalten -Diese Sitzung abmelden +Von dieser Sitzung abmelden Keine kryptografischen Informationen verfügbar Diese Sitzung ist für sichere Kommunikation vertrauenswürdig, da du sie überprüft hast: Verifiziere diese Sitzung, um sie als vertrauenswürdig zu markieren, und gewähren ihr Zugriff auf verschlüsselte Nachrichten. Wenn du dich nicht bei dieser Sitzung angemeldet hast, ist dein Konto möglicherweise gefährdet: @@ -1316,7 +1316,7 @@Nutze eine Wiederherstellungsmethode Wenn du auf keine existierende Sitzung zugreifen kannst Kann keine Geheimnisse im Speicher finden -Entfernen… +Entferne … Möchtest du diesen Anhang an %1$s senden\? - Sende Bild in Originalgröße
@@ -1374,7 +1374,7 @@Benachrichtigungskonfiguration Nachrichten mit \"@room\" Verschlüsselte Gruppenunterhaltungen -Sendet eine Nachricht als einfachen Text, ohne sie als Markdown zu interpretieren +Sendet eine Nachricht als Klartext, ohne sie als Markdown darzustellen Inkorrekter Benutzername und/oder Passwort. Das eingegebene Passwort beginnt oder endet mit Leerzeichen, bitte kontrolliere es. Nachrichtenschlüssel Wiederherstellungs-Passphrase @@ -1385,13 +1385,13 @@Verschlüsselung aktiviert Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr und verifiziere Benutzer in deren Profil. Die Verschlüsselung in diesem Raum wird nicht unterstützt -Warte auf %s… +Warte auf %s … Fehlerbehebung %s hat den Raum erstellt und konfiguriert. Fast geschafft! Zeigt das andere Gerät ein Häkchen an\? -Fast geschafft! Warte auf Bestätigung… +Fast geschafft! Warte auf Bestätigung … Verschlüsselte Direktnachrichten -Nachricht… +Nachricht … Verifiziere dich und andere, um eure Unterhaltungen zu schützen Gib zum Fortfahren deinen %s ein Datei benutzen @@ -1411,7 +1411,7 @@Schlüsselbackup-Wiederherstellungsschlüssel Bildschirmfotos der Anwendung verhindern Das Aktivieren dieser Einstellung setzt FLAG_SECURE in allen Aktivitäten. Starte die Anwendung neu, damit die Änderung wirksam wird. -Neues Benutzerpasswort festlegen… +Lege ein neues Kontopasswort fest … Nutze die neueste Version von ${app_name} auf deinen anderen Geräten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} für Android oder eine andere Matrix-Anwendung, die Quersignierung unterstützt ${app_name} Web \n${app_name} Desktop @@ -1445,7 +1445,7 @@%1$s: %2$s %3$s Mitglieder hinzufügen EINLADEN -Benutzer werden eingeladen… +Lade Benutzer ein … Personen einladen Einladung gesendet an %1$s Einladungen gesendet an %1$s und %2$s @@ -1494,16 +1494,16 @@Benutzer bannen Grund für den Bann Bann des Benutzers aufheben -Das Aufheben des Bannes wird dem Benutzer erlauben dem Raum wieder beizutreten. -Sicheres Backup -Backup einrichten -Backup zurücksetzen +Wenn du die Person entbannst, kann sie den Raum wieder betreten. +Verschlüsselte Sicherung +Sicherung einrichten +Sicherung zurücksetzen Auf diesem Gerät einrichten -Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden. +Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst. Generiere einen neuen Sicherheitsschlüssel oder setze eine neue Sicherheitspassphrase für dein existierendes Backup. Dieses wird deinen aktuellen Schlüssel oder deine aktuelle Phrase ersetzen. Integrationen sind deaktiviert -Aktiviere \'Erlaube Integrationen\' in den Einstellungen um dies zu machen. +Aktiviere hierfür „Integrationen erlauben“ in den Einstellungen. - %d gebannter Benutzer
- %d gebannte Benutzer
@@ -1512,14 +1512,14 @@ANSICHT Aktive Widgets Der Sicherheitsschlüssel ist gespeichert worden. -Backup +Verschlüsselte Sicherung Absicherung gegen den Verlust verschlüsselter Nachrichten -Richte Backup ein +Sicherung einrichten Nachricht entfernt Gelöschte Nachrichten zeigen Zeigt einen Platzhalter für gelöschte Nachrichten an Dedizierten Tab für ungelesene Nachrichten zur Hauptansicht hinzufügen -Wir haben dir eine Bestätigungsmail an %s gesendet. Bitte prüfe deine E-Mails und klicke auf den Bestätigungslink +Wir haben eine E-Mail an %s gesendet. Bitte prüfe deine E-Mails und klicke auf den Bestätigungslink Der Verifizierungscode ist nicht korrekt. MEDIEN Es gibt in diesem Raum keine Medien @@ -1534,10 +1534,10 @@Gib die Adresse des Servers ein, den du benutzen möchtest Einloggen mit Matrix-ID Einloggen mit Matrix-ID -Wenn du einen Account auf einem Homeserver eingerichtet hast, benutze deine Matrix-ID (z.B. @benutzer:domain.com) und Passwort. +Falls du ein Konto auf einem Heim-Server eingerichtet hast, verwende nachstehend deine Matrix-ID (z. B. @benutzer:domain.com) und dein Passwort. Matrix-ID Wenn du dein Passwort nicht weißt, gehe zurück um es zurücksetzen zu lassen. -Dies ist keine gültige Benutzerkennung. Erwartetes Format: \'@benutzer:homeserver.org\' +Dies ist keine gültige Benutzerkennung. Erwartetes Format: „@benutzer:homeserver.org“ Es konnte kein gültiger Homeserver gefunden werden. Bitte prüfe deine Kennung Sticker Administrative Aktionen @@ -1547,20 +1547,20 @@Gib eine Sicherheitsphrase ein, die nur du kennst. Diese wird benutzt um deine Daten auf dem Server geheim zu halten. Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten und Daten verlieren. \n -\nDu kannst auch ein Backup einrichten und deine Schlüssel in den Einstellungen verwalten. +\nDu kannst auch eine Sicherung einrichten und deine Schlüssel in den Einstellungen verwalten.Du hast den Raum erstellt und konfiguriert. Dieser Account ist deaktiviert worden. Konnte Mediendatei nicht speichern Aktuelle Sprache Andere verfügbare Sprachen -Lade verfügbare Sprachen… +Lade verfügbare Sprachen … Öffne AGBs von %s Verbindung zu Identitäts-Server %s trennen\? Dieser Identitäts-Server ist veraltet. ${app_name} unterstützt nur API V2. Diese Operation ist nicht möglich. Der Homeserver ist veraltet. Bitte konfiguriere zuerst einen Identitäts-Server. Bitte akzeptiere zuerst die AGB des Identitäts-Servers in den Einstellungen. -Deiner Privatsphäre wegen unterstützt ${app_name} nur das Senden gehashter E-Mail-Adressen und Telefonnummern. +Deiner Privatsphäre wegen unterstützt ${app_name} nur das Senden von E-Mail-Adressen und Telefonnummern als Streuwert (Hash). Die Assoziierung ist fehlgeschlagen. Für diese Kennung gibt es aktuell keine Zuordnung. Dein Heim-Server (%1$s) schlägt %2$s als Identitäts-Server vor @@ -1575,14 +1575,14 @@Aktiviere Mikrophon Stoppe Kamera Starte Kamera -Backup -Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden. +Verschlüsselte Sicherung +Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst. Sicherheitsschlüssel benutzen -Generiere einen Sicherheitsschlüssel, welcher z.B. in einem Passwortmanager oder in einem Tresor sicher aufbewahrt werden sollte. +Generiere einen Sicherheitsschlüssel, den du in einem Passwort-Manager oder Tresor sicher aufbewahren solltest. Eine Sicherheitsphrase benutzen Gib eine geheime Phrase ein, die nur du kennst und generiere einen Schlüssel als Backup. Speichere deinen Sicherheitsschlüssel -Bewahre deinen Sicherheitsschlüssel irgendwo sicher auf, wie z.B. in einem Passwortmanager oder in einem Tresor. +Bewahre deinen Sicherheitsschlüssel in einem Passwort-Manager oder Tresor sicher auf. Sicherheitsphrase setzen Gib eine Sicherheitsphrase ein, welche nur du kennst und deine Daten auf dem Server geheim halten soll. Sicherheitsphrase @@ -1592,7 +1592,7 @@Du hast die Raumeinstellungen erfolgreich geändert Du kannst auf diese Nachricht nicht zugreifen Warte auf diese Nachricht. Das könnte eine Weile dauern -Wegen der Ende-zu-Ende-Verschlüsselung könnte es sein, dass du auf jemandes Nachricht warten musst, weil die Schlüssel nicht ordnungsgemäß gesendet worden sind. +Wegen der Ende-zu-Ende-Verschlüsselung könnte es sein, dass du auf jemandes Nachricht warten musst, weil die Schlüssel nicht ordnungsgemäß gesendet wurden. Du kannst auf diese Nachricht nicht zugreifen, weil der Sender dich blockiert hat Du kannst auf diese Nachricht nicht zugreifen, weil der Sender deiner Sitzung nicht vertraut Du kannst auf diese Nachricht nicht zugreifen, weil der Sender absichtlich die Schlüssel nicht gesendet hat @@ -1602,7 +1602,7 @@VERSTANDEN MEHR ERFAHREN Speichere Wiederherstellungsschlüssel in -Ermittle deine Kontakte… +Ermittle deine Kontakte … Deine Kontaktliste ist leer Kontaktliste Einladung zurücknehmen @@ -1620,13 +1620,13 @@Neue PIN Um deine PIN zurückzusetzen, musst du dich erneut anmelden und eine neue erstellen. Aktiviere PIN -Wenn du deine PIN zurücksetzen möchtest, tippe \"PIN vergessen\" um dich abzumelden und sie anschließend zurückzusetzen. +Wenn du deine PIN zurücksetzen möchtest, tippe auf „PIN vergessen“, um dich abzumelden und sie zurückzusetzen. Versehentliche Anrufe verhindern Bitte um Bestätigung, bevor du einen Anruf tätigst Einrichten Dir fehlt die Berechtigung in diesem Raum eine Konferenz zu starten -Starte eine Videokonferenz -Starte eine Audiokonferenz +Beginne eine Videokonferenz +Beginne eine Audiokonferenz Konferenzen nutzen die Jitsi-Sicherheits- und Berechtigungsrichtlinien. Alle im Raum Anwesenden können während der Konferenz beitreten. Du kannst dich nicht selbst anrufen Du kannst dich nicht selbst anrufen, warte bis Teilnehmer die Einladung annehmen @@ -1650,21 +1650,21 @@- Falscher Code, %d verbleibende Versuche
Warnung! Letzter Versuch bevor du ausgeloggt wirst! -Zu viele Fehler. Du wurdest ausgeloggt +Zu viele Fehler, du wurdest abgemeldet Diese Telefonnummer ist bereits registriert. Deinem Konto wurde keine Telefonnummer hinzugefügt E-Mail-Adressen -Deinem Konto wurde keine E-Mail hinzugefügt +Deinem Konto wurde keine E-Mail-Adresse hinzugefügt Telefonnummern %s entfernen\? Stelle sicher, dass du auf den Link in der E-Mail geklickt hast, die wir dir gesendet haben. E-Mail und Telefon -Verwalte E-Mails und Telefonnummern, die mit deinem Matrix-Konto verknüpft sind +Verwalte E-Mail-Adressen und Telefonnummern, die mit deinem Matrix-Konto verknüpft sind Code -Verwende das internationale Format (Telefonnummer muss mit \'+\' beginnen) +Bitte nutze das internationale Format (muss mit „+“ beginnen) Bestätige deine Identität, indem du dieses Login verifizierst, um Zugriff auf verschlüsselte Nachrichten zu erhalten. -Raum, indem du gebannt wurdest, kann nicht geöffnet werden. -Raum kann nicht gefunden werden. Stelle sicher, dass er existiert. +Ein Raum, aus dem du verbannt wurdest, kann nicht geöffnet werden. +Kann diesen Raum nicht finden. Stelle sicher, dass er existiert. - %d Sekunde
- %d Sekunden
@@ -1672,7 +1672,7 @@Umfrage Reagierte mit: %s Der Link war fehlerhaft -Du bist nicht berechtigt, einen Anruf in diesem Raum zu starten +Du bist nicht berechtigt, einen Anruf in diesem Raum zu beginnen Ergebnis der Überprüfung Kontodaten vom Typ %1$s löschen\? \n @@ -1683,12 +1683,12 @@ Die Applikation wartet auf den PUSH Push testen Gebannte Nutzer filtern -Du bist nicht berechtigt einen Anruf zu starten +Du bist nicht berechtigt einen Anruf zu beginnen Du hast keine Berechtigung ein Konferenzgespräch zu starten Details wie Raumnamen und Nachrichteninhalt zeigen. Inhalt in Benachrichtigungen anzeigen PIN-Code ist die einzige Möglichkeit ${app_name} zu entsperren. -Aktiviere Gerät-spezifische Biometrie wie Fingerabdrücke und Gesichtserkennung. +Aktiviere gerätespezifische Biometrie wie Fingerabdrücke und Gesichtserkennung. Biometrie aktivieren Schutz konfigurieren Zugriffsschutz @@ -1726,10 +1726,10 @@Du siehst die Benachrichtigung! Klick mich! Benachrichtigungsanzeige Bei jedem Öffnen von ${app_name} ist der PIN-Code erforderlich. -PIN-Code ist erforderlich, nachdem ${app_name} 2 Minuten lang nicht verwendet wurde. -Fordere PIN nach 2 Minuten an +PIN-Code ist erforderlich, nachdem ${app_name} zwei Minuten lang nicht verwendet wurde. +Erfrage PIN nach zwei Minuten Nur die Anzahl ungelesener Nachrichten in der Benachrichtigung zeigen. -Bild hinzufügen mit +Füge Bild hinzu per Der Raum ist noch nicht erstellt. Raumerstellung abbrechen\? Zu niedrige Priorität hinzufügen Thema @@ -1742,7 +1742,7 @@Raumname Prüfung exportieren Direktnachricht -Verlauf der Anfragen von Schlüsselfreigaben senden +Schlüsselfreigabe-Anfragen übermitteln Keine weiteren Ergebnisse Beginne eine Unterhaltung Autorisieren @@ -1752,7 +1752,7 @@Vorschläge Bekannte Personen QR-Code -Hinzufügen via QR-Code +Per QR-Code hinzufügen Gib die Erlaubnis, um auf die Kamera zu zugreifen. Um den QR-Code zu scannen, muss der Zugriff auf die Kamera erlaubt werden. Öffentliche Adressen @@ -1762,7 +1762,7 @@Änderungen daran, wer die Chronik lesen kann, gelten nur für kommende Nachrichten in diesem Raum. Die Sichtbarkeit der bestehenden Chronik bleibt unverändert. Zurückziehen Hinzufügen -Mit Nachricht teilen +Per Nachricht teilen Erweiterte Optionen ausblenden Erweiterte Optionen anzeigen Die Sichtbarkeit des Raums konnte nicht abgerufen werden (%1$s). @@ -1777,8 +1777,8 @@Teile diesen Code, damit andere ihn einlesen und mit dir schreiben können. Meinen Code teilen Mein Code -Scanne einen QR-Code -Das ist kein korrekter QR-Code von Matrix +QR-Code einlesen +Das ist kein korrekter Matrix-QR-Code 🔐️ Komm mit zu ${app_name} Hey, schreibe mit mir auf ${app_name}: %s Freunde einladen @@ -1790,14 +1790,14 @@Das ist der Anfang dieser Konversation. Das ist der Anfang von %s. Du hast nicht die nötigen Berechtigungen, um die Verschlüsselung in diesem Raum zu aktivieren. -Erstelle Raum… +Erstelle Raum … Manche Zeichen sind nicht zulässig Bitte gib eine Raumadresse an Diese Adresse ist bereits vergeben Aktivieren, wenn der Raum nur von Mitgliedern deines Heim-Servers zur internen Kommunikation verwendet wird. Das kann später nicht mehr geändert werden. Begrenze Zugang zu diesem Raum (für immer!) auf Mitglieder von %s %1$d von %2$d -Keine Vorschau für diesen Raum verfügbar. Willst du direkt beitreten\? +Keine Vorschau für diesen Raum verfügbar. Willst du ihn betreten\? Der Raum ist gerade nicht zugänglich. \nVersuche es später nochmal, oder bitte einen Raum-Admin um Hilfe. Eine neue Adresse veröffentlichen @@ -1810,7 +1810,7 @@Dieser Raum hat keine lokalen Adressen Füge Adressen für diesen Raum hinzu, damit andere Nutzer ihn auf %1$s finden können Lokale Adresse -Neue öffentliche Adresse (z.B. #alias:server) +Neue öffentliche Adresse (z. B. #alias:server) Noch keine weiteren öffentlichen Adressen vorhanden. Noch keine weiteren öffentlichen Adressen vorhanden, füge unten eine hinzu. Die Adresse \"%1$s\" löschen\? @@ -1820,9 +1820,9 @@Raumname ändern Sichtbarkeit des Verlaufs ändern Raum-Verschlüsselung aktivieren -Haupt-Adresse des Raums ändern +Hauptadresse des Raums ändern Raumbild ändern -Widgets verändern +Widgets ändern Jeden benachrichtigen Von anderen gesendete Nachrichten entfernen Nutzer verbannen @@ -1830,7 +1830,7 @@Einstellungen ändern Nutzer einladen Nachrichten senden -Standard Rolle +Standard-Rolle Berechtigungen Berechtigungen Du hast nicht die Berechtigung zum Aktualisieren der Rollen, die zum Ändern verschiedener Teile des Raums erforderlich sind @@ -1918,17 +1918,17 @@Die Obergrenze ist nicht bekannt. Dein Heim-Server akzeptiert Anhänge (wie Dateien, Medien, etc.) mit einer Größe bis zu %s. -Datei-Upload-Obergrenze des Servers +Dateigrößenlimit des Servers Serverversion -Servername +Server-Name Raumeinstellungen Derzeitige Konferenz verlassen und zu einer anderen wechseln\? Raum-Version Neuer Wert Erste Synchronisation: -\nLade Daten herunter… +\nLade Daten herunter …Erste Synchronisation: -\nWarte auf Serverantwort… +\nWarte auf Antwort vom Server …Gesendet Raumverzeichnis Wechseln @@ -1947,9 +1947,9 @@Öffentlich Du kannst dies später ändern Ungeprüft -Jeder kann den Raum finden und beitreten +Jeder kann den Raum finden und betreten Öffentlich -Nur Eingeladene können es finden und beitreten +Nur sichtbar und zu betreten für Eingeladene Privat Unbekannte Zugriffseinstellung (%s) Gästen erlauben beizutreten @@ -1957,8 +1957,8 @@Spaces Jeder kann im Raum anklopfen, Mitglieder können dann zustimmen oder ablehnen Momentan bist nur du hier. Mit anderen Leuten wird %s noch viel besser. -Diese werden in der Lage sein, %s zu durchsuchen -Diese werden kein Teil von %s sein +Sie wird in der Lage sein, %s zu durchsuchen +Sie wird kein Teil von %s sein Tritt meinem Space %1$s %2$s bei Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Räume oder Spaces hinzufügen @@ -1975,11 +1975,11 @@Verlassen Räume hinzufügen Räume erkunden -Trotzdem beitreten -Space beitreten +Dennoch betreten +Space betreten Space erstellen Nur zu diesem Raum -In Space \"%s\" einladen +Zu %s einladen Link teilen Mithilfe einer E-Mail-Adresse einladen Personen einladen @@ -1988,13 +1988,13 @@Offen für Jeden. Am Besten für Communities Ein privater Space für meine Teamkameraden und mich Meine Teamkameraden und ich -Ein privater Space um deine Räume zu organisieren +Ein privater Space zum Organisieren deiner Räume Um einem bereits existierenden Space beizutreten, benötigst du eine Einladung. Dein privater Space Dein öffentlicher Space Betrete einen Space mit der angegebenen ID Beschreibung -Erzeuge Space … +Erstelle Space … Ohne Thema Allgemein Einen Space erstellen @@ -2002,7 +2002,7 @@Welche Art von Space möchtest du erstellen\? Space erstellen Space erstellen -Jeder, der sich in einem Space mit diesem Raum befindet, kann diesen Raum finden und ihm beitreten. Nur die Admins des Raums können diesen zu einem Space hinzufügen. +Jeder, der sich in einem Space mit diesem Raum befindet, kann diesen Raum finden und ihn betreten. Nur die Administration des Raums kann diesen zu einem Space hinzufügen. Nur Space-Mitglieder - %d Person, die du kennst, ist bereits beigetreten
@@ -2028,10 +2028,10 @@Die Datei ist zu groß. Video wird komprimiert (%d%%) -Bild wird komprimiert… +Komprimiere Bild … Als Standard festsetzen und nicht mehr fragen Jedes Mal fragen -Gib den Namen eines neuen Servers ein, den du erkunden möchtest. +Gib den Namen des neuen Servers ein, den du erkunden möchtest. Neuen Server hinzufügen Dein Server Du verwendest die Betaversion von Spaces. Mit Feedback hilfst du uns, die nächsten Versionen zu verbessern. Dabei wird uns deine Platform übermittelt, damit wir deine Rückmeldung optimal nutzen können. @@ -2044,7 +2044,7 @@Dieser Space hat noch keine Räume Für weitere Infos kontaktiere bitte die Administration des Homeservers Dein Homeserver scheint Spaces noch nicht zu unterstützen -Du bist der einzige Admin von diesem Space. Wenn du ihn verlässt, hat niemand Kontrolle über ihn. +Du bist der einzige Admin dieses Spaces. Wenn du ihn verlässt, hat niemand Kontrolle über ihn. Du wirst diesen Raum ohne erneute Einladung nicht betreten können. Du bist die einzige Person hier. Wenn du den Space verlässt, ist er für immer verloren (eine lange Zeit). Einladen in %s @@ -2067,7 +2067,7 @@Beim Versuch %s beizutreten, ist leider ein Fehler aufgetreten Zur empfohlenen Raumversion upgraden Ersatzraum betreten -Raum zu neuer Version upgraden +Aktualisiert den Raum auf eine neue Version stabil instabil Raumversionen 👓 @@ -2090,7 +2090,7 @@Du benötigst die Berechtigung, um einen Raum upzugraden Übergeordneten Space automatisch updaten Benutzer automatisch einladen -Du upgradest diesen Raum von %1$s zu %2$s. +Du aktualisierst diesen Raum von %1$s zu %2$s. Das Raumupgrade ist eine erweiterte Option und ist empfohlen wenn sich der Raum instabil verhält, von Sicherheitslücken betroffen ist oder Features fehlen. \nNormalerweise ändert sich dadurch nur wie der Raum am Server verarbeitet wird. Privaten Raum upgraden @@ -2108,7 +2108,7 @@Spaces wählen Mitglieder von %s können Räume finden, betrachten und betreten. Privat (Zutritt nur mit Einladung) -Raumupgrades +Raumaktualisierungen Nachrichten von Bots Raumeinladungen Verschlüsselte Gruppennachrichten @@ -2129,7 +2129,7 @@- Verpasster Sprachanruf
- %d verpasste Sprachanrufe
-Heim-Server API URL +Heim-Server-API-Adresse Um Sprachnachrichten zu senden, erlaube bitte Zugriff aufs Mikrofon. Um fortzufahren, erlaube bitte in den Systemeinstellungen Zugriff auf die Kamera. Für diese Aktion fehlen einige Berechtigungen, bitte erlaube diese in den Systemeinstellungen. @@ -2140,7 +2140,7 @@Andere Spaces oder Räume die du kennst Spaces mit diesem Raum und dir als Mitglied Nur Erwähnungen und Schlüsselwörter -Auflegen… +Auflegen … Sprachanruf mit %s Videoanruf mit %s Alle beigetretenen Räume werden auf der Startseite angezeigt. @@ -2157,13 +2157,13 @@Sprachanruf beendet • %1$s Benachrichtige mich bei \@room -Schlüsselwörter dürfen kein \"%s\" enthalten +Schlüsselwörter dürfen kein „%s“ enthalten Schlüsselwörter können nicht mit einem Punkt beginnen Nichts Nicht erreicht Die angerufene Person ist beschäftigt. Person beschäftigt -Klingeln… +Klingeln … Spaces Verpasster Videoanruf Verpasster Sprachanruf @@ -2184,17 +2184,17 @@Willst du %s wirklich verlassen\? Mit Benutzername oder E-Mail einladen Zum ausgewählten Space hinzufügen -Erstelle Space… +Erstelle Space … Hilfreiche Informationen zur Fehlersuche anzeigen Debug-Info anzeigen -Das schaut nicht nach einer gültigen E-Mail-Adresse aus +Das scheint keine gültige E-Mail-Adresse zu sein Mittels Name, ID oder E-Mail-Adresse suchen Neuen Space erstellen Zugriff Wer hat Zugriff\? -Benachrichtigungen per Email für %s aktivieren -Um Benachrichtigungen per E-Mail zu empfangen, musst du einen E-Mail-Adresse hinzufügen -Emailbenachrichtigungen +Benachrichtigungen per E-Mail für %s aktivieren +Um Benachrichtigungen per E-Mail zu empfangen, musst du eine E-Mail-Adresse hinzufügen +E-Mail-Benachrichtigungen Space upgraden Namen vom Space ändern Space verschlüsseln @@ -2202,7 +2202,7 @@Space-Icon ändern Du hast nicht die Berechtigung, Rollenrechte zu bearbeiten Space-Berechtigungen -Wenn du die Person entbannst, kann sie wieder beitreten. +Wenn du die Person entbannst, kann sie den Space wieder betreten. Die Verbannung einer Person entfernt sie aus diesem Space und hindert sie am erneuten Beitritt. Kicken entfernt die Person aus dem Space \n @@ -2222,9 +2222,9 @@ Ändert den Raumnamen Entblockt eine Person und zeigt deren Nachrichten wieder an Blockiert eine Person und versteckt deren Nachrichten -Jeder kann den Space finden und beitreten +Jeder kann den Space finden und betreten Du kannst deine Benachrichtigungen in den %1$s verwalten. -Beachte, dass Benachrichtigungen zu Erwähnungen und Schlüsselwörtern in verschlüsselten Räumen momentan nicht verfügbar sind. +Bitte beachte, dass Benachrichtigungen zu Erwähnungen und Schlüsselwörtern in verschlüsselten Räumen mobil nicht verfügbar sind. Wähle die Berechtigungen der Rollen aus Rollen deren Berechtigungen einsehen und bearbeiten. @@ -2232,14 +2232,14 @@ - %1$s weitere Optionen benötigt
Frage darf nicht leer sein -ABSTIMMUNG ERSTELLEN +Umfrage erstellen NEUE OPTION Option %1$d Optionen hinzufügen Frage oder Thema Abstimmungsthema oder Frage -Abstimmung erstellen -Abstimmung +Umfrage erstellen +Umfrage Auffindungseinstellungen öffnen Sitzung abgemeldet! Raum verlassen! @@ -2247,13 +2247,13 @@Es konnte kein Heim-Server mit der Adresse %s gefunden werden. Bitte überprüfe die Adresse oder wähle den Heim-Server manuell. Untergeordneten Space hinzufügen. Bist du dir wirklich sicher, dass du diese Informationen senden willst\? -E-Mail-Adressen und Telefonnummern an %s senden +E-Mail-Adressen und Telefonnummern an %s übermitteln Nicht jetzt Auf Benachrichtigungen warten Externe Bibliotheken Du kannst dies jederzeit in den Einstellungen deaktivieren -Wir teilen keine Informationen mit Drittpersonen -Wir erfassen und analysieren keine Accountdaten +Wir teilen keine Informationen mit Dritten +Wir erfassen und analysieren keine Kontodaten Hilf uns dabei Probleme zu identifizieren und ${app_name} zu verbessern, indem du anonyme Nutzungsdaten teilst. Um zu verstehen, wie Personen mehrere Geräte benutzen, werden wir eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird. \n \n%s kannst du alle unsere Bedingungen lesen. @@ -2269,7 +2269,7 @@Hilfe Rechtliches Entscheide, welche Spaces Zugriff auf den Raum haben sollen. Die Mitglieder der Spaces können diesen Räumen betreten. -hier +Hier Hilf mit, ${app_name} zu verbessern Aktivieren Farbe des Anzeigenamens ändern @@ -2294,33 +2294,33 @@Das System sendet automatisch Protokolle, wenn ein Fehler bei der Entschlüsselung auftritt Entschlüsselungsfehler automatisch melden. Auffindbarkeit (%s) -Per E-Mail einladen, finde deine Kontakte und mehr… +Lade per E-Mail ein, finde deine Kontakte und mehr … Schließe die Konfiguration des Auffindbarkeitsdienstes ab. Du verwendest derzeit keinen Identitäts-Server. Um Team-Mitglieder einzuladen und für sie auffindbar zu sein, konfiguriere zunächst einen. Ich habe bereits ein Konto Sichere Kommunikation. Besitze deine Unterhaltungen. -Um bestehende Kontakte ermitteln zu können, musst du Kontaktinformationen (E-Mail-Adressen und Telefonnummern) an deinen Identitäts-Server übermitteln. Wir verschlüsseln deine Daten vor der Übermittlung, um den Datenschutz gewährleisten zu können. +Um bestehende Kontakte ermitteln zu können, musst du Kontaktinformationen (E-Mail-Adressen und Telefonnummern) an deinen Identitäts-Server übermitteln. Deine Daten werden als Streuwert (Hash) übermittelt, um den Datenschutz zu gewährleisten. Deine Kontakte sind privat. Um unter deinen Kontakten Matrix-Nutzer finden zu können, benötigen wir deine Erlaubnis, Kontaktinformationen an deinen Identitäts-Server zu übermitteln. Dieser Server stellt keine Richtlinie bereit. Richtlinie deines Identitäts-Servers Richtlinie deines Heim-Servers -${app_name} Richtlinie -Abstimmung erstellen +Richtlinie von ${app_name} +Umfrage erstellen Kontakte öffnen Sticker verschicken Datei hochladen Verschicke Fotos und Videos Kamera öffnen Willst du diese Umfrage wirklich entfernen\? Du wirst sie nicht wiederherstellen können. -Abstimmung entfernen -Abstimmung beendet +Umfrage entfernen +Umfrage beendet Stimme abgegeben -Abstimmung beenden +Umfrage beenden Dies verhindert, dass andere Personen abstimmen können, und zeigt die Endergebnisse der Umfrage an. -Diese Abstimmung beenden\? +Diese Umfrage beenden\? Gewinneroption -Abstimmung beenden +Umfrage beenden - Endgültiges Ergebnis basiert auf %1$d Stimme
- Endgültiges Ergebnis basiert auf %1$d Stimmen
@@ -2332,9 +2332,9 @@${app_name} konnte nicht auf deinen Standort zugreifen. Bitte versuche es später noch einmal. ${app_name} konnte nicht auf deinen Standort zugreifen Standort -Ergebnisse werden erst angezeigt, wenn du die Umfrage beendest -Geschlossene Umfrage -Ergebnisse werden direkt nach Stimmabgabe angezeigt +Die Ergebnisse werden erst sichtbar, sobald du die Umfrage beendest +Versteckte Umfrage +Abstimmende können die Ergebnisse nach Stimmabgabe sehen Offene Umfrage Umfragetyp Umfrage bearbeiten @@ -2362,7 +2362,7 @@Möchtest du einem existierenden Server beitreten\? Communities Teams -Wir helfen dir, in Verbindung zu kommen +Wir helfen dir, dich zu vernetzen Mit wem wirst du am meisten schreiben\? Link zu Thread kopieren Threads anzeigen @@ -2399,13 +2399,13 @@- %d Server-ACLs geändert
Beenden -Live-Standort aktiviert +Echtzeit-Standort aktiviert Standort teilen Diesen Standort teilen Meinen Standort teilen Meinen Standort teilen -Live-Standort teilen -Live-Standort teilen +Echtzeit-Standort freigeben +Echtzeit-Standort teilen Threads nähern sich der Beta 🎉 Deaktivieren BETA @@ -2415,14 +2415,14 @@Threads Beta Bildschirm teilen Probiere es aus -Live bis %1$s -Wähle Deine Benachrichtigungsmethode -Vorläufige Implementierung: Standorte bleiben im Nachrichtenverlauf von Räumen erhalten +Echtzeit bis %1$s +Wähle deine Benachrichtigungsmethode +Vorläufige Implementierung: Standorte verbleiben im Raumverlauf Profil-Tag: h -Standortfreigabe aktivieren +Aktiviere Standortfreigabe Bitte beachte: Dies ist eine experimentelle Funktion, die eine temporäre Implementierung nutzt. Das bedeutet, dass du deinen Standortverlauf nicht löschen kannst und erfahrene Nutzer ihn sehen können, selbst wenn du deinen Live-Standort nicht mehr mit diesem Raum teilst. -Live-Standortfreigabe +Echtzeit-Standortfreigabe Aktuelles Gateway: %s Gateway Kann den Endpunkt nicht finden. @@ -2443,17 +2443,17 @@Bildschirmfreigabe ist in Arbeit ${app_name} Bildschirmfreigabe Aktualisiert vor %1$s -Aktiviere Live-Standortfreigabe +Aktiviere Echtzeit-Standortfreigabe Standortfreigabe ist in Arbeit -${app_name} Live-Standort +${app_name} Echtzeit-Standort %1$s übrig -Live-Standort anzeigen -Live-Standort beendet -Live-Standort laden… +Echtzeit-Standort anzeigen +Echtzeit-Standort beendet +Lade Echtzeit-Standort … 8 Stunden 1 Stunde 15 Minuten -Teile Deinen Live-Standort für +Gebe deinen Echtzeit-Standort frei für Auf aktuellen Standort zoomen Pin des ausgewählten Ortes auf der Karte (%1$s) @@ -2507,7 +2507,7 @@Die biometrische Authentifizierung konnte nicht aktiviert werden. Die biometrische Authentifizierung wurde deaktiviert, weil kürzlich eine neue biometrische Authentifizierungsmethode hinzugefügt wurde. Du kannst sie in den Einstellungen wieder aktivieren. Der Heim-Server akzeptiert keine Benutzernamen, die nur aus Ziffern bestehen. -teilten ihren Live-Standort +Teilte den eigenen Echtzeit-Standort Schritt überspringen Speichern und fortfahren Öffne die Einstellungen jederzeit um dein Profil zu aktualisieren @@ -2523,11 +2523,11 @@Threads sind noch in Arbeit, und es stehen neue, aufregende Funktionen an, wie z. B. verbesserte Benachrichtigungen. Wir würden uns sehr über Dein Feedback freuen! Nachrichten in dieser Unterhaltung werden Ende-zu-Ende-verschlüsselt. Bist du ein Mensch\? -Bitte lies dir %ss Bedingungen und Richtlinien durch +Bitte lies dir die Bedingungen und Richtlinien von %s durch Server-Richtlinien Folge den Anweisungen, die an %s gesendet wurden E-Mail bestätigen -Ergebnisse werden nach Abschluss der Abstimmung sichtbar sein +Ergebnisse werden nach Abschluss der Umfrage sichtbar sein Prüfe deine E-Mails. Passwort zurücksetzen Gib mindestens 8 Zeichen ein. @@ -2545,7 +2545,7 @@Kann Link nicht öffnen: Communities wurden durch Spaces ersetzt MSC3061: Raumschlüssel für vorherige Nachrichten teilen Beim Einladen in einen Raum mit sichtbarem Verlauf wird der verschlüsselte Verlauf sichtbar sein. -Live-Standort +Echtzeit-Standort - %d Nachricht gelöscht
- %d Nachrichten gelöscht
@@ -2553,10 +2553,10 @@Keine Element-Call-Berechtigungsabfragen Bestätige automatisch Element-Call-Widgets und erlaube Kamera- und Mikrofonzugriff Los -ändern -oder -Der Ort, an dem deine Gespräche stattfinden -Das zukünftige Zuhause für deine Gespräche +Bearbeiten +Oder +Der zukünftige Ort deiner Gespräche +Der zukünftige Ort deiner Gespräche Systemstandard nutzen Automatisch festlegen Schriftgröße wählen @@ -2568,7 +2568,7 @@Nutzername / E-Mail-Adresse / Telefonnummer Erstelle dein Konto Server-URL -Wie lautet die Adresse deines Servers\? Das wird eine Art Zuhause für deine Daten +Wie lautet die Adresse deines Servers\? Dies ist eine Art Zuhause für all deine Daten Wie lautet die Adresse deines Servers\? Muss 8 oder mehr Zeichen umfassen Wähle deinen Server @@ -2586,7 +2586,6 @@Ungelesene Personen Schreibe deine erste Nachricht, um %s zur Unterhaltung 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 @@ -2601,9 +2600,9 @@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 +Standort freigeben +Du benötigst die entsprechenden Berechtigungen, um deinen Echtzeit-Standort in diesem Raum freizugeben. +Dir fehlt die Berechtigung, deinen Echtzeit-Standort freigeben zu dürfen Passwort zurückgesetzt Code erneut schicken Ein Code wurde an %s gesendet @@ -2666,7 +2665,7 @@Sitzungsdetails Filter zurücksetzen Keine inaktiven Sitzungen gefunden. -Keine nicht verifizierten Sitzungen gefunden. +Keine unverifizierten Sitzungen gefunden. Keine verifizierten Sitzungen gefunden. - Erwäge, dich aus alten (ein Tag oder mehr), nicht mehr verwendeten Sitzungen abzumelden.
@@ -2707,6 +2706,163 @@Verifiziere deine aktuelle Sitzung für besonders sichere Kommunikation. Deine aktuelle Sitzung ist für sichere Kommunikation bereit. Details anzeigen -Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzungen oder melde dich von ihr ab. -Für die bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder nicht mehr benutzt. +Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzung oder melde sie ab. +Für bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder benutzt. +Andere Nutzer in Direktnachrichten und Räumen, in denen du dich befindest, können eine vollständige Liste deiner Sitzungen einsehen. +\n +\nDies gibt ihnen die Sicherheit, dass sie auch wirklich mit dir kommunizieren. Allerdings bedeutet es auch, dass sie die Sitzungsnamen sehen können, die du hier angibst. +Verifizierte Sitzungen wurden mit deinen Daten angemeldet und anschließend mit deiner Sicherheitspassphrase oder durch Quersignierung verifiziert. +\n +\nDies bedeutet, dass sie die Verschlüsselungs-Schlüssel für deine bisherigen Nachrichten besitzen und anderen Nutzern bestätigen können, dass diese Sitzungen tatsächlich von dir stammen. +Sitzungen umbenennen +Verifizierte Sitzungen +Nicht verifizierte Sitzungen sind jene, die angemeldet, aber nicht quer signiert sind. +\n +\nDu solltest besonders sicherstellen, dass du diese Sitzungen erkennst, da sie auf eine nicht autorisierte Nutzung deines Kontos hindeuten könnten. +Nicht verifizierte Sitzungen +Inaktive Sitzungen sind jene, die du einige Zeit nicht mehr verwendet hast, die aber weiterhin Verschlüsselungs-Schlüssel erhalten. +\n +\nDas Löschen von inaktiven Sitzungen verbessert Sicherheit und Leistung, und hilft dir zu erkennen, ob eine neue Sitzung verdächtig ist. +Inaktive Sitzungen +Sei dir bitte bewusst, dass Sitzungsnamen auch für Personen, mit denen du kommunizierst, sichtbar sind. +Individuelle Sitzungsnamen können dir helfen, deine Geräte einfacher zu erkennen. +Sitzungsname +Sitzung umbenennen +Von dieser Sitzung abmelden +Nicht verifiziert · Deine aktuelle Sitzung +Beginne eine Sprachübertragung +Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden. +Tastatur auffordern, keine personalisierten Daten, z. B. den Tippverlauf und Wörterbücher, basierend auf deinen Konversationen zu aktualisieren. Beachte, dass einige Tastaturen diese Einstellung ignorieren werden. +Inkognito-Tastatur +Stellt (╯°□°)╯︵ ┻━┻ einer Klartextnachricht voran +Sprachübertragung +Öffne die Entwicklungswerkzeuge +🔒 Du hast in den Sicherheitseinstellungen aktiviert, dass Verschlüsselung in allen Räumen ausschließlich für verifizierte Sitzungen erlaubt ist. +⚠ Es befinden sich nicht verifizierte Geräte in diesem Raum. Sie werden deine Nachrichten nicht entschlüsseln können. +Niemals verschlüsselte Nachrichten zu unverifizierten Sitzungen in diesem Raum senden. +Verstanden +Probiere den Textverarbeitungs-Editor (bald auch mit Klartext-Modus) +Textverarbeitungs-Editor aktivieren +Browser +Durchgestrichen formatieren +Kursiv formatieren +Fett formatieren +Unterstrichen formatieren +${app_name} benötigt die Berechtigung zur Anzeige von Benachrichtigungen. +\nBitte gewähre diese Berechtigung. +Bezeichnung, Version und URL der Anwendung registrieren, damit diese Sitzung in der Sitzungsverwaltung besser erkennbar ist. +Anwendungsinformationen erfassen +URL +Bessere Übersicht und Kontrolle über all deine Sitzungen. +Aktiviere neue Sitzungsverwaltung +Betriebssystem +Modell +Version +Name +Anwendung +Erhalte Push-Benachrichtigungen in dieser Sitzung. +Push-Benachrichtigungen +Verifiziere deine aktuelle Sitzung, um den Verifizierungsstatus dieser Sitzung anzuzeigen. +Unbekannter Verifizierungsstatus +Sitzungs-ID: +Aktiviert: +Etwas ist schiefgelaufen. Bitte überprüfe deine Internetverbindung und versuche es erneut. +Berechtigung geben +${app_name} braucht die Berechtigung, um Benachrichtigungen anzuzeigen. Benachrichtigungen können deine Nachrichten, Einladungen etc. anzeigen. +\n +\nBitte erlaube den Zugriff im nächsten Dialog, damit Benachrichtigungen angezeigt werden können. +Bitte vergewissere dich, dass du den Ursprung dieses Codes kennst. Durch Verbindung neuer Geräte gewährst du vollen Zugriff auf dein Konto. +Bestätigen +Erneut versuchen +Keine Übereinstimmung\? +Du wirst angemeldet +Verbinde mit Gerät +QR-Code einlesen +Mobiles Gerät anmelden\? +QR-Code auf diesem Gerät anzeigen +Wähle „QR-Code einlesen“ +Beginne auf dem Anmeldebildschirm +Wähle „Mit QR-Code anmelden“ +Beginne auf dem Anmeldebildschirm +Wähle „QR-Code anzeigen“ +Gehe zu Einstellungen -> Sicherheit und Privatsphäre +Öffne die App auf deinem anderen Gerät +Die Anfrage wurde auf dem anderen Gerät abgelehnt. +Die Verbindung konnte nicht in der erforderlichen Zeit hergestellt werden. +Verbindung mit diesem Gerät nicht unterstützt. +Verbindung fehlgeschlagen +Überprüfe dein angemeldetes Gerät. Der unten gezeigte Code sollte angezeigt werden. Bestätige, dass beide Codes übereinstimmen: +Sichere Verbindung hergestellt +Lese den unten angezeigten QR-Code mit deinem nicht angemeldeten Gerät ein. +Benutze dein angemeldetes Gerät um den unten angezeigten QR-Code einzulesen: +Mit QR-Code anmelden +Benutze die Kamera auf diesem Gerät um den vom anderen Gerät angezeigten QR-Code zu scannen: +QR-Code einlesen +3 +2 +1 +Du kannst dieses Gerät benutzen um ein anderes Gerät per QR-Code anzumelden. Dafür gibt es zwei Wege: +Mit QR-Code anmelden +QR-Code einlesen +Zeichne Sprachnachrichten auf, während du sie in Echtzeit in den Raumverlauf sendest. +Sprachübertragung aktivieren (in aktiver Entwicklung) +Der Heim-Server unterstützt Anmelden per QR-Code nicht. +Die Anmeldung wurde vom anderen Gerät abgebrochen. +Der QR-Code ist ungültig. +Das andere Gerät muss angemeldet sein. +Das andere Gerät ist bereits angemeldet. +Es ist ein Problem bei der Herstellung der sicheren Kommunikation aufgetreten. Eines der folgenden Dinge könnte kompromittiert sein: Dein Heim-Server; deine Internetverbindung(en); dein(e) Gerät(e); +Die Anfrage ist fehlgeschlagen. +Abspielen oder fortsetzen der Sprachübertragung +Fortsetzen der Sprachübertragung +Puffere +Pausiere Sprachübertragung +Stoppe Aufzeichnung der Sprachübertragung +Pausiere Aufzeichnung der Sprachübertragung +Live +Sticker +Sitzungen auswählen +Kontakt +Kamera +Standort +Umfragen +Sprachübertragung +Anhänge +Fotobibliothek +Alle abwählen +Alle auswählen ++ +- %1$d ausgewählt
+- %1$d ausgewählt
+Du hast nicht die nötigen Berechtigungen, um eine Sprachübertragung in diesem Raum zu starten. Kontaktiere einen Raumadministrator, um deine Berechtigungen anzupassen. +Sprachübertragung kann nicht gestartet werden +Vollbildmodus umschalten +Textformatierung +Du zeichnest bereits eine Sprachübertragung auf. Bitte beende die laufende Übertragung, um eine neue zu beginnen. +Jemand anderes nimmt bereits eine Sprachübertragung auf. Warte auf das Ende der Übertragung, bevor du eine neue startest. +30 Sekunden vorspulen +30 Sekunden zurückspulen +Auf verifizierte Sitzungen kannst du überall mit deinem Konto zugreifen, wenn du deine Passphrase eingegeben oder Element mit einer anderen Sitzung verifiziert hast. +\n +\nDies bedeutet, dass du alle Schlüssel zum Entsperren deiner verschlüsselten Nachrichten hast und anderen bestätigst, dieser Sitzung zu vertrauen. ++ +- Von %1$d Sitzung abmelden
+- Von %1$d Sitzungen abmelden
+Abmelden +%1$s übrig +Zitieren +Bearbeiten +erstellte eine Umfrage. +sandte einen Sticker. +sandte ein Video. +sandte ein Bild. +sandte eine Sprachnachricht. +sandte eine Audiodatei. +sandte eine Datei. +Als Antwort auf +%s antworten +IP-Adresse ausblenden +IP-Adresse anzeigen \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml index bc4299c1bd..3d10997233 100644 --- a/library/ui-strings/src/main/res/values-es/strings.xml +++ b/library/ui-strings/src/main/res/values-es/strings.xml @@ -2548,7 +2548,6 @@Escritorio Web Móvil -Mostrar todas las sesiones (V2, WIP) Auto aprovar widgets de Element Call y dar permisos de cámara y micrófono - %d mensaje borrado
@@ -2650,4 +2649,10 @@Crear sala Iniciar conversación Todas las conversaciones - +Seleccionar todo +De acuerdo ++ + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index dbdbbdbb00..156221379d 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -1158,10 +1158,10 @@- %1$d seleccionado
+- %1$d seleccionados
+Tõuketeavituste reeglid Tõuketeavituste reegleid pole kirjeldatud Tõuketeavituste võrguväravaid pole registreeritud -app_id: -push_key: -app_display_name: -session_name: +Rakenduse ID: +Tõuketeenuse võti: +Rakenduse kuvatav nimi: +Sessiooni nimi: URL: Vorming: Heli ja video @@ -1197,8 +1197,8 @@Isikutuvastusserveri kasutamise lõpetamine tähendab, et sa ei ole leitav teiste kasutajate poolt ega sulle ei saa telefoninumbri või e-posti aadressi alusel kutset saata. Küll aga saab kutset saata Matrix\'i kasutajatunnuse alusel. Leitavad telefoninumbrid Lisa avaekraanile eraldi sakk lugemata teavituste jaoks. -Saatsime kinnituskirja e-posti aadressile %s. Palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki -Saatsime kinnituskirja e-posti aadressile %s. Esmalt palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki +Saatsime kirja e-posti aadressile %s. Palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki +Saatsime kirja e-posti aadressile %s. Esmalt palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki Selleks, et sind võiks leida e-posti aadressi või telefoninumbri alusel, nõustu isikutuvastusserveri (%s) kasutustingimustega. Lülita sisse detailsete logide salvestamine. Sa parasjagu ei eira ühtegi kasutajat @@ -1336,7 +1336,7 @@Kas katkestame ühenduse isikutuvastusserveriga %s\? Palun esmalt seadista isikutuvastusserver. Palun esmalt nõustu seadistustes isikutuvastusserveri kasutustingimustega. -Sinu privaatsuse nimel saadab ${app_name} e-posti aadressid ja telefoninumbrid serverisse vaid räsituna. +Sinu privaatsuse nimel saadab ${app_name} e-posti aadressid ja telefoninumbrid serverisse vaid räsidena. Seoste loomine ei õnnestunud. Selle tunnusega ei ole hetkel ühtegi seost. Sinu koduserver (%1$s) soovitab kasutada sinu isikutuvastusserverina %2$s teenust @@ -1778,7 +1778,7 @@See ei ole korralik Matrix\'i QR-kood Nõustu Tühista minu nõusolek -Selleks, et leida Matrixikasutajaid oma kontaktide hulgast, oled sa andnud nõusoleku saata e-posti aadresse ja telefoninumbreid sellele isikutuvastusserverile. +Selleks, et leida Matrixikasutajaid oma kontaktide hulgast, oled sa andnud nõusoleku e-posti aadresside ja telefoninumbrite saatmiseks sellele isikutuvastusserverile. Saada e-posti aadresse ja telefoninumbreid Soovitused Tuttavad kasutajad @@ -2211,7 +2211,7 @@Ligipääs siia kogukonda Kes pääsevad ligi siia jututuppa\? Saada teavitusi %s e-posti aadressile -Kui soovi e-posti teel saada teavitusi, siis palun seosta oma e-posti aadress oma Matrix\'i kasutajakontoga +Kui soovid e-posti teel saada teavitusi, siis palun seosta oma e-posti aadress oma Matrix\'i kasutajakontoga E-posti teel saadetavad teavitused Uuenda kogukonda Muuda kogukonna nime @@ -2591,7 +2591,6 @@ \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 Sessioonid @@ -2691,7 +2690,7 @@Filtreeri Viimati kasutusel %1$s Seade -Sessioonid +Sessioon Praegune sessioon Parima turvalisuse ja töökindluse nimel verifitseeri see sessioon või logi ta võrgust välja. Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon. @@ -2701,4 +2700,161 @@Võta kasutusele viivitusega otsevestlused Lihtsustatud Element valikuliste kaartidega Võta kasutusele rakenduse uus välimus +🔒 Sa oled rakenduse turvaseadistustes määranud, et krüptimine kehtib vaid verifitseeritud sessioonidele. +⚠ Selles jututoas on verifitseerimata seadmeid, mis ei saa sinu saadetud sõnumeid dekrüptida. +Teised osapooled nii otsesõnumites kui jututubades näevad sinu kõikide sessioonide loendit. +\n +\nSee tähendab, et nad võivad uskuda, et tegemist on tõesti sinuga. Samal ajal näevad ka siin sisestatud sessiooninime. +Sessioonide nimede muutmine +Verifitseeritud sessioonid on sellised, kuhu sa oled oma kasutajanime ja salasõnaga sisse loginud ning mille puhul oled risttunnustamise läbi teinud või paroolifraasi abil ta turvaliseks märkinud. +\n +\nSee tähendab, et nendes sessioonides on olemas sinu varasemate sõnumite krüptovõtmed ja teistele osapooltele on nad tuvastatavad nii, et tegemist on tõesti sinuga. +Verifitseeritud sessioonid +Verifitseerimata sessioonid on sellised, kuhu sa oled oma kasutajanime ja salasõnaga sisse loginud, kuid mille puhul on risttunnustamine tegemata. +\n +\nPalun kontrolli, et need on sulle teadaolevad sessioonid. Vastasel korral võib olla tegemist sinu kasutajakonto lubamatu kasutamisega. +Verifitseerimata sessioonid +Sessioonid, mida sa pole mõnda aega kasutanud, saavad jätkuvalt pärida sinu krüptovõtmeid. +\n +\nSelliste sessioonide eemaldamine parandab jõudlust ja turvalisust ning sul on lihtsam märgata, kui loendisse tekib midagi kahtlast. +Mitteaktiivsed sessioonid +Palun arvesta, et sessioonide nimed on näha ka kõikidele osapooltele, kellega sa suhtled. +Sinu enda kirjutatud sessiooninimede alusel on sul oma seadmeid lihtsam ära tunda. +Sessiooni nimi +Muuda sessiooni nime +Logi sellest sessioonist välja +Verifitseerimata · Sinu praegune sessioon +Alusta ringhäälingukõnet +Selle krüptitud sõnumi autentsus pole selles seadmes tagatud. +Väldi sisetuste ja muude isikustatud andmete edastamist väljaspoole seda rakendust. Palun arvesta, et kõik klaviatuurirakendused ei pruugi seda seadistust tunnistada. +Privaatne klahvistik +Lisab vormindamata sõnumi ette (╯°□°)╯︵ ┻━┻ +Ringhäälingukõne +Ava arendaja töövahendite vaade +Ära iialgi saada selles jututoas krüptitud sõnumeid verifitseerimata sessioonidesse. +Selge lugu +Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim) +Võta kasutusele vormindatud teksti pruukiv tekstitoimeti +Vaata seadet, kus sa oled Matrix\'i võtku loginud - seal peaks nüüd kuvatama QR-koodi. Kinnita, et allpool toodud QR-kood on sama kui tolles seadmes kuvatav kood: +Sa võid seda seadet kasutada nutiseadme või veebirakenduse sisselogimiseks QR-koodi alusel. Sa saad seda teha kahel moel: +Kasuta allajoonitud kirja +Kasuta läbijoonitud kirja +Kasuta kaldkirja +Kasuta paksu kirja +Palun vaata, et sa kindlasti tead, kust see QR-kood kuvatakse. Sellisel viisil seadmete sidumisel sa annad oma kasutajakontole täiemahulise ligipääsu. +Kinnita +Proovi uuesti +Ei klapi\? +Logime sind võrku +Loon ühendust seadmega +Loe QR-koodi +Kas logid sisse nutiseadmest\? +Näita selles seadmes QR-koodi +Vali „Loe QR-koodi“ +Alusta sisselogimisvaatest +Vali „Logi võrku QR-koodi abil“ +Alusta sisselogimisvaatest +Vali „Näita QR-koodi“ +Ava Seadistused -> Turvalisus ja privaatsus +Ava sama rakendus oma teises seades +Teine seade lükkas päringu tagasi. +Sidumine ei lõppenud etteantud aja jooksul. +Sidumine selle seadmega ei ole toetatud. +Seoste loomine ei õnnestunud +Turvaline ühendus on olemas +Loe QR-koodi seadmega, kus sa oled Matrix\'i võrgust välja loginud. +Järgneva QR-koodi skaneerimiseks kasuta seadet, kus sa oled Matrix\'i võrku loginud: +Logi sisse QR-koodi abil +Kasuta selle seadme kaamerat ja logi sisse teises seadmes kuvatud QR-koodi alusel: +Loe QR-koodi +3 +2 +1 +Sessioonide paremaks tuvastamiseks saad nüüd sessioonihalduris salvestada klientrakenduse nime, versiooni ja aadressi. +Luba klientrakenduse teabe salvestamine +Sellega saad parema ülevaate oma sessioonidest ja võimaluse neid mugavasti hallata. +Kasuta uut sessioonihaldurit +Logi sisse QR-koodi abil +Operatsioonisüsteem +Mudel +Brauser +URL +Versioon +Nimi +Rakendus +Luba selles sessioonis tõuketeavitused. +Tõuketeavitused +Selle sessiooni olekut ei saa tuvastada enne kui oled ta verifitseerinud. +Verifitseerimise olek on määratlemata +Loe QR-koodi +Kasutusel: +Sessiooni tunnus: +Midagi läks nüüd sassi. Palun kontrolli oma seadme võrguühendust ja proovi uuesti. +Anna õigused +${app_name} vajab teavituste näitamiseks õigusi. +\nPalun luba vastavad õigused. +${app_name} vajab teavituste näitamiseks õigusi. Teavituste sisuks võivad olla sulle saadetud sõnumid, kutsed ja muud olulist. +\n +\nJärgmistes vaadetes palun anna sellele rakendusele teavituste kuvamiseks vajalikud õigused. +Võimalus salvestada ja postitada ringhäälingukõnesid jututoa ajajoonele. +Võta kasutusele ringhäälingukõned (aktiivses arenduses) +Koduserver ei toeta muude seadmete võrku logimise võimalust. +Sisselogimine katkestati teises seadmes. +See QR-kood on vigane. +Teine seade peab olema võrku loginud. +Teine seade on juba võrku loginud. +Turvalise sõnumivahetuse ülesseadmisel tekkis turvaviga. Üks kolmest võib olla sattunud vale osapoole kontrolli alla: sinu koduserver, sinu internetiühendus või sinu seade; +Päring ei õnnestunud. +Andmed on puhverdamisel +Alusta või jätka ringhäälingukõne esitamist +Lõpeta ringhäälingukõne salvestamine +Peata ringhäälingukõne salvestamine +Jätka ringhäälingukõne salvestamist +Peata ringhäälingukõne esitamine +Otse eetris +Vali sessioonid +Kontakt +Kaamera +Asukoht +Küsitlused +Ringhäälingukõne +Manused +Kleepsud +Fotode kogu +Eemalda kõik valikud +Vali kõik ++ +- %1$d valitud
+- %1$d valitud
+Lülita täisekraanivaade sisse/välja +Tekstivorming +Sa juba salvestad ringhäälingukõnet. Uue alustamiseks palun lõpeta eelmine salvestus. +Keegi juba salvestab ringhäälingukõnet. Uue ringhäälingukõne salvestamiseks palun oota, kuni see teine ringhäälingukõne on lõppenud. +Sul pole piisavalt õigusi selles jututoas ringhäälingukõne algatamiseks. Õiguste lisamiseks palun võta ühendust jututoa haldajaga. +Uue ringhäälingukõne alustamine pole võimalik +Keri tagasi 30 sekundi kaupa +Keri edasi 30 sekundi kaupa +Verifitseeritud sessioonideks loetakse Element\'is või mõnes muus Matrix\'i rakenduses selliseid sessioone, kus sa kas oled sisestanud oma salafraasi või tuvastanud end mõne teise oma verifitseeritud sessiooni abil. +\n +\nSee tähendab, et selles sessioonis on ka kõik vajalikud võtmed krüptitud sõnumite lugemiseks ja teistele kasutajatele kinnitamiseks, et sa usaldad seda sessiooni. ++ +- Logi välja %1$d\'st sessioonist
+- Logi välja %1$d\'st sessioonist
+Logi välja +jäänud %1$s +Muudan sõnumit +Vastan sõnumile %s +Tsiteerides +Näita IP-aadressi +Peida IP-aadress +Vastuseks kasutajale +saatis faili. +saatis helifaili. +saatis häälsõnumi. +saatis pildi. +saatis video. +saatis kleepsu. +koostas küsitluse. \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index 9012bc2ebe..cc8d60a87b 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -155,7 +155,7 @@به میهمانان اجازهٔ پیوستن به گروه دادید. میمهانان را از پیوستن به گروه بازداشتید. رمزنگاری سرتاسری را روشن کردید. -رمزنگاری سرتاسری را روشن کردید (الگوریتم ناشناخته %1$s). +رمزنگاری سرتاسری را روشن کردید (الگوریتم تشخیصدادهنشده %1$s). مهمانها را از پیوستن به اتاق بازداشتید. %1$s مهمانها را از پیوستن به اتاق بازداشت. به مهمانها اجازه دادید به اینجا بپیوندند. @@ -659,7 +659,7 @@مدیریت تنظیمات کشفتان. نمایش همهٔ پیامها از %s؟ رایانامهها و شماره تلفنها -مدیریت رایانامهها و شماره تلفنهای پیوسته به حساب ماتریکستان +مدیریت نشانیهای رایانامه و شماره تلفنهای پیوسته به حساب ماتریکستان ۳ روز ۱ هفته ۱ ماه @@ -822,7 +822,7 @@پیکربندی کارساز هویت تغییر کارساز هویت نشانیهای رایانامهٔ قابلکشف -گزینههای کشف به محض افزودن یک رایانامه، ظاهر خواهند شد. +گزینههای کشف به محض افزودن یک نشانی رایانامه، ظاهر خواهند شد. گزینههای کشف به محض افزودن یک شماره تلفن، ظاهر خواهند شد. شماره تلفنهای قابلکشف به کار انداختن گزارشهای پرگو. @@ -1234,7 +1234,7 @@مطمئن شوید لینک فعالسازیای را که به ایمیل شما ارسال شده، باز کردهاید. برداشتن %s؟ شماره تلفنها -هیچ رایانامهای به حسابتان افزوده نشده +هیچ نشانی رایانامهای به حسابتان افزوده نشده نشانیهای رایانامه هیچ شماره تلفنی به حسابتان افزوده نشده نتیجهای در پی نداشت @@ -1397,7 +1397,7 @@ثبت نام در %1$s شماره تلفن نامعتبر است. لطفا آن را بررسی کنید ما یک کد فعالسازی به %1$s ارسال کردیم. لطفا آن را وارد کنید. -برای بازیابی حساب خود یک ایمیل تنظیم کنید. بعداً، در صورتی که تمایل داشتید میتوانید به افراد اجازه دهید با ایمیل، شما را پیدا کنند. +برای بازیابی حسابتان، نشانی رایانامهای تنظیم کنید. بعداً میتوانید بگذارید افرادی که میشناسید، با این نشانی بیابندتان. یک شماره تلفن تنظیم کنید تا در صورتی که تمایل دارید، به افراد اجازه پیدا کردن شما را با استفاده از آن بدهد. گذرواژه شما هنوز تغییر نکردهاست. \n @@ -1407,12 +1407,12 @@ برای تایید گذرواژه جدید لینک ارسالی را باز کرده، سپس روی متن زیر کلیک کنید. یک ایمیل تائید به %1$s ارسال شد. صندوق ایمیل خود را بررسی کنید -این ایمیل به هیچ حسابی متصل نشدهاست +این نشانی رایانامه به هیچ حسابی پیوند داده نشده با تغییر گذرواژه ، کلیدهای رمزگذاری سرتاسر در تمام نشستهای شما تغییر کرده و تاریخچه گفتگوی رمزشدهی شما غیرقابل خواندن میشود. قبل از تغییر گذرواژه، کلید امنیتی یا کلید پشتیبان را وارد کرده و یا کلیدهای اتاق خود را از نشست دیگری استخراج کنید. بعدی برای تأیید تنظیم گذرواژهی جدید ، یک ایمیل تأیید به آدرس ایمیل شما ارسال خواهد شد. بازنشانی گذرواژه در %1$s -این ایمیل به هیچ حسابی مرتبط نیست. +این نشانی رایانامه به هیچ حسابی مرتبط نیست. برنامه قادر به ایجاد حساب در این سرور نیست. \n \nآیا می خواهید با استفاده از مرورگر حساب کاربری بسازید؟ @@ -1427,7 +1427,7 @@درست مانند ایمیل، حسابهای کاربری یک خانه دارند؛ اگرچه می توانید با هر کسی که دوست دارید، صحبت کنید کارسازی برگزینید شروع کنید -تجارب خود را گسترش داده و شخصیسازی کنید +تجربههایتان را گسترش داده و شخصیسازی کنید گفتگوهای خصوصی خود را با رمزگذاری محافظت کنید با افراد به صورت مستقیم و یا در قالب گروه گپ بزنید صاحب گفتگوهای خود باشید. @@ -1484,18 +1484,18 @@لطفاً نشانی کارساز هویت را وارد کنید نتوانست به کارساز هویت وصل شود یک نشانی کارساز هویت وارد کنید -ما یک ایمیل تأیید به %s ارسال کردیم، ابتدا ایمیل خود را بررسی کرده و روی لینک تأیید کلیک کنید -ما یک ایمیل تأیید به %s ارسال کردیم، ایمیل خود را بررسی کرده و روی لینک تأیید کلیک کنید +رایانامهای به %s فرستادیم. لطفاً نخست رایانامهتان را بررسی کرده و روی پیوند تأیید کلیک کنید +رایانامهای به %s فرستادیم. رایانامهتان را بررسی کرده و روی پیوند تأیید کلیک کنید قطع ارتباط با سرور هویتسنجی به این معنی است که توسط کاربران دیگر قابل شناسایی نخواهید بود و نمی توانید دیگران را از طریق ایمیل یا تلفن دعوت کنید. در حال استفاده از کارساز هویتی نیستید. برای کشق و قابل کشف بودن به دست آشنایان موجودی که میشناسید، یک کارساز هویت در زیر پیکربندی کنید. دارید برای کشف و قابل کشف بودن به دست آشنایان موجودی که میشناسید از %1$s استفاده میکنید. ثبت ژتون فرمت: آدرس: -نام نشست: -نام برنامه: -کلید push: -شناسه برنامه: +نام نمایشی نشست: +نام نمایشی کاره: +کلید ارسال: +شناسهٔ کاره: هیچ push gatewayای ثبت نشده است هیچ قانونی برای push تعریف نشده است شما در حال مشاهده این اتاق هستید! @@ -1680,7 +1680,7 @@عبارت رمزی که فقط خودتان میدانید را وارد کرده و کلیدی برای پشتیبان تولید کنید. به هنوتم جایگزین، میتوانید نشامی هر کارساز هویت دیگری را وارد کنید کارساز خانگیتان (%1$s) پیشنهاد استفاده از %2$s برای کارساز هویتتان را میدهد -برای محرمانگیتان، المنت تنها از فرستادن شماره تلفن و رایانامههای کاربری در هم ریخته پشتیبانی میکند. +برای محرمانگیتان، ${app_name} تنها از فرستادن شمارههای تلفن و نشانیهای رایانامهٔ کاربری در هم ریخته پشتیبانی میکند. این عملیات ممکن نیست. کارساز خانگی منقضی شده است. نتوانستیم کاربران را دعوت کنیم. لطفاً کاربرانی که میخواهید دعوت کنید را بررسی کرده و دوباره تلاش کنید. نتوانستیم پیامتان را ایجاد کنیم. لطفاً کاربرانی که میخواهید دعوت کنید را بررسی کرده و دوباره تلاش کنید. @@ -1747,7 +1747,7 @@- دستگاهی را که میتوانید با استفاده از آنها خود را تایید کنید نشان بده
- %d دستگاهی را که میتوانید با استفاده از آنها خود را تایید کنید نشان بده
-رضاییتان را برای فرستادن رایانامهها و شمارهتلفنها به این کارساز هویت به منظور کشف دیگر کاربران از آشنایانتان، دادهاید. +رضاییتان را برای فرستادن نشانیهای رایانامه و شمارههای تلفن به این کارساز هویت به منظور کشف دیگر کاربران از آشنایانتان، دادهاید. در حال حاضر این اتاق قابل دسترسی نیست. \nبعدا دوباره تلاش کنید، یا از مدیر اتاق بخواهید بررسی کند که آیا دسترسی دارید. انتشار این نشانی @@ -2141,7 +2141,7 @@اشارهها و کلیدواژگان آگاهیهای پیشگزیده %s در تنظیمات برای دریافت مستقیم دعوتها در المنت. -این رایانامه را به حسابتان پیوند دهید +پیوند این نشانی رایانامه به حسابتان این دعوت به این فضا به %s فرستاده شده که با حسابتان در ارتباط نیست این دعوت به این اتاق به %s فرستاده شده که با حسابتان در ارتباط نیست این اتاق را از %1$s به %2$s ارتقا خواهید داد. @@ -2211,7 +2211,7 @@دسترسی فضا چه کسی میتواند دسترسی داشته باشد؟ به کار انداختن آگاهیهای رایانامهای برای %s -برای دریافت رایانامه با آگاهی، لطفاً رایانامهای را به حساب ماتریکستان وصل کنید +برای دریافت رایانامه با آگاهی، لطفاً نشانی رایانامهای را به حساب ماتریکستان وصل کنید آگاهی رایانامهای ارتقای فضا تغییر نام فضا @@ -2258,13 +2258,13 @@پرسش یا موضوع نظرسنجی ایجاد نظرسنجی نظرسنجی -فرستادن رایانامهّا و شمارههای تلفن به %s +فرستادن نشانیهای رایانامه و شمارههای تلفن به %s آشنایانتان خصوصی هستند. برای کشف کاربران از آشنایانتان، نیاز به اجازهتان برای فرستادن اطّلاعات آشنا به کارساز هویتتان داریم. نشست خارج شده است! اتاق ترک شده است! با فرستادن این اطّلاعات موافقید؟ اکنون نه -برای کشف آشنایان موجود، لازم است اطلاعات آشنایان (رایانامهها و شماره تلفنها) را به کارساز هویتتان بفرستید. برای محرمانگیتان، دادههایتان را پیش از فرستادن، در هم میریزیم. +برای کشف آشنایان موجود، لازم است اطلاعات آشنایان (نشانیهای رایانامه و شمارههای تلفن) را به کارساز هویتتان بفرستید. برای محرمانگیتان، دادههایتان را پیش از فرستادن، در هم میریزیم. با همرسانی دادهّای استفادهٔ ناشناس، در تشخیص مشکلها و بهبود المنت یاریمان کنید. برای درک چگونگی استفادهٔ مردم از چندین افزاره، شناسهای کاتورهای بین افزارههایتان همرسانی خواهیم کرد. \n \nمیتوانید از %s قوانینمان را بخوانید. @@ -2600,7 +2600,6 @@ \nشاید این کارساز خانگی برای نمایش نقشهها پیکربندی نشده باشد.گشودن تنظیمات تمامی گپها -نمایش تمامی نشستها (ن۲، دحت) برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید. دیگر نشستها نشستها @@ -2705,4 +2704,144 @@- غیرفعّال برای ۱ روز یا بیشتر
- غیرفعّال برای %1$d روز یا بیشتر
+تأیید نشده · نشست کنونیتان +اعتبار این پیام رمز شده نمیتواند روی این افزاره تأیید شود. +در این اتاق، هرگز پیامهای رمز شده به نشستهای تأیید نشده فرستاده نشود. +تغییر نام نشستها +نشستهای تأییدشده +نشستهای تأییدنشده +نشستهای غیرفعّال +لطفاً آگاه باشید که نامهای نشست برای افرادی که با آنها در تماسید نیز نشان داده میشود. +نامهای نشست شخصی به نظم دادن آسانتر افزارههایتان کمک میکند. +نام نشست +تغییر نام نشست +خروج از این نشست +صفحهکلید ناشناس +گشودن صفحهٔ ابزارهای توسعهدهنده +به کار انداختن پیامهای مستقیم تعویقی +گرفتم +آغاز یک پخش همگانی صوتی +(╯°□°)╯︵ ┻━┻ را به ابتدای پیام متنی خام میافزاید +پخش همگانی صدا +اعطای دسترسی +ویرایشگر متن غنی را بیازمایید (حالت متن خام به زودی) +به کار انداختن ویرایشگر متن غنی +برای آشکارسازی وضعیت تأیید نشست کنونیتان، تأییدش کنید. +کارساز خانگی از ورود با کد QR پشتیبانی نمیکند. +ورود روی افزارهٔ دیگر لغو شد. +دید و واپایش بیشتری روی نشستهایتان داشته باشید. +درخواست روی افزارهٔ دیگر رد شد. +پیوند در مدّت مقرّر کامل نشد. +افزارهٔ دیگر باید وارد شده باشد. +افزارهٔ دیگر از پیش وارد شده. +به تنظیمات -> امنیت و محرمانگی بروید +کاره را روی افزارهٔ دیگرتان بگشایید +پیوند دادن با این افزاره پشتیبانی نمیشود. +به کار انداختن پخش صدا (زیر توسعهٔ فعّال) +نمایش کد QR روی این افزاره +آغاز در صفحهٔ ورود +گزینش«ورود با کد QR» +آغاز در صفحهٔ ورود +گرفتن آگاهیهای ارسالی روی این نشست. +پخش یا مکث پخش صدا +کد QR نامعتبر است. +وارد شدن در افزارهای همراه؟ +ورود با کد QR +ورود با کد QR +توقّف ضبط پخش صدا +مکث ضبط پخش صدا +از سر گیری ضبط پخش صدا +گزینش «نمایش کد QR» +گزینش «نمایش کد QR» +به کار انداختن ضبط اطّلاعات کارخواه +به کار انداختن مدیر نشست جدید +مکث پخش صدا +درخواست شکست خورد. +وارد کردنتان +وصل شدن به افزاره +پویش کد QR +اتّصال امن برقرار شده +پویش کد QR +پویش کد QR +اعمال قالب زیرخطدار +اعمال قالب خطخورده +اعمال قالب کج +اعمال قالب توپر +وضعیت تأیید نامعلوم +گزینش نشستها +پخش صدا +کتابخانهٔ عکس +دوباره تلاش کنید +مطابق نیستند؟ +اتّصال ناموفّق +سیستمعامل +آگاهیهای ارسالی +شناسهٔ نشست: +آشنا +دوربین +مکان +نظرسنجیها +پیوستها +برچسبها +میانگیری +زنده +تأیید +۳ +۲ +۱ +مدل +مرورگر +نشانی +نگارش +نام +برنامه +به کار افتاده: +ناگزینش همه +گزینش همه ++ +- ۱ گزیده
+- %1$d گزیده
+اجازههای لازم برای آغاز پخش صوتی در این اتاق را ندارید. برای ارتقای اجازههایتان با یک مدیر اتاق تماس بگیرید. +فرد دیگری در حال ضبط یک پخش صوتی است. برای آغاز یک پخش جدید، منتظر پایان پخشش بمانید. +با بررسی افزارههای وارد شدهتان باید کد زیر را ببینید. تأیید کنید که این کد با آن افزاره مطابق است: +دارید یک پخش صوتی ضبط میکنید. لطفاً برای آغاز یک پخش جدید، به پخش کنونی پایان دهید. +⚠ افزارههای تأییدنشدهای در این اتاق وجود دارند. آنها قادر به رمزگشایی پیامهایی که فرستادهاید نیستند. +استفاده از دوربین روی این افزاره برای پویش کد QR نشان داده شده روی افزارهٔ دیگرتان: +ضبط نام کارخواه، نگارش و نشانی برای بازشناسی آسانتر نشستها در مدیر نشست. +🔒 رمزگذاری به نشستهای تأیید شده را فقط برای تمامی اتاقها در تنظیمات امنیت به کار انداختهاید. ++ +- خارج شدن از نشستهای قدیمی (۱ روز یا بیشتر) که دیگر استفاده نمیکنید را در نظر داشته باشید.
+- خارج شدن از نشستهای قدیمی (%1$d روز یا بیشتر) که دیگر استفاده نمیکنید را در نظر داشته باشید.
+توانایی ضبط و فرستادن پخش صدا در خط زمانی اتاق. +پویش کد QR زیر با افزارهای که خارج شده. +استفاده از افزارهٔ وارد شدهتان برای پویش کد QR زیر: +چیزی اشتباه پیش رفت. لطفاً اتّصال شبکهتان را بررسی و دوباره تلاش کنید. +${app_name} برای نمایش آگاهیها نیازمند اجازه است. +\nلطفاً اجازه را اعطا کنید. +نمیتوان پخش صدایی جدید را آغاز کرد +تغییر حالت تمامصفحه +۳۰ ثانیه پیشروی +۳۰ ثانیه پسروی +قالببندی متن +خروج ++ +- خروج از ۱ نشست
+- خروج از %1$d نشست
+%1$s مانده +پیام صوتیای فرستاد. +پروندهٔ صوتیای فرستاد. +نظرسنجیای ایجاد کرد. +عکسبرگردانی فرستاد. +ویدیویی فرستاد. +تصویری فرستاد. +پروندهای فرستاد. +در پاسخ به +نهفتن نشانی آیپی +نمایش نشانی آیپی +نقل کردن +پاسخ دادن به %s +ویرایش کردن \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-fi/strings.xml b/library/ui-strings/src/main/res/values-fi/strings.xml index a576e7f0dc..4976f49a92 100644 --- a/library/ui-strings/src/main/res/values-fi/strings.xml +++ b/library/ui-strings/src/main/res/values-fi/strings.xml @@ -1,5 +1,5 @@ -+ \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index c7100e3a1e..cf49733bdf 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -869,10 +869,10 @@+ Käyttäjän %s kutsu %1$s kutsui käyttäjän %2$s %1$s kutsui sinut @@ -401,7 +401,7 @@1 kuukausi Ikuisesti Teema -Kirjaisinkoko +Fontin koko Pienin Pieni Normaali @@ -955,8 +955,8 @@Ei rekisteröityjä viesti-ilmoitusten yhdyskäytäviä app_id: push_key: -app_display_name: -device_name: +Sovelluksen näyttönimi: +Istunnon näyttönimi: Formaatti: Rekisteröi tunniste Salataan pikkukuvaa… @@ -1039,7 +1039,7 @@Sovellus ei pysty luomaan uusia tunnuksia tälle kotipalvelimelle. \n \nHaluatko rekisteröityä web-klientillä\? -Tämä sähköpostiosoite ei ole liitettynä mihinkään tunnukseen. +Tämä sähköpostiosoite ei ole liitettynä mihinkään tiliin. Palauta salasana palvelimella %1$s Sähköpostiisi lähetetään viesti uuden salananan asettamiseksi. Seuraava @@ -1048,7 +1048,7 @@Varoitus! Salasanan vaihtaminen nollaa kaikki osapuolten välisen salauksen avaimet kaikilla laitteillasi, joka estää sinua lukemasta vanhoja viestejä. Ota käyttöön avainten varmuuskopiointi tai vie huoneen avaimet toiselta laitteelta ennen kuin vaihdat salasanasi. Jatka -Tämä sähköposti ei ole liitettynä mihinkään tunnukseen +Tämä sähköposti ei ole liitettynä mihinkään tiliin Tarkista sähköpostisi Vahvistusviesti lähetettiin osoitteeseen %1$s. Näpäytä linkkiä vahvistaaksesi uuden salasanasi. Seurattuasi siinä olevaa linkkiä, klikkaa alapuolelta. @@ -1062,7 +1062,7 @@ \n \nPeru salasananvaihtoprosessi\?Aseta sähköpostiosoite -Aseta sähköpostiosoite palauttaaksesi tunnuksesi. Myöhemmin, voit antaa muiden löytää sinut sähköpostillasi. +Aseta sähköpostiosoite palauttaaksesi tilisi. Myöhemmin voit antaa muiden löytää sinut sähköpostiosoitettasi etsimällä. Sähköposti Sähköposti (vapaaehtoinen) Seuraava @@ -2116,4 +2116,197 @@%1$s muutti tämän huoneen vaihtoehtoisia osoitteita. Alkusynkronointipyyntö Poista tämän osoitteen julkaisu -Vahvistamaton +Parhaan turvallisuuden takaamiseksi kirjaudu ulos istunnoista, joita et tunnista tai et enää käytä. +Vahvistetu +Suodata ++ +- Käyttämättä %1$d päivän tai pidempään
+- Käyttämättä %1$d päivää tai pidempään
+Käyttämätön +Ei valmis turvallista viestintää varten +Vahvistamaton +Valmis turvallista viestintää varten +Vahvistettu +Kaikki istunnot +Suodata +Viimeisin toiminta %1$s +Laite +Istunto +Nykyinen istunto +Käyttämättä olevat istunnot +Vahvista nämä istunnot tai kirjaudu niistä ulos. +Vahvistamattomat istunnot +Paranna tilisi turvallisuutta seuraamalla näitä suosituksia. +Turvallisuussuositukset ++ +- Käyttämättä %1$d+ päivän (%2$s)
+- Käyttämättä %1$d+ päivää (%2$s)
+Vahvistamaton · Nykyinen istuntosi +Vahvistamaton · Viimeisin toiminta %1$s +Vahvistettu · Viimeisin toiminta %1$s +Näytä kaikki (%1$d) +Näytä tiedot +Vahvista istunto +Tuntematon vahvistuksen tila +Vahvistamaton istunto +Vahvistettu istunto +Tuntematon laitetyyppi +Työpöytä +Mobiili +Turvallisuuden vuoksi vahvista istunnot ja kirjaudu ulos niistä istunnoista, joita et tunnista tai et enää käytä. +Muut istunnot ++ +- %d viesti poistettu
+- %d viestiä poistettu
+Käytä sijainnin jakamista +Tällä hetkellä käytössä %s. +Menetelmä ++ +- Löytyi %d menetelmä.
+- Löytyi %d menetelmää.
+Saatavilla olevat menetelmät +Ilmoitusmenetelmä +Taustasynkronointi +Valitse miten ilmoitukset vastaanotetaan +Näytönjako on päällä +${app_name}-näytönjako +Huoneilmoitus +Ilmoita koko huoneelle +Jaa sijainti +Päivitetty %1$s sitten +%1$s jäljellä +Avaa sovelluksella +8 tuntia +1 tunti +15 minuuttia +Tulokset näytetään vain kun lopetat kyselyn +Kysely lopetettu +Lopeta kysely +Lopetetaanko tämä kysely\? +Tulokset tulevat näkyviin kun kysely lopetetaan +Lopeta kysely +(%1$s) +%1$s (%2$s) +Ei voi toistaa %1$s +Keskeytä %1$s +Toista %1$s +%1$d minuuttia %2$d sekuntia +Tuloksia ei löydy +Avaa asetukset +Incognito-näppäimistö +Istunnot +Tätä linkkiä ei voi avata: yhteisöt on korvattu avaruuksilla +Skannaa QR-koodi +Käyttäjänimi / sähköposti / puhelin +Olethan ihminen\? +Seuraa sähköpostiosoitteeseen %s lähetettyjä ohjeita +Salasanan nollaus +Unohtunut salasana +Lähetä sähköposti uudelleen +Etkö saanut sähköpostia\? +Seuraa sähköpostiosoitteeseen %s lähetettyjä ohjeita +Vahvista sähköpostiosoitteesi +Lähetä koodi uudelleen +Koodi lähetettiin numeroon %s +Vahvista puhelinnumerosi +Kirjaudu ulos kaikilta laitteilta +Nollaa salasana +Vähintään kahdeksan merkkiä. +Valitse uusi salasana +Uusi salasana +Tarkista sähköpostisi. +%s lähettää sinulle vahvistuslinkin +Vahvistuskoodi +Puhelinnumero +%s haluaa vahvistaa tilisi +Anna puhelinnumerosi +Sähköpostiosoite +%s haluaa vahvistaa tilisi +Anna sähköpostiosoitteesi +Lue palvelimen %s käyttöehdot +Palvelimen käytännöt +Haluatko ylläpitää omaa palvelinta\? +Palvelimen verkko-osoite +Mikä on palvelimesi osoite\? +Mikä on palvelimesi osoite\? Se on kuin koti kaikille tiedoillesi +Valitse palvelin +Tervetuloa takaisin! +Muokkaa +Vähintään kahdeksan merkkiä +Luo tili +Vie minut kotiin +Aikeissa liittyä olemassa olevalle palvelimelle\? +Yhteisöt +Tiimit +Kaverit ja perhe +Avaa avaruusluettelo +Luo uusi keskustelu tai huone +Anna palautetta +Käytössä: +Istunnon ID: +Katselet jo tätä ketjua! +Päivitetään tietojasi… +Jokin meni vikaan. Tarkista verkkoyhteys ja yritä uudelleen. +Ihmiset +Suosikit +Lukemattomat +Kaikki +Käytä järjestelmän oletusta +Valitse itse +Aseta automaattisesti +Valitse fontin koko +Myönnä oikeus ++ +- %1$s ja %2$d muu
+- %1$s ja %2$d muuta
+%1$s ja %2$s +Pidä keskustelut organisoituna ketjujen avulla +Omat ketjut +Kaikki ketjut +Muuta avaruuden pääosoitetta +Täällä näkyvät uudet pyynnöt ja kutsut. +Ei mitään uutta. +Kutsut +Avaruudet ovat uusi tapa ryhmitellä huoneita ja ihmisiä. Luo avaruus aloittaaksesi. +Ei avaruuksia vielä. +Selvä +Seuraava +Näytä ketjut +Käytä avaruuksia (oikealla alhaalla) nopeammin ja helpommin kuin koskaan aiemmin. +Käytä avaruuksia +Selaa huoneita +Vaihda avaruutta +Luo huone +Aloita keskustelu +Kaikki keskustelut +Kokeile +Anna palautetta napauttamalla oikeaa yläkulmaa. +Anna palautetta +${app_name}in yksinkertaistaminen asetti välilehdet valinnaiseksi. Hallitse välilehtiä oikean yläkulman valikosta. +Tervetuloa uuteen näkymään! +Yksinkertaistettu Element valinnaisilla välilehdillä +Ota uusi asettelu käyttöön +A - Ö +Aktiivisuus +Järjestysperuste +Näytä viimeisimmät +Näytä suodattimet +Asettelun asetukset +%s +\nvaikuttaa hieman tyhjältä. +Ketkä ovat tiimikavereitasi\? +Yksityinen avaruus sinulle ja tiimikavereillesi +Minä ja tiimikaverit +Päivitä avaruus +Vaihda avaruuden kuva ++ +- %1$d valittu
+- %1$d valittu
+Règles de notification Aucune règle de notification définie Aucune passerelle de notification enregistrée -app_id : -push_key : -app_display_name : -session_name : +App ID : +Clé Push : +Nom d’affichage de l’application : +Nom d’affichage de la session : URL : Format : Voix et vidéo @@ -934,11 +934,11 @@Vous utilisez actuellement %1$s pour découvrir et être découvrable par les contacts existants que vous connaissez. Vous n’utilisez actuellement aucun serveur d’identité. Pour découvrir et être découvrable par les contacts existants que vous connaissez, configurez-en un ci-dessous. Adresses électronique découvrables -Les options de découverte apparaîtront quand vous aurez ajouté un courriel. +Les options de découverte apparaîtront quand vous aurez ajouté une adresse courriel. Les options de découverte apparaîtront quand vous aurez ajouté un numéro de téléphone. La déconnexion du serveur d’identité signifie que vous ne pourrez plus être découvrable par les autres utilisateurs et que vous ne pourrez plus inviter d’autres personnes par courriel ou par téléphone. Numéros de téléphone découvrables -Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation +Nous vous avons envoyé un courriel à %s, consultez vos courriels et cliquez sur le lien de confirmation Renseignez l’URL d’un serveur d’identité Impossible de se connecter au serveur d’identité Veuillez renseigner l’URL du serveur d’identité @@ -1066,7 +1066,7 @@L’application ne peut pas créer de compte sur ce serveur d’accueil. \n \nVoulez-vous vous inscrire en utilisant un client web \? -Ce courriel n’est associé à aucun compte. +Cette adresse de courriel n’est associée à aucun compte. Réinitialiser le mot de passe sur %1$s Un courriel de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe. Suivant @@ -1075,7 +1075,7 @@Attention ! Le changement de mot de passe réinitialisera toutes les clés de chiffrement sur toutes vos sessions, rendant l’historique des discussions chiffrées illisible. Configurez la sauvegarde de clés ou exportez vos clés de salon depuis une autre session avant de réinitialiser votre mot de passe. Poursuivre -Ce courriel n’est lié à aucun compte +Ce adresse de courriel n’est liée à aucun compte Vérifiez votre boîte de réception Un courriel de vérification a été envoyé à %1$s. Touchez le lien pour confirmer votre nouveau mot de passe. Après avoir suivi le lien qu’il contient, cliquez ci-dessous. @@ -1089,7 +1089,7 @@ \n \nArrêter le processus de changement \?Définir l’adresse électronique -Définir une adresse électronique pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec votre adresse électronique. +Définir une adresse de courriel pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec cette adresse. Courriel Courriel (facultatif) Suivant @@ -1432,7 +1432,7 @@Message supprimé Afficher les messages supprimés Afficher un remplaçant pour les messages supprimés -Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation +Nous vous avons envoyé un courriel à %s, consultez vos courriels et cliquez sur le lien de confirmation Le code de vérification n’est pas correct. MÉDIA Il n’y a aucun média dans ce salon @@ -1455,7 +1455,7 @@Cette opération n’est pas possible. Le serveur d’accueil est obsolète. Veuillez d’abord configurer un serveur d’identité. Veuillez d’abord accepter les termes du serveur d’identité dans les paramètres. -Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses électronique et des numéros de téléphone hachés. +Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses de courriel et des numéros de téléphone hachés. L’association a échoué. Il n’y a actuellement aucune association avec cet identifiant. Votre serveur d’accueil (%1$s) propose d’utiliser %2$s comme serveur d’identité @@ -1669,7 +1669,7 @@Assurez-vous d\'avoir cliqué sur le lien envoyé par courriel. Supprimer %s \? Numéros de téléphone -Aucune adresse électronique n’a été ajoutée à votre compte +Aucune adresse de courriel n’a été ajoutée à votre compte Adresses électroniques Aucun numéro de téléphone n’a été ajouté à votre compte Filtrer les utilisateurs exclus @@ -1734,7 +1734,7 @@%1$d de %2$d Autoriser Révoquer mon autorisation -Vous avez donné votre autorisation pour envoyer des courriels et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts. +Vous avez donné votre autorisation pour envoyer des adresses de courriel et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts. Envoyer des courriels et des numéros de téléphone Suggestions Utilisateurs connus @@ -2142,7 +2142,7 @@Mentions et mots-clés Notifications par défaut %s dans les paramètres pour recevoir les invitations directement dans ${app_name}. -Lier ce courriel à votre compte +Lier cette adresse de courriel à votre compte Cette invitation à cette espace a été envoyée à %s qui n’est pas associé à votre compte Cette invitation à ce salon a été envoyée à %s qui n’est pas associé à votre compte Tous les salons dans lesquels vous vous trouvez seront affichés sur l’Accueil. @@ -2258,7 +2258,7 @@Question ou sujet du sondage Créer un sondage Sondage -Envoyer des courriels et des numéros de téléphone à %s +Envoyer des adresses de courriel et des numéros de téléphone à %s Vos contacts sont personnels et privés. Pour découvrir des utilisateurs à partir de vos contacts, nous avons besoin de votre permission pour envoyer les informations des contacts à votre serveur d’identité. La session a été déconnectée ! Le salon a été quitté ! @@ -2600,7 +2600,6 @@ \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 @@ -2710,4 +2709,161 @@Activer les conversations privées différées Un Element simplifié avec des onglets optionnels Activer la nouvelle présentation +Les autres utilisateurs en conversations privées ou salons qui peuvent vous parler sont capables de voir la liste complète de vos sessions. +\n +\nCela leur fournit une preuve de confiance que c’est bien avec vous qu\'ils communiquent, mais cela veut également dire qu’ils peuvent voir le nom de la session que vous saisissez ici. +Renommer les sessions +Les sessions vérifiées sont celles qui sont identifiées avec votre mot de passe puis vérifiée, soit à l’aide de votre phrase de sécurité, ou bien par la vérification croisée. +\n +\nCela signifie qu’elles possèdent les clés de chiffrement de vos messages passés, et certifient aux autres utilisateurs avec qui vous communiquez que ces sessions viennent vraiment de vous. +Sessions vérifiées +Les sessions non vérifiées sont celles qui sont identifiées avec votre mot de passe sans avoir fait de vérification croisée. +\n +\nVous devriez tout particulièrement vous assurer de reconnaître ces sessions, car elles pourraient être la preuve d’un usage non autorisé de votre compte. +Sessions non vérifiées +Les sessions inactives sont celles que vous n’avez pas utilisées depuis un certain temps, mais qui continue de recevoir les clés de chiffrements. +\n +\nLa suppression des sessions inactives améliore la sécurité et la performance, et vous aide à identifier si une nouvelle session est douteuse. +Sessions inactives +Soyez conscient que les noms de sessions sont également visibles pour les personnes avec lesquelles vous communiquez. +Les noms de sessions personnalisés peuvent vous aider à reconnaître vos appareils plus facilement. +Nom de la session +Renommer la session +Se déconnecter de cette session +Non vérifiée · Votre session actuelle +Démarrer une diffusion audio +L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil. +Demande au clavier de ne pas mettre à jour les données personnalisées, comme l’historique de la frappe et le dictionnaire composé de ce que vous avez tapé dans vos conversations. Il est possible que certains claviers ne respectent pas ce paramètre. +Clavier incognito +Préfixe le message par (╯°□°)╯︵ ┻━┻ +Diffusion audio +Ouvrir l’écran des outils développeurs +🔒 Vous avez activé le chiffrement vers les sessions vérifiées uniquement, pour tous les salons, dans les paramètres Sécurité. +⚠ Il y a des appareils non vérifiés dans ce salon, ils ne pourront pas déchiffrer vos messages envoyés. +Ne jamais envoyer de messages chiffrés aux sessions non vérifiées dans ce salon. +Compris +Souligner le texte +Barrer le texte +Mettre en italique +Mettre en gras +Enregistre le nom du client, sa version, et son URL pour retrouvez vos sessions plus facilement dans le gestionnaire de sessions. +Activer l’enregistrement des informations du client +Ayez une meilleur visibilité et plus de contrôle sur toutes vos sessions. +Activer le nouveau gestionnaire de session +Système d’exploitation +Modèle +Navigateur +URL +Version +Nom +Application +Recevoir les notifications push sur cette session. +Notifications push +Vérifiez votre session actuelle pour découvrir le statut de vérification de cette session. +Status de vérification inconnu +Activer : +Identifiant de session : +Quelque chose s’est mal passé. Vérifiez votre connexion réseau et réessayez. +Accorder la permission +${app_name} a besoin d’une permission pour afficher les notifications. +\nVeuillez accorder la permission. +${app_name} a besoin de la permission pour afficher les notifications. Les notifications peuvent afficher vos messages, vos invitations, etc. +\n +\nVeuillez autoriser l’accès sur la prochaine fenêtre pour pouvoir voir des notifications. +Essayer l’éditeur de texte formaté (le mode texte brut arrive bientôt) +Activer l’éditeur de texte formaté +Vérifiez l’origine de ce code. En appairant un appareil, vous lui fournissez un accès complet à votre compte. +Confirmer +Réessayez +Pas de correspondance \? +Connexion +Connexion à l’appareil +Scanner le QR code +Connexion sur un appareil mobile \? +Afficher le QR code sur cet appareil +Sélectionnez « Scanner le QR code » +Démarrez à l’écran de connexion +Sélectionnez « Se connecter avec un QR code » +Démarrez à l’écran de connexion +Sélectionnez « Afficher le QR code » +Allez dans Réglages -> Confidentialité et sécurité +Ouvrez l’application sur votre autre appareil +Le serveur d’accueil ne prend pas en charge la connexion avec un QR code. +La connexion a été annulée sur l’autre appareil. +Ce QR code est invalide. +L’autre appareil doit être connecté. +L’autre appareil est déjà connecté. +La configuration de la messagerie sécurisée a rencontré un problème de sécurité. Un des éléments suivants pourrait être compromis : votre serveur d’accueil ; votre connexion Internet ; votre (vos) appareil(s) ; +La requête a échoué. +La requête a été refusée sur l’autre appareil. +L’appairage n’a pas été effectué dans le temps imparti. +L’appairage avec cet appareil n’est pas pris en charge. +Échec de la connexion +Vérifiez votre appareil connecté, le code ci-dessous devrait y être affiché. Confirmez que le code ci-dessous correspond à celui de l’autre appareil : +Connexion sécurisée établie +Scannez le QR code ci-dessous avec l’appareil qui n’est pas connecté. +Utilisez votre appareil connecté pour scanner le QR code ci-dessous : +Se connecter avec un QR code +Utilisez l’appareil photo de cet appareil pour scanner le QR code affiché sur votre autre appareil : +Scanner le QR code +3 +2 +1 +Pouvoir enregistrer et envoyer une diffusion audio dans l’historique du salon. +Activer la diffusion audio (en cours de développement) +Vous pouvez utiliser cet appareil pour connecter un appareil mobile ou un client web avec un QR code. Il y a deux façons de le faire : +Se connecter avec un QR code +Scanner le QR code +Mise en mémoire tampon +Mettre en pause la diffusion audio +Lire ou continuer la diffusion audio +Arrêter l’enregistrement de la diffusion audio +Mettre en pause l’enregistrement de la diffusion audio +Continuer l’enregistrement de la diffusion audio +Direct +Sélectionner des sessions +Contact +Appareil photo +Position +Sondages +Diffusion audio +Pièces jointes +Autocollants +Galerie photo +Tout désélectionner +Tout sélectionner ++ +- %1$d sélectionné
+- %1$d sélectionnés
+Basculer en mode plein écran +Formatage de texte +Vous êtes déjà en train de réaliser une diffusion audio. Veuillez terminer votre diffusion audio actuelle pour en démarrer une nouvelle. +Une autre personne est déjà en train de réaliser une diffusion audio. Attendez que sa diffusion audio soit terminée pour en démarrer une nouvelle. +Vous n’avez pas les permissions requises pour démarrer une nouvelle diffusion audio dans ce salon. Contactez un administrateur du salon pour mettre-à-jour vos permissions. +Impossible de commencer une nouvelle diffusion audio +Avance rapide de 30 secondes +Retour rapide de 30 secondes +Les sessions vérifiées sont toutes celles qui utilisent ce compte après avoir saisie la phrase de sécurité ou confirmé votre identité à l’aide d’une autre session vérifiée. +\n +\nCela veut dire qu’elles disposent de toutes les clés nécessaires pour lire les messages chiffrés, et confirment aux autres utilisateurs que vous faites confiance à cette session. ++ +- Déconnecter %1$d session
+- Déconnecter %1$d sessions
+Déconnecter +%1$s restant +a créé un sondage. +a envoyé un autocollant. +a envoyé une vidéo. +a envoyé une image. +envoyer un message vocal. +a envoyé un fichier audio. +a envoyé un fichier. +En réponse à +Masquer l’adresse IP +Afficher l’adresse IP +Citation de +Réponse à %s +Modification \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index cac0a2eb5d..1dd2134b90 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -569,7 +569,7 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlésAlapszintű diagnosztika nem talált hibát. Ha még mindig nem kapsz értesítéseket, kérlek küldj egy hiba jegyet amivel segítheted a hibakeresésünket. Egy vagy több teszt is sikertelen volt, próbáld ki a javasolt javítást, javításokat. Egy vagy több teszt sikertelenül végződött, kérlek küldj egy hibabejelentést ami segít nekünk a problémát kivizsgálni. -Rendszer beállítások. +Rendszerbeállítások. Az értesítések engedélyezve vannak a rendszerbeállításokban. Az értesítések tiltva vannak a rendszerbeállításokban. Kérlek ellenőrizd a rendszerbeállításokat. @@ -582,7 +582,7 @@ Kérlek ellenőrizd a fiókbeállításokat.Munkamenet beállítások. Az értesítések engedélyezve vannak ezen az munkameneten. Az értesítések tiltva vannak ezen a munkameneten. Kérlek ellenőrizd a ${app_name} beállításokat. -Engedélyez +Engedélyezés Play Szolgáltatások ellenőrzése Google Play Services APK elérhető és a legújabb verziójú. "${app_name} a Google Play Services-t használja a „push” értesítések fogadásához, de úgy tűnik az nincs megfelelően beállítva: @@ -824,18 +824,18 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze \nMohon periksa pengaturan akun anda.Már nézed ezt a szobát! Általános Beállítások -Biztonság & Adatvédelem +Biztonság és adatvédelem „Push” szabályok „Push” szabályok nincsenek „Push” átjárók nincsenek regisztrálva -app_id: -push_key: -app_display_name: -session_name: +Alk azon: +Push kulcs: +Alk. képernyő név: +Munkamenet képernyő név: Url: Formátum: -Hang & Videó -Segítség & Névjegy +Hang és videó +Súgó és névjegy Token regisztrálása Javaslat tétel A javaslatodat kérlek ír le alulra. @@ -897,7 +897,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókezeAmint hozzáadtál egy telefonszámot megjelenik a felderítési beállítási lehetőség. Az azonosítási szerverről való lecsatlakozással nem leszel mások által megtalálható és másokat sem tudsz meghívni e-mail címmel vagy telefonszámmal. Felderíthető telefonszámok -Megerősítő levelet küldtünk ide: %s, ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra +E-mailt küldtünk ide: %s, ellenőrizd és kattints a megerősítő hivatkozásra Add meg az azonosítási szerver URL-jét Az azonosítási szerverhez nem lehet csatlakozni Kérlek add meg az azonosítási szerver url-jét @@ -1391,7 +1391,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókezeFelhasználókat nem tudtuk meghívni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra. Üzenet eltávolítva Helykitöltő mutatása a törölt szövegek helyett -Megerősítő levelet küldtünk ide: %s, először ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra +E-mailt küldtünk ide: %s, először ellenőrizd és kattints a megerősítő hivatkozásra MÉDIA FÁJLOK %1$s itt: %2$s @@ -2611,7 +2611,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókezeAsztali 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 @@ -2710,4 +2709,161 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókezeKésleltetett közvetlen üzenetek engedélyezése Egyszerűsített Element opcionálisan lapokkal Új kinézet engedélyezése +Más felhasználók akikkel közvetlenül vagy szobában beszélgetsz látják a teljes listát a munkameneteidről. +\n +\nEzzel ők biztosak lehetnek abban, hogy ténylegesen veled beszélgetnek. Ez azt is jelenti, hogy látják a munkamenet nevét amit itt megadsz. +Ellenőrzött munkamenetbe a neveddel és jelszavaddal léptek be és ellenőrizve lett vagy a biztonsági jelmondattal vagy másik munkamenetből. +\n +\nEz azt jelenti, hogy tartalmazzák a titkosítási kulcsokat az régi üzenetekhez, és biztosítja a többieket a kommunikációban, hogy ezt a munkamenetet tényleg te használod. +Aláhúzott +Áthúzott +Dőlt +Félkövér +Kliens neve, verziója és url felvétele a munkamenet könnyebb azonosításához a munkamenet kezelőben. +Kliens információ felvételének engedélyezése +Jobb áttekintés és felügyelet a munkamenetek felett. +Új munkamenet kezelő engedélyezése +Munkamenet átnevezése +Hitelesített munkamenetek +Az ellenőrizetlen munkamenetek azok amikre a felhasználói neveddel és jelszavaddal léptek be de nem lett ellenőrizve. +\n +\nMindenképpen győződj meg arról, hogy felismered ezeket a munkameneteket mert lehet, hogy illetéktelenül használják a fiókodat. +Ellenőrizetlen munkamenetek +Az inaktív munkamenetek azok amiket egy ideje nem használtál, de továbbra is megkapják a titkosítási kulcsokat. +\n +\nA nem aktív munkamenetek törlésével növelhető a biztonság és a sebesség valamint könnyebb lesz felismerni a gyanús munkameneteket. +Nem aktív munkamenetek +Fontos, hogy a munkamenet neve a kommunikációban résztvevők számára látható. +Az egyedi munkamenet név segíthet az eszköz könnyebb felismerésében. +Munkamenet neve +Munkamenet átnevezése +Operációs rendszer +Modell +Böngésző +URL +Verzió +Név +Alkalmazás +Push értesítések fogadása ebben a munkamenetben. +Push értesítések +Kijelentkezés ebből a munkamenetből +Ellenőrizetlen · A jelenlegi munkameneted +Ellenőrizd a jelenlegi munkamenetedet, hogy ismert állapotba kerüljön. +Ismeretlen ellenőrzési státusz +Hang közvetítés indítása +A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni. +Utasítja a billentyűzetet, hogy ne mentsen személyre szabott adatokat, mint előzmények vagy szótár abból amit a beszélgetésekben írsz. Vedd figyelembe, hogy nem minden billentyűzet veszi ezt figyelembe. +Inkognitó billentyűzet +(╯°□°)╯︵ ┻━┻ -t tesz a szöveg elejére +Hang közvetítés +Engedélyezve: +Munkamenet azon.: +Valami nem sikerült. Kérlek ellenőrizd a hálózati kapcsolatot és próbáld újra. +A fejlesztői eszközök képernyő megnyitása +🔒 Bekapcsoltad a Biztonsági beállításoknál, hogy csak ellenőrzött munkamenetek számára legyen titkosítva az üzenet bármely szobában. +⚠ Ellenőrizetlen eszközök vannak a szobában, ezek nem fogják tudni visszafejteni az általad küldött üzeneteket. +Sose küldj titkosított üzenetet ellenőrizetlen munkamenetbe ebből a munkamenetből ebben a szobában. +Engedély megadása +${app_name} alkalmazásnak értesítések megjelenítéséhez engedélyre van szüksége. +\nKérjük, adj rá engedélyt. +${app_name} alkalmazásnak szüksége van engedélyre az értesítések megjelenítéséhez. Az értesítés megjelenítheti az üzenetet, meghívót, stb. +\n +\nA következő felugró ablakban adj rá engedélyt, hogy az értesítések megjelenhessenek. +Próbálja ki az új szövegbevitelt (hamarosan érkezik a sima szöveges üzemmód) +Vizuális szerkesztő engedélyezése +Értem +Nem egyezik\? +Bejelentkeztetés +Mobil eszközzel jelentkezel be\? +Kezd a bejelentkező képernyőn +Kezd a bejelentkező képernyőn +Nézd meg a már bejelentkezett eszközödet, az alábbi kódot kell megjelenítenie. Erősítsd meg, hogy az alábbi kód megegyezik a másik eszközön láthatóval: +Használd a már belépett eszközt az alábbi QR kód beolvasásához: +Ezzel az eszközzel, QR kód segítségével, bejelentkezhetsz mobil és webes munkamenetbe. Két lehetőséged is van: +Győződj meg a kód eredetéről. Az eszközök összekötésével esetleg valakinek teljes hozzáférést adhatsz a fiókodhoz. +Megerősítés +Próbáld újra +Csatlakozás az eszközhöz +QR kód beolvasása +QR kód megjelenítése ezen az eszközön +Válaszd ezt: „QR kód beolvasása” +Válaszd ezt: „Belépés QR kóddal” +Válaszd ezt: „QR kód megjelenítése” +Menj a Beállítások -> Biztonság és Adatvédelem +Nyisd meg az alkalmazást a másik eszközön +A kérést elutasították a másik eszközön. +Az összekötés az elvárt időn belül nem fejeződött be. +Összekötés ezzel az eszközzel nem támogatott. +Kapcsolat sikertelen +Biztonságos kapcsolat beállítva +A kijelentkezett eszközzel olvasd be a QR kódot alább. +Belépés QR kóddal +Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására: +QR kód beolvasása +3 +2 +1 +Belépés QR kóddal +QR kód beolvasása +A matrix szerver nem támogatja más eszköz bejelentkeztetését. +A bejelentkezés a másik eszköz által meg lett szakítva. +QR kód érvénytelen. +A másik eszköznek már bejelentkezve kell lennie. +A másik eszköz már bejelentkezett. +Biztonsági probléma lépett fel a biztonságos üzenetküldés beállításánál. Valamihez illetéktelenül fértek hozzá: Matrix szervered, Internet kapcsolatod, Eszközöd, +A kérés sikertelen. +Hang közvetítés felvételéhez és a szoba idővonalára küldéséhez. +Hang közvetítés engedélyezése (aktív fejlesztés alatt) +Pufferelés +Hang közvetítés szüneteltetése +Hang közvetítés lejátszása vagy lejátszás folytatása +Hang közvetítés felvétel leállítása +Hang közvetítés felvétel megállítása +Hang közvetítés felvétel újraindítása +Élő +Munkamenetek kiválasztása +Névjegy +Kamera +Földrajzi helyzet +Szavazások +Hang közvetítés +Mellékletek +Matricák +Fénykép könyvtár +Semmit nem jelöl ki +Mindet kijelöli ++ +- %1$d kiválasztva
+- %1$d kiválasztva
+Teljes képernyő váltás +Mindenhol ellenőrzött munkamenetek vannak ahol ezt a fiókot használva megadtad a jelmondatodat vagy egy másik már hitelesített munkamenetből megerősítetted az identitásodat. +\n +\nEz azt jelenti, hogy a titkosított üzenetek visszafejtéséhez rendelkezel a kulcsokkal és megerősíted a többiek felé, hogy megbízol a munkamenetben. ++ +- Kijelentkezés %1$d munkamenetből
+- Kijelentkezés %1$d munkamenetből
+Kijelentkezés +Szöveg formázás +Egy hang közvetítés már folyamatban van. Először fejezze be a jelenlegi közvetítést egy új indításához. +Valaki már elindított egy hang közvetítést. Várd meg a közvetítés végét az új indításához. +Nincs jogosultságod hang közvetítést indítani ebben a szobában. Vedd fel a kapcsolatot a szoba adminisztrátorával a szükséges jogosultság megszerzéséhez. +Az új hang közvetítés nem indítható el +30 másodperccel előre +30 másodperccel vissza +visszavan: %1$s +szavazás elkészítve. +matrica elküldve. +videót küldött. +kép elküldve. +hang üzenet elküldve. +hangfájl elküldve. +fájl elküldve. +Válaszolva erre +IP címek elrejtése +IP címek megjelenítése +Idézet +Válasz erre: %s +Szerkesztés \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 7b103a9131..ce9e524067 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -95,7 +95,7 @@Nama pengguna dan/atau kata sandi salah Verifikasi alamat email gagal: pastikan tautan yang termuat di email telah diklik JSON amburadul -Tidak berisi JSON yang sah +Tidak berisi JSON yang absah Pengajuan yang dikirimkan terlalu banyak Panggilan Video Masuk Panggilan Suara Masuk @@ -183,8 +183,8 @@Tampilkan info aplikasi dalam pengaturan sistem. Info aplikasi Suara pemberitahuan -Perbolehkan pemberitahuan untuk akun ini -Perbolehkan pemberitahuan untuk perangkat ini +Aktifkan pemberitahuan untuk akun ini +Aktifkan pemberitahuan untuk sesi ini Pesan yang berisikan nama layarku Pesan berisikan nama layarku Pesan percakapan empat mata @@ -216,7 +216,7 @@Tidak ada user_id dalam permohonan. Ruang %s tidak terlihat. Ada parameter penting yang hilang. -Tambahkan apps Matrix +Tambahkan aplikasi Matrix Gunakan kamera bawaan Anda menambahkan perangkat baru \'%s\', yang sedang meminta kunci enkripsi. Perangkat Anda yang belum terverifikasi \'%s\' sedang meminta kunci enkripsi. @@ -234,7 +234,7 @@Keluarkan pengguna dengan id berikut Ubah nama panggilan layar Anda Mati/Nyalakan markdown -Untuk memperbaiki kepengurusan Apps Matrix +Untuk memperbaiki kepengurusan Aplikasi Matrix Mati Berisik Pesan terenkripsi @@ -394,7 +394,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Perbolehkan Pengaturan Perangkat. -Pemberitahuan diperbolehkan untuk perangkat ini. +Pemberitahuan diperbolehkan untuk sesi ini. Notifikasi tidak diaktifkan pada sesi ini. \nMohon periksa pengaturan ${app_name}. Perbolehkan @@ -414,7 +414,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \n%1$sMulai ketika menyalakan perangkat Layanan akan dimulai ketika perangkat dinyalakan kembali. -Layanan tidak akan mulai ketika perangkat dinyalakan kembali, Anda tidak akan menerima pemberitahuan hingga Anda membuka ${app_name}. +Layanan tidak akan mulai ketika perangkat dinyalakan kembali, Anda tidak akan menerima pemberitahuan sampai Anda membuka ${app_name}. Perbolehkan memulai ketika perangkat dinyalakan Periksa halangan di balik layar Larangan latar belakang dinonaktifkan untuk ${app_name}. Percobaan ini sebaiknya dijalankan menggunakan jaringan data ponsel (bukan WiFi). @@ -442,7 +442,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Urgensi pemberitahuan lewat kejadian Pengaturan Sesukanya. Perhatikan bahwa sebagian jenis pesan tersetel diam (mengeluarkan pemberitahuan tanpa suara). -Sebagian pemberitahuan dimatikan dalam aturan Anda. +Sebagian pemberitahuan dimatikan dalam pengaturan Anda. [%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] @@ -450,7 +450,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. [%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 -Apabila perangkat tidak sedang diisi atau dipergunakan dengan layar dimatikan, perangkat masuk mode Doze. Ini akan menghalangi aplikasi mengakses jaringan dan menunda tugas, sinkronisasi, dan alarm standar. +Apabila perangkat tidak sedang diisi atau dipergunakan dengan layar dimatikan, perangkat masuk mode tidur. Ini akan menghalangi aplikasi mengakses jaringan dan menunda tugas, sinkronisasi, dan alarm standar. Abaikan Optimisasi Kelola Pemberitahuan Berisik Kelola Pemberitahuan Panggilan @@ -459,7 +459,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Pengelolaan Kunci Kriptografi Pratinjau tautan dalam obrolan apabila homeserver mendukung fitur ini. Kirim pemberitahuan mengetik -Beritahu pengguna lain bahwa Anda sedang mengetik. +Beri tahu pengguna lain bahwa Anda sedang mengetik. Format markdown Format pesan menggunakan sintaks markdown sebelum dikirim. Ini mengizinkan format lanjutan seperti menggunakan tanda bintang untuk menunjukkan teks miring. Tunjukkan tanda telah dibaca @@ -565,7 +565,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Mengubah alamat utama untuk ruangan ini Mengubah avatar ruangan Mengubah widget -Beritahu semuanya +Beri tahu semuanya Menghapus pesan yang dikirim dari yang lain Ubah pengaturan Peran bawaan @@ -610,11 +610,11 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Meminta untuk konfirmasi sebelum memulai panggilan Cegah panggilan tidak disengaja -Tidak sah, tidak ada kredensial otentikasi yang valid +Tidak sah, tidak ada kredensial otentikasi yang absah Kesalahan SSL. Kesalahan SSL: identitas peer belum diverifikasi. Tidak dapat mencapai homeserver pada URL ini, silakan periksa -Ini bukan alamat server Matrix yang valid +Ini bukan alamat server Matrix yang absah Nomor telepon ini sudah ditentukan. Masuk dengan single sign-on Gunakan sebagai bawaan dan jangan tanya lagi @@ -676,10 +676,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Batalkan Tidak Ada 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 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 ke ruangan. %1$s telah mencegah para tamu untuk bergabung ke ruangan. Anda telah mencegah para tamu untuk bergabung ke ruangan. @@ -816,7 +816,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.🎉 Semua server dilarang untuk berpartisipasi! Ruangan ini tidak lagi dapat digunakan. Tidak ada berubahan. Nomor telepon -Tidak ada email yang ditambahkan ke akun Anda +Tidak ada alamat email yang ditambahkan ke akun Anda Surel • Server yang cocok dengan literal IP sekarang dilarang. • Server yang cocok dengan %s sekarang dilarang. @@ -962,16 +962,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Pengaturan akun Anda dapat mengelola notifikasi di %1$s. Harap dicatat bahwa pemberitahuan sebutan & kata kunci tidak tersedia dalam ruangan terenkripsi di ponsel. -Beritahu saya untuk +Beri tahu saya untuk Putar suara rana Pilih Sumber media bawaan Pilih Kompresi bawaan Media -Kelola email dan nomor telepon yang ditautkan ke akun Matrix Anda +Kelola alamat email dan nomor telepon yang ditautkan ke akun Matrix Anda Email dan nomor telepon -Sandi tidak valid +Kata sandi tidak absah Sandi Aktifkan \'Izinkan integrasi\' di Pengaturan untuk melakukan ini. Integrasi dinonaktifkan @@ -997,7 +997,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.- - %d detik
Anda tidak akan diberitahu tentang pesan masuk saat aplikasi berada di latar belakang. +Anda tidak akan diberi tahu tentang pesan masuk saat aplikasi berada di latar belakang. Tidak ada sinkronisasi latar belakang ${app_name} akan disinkronkan di latar belakang secara berkala pada waktu yang tepat (dapat dikonfigurasi). \nIni akan memengaruhi penggunaan radio dan baterai, dan ada juga pemberitahuan yang ditampilkan permanen menyatakan bahwa ${app_name} sedang mendengarkan peristiwa. @@ -1021,7 +1021,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Kata kunci tidak boleh diawali dengan \'.\' Tambahkan kata kunci baru Kata kunci Anda -Beritahu saya untuk +Beri tahu saya untuk Lainnya Sebutan dan Kata Kunci Notifikasi Bawaan @@ -1035,16 +1035,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Panggilan video dengan %s Panggilan berdering… Space -Kami mengirimi Anda email konfirmasi ke %s, mohon periksa email Anda dan klik tautan konfirmasi -Opsi penemuan akan muncul setelah Anda menambahkan email. +Kami mengirim Anda email konfirmasi ke %s, mohon periksa email Anda dan klik tautan konfirmasi +Opsi penemuan akan muncul setelah Anda menambahkan sebuah alamat email. Memutuskan sambungan dari server identitas Anda akan membuat Anda tidak dapat ditemukan oleh pengguna lain dan Anda tidak akan dapat mengundang orang lain melalui email atau nomor telepon. Kirim email dan nomor telepon -Anda telah memberikan persetujuan untuk mengirim email dan nomor telepon ke server identitas ini untuk menemukan pengguna lain dari kontak Anda. +Anda telah memberikan persetujuan untuk mengirim alamat email dan nomor telepon ke server identitas ini untuk menemukan pengguna lain dari kontak Anda. Anda sedang berbagi email atau nomor telepon di server identitas %1$s. Anda harus menyambungkan kembali ke %2$s untuk berhenti membagikannya. Setujui Persyaratan Layanan server identitas (%s) agar Anda dapat ditemukan melalui email atau nomor telepon. -Kami mengirimi Anda email konfirmasi ke %s, periksa email Anda dan klik tautan konfirmasi +Kami mengirim Anda sebuah email ke %s, periksa email Anda dan klik tautan konfirmasi Setel ulang sandi di %1$s -Email ini tidak terkait dengan akun apa pun. +Alamat email ini tidak terkait dengan akun apa pun. Aplikasi tidak dapat membuat akun di homeserver ini. \n \nApakah Anda ingin mendaftar menggunakan klien web\? @@ -1080,7 +1080,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Sama seperti email, akun memiliki satu tempat, tetapi Anda dapat berkomunikasi dengan siapa saja Pilih server Mulai -Luaskan & sesuaikan pengalaman Anda +Tingkatkan & sesuaikan pengalaman Anda Ini adalah percakapan Anda. Miliki percakapan Anda. Jaga percakapan tetap pribadi dengan enkripsi Chat dengan orang-orang secara langsung atau dalam grup @@ -1148,9 +1148,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.%1$s: %2$s %3$s Tambahkan tab terdedikasi untuk notifikasi yang belum dibaca di layar utama. File ini terlalu besar untuk diupload. -Cadangan mempunyai tanda tangan yang tidak valid dari sesi %s yang belum diverifikasi -Cadangan mempunyai tanda tangan yang tidak valid dari sesi %s yang terverifikasi -Cadangan mempunyai tanda tangan yang valid dari sesi %s yang belum diverifikasi +Cadangan mempunyai tanda tangan yang tidak absah dari sesi %s yang belum diverifikasi +Cadangan mempunyai tanda tangan yang tidak absah dari sesi %s yang terverifikasi +Cadangan mempunyai tanda tangan yang absah dari sesi %s yang belum diverifikasi Mengirim gambar mini (%1$s / %2$s) - %d pengguna telah membaca
@@ -1240,10 +1240,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Suara & Video Format: Url: -session_name: -app_display_name: -push_key: -app_id: +Nama Tampilan Sesi: +Nama Tampilan Aplikasi: +Kunci Dorongan: +ID Aplikasi: Tidak ada gateway dorong terdaftar Tidak ada aturan push yang ditentukan Aturan Push @@ -1278,8 +1278,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Hapus Cadangan Memeriksa status cadangan Menghapus cadangan… -Cadangan mempunyai tanda tangan yang valid dari sesi %s yang terverifikasi. -Cadangan mempunyai tanda tangan yang valid dari sesi ini. +Cadangan mempunyai tanda tangan yang absah dari sesi %s yang terverifikasi. +Cadangan mempunyai tanda tangan yang absah dari sesi ini. Cadangan mempunyai tanda tangan dari sesi tidak dikenal dengan ID %s. Kunci Anda tidak dicadangkan dari sesi ini. Cadangan Kunci belum aktif di sesi ini. @@ -1342,7 +1342,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Ekspor kunci secara manual (Lanjutan) Mulai menggunakan Cadangan Kunci -Pesan di ruangan terenkripsi diamankan dengan enkripsi ujung-ke-ujung. Hanya Anda dan penerima memiliki kunci untuk membaca pesan-pesan ini. + Pesan di ruangan terenkripsi diamankan dengan enkripsi ujung ke ujung. Hanya Anda dan penerima memiliki kunci untuk membaca pesan ini. \n \nCadangkan kunci Anda dengan aman untuk menghindari kehilangan kunci Anda. Jangan pernah kehilangan pesan terenkripsi @@ -1351,7 +1351,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Mohon masukkan frasa sandi Frasa sandi tidak cocok Buat frasa sandi -Tidak menemukan APK Layanan Google Play yang valid. Notifikasi mungkin tidak berkerja dengan seharusnya. +Tidak menemukan APK Layanan Google Play yang absah. Notifikasi mungkin tidak bekerja dengan seharusnya. Keamanan & Privasi Preferensi Umum @@ -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 bawaan di ruangan & Pesan Langsung privat. +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. @@ -1446,7 +1446,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \nKunci Privat di perangkat.Tanda Tangan Silang Sesi baru Anda telah diverifikasi. Ini memiliki akses ke pesan terenkripsi Anda, dan pengguna lain akan melihatnya sebagai tepercaya. -Pesan dengan pengguna ini dienkripsi ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga. +Pesan dengan pengguna ini dienkripsi ujung ke ujung dan tidak dapat dibaca oleh pihak ketiga. Bandingkan kode dengan yang ditampilkan di layar pengguna lain. Bandingkan emoji yang unik, dan pastikan mereka muncul di urutan yang sama. Supaya aman, lakukan secara langsung atau gunakan cara lain untuk berkomunikasi. @@ -1454,8 +1454,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Aktifkan enkripsi Ketika diaktifkan, enkripsi tidak dapat dinonaktifkan. Pesan yang dikirim di ruangan terenkripsi tidak dapat dilihat oleh servernya, hanya anggota ruangan. Mengaktifkan enkripsi mungkin mencegah banyaknya bot dan jembatan bekerja dengan seharusnya. Aktifkan enkripsi\? -Anda tidak memiliki izin untuk mengaktifkan enkripsi ujung-ke-ujung di ruangan ini. -Aktifkan enkripsi ujung-ke-ujung… +Anda tidak memiliki izin untuk mengaktifkan enkripsi ujung ke ujung di ruangan ini. +Aktifkan enkripsi ujung ke ujung… Editor pesan Linimasa Mengirim emote yang dicantum berwarna pelangi @@ -1490,14 +1490,14 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Lebih banyak Pelajari lebih lanjut Keamanan -Pesan di ruangan ini dienkripsi ujung-ke-ujung. + Pesan di ruangan ini dienkripsi secara ujung ke ujung. \n \nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima memiliki kunci unik untuk mengakses mereka. -Pesan di ruangan ini dienkripsi ujung-ke-ujung. + \n• Administrator server Anda telah menghilangkan akses Anda untuk keamanan.Pesan di ruangan ini dienkripsi secara ujung ke ujung. \n \nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima memiliki kunci unik untuk mengakses mereka. -Pesan ini tidak terenkripsi secara ujung-ke-ujung. -Pesan di ruangan ini tidak terenkripsi secara ujung-ke-ujung. +Pesan ini tidak terenkripsi secara ujung ke ujung. +Pesan di ruangan ini tidak terenkripsi secara ujung ke ujung. Menunggu untuk %s… Diverifikasi %s Verifikasi %s @@ -1587,8 +1587,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Anda telah keluar Dilihat oleh -Tidak dapat menemukan homeserver yang valid. Mohon cek pengenal Anda -Ini bukan pengenal pengguna yang valid. Format yang diharapkan: \'@pengguna:homeserver.org\' +Tidak dapat menemukan homeserver yang absah. Mohon cek pengenal Anda +Ini bukan pengenal pengguna yang absah. Format yang diharapkan: \'@pengguna:homeserver.org\' Jika Anda tidak tahu kata sandi Anda, kembali untuk mengatur ulang. ID Matrix Jika Anda membuat akun di sebuah homeserver, gunakan ID Matrix Anda (mis. @pengguna:domain.com) dan kata sandi dibawah. @@ -1616,7 +1616,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Kata sandi Nama pengguna Nama pengguna atau email -Nomor telepon kelihatannya tidak valid. Mohon dicek lagi +Nomor telepon kelihatannya tidak absah. Mohon dicek lagi Nomor telepon internasional harus mulai dengan \'+\' Mohon menggunakan format internasional (nomor telepon harus mulai dengan \'+\') Lanjut @@ -1632,7 +1632,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Lanjut Email (opsional) Atur sebuah email untuk memulihkan akun Anda. Nantinya, Anda dapat mengizinkan orang yang Anda tahu untuk menemukan Anda dari email secara opsional. +Atur sebuah alamat email untuk memulihkan akun Anda. Nantinya, Anda dapat mengizinkan orang yang Anda tahu untuk menemukan Anda dari email ini secara opsional. Atur alamat email Kata sandi Anda belum diubah. \n @@ -1646,9 +1646,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Ketuk tautan untuk mengkonfirmasi kata sandi baru Anda. Setelah Anda mengikuti petunjuk yang ada di tautan, klik bawahnya. Email verifikasi terkirim ke %1$s. Cek kotak masuk Anda -Email ini tidak tertaut dengan akun apa pun +Alamat email ini tidak tertaut dengan akun apa pun Lanjut -Mengubah kata sandi Anda akan mengatur ulang kunci enkripsi ujung-ke-ujung pada semua sesi Anda, yang akan membuat riwayat obrolan terenkripsi tidak dapat dibaca. Atur Cadangan Kunci atau ekspor kunci ruangan Anda dari sesi lain sebelum mengatur ulang kata sandi Anda. +Mengubah kata sandi Anda akan mengatur ulang kunci enkripsi ujung ke ujung pada semua sesi Anda, yang akan membuat riwayat obrolan terenkripsi tidak dapat dibaca. Atur Cadangan Kunci atau ekspor kunci ruangan Anda dari sesi lain sebelum mengatur ulang kata sandi Anda. Peringatan! Kata sandi baru Publik Space privat untuk Anda & tim Anda Saya dan tim saya -Space yang privat untuk mengorganisir ruangan Anda +Space yang privat untuk mengelola ruangan Anda Saya saja Pastikan orang yang tepat memiliki akses ke %s. Dengan siapa Anda bekerja\? @@ -1803,9 +1803,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Alat Pengembang Ruangan publik Lihat laporan dibaca -Jangan beritahu -Beritahu tanpa suara -Beritahu dengan suara +Jangan beri tahu +Beri tahu tanpa suara +Beri tahu dengan suara Pesan tidak terkirim karena kesalahan Tidak dicentang Dicentang @@ -1814,7 +1814,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Ruangannya belum dibuat. Batalkan pembuatan ruangan\? Tautannya cacat Kode QR tidak dipindai! -Kode QR tidak valid (URI tidak valid)! +Kode QR tidak absah (URI tidak absah)! Tidak dapat membuat pesan langsung dengan Anda sendiri! Bagikan melalui teks Tidak dapat mencari ruangan ini. Pastikan ruangannya sudah ada. @@ -1875,7 +1875,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Anda tidak dapat mengakses pesan ini karena pengirim telah sengaja tidak mengirim kuncinya Anda tidak dapat mengakses pesan ini karena sesi Anda tidak dipercayai oleh pengirim Anda tidak dapat mengakses pesan ini karena Anda telah diblokir oleh pengirim -Karena enkripsi ujung-ke-ujung, Anda mungkin harus menunggu untuk pesan dari seseorang untuk datang karena kunci enkripsinya tidak dikirim secara benar ke Anda. +Karena adanya enkripsi ujung ke ujung, Anda mungkin harus menunggu untuk pesan dari seseorang untuk datang karena kunci enkripsinya tidak dikirim secara benar ke Anda. Menunggu untuk pesan ini, mungkin membutuhkan beberapa waktu Anda tidak dapat mengakses pesan ini Atur avatar @@ -1910,7 +1910,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Izin pengguna belum diberikan. Tidak ada asosiasi saat ini dengan pengenal ini. Asosiasi telah gagal. -Untuk pricvasi Anda, ${app_name} hanya mendukung pengiriman email pengguna yang telah di-hash dan nomor telepon. +Demi privasi Anda, ${app_name} hanya mendukung pengiriman email pengguna dan nomor telepon yang telah di-hash. Mohon terima ketentuan server identitas ini di pengaturan. Mohon konfigurasi server identitas. Server identitas ini telah usang. ${app_name} hanya mendukung API V2. @@ -1946,7 +1946,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Beri nama untuk melanjutkan. Gagal untuk memvalidasi PIN, mohon ketuk yang baru. %s di Pengaturan untuk menerima undangan secara langsung di ${app_name}. -Tautkan email ini ke akun Anda +Tautkan alamat email ini ke akun Anda Undangan space ini telah dikirim ke %s yang tidak diasosiasikan dengan akun Anda Undangan ruangan ini telah dikirim ke %s yang tidak diasosiasikan dengan akun Anda Periksa ulang tautan ini @@ -2001,7 +2001,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Memeriksa kunci cadangan (%s) Memeriksa kunci cadangan Mohon masukkan sebuah kunci pemulihan -Bukan kunci pemulihan yang valid +Bukan kunci pemulihan yang absah Gunakan File Masukkan %s Anda untuk melanjutkan Verifikasi diri Anda dan lainnya untuk tetap membuat pesan Anda aman @@ -2027,8 +2027,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Anda membuat dan mengatur ruangan ini. %s membuat dan mengatur ruangan ini. Enkripsi tidak diaktifkan -Pesan di obrolan ini dienkripsi secara ujung-ke-ujung. -Pesan di ruangan ini dienkripsi secara ujung-ke-ujung. Pelajari lebih lanjut & verifikasi pengguna di profil mereka. +Pesan di obrolan ini dienkripsi secara ujung ke ujung. +Pesan di ruangan ini dienkripsi secara ujung ke ujung. Pelajari lebih lanjut & verifikasi pengguna di profil mereka. Enkripsi diaktifkan Jika Anda batalkan, Anda mungkin kehilangan pesan terenkripsi dan data Anda jika Anda kehilangan akses ke login Anda. \n @@ -2165,7 +2165,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Membuat space… Tampilkan info yang berguna untuk membantu debugging aplikasi Tampilkan info debug di layar -Tidak terlihat sebagai alamat email yang valid +Tidak terlihat sebagai alamat email yang absah Buka Pengaturan Penemuan Cari dengan nama, ID atau email Buat Space Baru @@ -2173,7 +2173,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Akses space Siapa yang dapat akses\? Aktifkan notifikasi email untuk %s -Untuk menerima email dengan notifikasi, mohon tautkan sebuah email ke akun Matrix Anda +Untuk menerima email dengan notifikasi, mohon tautkan sebuah alamat email ke akun Matrix Anda Notifikasi email Tingkatkan space ini Ubah nama space @@ -2219,12 +2219,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Pertanyaan atau topik poll Buat Poll Poll -Kirim email dan nomor telepon ke %s +Kirim alamat email dan nomor telepon ke %s Kontak Anda privat. Untuk menemukan pengguna dari kontak Anda, kami membutuhkan izin untuk mengirim info kontak ke server identitas Anda. Sesinya telah dikeluarkan! Ruangannya telah ditinggalkan! Apakah Anda setuju untuk mengirimkan info ini\? -Untuk menemukan kontak yang sudah ada, Anda harus mengirim info kontak (email dan nomor telepon) ke server identitas Anda. Kami meng-hash data Anda sebelum mengirim untuk privasi. +Untuk menemukan kontak yang sudah ada, Anda harus mengirim info kontak (email dan nomor telepon) ke server identitas Anda. Kami hash data Anda sebelum mengirim demi privasi. Nanti Apakah Anda yakin untuk menghapus poll ini\? Anda tidak akan dapat memulihkannya setelah dihapus. Hapus poll @@ -2298,17 +2298,17 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Tidak ada suara Enkripsi dikonfigurasi dengan salah Pulihkan Enkripsi -Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang valid. +Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang absah. Enkripsi telah dikonfigurasi dengan salah. Membagikan lokasinya Buat akun Perpesanan untuk tim Anda. -Terenkripsi secara ujung-ke-ujung dan tidak memerlukan nomor telepon. Tidak ada iklan atau penambangan data. +Terenkripsi secara ujung ke ujung dan tidak memerlukan nomor telepon. Tanpa iklan atau penambangan data. Anda pilih di mana percakapan Anda disimpan, memberikan Anda kendali dan kebebasan. Terhubung via Matrix. Komunikasi aman dan independen yang memberikan tingkat privasi yang sama seperti percakapan wajah-ke-wajah di dalam rumah Anda sendiri. Lokasi Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Klik untuk membuka pengaturan. -Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang valid. +Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang absah. Belum yakin\? %s Tampilkan gelembung pesan Gagal untuk memuat peta @@ -2344,7 +2344,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Tampilkan Utasan Notifikasi ruangan Pengguna -Beritahu seluruh ruangan +Beri tahu seluruh ruangan @@ -2429,7 +2429,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. - %1$d lagi
Tampilkan info profil (avatar dan nama tampilan) terkini untuk semua pesan. Tampilkan info pengguna terkini Sibuk -Cadangan memiliki tandatangan yang valid dari pengguna ini. +Cadangan memiliki tandatangan yang absah dari pengguna ini. Langsung sampai %1$s Diperbarui %1$s yang lalu Implementasi sementara: lokasi tetap di riwayat ruangan @@ -2480,7 +2480,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Ketika mengundang ke ruangan terenkripsi yang juga membagikan riwayat, riwayat terenkripsi akan dapat dilihat. MSC3061: Pembagian kunci ruangan untuk pesan lama Kirim pesan pertama Anda untuk mengundang %s ke obrolan -Pesan di obrolan ini akan dienkripsi secara ujung-ke-ujung. +Pesan di obrolan ini akan dienkripsi secara ujung ke ujung. Mulai Ikuti petunjuk yang terkirim ke %s Ikuti petunjuk yang terkirim ke %s @@ -2552,7 +2552,6 @@ 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 @@ -2658,4 +2657,159 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Aktifkan pesan langsung tangguhan Sebuah Element yang sederhana dengan fitur tab opsional Aktifkan tata letak baru +Pengguna lain dalam pesan langsung dan ruangan yang Anda bergabung dapat melihat daftar sesi Anda yang lengkap. +\n +\nIni memberikan mereka kepastian bahwa mereka berbicara dengan Anda, tetapi ini juga berarti bahwa mereka dapat melihat nama sesi yang Anda masukkan di sini. +Mengubah nama sesi +Sesi yang terverifikasi telah masuk dengan kredensial Anda dan juga telah diverifikasi, menggunakan frasa sandi atau memverifikasi secara silang. +\n +\nIni berarti mereka memegang kunci enkripsi ke pesan Anda sebelumnya, dan mengonfirmasi pengguna lain yang Anda berkomunikasi bahwa sesi ini memang Anda. +Sesi tidak aktif +Sesi belum diverifikasi +Sesi terverifikasi +Sesi yang belum diverifikasi adalah sesi yang telah masuk dengan kredensial Anda tetapi belum diverifikasi secara silang. +\n +\nAnda seharusnya yakin bahwa Anda mengenal sesi ini karena mereka bisa saja berarti seseorang menggunakan akun Anda secara tidak sah. +Sesi yang tidak aktif adalah sesi yang Anda tidak gunakan dalam beberapa waktu, tetapi mereka masih mendapatkan kunci enkripsi. +\n +\nMenghapus sesi yang sudah tidak aktif meningkatkan keamanan dan performa, dan membuatnya lebih mudah untuk mengenal jika sebuah sesi baru mencurigakan. +Harap diketahui bahwa nama sesi juga terlihat ke orang-orang yang Anda berkomunikasi. +Nama sesi khusus dapat membantu Anda mengenal perangkat Anda dengan lebih mudah. +Nama sesi +Ubah nama sesi +Keluar dari sesi ini +Belum diverifikasi · Sesi Anda saat ini +Mulai sebuah siaran suara +Keaslian pesan terenkripsi ini tidak dapat dijamin pada perangkat ini. +Minta papan ketik untuk tidak memperbarui data yang dipersonalisasi seperti riwayat pengetikan dan kamus berdasarkan apa yang Anda ketik dalam percakapan. Dicatat bahwa beberapa papan ketik mungkin tidak menghormati pengaturan ini. +Papan ketik samaran +Menambahkan (╯°□°)╯︵ ┻━┻ ke pesan teks biasa +Siaran Suara +Buka layar alat pengembang +🔒 Anda telah mengaktifkan enkripsi ke sesi yang terverifikasi hanya untuk semua ruangan di Pengaturan Keamanan. +⚠ Ada perangkat yang belum diverifikasi di ruangan ini, mereka tidak akan mendekripsikan pesan yang Anda kirim. +Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi di ruangan ini. +Saya mengerti +Terapkan format garis bawah +Terapkan format coret +Terapkan format miring +Terapkan format tebal +Rekam nama klien, versi, dan URL untuk lebih mudah mengenal sesi di pengelola sesi. +Aktifkan perekaman info klien +Miliki keterlihatan dan kendali yang lebih baik pada semua sesi Anda. +Aktifkan pengelola sesi baru +Sistem operasi +Model +Peramban +URL +Versi +Nama +Aplikasi +Terima notifikasi dorongan di sesi ini. +Notifikasi dorongan +Verifikasi sesi Anda saat ini untuk menampilkan status verifikasi sesi ini. +Status verifikasi tidak diketahui +Diaktifkan: +ID Sesi: +Ada sesuatu yang salah. Mohon periksa koneksi jaringan Anda dan coba lagi. +Berikan Izin +${app_name} membutuhkan izin untuk menampilkan notifikasi. +\nMohon berikan izin itu. +${app_name} membutuhkan izin untuk menampilkan notifikasi. Notifikasi dapat menampilkan pesan Anda, undangan Anda, dll. +\n +\nMohon perbolehkan akses di munculan berikutnya untuk dapat melihat notifikasi. +Coba editor teks kaya (mode teks biasa akan datang) +Aktifkan editor teks kaya +Pastikan Anda tahu asal kode ini. Dengan menautkan perangkat, Anda akan memberikan seseorang akses penuh ke akun Anda. +Konfirmasi +Coba lagi +Tidak cocok\? +Memasukkan Anda +Menghubungkan ke perangkat +Pindai kode QR +Ingin masuk di perangkat ponsel\? +Tampilkan kode QR di perangkat ini +Pilih \'Pindai kode QR\' +Mulai dari layar masuk +Pilih \'Masuk dengan kode QR\' +Mulai dari layar masuk +Pilih \'Tampilkan kode QR\' +Pergi ke Pengaturan → Keamanan & Privasi +Buka aplikasi di perangkat Anda yang lain +Permintaan ditolak di perangkat lain. +Penautan tidak selesai dalam waktu yang dibutuhkan. +Penautan dengan perangkat ini tidak didukung. +Koneksi tidak berhasil +Periksa perangkat yang masuk, kode di bawah seharusnya ditampilkan. Konfirmasi bahwa kode di bawah cocok dengan perangkat itu: +Koneksi aman dibuat +Pindai kode QR di bawah dengan perangkat Anda yang telah keluar dari akun. +Gunakan perangkat yang sudah masuk untuk memindai kode QR di bawah: +Masuk dengan kode QR +Gunakan kamera pada perangkat ini untuk memindai kode QR yang ditampilkan pada perangkat Anda yang lain: +Pindai kode QR +3 +2 +1 +Anda dapat menggunakan perangkat ini untuk masuk ke perangkat ponsel atau web dengan sebuah kode QR. Ada dua cara untuk melalukan ini: +Masuk dengan Kode QR +Pindai kode QR +Sebuah masalah keamanan ditemukan ketika menyiapkan perpesanan aman. Salah satu dari berikut mungkin dikompromikan: homeserver Anda; koneksi internet Anda; perangkat Anda; +Pemasukan dibatalkan di perangkat yang lain. +Kode QR tidak absah. +Perangkat yang lain harus masuk. +Perangkat yang lain sudah masuk. +Homeserver tidak mendukung masuk dengan kode QR. +Permintaan gagal. +Memungkinkan untuk merekam dan mengirim siaran suara dalam linimasa ruangan. +Aktifkan siaran suara (dalam pengembangan aktif) +Memuat +Jeda siaran suara +Mainkan atau lanjutkan siaran suara +Hentikan rekaman siaran suara +Jeda rekaman siaran suara +Lanjutkan rekaman siaran suara +Langsung +Pilih sesi +Kontak +Kamera +Lokasi +Pemungutan suara +Siaran suara +Lampiran +Stiker +Pustaka foto +Batalkan semua pilihan +Pilih semua ++ +- %1$d dipilih
+Ubah mode layar penuh +Format teks +Anda sedang merekam sebuah siaran suara. Mohon akhiri siaran suara Anda saat ini untuk memulai yang baru. +Orang lain sedang merekam sebuah siaran suara. Tunggu untuk siaran suara berakhir untuk memulai yang baru. +Anda tidak memiliki izin yang dibutuhkan untuk memulai sebuah siaran suara di ruangan ini. Hubungi sebuah administrator ruangan untuk meningkatkan izin Anda. +Tidak dapat memulai siaran suara baru +Maju cepat 30 detik +Mundur cepat 30 detik +Sesi terverifikasi ada di mana pun Anda menggunakan Element setelah memasukkan frasa sandi atau mengonfirmasi identitas Anda dengan sesi terverifikasi lainnya. +\n +\nIni berarti Anda memiliki semua kunci yang diperlukan untuk membuka kunci pesan terenkripsi dan mengonfirmasi kepada pengguna lain bahwa Anda memercayai sesi ini. ++ +- Keluarkan %1$d sesi
+Keluarkan +%1$s tersisa +membuat pemungutan suara. +mengirim stiker. +mengirim video. +mengirim gambar. +mengirim file. +mengirim file audio. +mengirim pesan suara. +Membalas ke +Sembunyikan alamat IP +Mengutip +Mengedit +Tampilkan alamat IP +Membalas ke %s \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml index 69191e1741..ceb4d614de 100644 --- a/library/ui-strings/src/main/res/values-is/strings.xml +++ b/library/ui-strings/src/main/res/values-is/strings.xml @@ -152,7 +152,7 @@Útgáfa Útgáfa olm Skilmálar og kvaðir -Athugasemdir frá þriðja aðila +Tilkynningar frá utanaðkomandi aðilum Höfundarréttur Meðferð persónuupplýsinga Hreinsa skyndiminni @@ -1218,7 +1218,7 @@Öll skilaboð (hávært) Tilkynnt sem óviðeigandi Tilkynnt sem ruslpóstur -Efni tilkynnt +Efni kært KÆRA Ástæður fyrir kæru á þessu efni Kæra þetta efni @@ -1990,7 +1990,7 @@Séð af Sleppa þessu skrefi Vista og halda áfram -Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu. +Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu Nú ertu tilbúin(n)! Hefjumst handa Þú getur breytt þessu hvenær sem er @@ -2214,4 +2214,53 @@Kanna spjallrásir Búa til spjallrás Hefja spjall +Auðkennisþjónninn sem þú valdir er ekki með neina þjónustuskilmála. Ekki halda áfram nema þú treystir eiganda netþjónsins +Mistókst að skrá FCM-teikn á heimaþjóninn: +\n%1$s +Það tókst að skrá FCM-teikn á heimaþjóninn. +Ein eða fleiri prófanir mistókust, prófaðu tillögur að lagfæringum. +Gakktu úr skugga um að þú hafir smellt á tengilinn í tölvupóstinum sem við sendum þér. +Þú hefur ekki heimild til að uppfæra þau hlutverk sem krafist er til að breyta ýmsum þáttum svæðisins +Þú hefur ekki heimild til að uppfæra þau hlutverk sem krafist er til að breyta ýmsum þáttum spjallrásarinnar +Veldu þau hlutverk sem krafist er til að breyta ýmsum þáttum svæðisins +Veldu þau hlutverk sem krafist er til að breyta ýmsum þáttum spjallrásarinnar +Dulritun er rangt stillt þannig að þú getur ekki sent skilaboð. Smelltu til að opna stillingar. +Dulritun er rangt stillt þannig að þú getur ekki sent skilaboð. Hafðu samband við einhvern stjórnanda til að koma dulritun í lag. +Afbönnun á þessum notanda mun gera viðkomandi kleift að taka þátt aftur í svæðinu. +Afbönnun á þessum notanda mun gera viðkomandi kleift að taka þátt aftur í spjallrásinni. +Bann á notanda mun henda honum út af þessu svæði og koma í veg fyrir að viðkomandi komi aftur. +Notandinn verður fjarlægður af þessu svæði. +\n +\nTil koma í veg fyrir að viðkomandi komi aftur, ætti frekar að banna hann. +Notandinn verður fjarlægður af þessari spjallrás. +\n +\nTil koma í veg fyrir að viðkomandi komi aftur, ætti frekar að banna hann. +Ertu viss um að þú viljir hætta við boðið til þessa notanda\? +Afhunsun á þessum notanda mun sýna öll skilaboð frá viðkomandi aftur. +Að hunsa þennan notanda mun fjarlægja skilaboð frá viðkomandi í þeim spjallrásum sem þið eigið sameiginlegar. +\n +\nÞú getur afturkallað þessa aðgerð hvenær sem er í almennu stillingunum. +Þú getur ekki afturkallað þessa aðgerð, þar sem þú ert að lækka sjálfa/n þig í tign, og ef þú ert síðasti notandinn með nógu mikil völd á þessari spjallrás, verður ómögulegt að ná aftur stjórn á henni. +Ræstu ${app_name} á öðru tæki sem getur afkóðað skilaboðin og síðan sent dulritunarlyklana yfir í þessa setu. +Biðja aftur um dulritunarlykla frá hinum setunum þínum. +Þetta er þar sem nýjar beiðnir og boðsgestir birtast. +Svæði eru ný leið til að hópa fólk og spjallrásir. Útbúðu svæði til að komast í gang. +Öryggisafritun dulritunarlykla ætti að vera virk í öllum setunum þínum til að koma í veg fyrir að þú getir tapað aðgangi að dulrituðu skilaboðunum þínum. +- Sumir tengiliðir hafa verið afhunsaðir +${app_name} þarf að hreinsa skyndiminnið til að haldast uppfært, af eftirfarandi ástæðu: +\n%s\? +\n +\nAthugaðu að þessi aðgerð mun endurræsa forritið og það getur tekið nokkurn tíma. +Fella saman undirsvæði %s +Fella út undirsvæði %s +Farðu eftir leiðbeiningunum sem sendar voru á %s +Fékkstu ekki tölvupóst\? +Farðu eftir leiðbeiningunum sem sendar voru á %s +Hafðu það að minnsta kosti 8 stafa langt. +%s mun senda þér staðfestingartengil +%s þarf að sannreyna notandaaðganginn þinn +%s þarf að sannreyna notandaaðganginn þinn +Endilega lestu í gegnum stefnur og skilmála fyrir %s +Stefnur netþjónsins +Element Matrix Services (EMS) er afkastamikil og áreiðanleg hýsingarþjónusta fyrir hraðvirk og örugg samskipti í rauntíma. Skoðaðu hvernig við förum að því á element.io/ems \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index b2f9fa9238..d244f26a43 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -155,7 +155,7 @@Hai permesso l\'accesso alla stanza per gli ospiti. Hai impedito l\'accesso alla stanza per gli ospiti. Hai attivato la crittografia end-to-end. -Hai attivato la crittografia E2E (algoritmo %1$s sconosciuto). +Hai attivato la crittografia E2E (algoritmo %1$s non riconosciuto). Hai impedito l\'accesso alla stanza agli ospiti. %1$s ha impedito l\'accesso alla stanza agli ospiti. Hai permesso l\'accesso agli ospiti. @@ -889,10 +889,10 @@Regole di push Nessuna regola di push definita Nessun gateway di push registrato -id_app: -chiave_push: -nome_visualizzato_app: -nome_sessione: +ID app: +Chiave push: +Nome mostrato app: +Nome mostrato sessione: Url: Formato: Audio e Video @@ -955,11 +955,11 @@Attualmente stai usando %1$s per trovare altri utenti ed essere a tua volta rintracciabile da loro. Attualmente non stai usando alcun server d\'identità. Per trovare e farti rintracciare dagli altri utenti, configurane uno qua sotto. Indirizzi email visibili pubblicamente -Le opzioni su come farsi trovare appariranno dopo che avrai aggiunto un\'email. +Le opzioni su come farsi trovare appariranno dopo che avrai aggiunto un indirizzo email. Le opzioni su come farsi trovare appariranno dopo che avrai aggiunto un numero di telefono. Se ti disconnetti dal server d\'identità gli altri utenti non potranno trovarti e tu non potrai invitarli tramite le loro email o numeri di telefono. Numeri di telefono visibili pubblicamente -Abbiamo inviato un\'email di conferma a %s, controlla la tua posta e clicca sul link di conferma +Abbiamo inviato un\'email a %s, controlla la tua posta e clicca sul link di conferma Inserisci un URL di un server d\'identità Impossibile connettersi al server d\'identità Inserisci l\'URL del server d\'identità @@ -1087,7 +1087,7 @@L\'applicazione non riesce a creare un account su questo Home Server. \n \nVuoi registrarti usando un client web\? -Questa email non è associata ad alcun account. +Questo indirizzo email non è associato ad alcun account. Reimposta la password su %1$s Per confermare la nuova password ti verrà inviata un\'email di verifica. Avanti @@ -1096,7 +1096,7 @@Attenzione! Cambiando la password verranno reimpostate le chiavi crittografiche E2E di tutte le tue sessioni rendendo illeggibile la cronologia delle chat criptate. Prima di reimpostare la password imposta il Backup delle Chiavi o esporta le chiavi della tua stanza da un\'altra sessione. Continua -Questa email non è collegata ad alcun account +Questo indirizzo email non è collegato ad alcun account Controlla la tua posta Un\'email di verifica è stata inviata a %1$s. Clicca sul link per confermare la tua nuova password. Una volta fatto, clicca sotto. @@ -1110,7 +1110,7 @@ \n \nFermare il processo di cambio password\?Imposta indirizzo email -Imposta un\'email per recuperare il tuo account. Più tardi potrai decidere se permettere alle persone che conosci di trovarti tramite questa email. +Imposta un indirizzo email per recuperare il tuo account. Più tardi potrai decidere se permettere alle persone che conosci di trovarti tramite questo indirizzo. Email (facoltativa) Avanti @@ -1448,7 +1448,7 @@Messaggio rimosso Mostra messaggi rimossi Mostra un segnaposto per i messaggi rimossi -Ti abbiamo inviato un\'email di conferma a %s, controlla la tua posta e clicca il link di conferma +Abbiamo inviato un\'email a %s, controlla la tua posta e clicca il link di conferma Il codice di verifica non è corretto. MEDIA In questa stanza non ci sono file multimediali @@ -1471,7 +1471,7 @@Questa operazione non è possibile. L\'Home Server è obsoleto. Prima configura un server d\'identità. Prima accetta le condizioni del server d\'identità nelle impostazioni. -Per la tua privacy, ${app_name} supporta solo l\'invio di email e numeri di telefono degli utenti in modalità oscurata (hash). +Per la tua privacy, ${app_name} supporta solo l\'invio di hash degli indirizzi email e dei numeri di telefono degli utenti. L\'associazione è fallita. Non c\'è alcuna associazione con questo identificativo. Il tuo home server (%1$s) propone di usare %2$s come tuo server d\'identità @@ -1645,12 +1645,12 @@Questo numero di telefono è già definito. Nessun numero di telefono aggiunto al tuo account Indirizzi email -Nessuna email aggiunta al tuo account +Nessun indirizzo email aggiunto al tuo account Numeri di telefono Rimuovere %s\? Assicurati di avere cliccato il link nell\'email che ti abbiamo inviato. Email e numeri di telefono -Gestisci le email e i numeri di telefono collegati al tuo account Matrix +Gestisci gli indirizzi email e i numeri di telefono collegati al tuo account Matrix Codice Si prega di usare il formato internazionale (il numero deve iniziare con \'+\') Conferma la tua identità verificando questo accesso, dandogli l\'accesso ai messaggi cifrati. @@ -1769,7 +1769,7 @@%1$d di %2$d Dai il consenso Revoca il mio consenso -Hai acconsentito ad inviare email e numeri di telefono a questo server d\'identità per poter rintracciare altri utenti tra i tuoi contatti. +Hai acconsentito ad inviare indirizzi email e numeri di telefono a questo server d\'identità per poter rintracciare altri utenti tra i tuoi contatti. Invia email e numeri di telefono Suggerimenti Utenti conosciuti @@ -1978,7 +1978,7 @@Pubblico Uno Spazio privato per te e i tuoi compagni Io e i miei compagni -Uno Spazio privato per organizzare le tue stanze +Uno spazio privato per organizzare le tue stanze Solo io Assicurati che le persone giuste abbiano accesso a %s. Con chi stai lavorando\? @@ -2133,7 +2133,7 @@Menzioni e parole chiave Notifiche predefinite %s nella impostazioni per ricevere inviti direttamente in ${app_name}. -Collega questa email con il tuo account +Collega questo indirizzo email con il tuo account Questo invito per questo spazio è stato inviato a %s, la quale non è associata al tuo account Questo invito per questa stanza è stato inviato a %s, la quale non è associata al tuo account Tutte le stanze in cui sei appariranno nella pagina principale. @@ -2249,12 +2249,12 @@Domanda o argomento del sondaggio Crea sondaggio Sondaggio -Invia email e numeri di telefono a %s +Invia indirizzi email e numeri di telefono a %s I tuoi contatti sono privati. Per trovare utenti dai tuoi contatti, ci serve l\'autorizzazione per inviare le informazioni dei contatti al tuo server d\'identità. La sessione è stata disconnessa! La stanza è stata lasciata! Sei d\'accordo con l\'invio di queste informazioni\? -Per trovare i contatti esistenti, devi inviare le informazioni dei contatti (email e numeri di telefono) al tuo server d\'identità. Facciamo un hash dei dati prima di inviarli per privacy. +Per trovare i contatti esistenti, devi inviare le informazioni dei contatti (indirizzi email e numeri di telefono) al tuo server d\'identità. Facciamo un hash dei dati prima di inviarli per privacy. Non ora Vuoi davvero rimuovere questo sondaggio\? Non potrai recuperarlo una volta rimosso. Rimuovi sondaggio @@ -2591,7 +2591,6 @@ \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 @@ -2701,4 +2700,161 @@Crea messaggio diretto solo al primo messaggio Un Element semplificato con schede opzionali Attiva nuova disposizione +Gli altri utenti nei messaggi diretti e nelle stanze in cui entri, possono vedere una lista completa delle tue sessioni. +\n +\nIn questo modo hanno la certezza che stanno parlando davvero con te, ma significa anche che possono vedere il nome della sessione che inserisci qui. +Rinominare le sessioni +Le sessioni verificate hanno effettuato l\'accesso con le tue credenziali e sono state verificate, usando la frase di sicurezza o la verifica incrociata. +\n +\nCiò significa che hanno le tue chiavi di crittografia per i messaggi passati, e confermano agli altri utenti con cui comunichi che queste sessioni sono usate da te. +Sessioni verificate +Le sessioni non verificate sono quelle in cui è stato fatto l\'accesso con le tue credenziali, ma che non sono state verificate. +\n +\nDovresti essere particolarmente sicuro di riconoscere queste sessioni dato che potrebbero rappresentare un uso non autorizzato del tuo account. +Sessioni non verificate +Le sessioni inattive sono quelle che non usi da un po\' di tempo, ma che continuano a ricevere chiavi di crittografia. +\n +\nLa rimozione di sessioni inattive migliora la sicurezza e le prestazioni, e ti rende più facile capire se una sessione nuova è sospetta. +Sessioni inattive +Ricorda che i nomi di sessione sono anche visibili alle persone con cui comunichi. +I nomi di sessione personalizzati possono aiutarti a riconoscere i tuoi dispositivi più facilmente. +Nome sessione +Rinomina sessione +Disconnetti questa sessione +Non verificata · La sessione attuale +Inizia una trasmissione vocale +L\'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo. +Richiedi che la tastiera non debba aggiornare dati personalizzati come la cronologia di digitazione e il dizionario in base a cosa digiti nelle conversazioni. Nota che alcune tastiere potrebbero non rispettare questa impostazione. +Tastiera incognito +Antepone (╯°□°)╯︵ ┻━┻ ad un messaggio di testo +Trasmissione vocale +Apri la schermata degli strumenti per sviluppatori +🔒 Hai attivato la crittografia solo per sessioni verificate in tutte le stanze nelle impostazioni di sicurezza. +⚠ Ci sono dispositivi non verificati in questa stanza, non potranno decifrare i messaggi che invii. +Non inviare mai messaggi cifrati a sessioni non verificate in questa stanza. +Capito +Applica formato sottolineato +Applica formato sbarrato +Applica formato corsivo +Applica formato grassetto +Registra il nome, la versione e l\'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni. +Attiva registrazione info client +Maggiore visibilità e controllo su tutte le tue sessioni. +Attiva il nuovo gestore di sessioni +Sistema operativo +Modello +Browser +URL +Versione +Nome +Applicazione +Ricevi notifiche push in questa sessione. +Notifiche push +Verifica l\'attuale sessione per rivelare lo stato di verifica di questa sessione. +Stato di verifica sconosciuto +Attivato: +ID sessione: +Qualcosa è andato storto. Controlla la tua connessione di rete e riprova. +Concedi l\'autorizzazione +${app_name} chiede l\'autorizzazione per mostrare notifiche. +\nConcedi l\'autorizzazione. +${app_name} chiede l\'autorizzazione per mostrare notifiche. Le notifiche possono mostrare i messaggi, gli inviti, ecc. +\n +\nConsenti l\'accesso nelle prossime schermate per potere vedere la notifica. +Prova l\'editor in rich text (il testo semplice è in arrivo) +Attiva editor in rich text +Assicurati di conoscere l\'origine di questo codice. Collegando i dispositivi, fornirai a qualcuno l\'accesso totale al tuo account. +Conferma +Riprova +Non corrisponde\? +Accesso in corso +Connessione al dispositivo +Scansiona codice QR +Effettuare l\'accesso in un dispositivo mobile\? +Mostra codice QR in questo dispositivo +Seleziona \'Scansiona codice QR\' +Inizia nella schermata di accesso +Seleziona ‘Accedi con codice QR’ +Inizia nella schermata di accesso +Seleziona ‘Mostra codice QR’ +Vai in Impostazioni -> Sicurezza e privacy +Apri l\'app sull\'altro dispositivo +La richiesta è stata negata sull\'altro dispositivo. +Il collegamento non è stato completato nel tempo previsto. +Il collegamento con questo dispositivo non è supportato. +Connessione non riuscita +Controlla il dispositivo che ha l\'accesso, dovresti vedere il codice sotto. Conferma che il codice corrisponda con quel dispositivo: +Connessione sicura stabilita +Scansiona il codice QR sottostante con il dispositivo che è disconnesso. +Usa il dispositivo che ha l\'accesso per scansionare il codice QR sotto: +Accedi con codice QR +Usa la fotocamera di questo dispositivo per scansionare il codice QR mostrato nell\'altro dispositivo: +Scansiona codice QR +3 +2 +1 +Puoi usare questo dispositivo per accedere in un dispositivo mobile o web con un codice QR. Ci sono due modi: +Accedi con codice QR +Scansiona codice QR +Registra e invia trasmissioni vocali nella linea temporale della stanza. +Attiva trasmissione vocale (in sviluppo attivo) +L\'homeserver non supporta l\'accesso con codice QR. +L\'accesso è stato annullato sull\'altro dispositivo. +Quel codice QR non è valido. +L\'altro dispositivo deve fare l\'accesso. +L\'altro dispositivo ha già fatto l\'accesso. +Si è verificato un problema di sicurezza configurando i messaggi sicuri. Una delle seguenti cose potrebbe essere compromessa: il tuo homeserver; la/e connessione/i internet; il/i dispositivo/i; +La richiesta è fallita. +Buffering +Sospendi trasmissione vocale +Avvia o riprendi trasmissione vocale +Ferma registrazione trasmissione vocale +Sospendi registrazione trasmissione vocale +Riprendi registrazione trasmissione vocale +In diretta +Seleziona sessioni +Contatto +Fotocamera +Posizione +Sondaggi +Trasmissione vocale +Allegati +Adesivi +Album di foto +Deseleziona tutto +Seleziona tutto ++ +- %1$d selezionato
+- %1$d selezionati
+Attiva/disattiva schermo intero +Le sessioni verificate sono ovunque usi questo account dopo l\'inserimento della password o la conferma della tua identità con un\'altra sessione verificata. +\n +\nCiò significa che hai tutte le chiavi necessarie per sbloccare i tuoi messaggi cifrati e per confermare agli altri utenti che ti fidi di questa sessione. ++ +- Disconnetti da %1$d sessione
+- Disconnetti da %1$d sessioni
+Disconnetti +Formattazione testo +Stai già registrando una trasmissione vocale. Termina quella in corso per iniziarne una nuova. +Qualcun altro sta già registrando una trasmissione vocale. Aspetta che finisca prima di iniziarne una nuova. +Non hai l\'autorizzazione necessaria per iniziare una trasmissione vocale in questa stanza. Contatta un amministratore della stanza per aggiornare le tue autorizzazioni. +Impossibile iniziare una nuova trasmissione vocale +Manda avanti di 30 secondi +Manda indietro di 30 secondi +%1$s rimasti +creato un sondaggio. +inviato un adesivo. +inviato un video. +inviata un\'immagine. +inviato un messaggio vocale. +inviato un file audio. +inviato un file. +In risposta a +Nascondi indirizzo IP +Mostra indirizzo IP +Citazione +Risposta a %s +Modifica \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml index ff19310c8e..b9f81ae446 100644 --- a/library/ui-strings/src/main/res/values-iw/strings.xml +++ b/library/ui-strings/src/main/res/values-iw/strings.xml @@ -861,9 +861,7 @@בחר שרת בית מותאם אישית בחר שירותי מטריקס אלמנט בחר matrix.org -חשבונך טרם נוצר. -\n -\nלהפסיק את תהליך ההרשמה\? +חשבונך טרם נוצר. להפסיק את תהליך ההרשמה\? אזהרה שם המשתמש הזה תפוס הבא @@ -2304,7 +2302,7 @@קהילות צוותים חברים ומשפחה -נעזור לך להתחבר. +נעזור לך להתחבר עם מי תדברו הכי הרבה\? מוצפן מקצה לקצה ואין צורך במספר טלפון. ללא פרסומות או עיבוד נתונים. בחר היכן השיחות שלך נשמרות, נותן לך שליטה ועצמאות. מחובר דרך Matrix. @@ -2508,4 +2506,4 @@ \nזה יהיה מעבר חד פעמי שכן שרשורים הם כעת חלק ממפרט Matrix.שיתוף מסך של ${app_name} המסך משותף כרגע - + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 3e817e398c..11ab6ee857 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -987,7 +987,7 @@プッシュ通知のテスト FCMトークンのホームサーバーへの登録に失敗しました: \n%1$s -FCMトークンのホームサーバーへの登録が成功しました。 +FCMトークンがホームサーバーに登録されました。 トークンの登録 アカウントを追加 [%1$s] @@ -1233,7 +1233,7 @@ 続行するには利用規約を承認してください ホームサーバーの利用規約を承認したら、再試行してください。 次に -次に +次へ 次に 次に 次に @@ -1282,9 +1282,9 @@提案の送信に失敗しました(%s) ありがとうございます、提案は正常に送信されました トークンの登録 -app_display_name: -app_id: -push_key: +アプリケーションの表示名: +App ID: +Push Key: 登録されたプッシュゲートウェイはありません プッシュ通知に関するルールが定義されていません プッシュ通知に関するルール @@ -1383,8 +1383,8 @@同意を撤回 あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意しています。 メールと電話番号を送信 -%sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください -%sに確認のためのメールを送りました。メールにて確認リンクをクリックしてください +%sにメールを送りました。メールを確認してリンクをクリックしてください +%sにメールを送りました。メールの確認リンクをクリックしてください 発見可能な電話番号 IDサーバーとの接続を解除すると、他のユーザーによって発見されなくなり、また、メールアドレスや電話で他のユーザーを招待することができなくなります。 電話番号を追加すると、発見可能に設定する電話番号を選択できるようになります。 @@ -1412,7 +1412,7 @@提案する フォーマット: URL: -セッション名: +セッションの表示名: 以下のうちいずれかが流出、あるいはハッキングされた恐れがあります。 \n \n- あなたのパスワード @@ -1696,7 +1696,7 @@ メッセージを送る… このファイルは大きすぎてアップロードできません。 この情報の送信に同意しますか? -連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。 +連絡先を発見するには、連絡先のデータ(メールアドレスと電話番号)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。 メールアドレスと電話番号を%sに送信 このIDサーバーは運営方針を提供していません IDサーバーの運営方針を隠す @@ -2359,4 +2359,118 @@ベータ版 ベータ版 試す - +オフラインモード +新着はありません。 +- ユーザーの無視が解除されました +試してみる +右上をタップするとフィードバックを送信するオプションが表示されます。 +フィードバックを送信 +右下からスペースにより早く簡単にアクセスできます。 +スペースにアクセス +${app_name}をシンプルにするために、タブはオプションになりました。右上のメニューから管理できます。 +新しいレイアウトにようこそ! +アニメーション画像を自動再生 +エンドポイントのホームサーバーへの登録に失敗しました: +\n%1$s +エンドポイントがホームサーバーに登録されました。 +エンドポイントの登録 +権限を与える +${app_name}は通知の表示に権限が必要です。 +\n権限を与えてください。 ++ +- %1$sと他%2$d名
+%1$sと%2$s +ホームサーバーがサポートしていないため、スレッド機能は不安定かもしれません。スレッドのメッセージは安定して表示されないおそれがあります。%sスレッド機能を有効にしてよろしいですか? +スレッド(ベータ版) +スレッドを用いると、会話のテーマを保ったり、会話を追跡したりするのが容易になります。%sスレッドを有効にするとアプリケーションが再起動します。再起動には時間がかかる可能性があります。 +スレッド(ベータ版) +${app_name}は通知を表示するために許可を必要としています。通知にはメッセージや招待などが表示されます。 +\n +\n通知を表示するには、次のポップアップでアクセスを許可してください。 +メールアドレスが認証されていません。メールボックスを確認してください +画面共有を停止 +画面を共有 +招待 +プッシュ通知 +セッション名 +セッションを改名 +IPアドレス +オペレーティングシステム +形式 +ブラウザー +URL +バージョン +名称 +アプリケーション +このステップをスキップ +問題ありません! +進みましょう +ユーザー名 / メールアドレス / 電話番号 +あなたは人間ですか? +%sに送信された手順に従ってください +パスワードを再設定 +パスワードを忘れた場合 +電子メールを再送信 +電子メールが届いていませんか? +%sに送信された手順に従ってください +メールアドレスを認証 +コードを再送信 +コードが%sに送信されました +電話番号を確認してください +全ての端末からサインアウト +パスワードを再設定 +パスワードは8文字以上に設定してください。 +パスワードを選択 +新しいパスワード +電子メールを確認してください。 +%sは認証リンクを送信します +確認コード +電話番号 +%sはアカウントの認証が必要です +電話番号を入力してください +メールアドレス +%sはアカウントの認証が必要です +リッチテキストエディターを有効にする +最初のメッセージを送信する際にダイレクトメッセージを作成 +遅延DMを有効にする +スペースがありません。 +新しいレイアウトを有効にする +アクティビティー順 +アルファベット順 +並び替え +フィルターを表示 +レイアウトの設定 +了解 +次へ +詳しく知る +秒 +分 +時 +${app_name}は以下の理由で、キャッシュを消去して最新の状態にする必要があります。 +\n%s +\n +\nアプリケーションが再起動します。再起動には時間がかかる可能性があります。 +初期同期のリクエスト +%sの子スペースを折りたたむ +%sの子スペースを展開 +ルームを探索 +スペースを変更 +ルームを作成 +チャットを開始 +全ての会話 +${app_name}にようこそ、 +\n%s。 +認証済のセッション +QRコードでサインイン +新しいセッションマネージャーを有効にする +QRコードでサインイン +3 +2 +1 +リクエストが失敗しました。 +QRコードをスキャン +QRコードをスキャン +QRコードをスキャン +QRコードが不正です。 + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-ko/strings.xml b/library/ui-strings/src/main/res/values-ko/strings.xml index 37e8849fa8..11e870f581 100644 --- a/library/ui-strings/src/main/res/values-ko/strings.xml +++ b/library/ui-strings/src/main/res/values-ko/strings.xml @@ -47,7 +47,8 @@초기 동기화: \n방 가져오는 중 초기 동기화: -\n들어간 방 가져오는 중 +\n대화 가져오는 중 +\n많은 방에 참여하신 경우, 오래 걸릴 수 있습니다초기 동기화: \n초대받은 방 가져오는 중 초기 동기화: @@ -958,4 +959,39 @@ 방 이름을 바꾸었습니다: %1$s 방 사진을 바꾸었습니다 %1$s님이 방 사진을 바꾸었습니다 - +초기 동기화 요청 +초기 동기화: +\n데이터 내려받는 중… +초기 동기화: +\n서버 응답을 기다리는 중… +빈 방 (기존 %s) ++ +- %1$s님, %2$s님, %3$s님과 %4$d님 등
+%1$s님, %2$s님, %3$s님과 %4$s님 +%1$s님, %2$s님과 %3$s님 +이 방에 참여할 수 없습니다 +방 둘러보기 +방 만들기 +채팅 시작 +모든 채팅 +중재자 +관리자 +%1$s위젯을 수정했습니다 +%1$s님이 %2$s위젯을 수정했습니다 +%1$s위젯을 삭제했습니다 +%1$s님이 %2$s위젯을 삭제했습니다 +%1$s위젯을 추가했습니다 +%1$s님이 %2$s위젯을 추가했습니다 +%1$s님의 초대를 수락했습니다 +%1$s님 초대를 취소했습니다 +%1$s님이 %2$s님 초대를 취소했습니다 +%1$s님에게 방에 참가하라고 보낸 초대를 취소했습니다 +%1$s님을 초대했습니다 +%1$s님이 %2$s님을 초대했습니다 +%1$s님에게 방 초대를 보냈습니다 +방 아바타를 삭제했습니다 +%1$s님이 방 아바타를 삭제했습니다 +방 주제를 삭제했습니다 +방 이름을 삭제했습니다 + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml index 7af718d920..067dbbbc28 100644 --- a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml +++ b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml @@ -36,7 +36,7 @@Kopiert til utklippstavle Advarsel Feil -Folk +Personer Rom Invitasjoner Lavprioritet @@ -65,10 +65,6 @@Fjern Bli med Avvis - - - -Inviter Utesteng Opphev utestengelse @@ -265,7 +261,6 @@%s skriver … Søk Filtrer rommets medlemmer -Alle meldinger olm-versjon Deaktiver kontoen @@ -314,8 +309,6 @@Bråkete Kryptert melding Rom - -Årsak: %1$s %d+ Begynn å bruke Nøkkelsikkerhetskopiering @@ -379,7 +372,6 @@Du kommer til å miste tilgang til dine enkrypterte meldinger med mindre du sikkerhetskopierer nøklene dine før du logger av. Se dekryptert kilde Rapporter innhold -Er du sikker på at vil logge ut\? Telefonsamtale Videosamtale @@ -393,7 +385,6 @@Systemadvarsler Samtaler Bare matrix-kontakter -Send kjæsjlogg Send skjermbilde Vennligst forklar feilen. Hva gjorde du\? Hva forventet du at skulle skje\? Hva skjedde i stedet\? @@ -446,7 +437,6 @@Dette ser ikke ut som en gyldig E-postadresse SSL-feil. For mange forespørsler har blitt sendt -Forlat rommet Direktemeldinger Ignorer bruker @@ -600,13 +590,10 @@Du har ikke tillatelse til å starte en konferansesamtale Tilbakestill Vennligst gjennomgå og godta retningslinjene til denne hjemmeserveren: -Klarte ikke verifisere e-postadressen: Pass på at du har klikket på lenken i e-posten Denne hjemmetjeneren vil vite om du er en robot -Klarte ikke å starte en sanntidskopling. \nVennligst be hjemmetjeneradministratoren din om å sette opp en TURN server så samtaler blir mer stabile. -Inneholdt ikke gyldig JSON Ugyldig JSON Sikkerhetsfrase @@ -702,7 +689,6 @@App info Ingen telefonnummer er lagt til kontoen din Legg til på startskjerm -Godta bare sertifikatet hvis serveradministratoren har publisert et fingeravtrykk som samsvarer med det over. Sertifikatet er endret fra en tidligere klarert til en som ikke er klarert. Serveren kan ha fornyet sertifikatet. Kontakt serveradministratoren for forventet fingeravtrykk. Sertifikatet har endret seg fra et som telefonen din klarerte. Dette er veldig uvanlig. Det anbefales at du IKKE godtar dette nye sertifikatet. @@ -734,19 +720,14 @@Nevne Avbryt invitasjonen Er du sikker på at du vil forlate rommet\? -Gå til første uleste melding. Liste medlemmer Tillat tillatelse til å få tilgang til kontaktene dine. For å skanne en QR-kode, må du gi tilgang til kameraet. -${app_name} trenger tillatelse for å få tilgang til kameraet og mikrofonen for å utføre videosamtaler. \n \nTillat tilgang til de neste popup-vinduene for å kunne ringe. -${app_name} trenger tillatelse for å få tilgang til mikrofonen din for å utføre lydanrop. - -Innkommende taleanrop Innkommende videosamtale Anrop avsluttet @@ -770,7 +751,6 @@Begynn å chatte [%1$s] \nDenne feilen er utenfor kontroll av ${app_name}, og ifølge Google indikerer denne feilen at enheten har for mange apper registrert hos FCM. Feilen oppstår bare i tilfeller der det er ekstremt mange apper, så det bør ikke påvirke gjennomsnittsbrukeren. -Ekstern vert kunne ikke plukke opp. Pågående videosamtale… Pågående samtale… @@ -1137,7 +1117,6 @@Bruk en integrasjonshåndterer til å administrere botter, broer, widgets og klistremerkepakker. \nIntegrasjonshåndterere mottar konfigurasjonsdata, og kan endre moduler, sende rominvitasjoner og angi maktnivåer på dine vegne. Forsinkelse mellom hver synkronisering -Tidsavbrudd for synkroniseringsforespørsel Du vil ikke bli varslet om innkommende meldinger når appen er i bakgrunnen. ${app_name} vil synkroniseres i bakgrunnen med jevne mellomrom på presis tid (konfigurerbar). @@ -1251,7 +1230,6 @@ Ta kontakt med din hjemmetjener -administrator for mer informasjon Noen rom kan være skjult fordi de er private, og du trenger en invitasjon. \nDu har ikke tillatelse til å legge til rom. -Oppgradering kreves Oppgrader offentlig rom Oppgrader privat rom @@ -1275,4 +1253,4 @@%1$s endret visningsnavnet sitt til %2$s %1$s utestengte %2$s %ss invitasjon - + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml index ce122b0646..c5616ed761 100644 --- a/library/ui-strings/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -2,7 +2,7 @@Uitnodiging van %s %1$s heeft %2$s uitgenodigd -%1$s heeft u uitgenodigd +%1$s heeft je uitgenodigd %1$s is deelnemer geworden van de kamer %1$s heeft het de kamer verlaten %1$s heeft de uitnodiging geweigerd @@ -47,7 +47,7 @@ \nKamers importerenInitiële synchronisatie: \nGesprekken worden geladen -\nAls u aan veel kamers deelneemt kan dit even duren +\nAls je aan veel kamers deelneemt kan dit even durenInitiële synchronisatie: \nUitgenodigde kamers worden geïmporteerd Initiële synchronisatie: @@ -59,7 +59,7 @@ %1$s heeft de uitnodiging voor %2$s om deelnemer te worden van de kamer ingetrokken Uitnodiging van %1$s. Reden: %2$s %1$s heeft %2$s uitgenodigd. Reden: %3$s -%1$s heeft u uitgenodigd. Reden: %2$s +%1$s heeft je uitgenodigd. Reden: %2$s %1$s neemt nu deel. Reden: %2$s %1$s is weggegaan. Reden: %2$s %1$s heeft de uitnodiging geweigerd. Reden: %2$s @@ -81,8 +81,8 @@%1$s heeft het hoofdadres voor dit gesprek verwijderd. %1$s heeft gasten de toegang tot dit gesprek verleend. %1$s heeft gasten de toegang tot het gesprek verhinderd. -%1$s heeft end-to-end-versleuteling ingeschakeld. -%1$s heeft end-to-end-versleuteling ingeschakeld (onbekend algoritme %2$s). +%1$s heeft eind-tot-eind-versleuteling ingeschakeld. +%1$s heeft eind-tot-eind-versleuteling ingeschakeld (onbekend algoritme %2$s). Instellingen Oké Annuleren @@ -124,14 +124,14 @@Crash-logboek versturen Schermafdruk versturen Probleem melden -Beschrijf de fout. Wat heeft u gedaan\? Wat verwachtte u dat er zou gebeuren\? Wat is er echt gebeurd\? -Beschrijf hier uw probleem -Om het probleem te kunnen onderzoeken worden logboeken van deze cliënt met de foutmelding verstuurd. Deze foutmelding, inclusief de logboeken en schermafdruk, zullen niet openbaar zichtbaar zijn. Indien u liever alleen de bovenstaande tekst verstuurt, haal dan het vinkje weg: -Het ziet er naar uit dat u de telefoon in frustratie schudt. Wilt u een probleem melden\? +Beschrijf de fout. Wat heb je gedaan\? Wat verwachtte je dat er zou gebeuren\? Wat is er echt gebeurd\? +Beschrijf hier jouw probleem +Om het probleem te kunnen onderzoeken worden logboeken van deze cliënt met de foutmelding verstuurd. Deze foutmelding, inclusief de logboeken en schermafdruk, zullen niet openbaar zichtbaar zijn. Indien je liever alleen de bovenstaande tekst verstuurt, haal dan het vinkje weg: +Het ziet er naar uit dat je de telefoon in frustratie schudt. Wil je een probleem melden\? De foutmelding is verzonden Versturen van foutmelding is mislukt (%s) Voortgang (%s%%) -De toepassing is de vorige keer gecrasht. Wilt u dit melden\? +De toepassing is de vorige keer gecrasht. Wil je dit melden\? Deelnemen aan kamer Inlognaam Afmelden @@ -147,8 +147,8 @@Dit is geen geldig e-mailadres Dit e-mailadres is al in gebruik. Wachtwoord vergeten? -Deze server wil graag weten of u geen robot bent -Verifiëren van het e-mailadres is mislukt: zorg dat u op de koppeling in de e-mail hebt geklikt +Deze server wil graag weten of je geen robot bent +Verifiëren van het e-mailadres is mislukt: zorg dat je op de koppeling in de e-mail hebt geklikt Voer een geldige URL in Ongeldige JSON Bevatte geen geldige JSON @@ -164,8 +164,8 @@Oproep gaande… De andere kant heeft niet opgenomen. Informatie -${app_name} heeft toegang nodig tot uw microfoon om spraakoproepen te maken. -${app_name} heeft toegang nodig tot uw camera en microfoon om video-oproepen te maken. + ${app_name} heeft toegang nodig tot je microfoon om spraakoproepen te maken. +${app_name} heeft toegang nodig tot je camera en microfoon om video-oproepen te maken. \n \nVerleen toegang op de volgende pop-ups om de oproep te maken. JA @@ -176,7 +176,7 @@Afwijzen Naar ongelezen springen Gesprek verlaten -Weet u zeker dat u het gesprek wilt verlaten\? +Weet je zeker dat je het gesprek wil verlaten\? TWEEGESPREKKEN Uitnodigen Verbannen @@ -184,22 +184,22 @@Alle berichten van deze persoon verbergen Alle berichten van deze persoon tonen Vermelden -U kunt deze veranderingen niet ongedaan maken aangezien u de persoon tot hetzelfde niveau als uzelf promoveert. -\nWeet u het zeker\? +Je kan deze veranderingen niet ongedaan maken aangezien je de persoon tot hetzelfde niveau als jezelf promoveert. +\nWeet je het zeker\? %s is aan het typen… %1$s en %2$s zijn aan het typen… %1$s, %2$s en anderen zijn aan het typen… -U heeft geen toestemming om dit naar dit gesprek te sturen. +Je hebt geen toestemming om dit naar dit gesprek te sturen. Vertrouwen Niet vertrouwen Afmelden Negeren Vingerafdruk (%s): Kan de identiteit van de externe server niet verifïeren. -Dit kan betekenen dat iemand uw internetverkeer met slechte bedoelingen probeert te onderscheppen, of dat uw telefoon het certificaat van de server niet vertrouwt. +Dit kan betekenen dat iemand jouw internetverkeer met slechte bedoelingen probeert te onderscheppen, of dat jouw telefoon het certificaat van de server niet vertrouwt. Als de serverbeheerder heeft gezegd dat dit normaal is, wees er dan zeker van dat de vingerafdruk hieronder overeenkomt met de door de beheerder verschafte vingerafdruk. -Het certificaat is veranderd van één dat door uw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN. -Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Contacteer de serverbeheerder voor de verwachte vingerafdruk. +Het certificaat is veranderd van één dat door jouw telefoon werd vertrouwd naar een ander. Dit is HEEL ONGEBRUIKELIJK. Het wordt aangeraden om dit nieuwe certificaat NIET TE AANVAARDEN. +Het certificaat is veranderd van een vertrouwd naar een onvertrouwd certificaat. De server heeft misschien zijn certificaat vernieuwd. Neem contact op met de serverbeheerder voor de verwachte vingerafdruk. Aanvaard het certificaat alleen als de serverbeheerder een vingerafdruk heeft gepubliceerd die overeenkomt met degene hierboven. Zoeken Gespreksleden filteren @@ -249,14 +249,14 @@Aangemeld als Server Identiteitsserver -Bekijk uw e-mail en tik op de koppeling erin. Tik zodra dit gedaan is op Verdergaan. +Bekijk je e-mail en tik op de koppeling erin. Tik zodra dit gedaan is op Verdergaan. Dit e-mailadres is al in gebruik. Dit telefoonnummer is al in gebruik. Wachtwoord veranderen Huidig wachtwoord Nieuw wachtwoord Bijwerken van wachtwoord is mislukt -Uw wachtwoord is gewijzigd +Je wachtwoord is gewijzigd Alle berichten van %s tonen\? Kies een land Onderwerp @@ -270,7 +270,7 @@Geavanceerd Interne ID van dit gesprek Experimenteel -Dit zijn experimentele functies die zich op onverwachte manieren kunnen gedragen. Wees behoedzaam bij het gebruik van deze functies. +Dit zijn experimentele functionaliteiten die zich op onverwachte manieren kunnen gedragen. Wees behoedzaam bij het gebruik van deze functies. Instellen als hoofdadres Niet instellen als hoofdadres Ontsleutelingsfout @@ -292,8 +292,8 @@NIET geverifieerd Geverifieerd Verifiëren -Om te verifiëren dat deze sessie vertrouwd kan worden, contacteert u de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraagt u hem/haar of de sleutel die hij/zij ziet in zijn/haar persoonsinstellingen van deze sessie overeenkomt met de sleutel hieronder: -Als het overeenkomt, drukt u op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en zou u het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. +Om te verifiëren dat deze sessie vertrouwd kan worden, neem je contact op met de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraag je ze of de sleutel die ze zien in hun persoonsinstellingen van deze sessie overeenkomt met de sleutel hieronder: +Als het overeenkomt, druk je op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en kan je het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. Kamermap kiezen Servernaam Alle gesprekken op server %s @@ -324,12 +324,12 @@Luisteren naar gebeurtenissen Meldingsgeluid Tijdsaanduidingen in 12-uursformaat weergeven -Weet u zeker dat u deze widget uit dit gesprek wilt verwijderen\? +Weet je zeker dat je deze widget uit dit gesprek wilt verwijderen\? Kan widget niet aanmaken. Versturen van verzoek mislukt. Het machtsniveau moet een positief geheel getal zijn. -U zit niet in dit gesprek. -U heeft geen toestemming om dat in dit gesprek te doen. +Je zit niet in dit gesprek. +Je hebt geen toestemming om dat in dit gesprek te doen. room_id ontbreekt in het verzoek. user_id ontbreekt in het verzoek. Gesprek %s is niet zichtbaar. @@ -344,8 +344,8 @@Berichten die mijn inlognaam bevatten Statistische gegevens Systeemcamera gebruiken -U heeft een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. -Uw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. +Je hebt een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. +Jouw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. Verificatie starten Opdrachtfout Onbekende opdracht: %s @@ -354,8 +354,8 @@Versleuteld bericht Laden… Schudden om een probleem te melden -Weet u zeker dat u een spraakoproep wilt beginnen\? -Weet u zeker dat u een video-oproep wilt beginnen\? +Weet je zeker dat je een spraakoproep wilt beginnen\? +Weet je zeker dat je een video-oproep wilt beginnen\? - - %d verandering in lidmaatschap
- %d veranderingen in lidmaatschap
@@ -365,7 +365,7 @@- %d deelnemer
- %d deelnemers
Als een persoon wordt verbannen, wordt deze uit deze kamer verwijderd en wordt er voorkomen dat hij opnieuw lid wordt. +Als een persoon wordt verbannen, wordt deze uit deze kamer verwijderd en wordt er voorkomen dat ze opnieuw lid worden. - %d nieuw bericht
- %d nieuwe berichten
@@ -387,40 +387,40 @@Thuis Gesprekken Uitgenodigd -%2$s heeft u uit %1$s gezet -%2$s heeft u uit %1$s verbannen +%2$s heeft je uit %1$s gezet +%2$s heeft je uit %1$s verbannen Reden: %1$s Avatar - - %d ongelezen bericht waarin u vermeld bent
-- %d ongelezen berichten waarin u vermeld bent
+- %d ongelezen bericht waarin je vermeld bent
+- %d ongelezen berichten waarin je vermeld bent
Verstuur een sticker Sticker versturen -U heeft momenteel geen stickerpakketten ingeschakeld. + Je hebt momenteel geen stickerpakketten ingeschakeld. \n -\nWilt u er nu een paar toevoegen\? +\nWil je er nu een paar toevoegen\?Account deactiveren Mijn account deactiveren Statistische gegevens (analytics) versturen ${app_name} verzamelt anonieme statistische gegevens (analytics) om het voor ons mogelijk te maken om de app te verbeteren. Er ontbreekt een vereiste parameter. -Om de %1$s-server verder te blijven gebruiken, dient u de voorwaarden te lezen en ermee akkoord te gaan. +Om de %1$s-server verder te blijven gebruiken, dien je de voorwaarden te lezen en ermee akkoord te gaan. Nu doorlezen Account deactiveren -Dit zal uw account voorgoed onbruikbaar maken. U zult zich niet meer kunnen aanmelden, en niemand anders zal met dezelfde persoon-ID kunnen registreren. Dit zal er voor zorgen dat uw account alle gesprekken verlaat waar deze momenteel lid van is, en het verwijdert de accountgegevens van de identiteitsserver. Deze actie is onomkeerbaar. + Dit zal je account voorgoed onbruikbaar maken. Je zal je niet meer kunnen aanmelden, en niemand anders zal met dezelfde persoon-ID kunnen registreren. Dit zal er voor zorgen dat jouw account alle gesprekken verlaat waar deze momenteel lid van is, en het verwijdert de accountgegevens van de identiteitsserver. Deze actie is onomkeerbaar. \n -\nHet deactiveren van uw account zal er niet standaard voor zorgen dat de berichten die u hebt verzonden worden vergeten. Indien u wilt dat wij de berichten vergeten, vinkt u het vakje hieronder aan. +\nHet deactiveren van je account zal er niet standaard voor zorgen dat de berichten die je hebt verzonden worden vergeten. Indien je wil dat wij de berichten vergeten, vink je het vakje hieronder aan. \n -\nDe zichtbaarheid van berichten in Matrix is gelijkaardig aan e-mails. Het vergeten van uw berichten betekent dat berichten die u verstuurd heeft niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie ervan. +\nDe zichtbaarheid van berichten in Matrix is gelijkaardig aan e-mails. Het vergeten van jouw berichten betekent dat berichten die je verstuurd hebt niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie ervan.Vergeet alle berichten die ik heb verstuurd wanneer mijn account gedeactiveerd is (Let op: dit zal er voor zorgen dat toekomstige personen een onvolledig beeld krijgen van gesprekken) Account deactiveren Downloaden -Beveiligingssleutels van uw sessies opnieuw aanvragen. +Beveiligingssleutels van je sessies opnieuw aanvragen. Start ${app_name} op een ander apparaat dat het bericht kan ontsleutelen, zodat het de sleutels naar deze sessie kan sturen. Spraakbericht versturen Sorry, er is geen externe toepassing gevonden om deze actie te voltooien. -Voer uw wachtwoord in. +Voer je wachtwoord in. Beschrijf het probleem in het Engels, indien mogelijk. Media bekijken vóór het versturen Geeft activiteit weer @@ -433,7 +433,7 @@Gesprek verlaten Onderwerp van het gesprek instellen Stuurt persoon met gegeven ID eruit -Wijzigt uw weergavenaam +Wijzig je weergavenaam Markdown aan/uit Dit gesprek is vervangen en is niet langer actief. Het gesprek wordt hier voortgezet @@ -445,7 +445,7 @@- %d geselecteerd
Om Matrix-appbeheer te herstellen -contact op te nemen met uw dienstbeheerder +contact op te nemen met je dienstbeheerder Deze server heeft een van zijn bronlimieten overschreden, dus sommige personen zullen zich niet kunnen aanmelden. Deze server heeft een van zijn bronlimieten overschreden. Deze server heeft zijn limiet voor maandelijks actieve personen overschreden, dus sommige personen zullen zich niet kunnen aanmelden. @@ -460,11 +460,11 @@Beltoon voor inkomende oproepen Selecteer beltoon voor oproepen: Eruit sturen -Voorvertoning van koppelingen in het gesprek tonen (als uw server deze functie ondersteunt). +Voorvertoning van koppelingen in het gesprek tonen (als je server deze functionaliteit ondersteunt). Typmeldingen versturen -Laat andere personen weten dat u aan het typen bent. +Laat andere personen weten dat je aan het typen bent. Markdown-opmaak -Maak berichten op met Markdown-syntax voordat ze verstuurd worden. Hiermee kunt u uitgebreide opmaak gebruiken, zoals sterretjes voor schuingedrukte tekst. +Maak berichten op met Markdown-syntax voordat ze verstuurd worden. Hiermee kan je uitgebreide opmaak gebruiken, zoals sterretjes voor schuingedrukte tekst. Leesbevestigingen weergeven Tik op de leesbevestigingen voor een uitgebreide lijst. Toetredingen en verlatingen weergeven @@ -473,18 +473,18 @@Omvat veranderingen in avatar en weergavenaam. Sleutelback-up Sleutelback-up gebruiken -Indien u zich nu afmeldt, zult u uw versleutelde berichten verliezen -Sleutelback-up is bezig. Indien u zich nu afmeldt, zult u de toegang tot uw versleutelde berichten verliezen. -Veilige sleutelback-up dient actief te zijn op al uw sessies om de toegang tot uw versleutelde berichten niet te verliezen. +Indien je jezelf nu afmeldt, zal je jouw versleutelde berichten verliezen +Sleutelback-up is bezig. Indien je jezelf nu afmeldt, zal je de toegang tot jouw versleutelde berichten verliezen. +Veilige sleutelback-up dient actief te zijn op al je sessies om de toegang tot je versleutelde berichten niet te verliezen. Ik wil mijn versleutelde berichten niet Sleutels worden geback-upt… -Weet u het zeker\? +Weet je het zeker\? Back-up maken -U zult de toegang tot uw versleutelde berichten verliezen, tenzij u eerst een back-up van uw sleutels maakt vooraleer u zich afmeldt. +Je zal de toegang tot je versleutelde berichten verliezen, tenzij je eerst een back-up van je sleutels maakt voordat je jezelf afmeldt. Overslaan Klaar Negeren -Weet u zeker dat u zich wilt afmelden\? +Weet je zeker dat je jezelf wilt afmelden\? Markeren als gelezen Aanmelden met unieke aanmelding Video-oproep gaande… @@ -494,7 +494,7 @@Diagnostische probleemoplossingsinformatie Testen uitvoeren Bezig met uitvoeren… (%1$d van %2$d) -Basisdiagnose is oké. Als u nog steeds geen meldingen ontvangt, gelieve dan een bugmelding in te dienen om ons te helpen onderzoeken. +Basisdiagnose is oké. Als je nog steeds geen meldingen ontvangt, gelieve dan een bugmelding in te dienen om ons te helpen onderzoeken. Er zijn één of meer tests mislukt, probeer de aanbevolen oplossing(en). Er zijn één of meer tests mislukt, gelieve een bugmelding in te dienen om ons te helpen onderzoeken. Systeeminstellingen. @@ -503,8 +503,8 @@ \nGelieve deze te controleren.Instellingen openen Accountinstellingen. -Meldingen zijn ingeschakeld voor uw account. -Meldingen zijn uitgeschakeld voor uw account. + Meldingen zijn ingeschakeld voor jouw account. +Meldingen zijn uitgeschakeld voor jouw account. \nGelieve de accountinstellingen te controleren. Inschakelen Sessie-instellingen. @@ -514,7 +514,7 @@Inschakelen Aangepaste instellingen. Sommige soorten berichten zijn stil (ze geven een geluidsloze melding). -Sommige meldingen zijn uitgeschakeld in uw aangepaste instellingen. +Sommige meldingen zijn uitgeschakeld in je aangepaste instellingen. Play-diensten controleren De APK van Google Play Services is beschikbaar en up-to-date. ${app_name} maakt gebruikt van Google Play Services om pushberichten af te leveren, maar dit lijkt niet juist geconfigureerd te zijn: @@ -528,7 +528,7 @@ [%1$s] \nDeze fout is onafhankelijk van ${app_name}. Volgens Google betekent deze fout dat het apparaat te veel apps heeft geregistreerd met FCM. De fout treedt enkel op ingeval er een enorm aantal apps is, dus zou dit de gemiddelde persoon niet mogen hinderen. [%1$s] -\nDeze fout is onafhankelijk van ${app_name}. Ze kan verschillende oorzaken hebben. Misschien werkt het als u het later opnieuw probeert. U kunt ook controleren of het gegevensverbruik van Google Play Services niet wordt beperkt in de systeeminstellingen, of dat de klok van uw apparaat wel juist staat, of dat het misschien aan een aangepaste ROM ligt. +\nDeze fout is onafhankelijk van ${app_name}. Ze kan verschillende oorzaken hebben. Misschien werkt het als je het later opnieuw probeert. Je kan ook controleren of het gegevensverbruik van Google Play Services niet wordt beperkt in de systeeminstellingen, of dat de klok van je apparaat wel juist staat, of dat het misschien aan een aangepaste ROM ligt.[%1$s] \nDeze fout is onafhankelijk van ${app_name}. Er is geen Google-account verbonden met de telefoon. Open het accountbeheer en voeg er een Google-account toe. Account toevoegen @@ -538,13 +538,13 @@ \n%1$sStarten bij opstarten van apparaat De dienst zal starten wanneer het apparaat wordt herstart. -De dienst zal niet starten wanneer het apparaat wordt herstart en u zult geen meldingen ontvangen tot u ${app_name} hebt geopend. +De dienst zal niet starten wanneer het apparaat wordt herstart en je zal geen meldingen ontvangen tot je ${app_name} hebt geopend. Starten bij opstarten inschakelen Achtergrondbeperkingen controleren Achtergrondbeperkingen zijn uitgeschakeld voor ${app_name}. Deze test dient uitgevoerd te worden met een mobiele verbinding (geen wifi). \n%1$s Achtergrondbeperkingen zijn ingeschakeld voor ${app_name}. -\nAl wat de app probeert te doen zal in de achtergrond hevig beperkt worden; dit kan het correct functioneren van meldingen beïnvloeden. +\nAlles wat de app probeert te doen zal in de achtergrond hevig beperkt worden; dit kan het correct functioneren van meldingen beïnvloeden. \n%1$s Beperkingen uitschakelen Accuoptimalisatie @@ -566,7 +566,7 @@Standaardmediabron Kiezen Sluitergeluid afspelen -Maak een wachtwoord aan om de geëxporteerde sleutels mee te versleutelen. U heeft dit wachtwoord nodig om de sleutels te kunnen importeren. +Maak een wachtwoord aan om de geëxporteerde sleutels mee te versleutelen. Je hebt dit wachtwoord nodig om de sleutels te kunnen importeren. Herstel van versleutelde berichten Sleutelback-up beheren @@ -599,27 +599,27 @@ Wachtwoorden komen niet overeen Voer een wachtwoord in Wachtwoord is te zwak -Verwijder het wachtwoord als u wilt dat ${app_name} een herstelsleutel genereert. -Verlies nooit uw versleutelde berichten -Berichten in versleutelde gesprekken worden beveiligd met end-to-end-versleuteling. Enkel de ontvanger(s) en u hebben de sleutels om deze berichten te lezen. + Verwijder het wachtwoord als je wil dat ${app_name} een herstelsleutel genereert. +Verlies nooit jouw versleutelde berichten +Berichten in versleutelde gesprekken worden beveiligd met eind-to-eind-versleuteling. Enkel de ontvanger(s) en jij hebben de sleutels om deze berichten te lezen. \n -\nMaak een veilige back-up van uw sleutels om ze niet te verliezen. +\nMaak een veilige back-up van jouw sleutels om ze niet te verliezen.Begin sleutelback-up te gebruiken (Geavanceerd) Sleutels handmatig exporteren -Beveilig uw back-up met een wachtwoord. -We bewaren een versleutelde kopie van uw sleutels op onze server. Bescherm uw back-up met een wachtwoord om deze veilig te houden. + Beveilig je back-up met een wachtwoord. +We bewaren een versleutelde kopie van jouw sleutels op onze server. Bescherm je back-up met een wachtwoord om deze veilig te houden. \n -\nVoor een maximale beveiliging zou deze sleutel moeten verschillen van uw accountwachtwoord. +\nVoor een maximale beveiliging zou deze sleutel moeten verschillen van je accountwachtwoord.Wachtwoord instellen Back-up wordt aangemaakt -Of beveilig uw back-up met een herstelsleutel, en bewaar deze op een veilige plaats. +Of beveilig je back-up met een herstelsleutel, en bewaar deze op een veilige plaats. (Geavanceerd) Instellen met herstelsleutel Klaar! -Uw sleutels worden geback-upt. -Uw herstelsleutel is een veiligheidsnet - u kunt deze gebruiken om de toegang tot uw versleutelde berichten te herstellen indien u uw wachtwoord vergeet. -\nBewaar uw herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) -Bewaar uw herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) +Jouw sleutels worden geback-upt. +Jouw herstelsleutel is een veiligheidsnet - je kan deze gebruiken om de toegang tot jouw versleutelde berichten te herstellen indien je jouw wachtwoord vergeet. +\nBewaar je herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) +Bewaar je herstelsleutel op een heel veilige plaats, zoals een wachtwoordbeheerder (of een kluis) Klaar Ik heb een kopie gemaakt Herstelsleutel opslaan @@ -630,23 +630,23 @@Herstelsleutel wordt gegenereerd met wachtwoord, dit proces kan enkele seconden duren. Herstelsleutel Onverwachte fout -Weet u het zeker\? -U kunt de toegang tot uw berichten verliezen indien u zich afmeldt of dit apparaat verliest. +Weet je het zeker\? +Je kunt de toegang tot je berichten verliezen indien je jezelf afmeldt of dit apparaat verliest. Back-upversie wordt opgehaald… -Gebruik uw herstelwachtwoord om uw versleutelde berichtgeschiedenis te ontgrendelen -uw herstelsleutel gebruiken -Als u uw herstelwachtwoord niet meer weet, kunt u %s. -Gebruik uw herstelsleutel om uw versleutelde berichtgeschiedenis te ontgrendelen +Gebruik je herstelwachtwoord om jouw versleutelde berichtgeschiedenis te ontgrendelen +jouw herstelsleutel gebruiken +Als je jouw herstelwachtwoord niet meer weet, kan je %s. +Gebruik je herstelsleutel om jouw versleutelde berichtgeschiedenis te ontgrendelen Voer de herstelsleutel in -Herstelsleutel verloren\? U kunt er een nieuwe instellen in de instellingen. -De back-up kan met dit wachtwoord niet ontsleuteld worden: controleer of u het juiste herstelwachtwoord heeft ingevoerd. +Herstelsleutel verloren\? Je kan er een nieuwe instellen in de instellingen. +De back-up kan met dit wachtwoord niet ontsleuteld worden: controleer of je het juiste herstelwachtwoord hebt ingevoerd. Back-up wordt hersteld: Herstelsleutel wordt berekend… Sleutels worden gedownload… Sleutels worden geïmporteerd… Geschiedenis ontgrendelen Voer een herstelsleutel in -De back-up kan met deze herstelsleutel niet ontsleuteld worden: controleer of u de juiste herstelsleutel heeft ingevoerd. +De back-up kan met deze herstelsleutel niet ontsleuteld worden: controleer of je de juiste herstelsleutel hebt ingevoerd. Back-up hersteld %s! - Back-up met %d sleutel hersteld.
@@ -661,18 +661,18 @@Back-up verwijderen Sleutelback-up is correct ingesteld voor deze sessie. Sleutelback-up is niet actief op deze sessie. -Uw sleutels worden niet geback-upt vanaf deze sessie. +Jouw sleutels worden niet geback-upt vanaf deze sessie. De back-up heeft een ondertekening van een onbekende sessie met ID %s. De back-up heeft een geldige ondertekening van deze sessie. De back-up heeft een geldige ondertekening van de geverifieerde sessie %s. De back-up heeft een geldige ondertekening van de ongeverifieerde sessie %s De back-up heeft een ongeldige ondertekening van de geverifieerde sessie %s De back-up heeft een ongeldige ondertekening van de ongeverifieerde sessie %s -Herstel nu met uw wachtwoord of herstelsleutel om sleutelback-up op deze sessie te gebruiken. +Herstel nu met je wachtwoord of herstelsleutel om sleutelback-up op deze sessie te gebruiken. Back-up wordt verwijderd… Back-up verwijderen -Uw geback-upte versleutelingssleutels verwijderen van de server\? U zult uw herstelsleutel niet meer kunnen gebruiken om de versleutelde berichtgeschiedenis te lezen. -Verlies nooit uw versleutelde berichten +Jouw geback-upte versleutelingssleutels verwijderen van de server\? Je zal jouw herstelsleutel niet meer kunnen gebruiken om de versleutelde berichtgeschiedenis te lezen. +Verlies nooit je versleutelde berichten Sleutelback-up gebruiken Nieuwe sleutels voor versleutelde berichten Beheren in sleutelback-up @@ -687,21 +687,21 @@Ondertekening Sorry, vergadergesprekken met Jitsi worden nog niet ondersteund op oudere apparaten (met een Android-versie lager dan 6.0) onbekend IP-adres -Een nieuwe sessie vraagt versleutelingssleutels aan. -\nSessienaam: %1$s -\nLaatst gezien: %2$s -\nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. -Een ongeverifieerde sessie vraagt versleutelingssleutels aan. -\nSessienaam: %1$s -\nLaatst gezien: %2$s -\nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. +Een nieuwe sessie vraagt versleutelingssleutels aan. +\nSessienaam: %1$s +\nLaatst gezien: %2$s +\nAls je jezelf niet hebt aangemeld op een andere sessie, negeer dan dit verzoek. +Een ongeverifieerde sessie vraagt versleutelingssleutels aan. +\nSessienaam: %1$s +\nLaatst gezien: %2$s +\nAls je jezelf niet hebt aangemeld op een andere sessie, negeer dan dit verzoek. Delen Sleuteldeelverzoek Negeren Geverifieerd! Ik snap het Verificatieverzoek -%s wil uw sessie verifiëren +%s wil je sessie verifiëren Onbekende fout Geen Intrekken @@ -712,17 +712,17 @@Synchroniseren op de achtergrond Geoptimaliseerd voor batterij ${app_name} zal op een batterijzuinige manier synchroniseren op de achtergrond. -\nAfhankelijk van de staat van uw apparaat kan het besturingssysteem de synchronisatie uitstellen. +\nAfhankelijk van de staat van je apparaat kan het besturingssysteem de synchronisatie uitstellen.Geoptimaliseerd voor snelheid ${app_name} zal periodiek op de achtergrond synchroniseren (configureerbaar). -\nDit heeft een negatieve impact op uw batterij- en datagebruik. Er zal een melding getoond worden ter informatie. +\nDit heeft een negatieve impact op je batterij- en datagebruik. Er zal een melding getoond worden ter informatie.Geen achtergrondssynchronisatie -U zal geen melding van berichten ontvangen als de app zich in de achtergrond bevindt. +Je zal geen melding van berichten ontvangen als de app zich in de achtergrond bevindt. Integraties Gebruik een integratiebeheerder om bots, bruggen, widgets en stickerpakketten te beheren. -\nIntegratiebeheerders ontvangen configuratiedata en kunnen widgets aanpassen, gespreksuitnodigingen versturen en bestuursniveaus instellen namens u. +\nIntegratiebeheerders ontvangen configuratiedata en kunnen widgets aanpassen, gespreksuitnodigingen versturen en bestuursniveaus instellen namens jou.Ontdekken -Beheer uw ontdekkingsinstellingen. +Beheer jouw ontdekkingsinstellingen. Integraties toestaan Integratiebeheerder Widget @@ -735,10 +735,10 @@Widget herladen Openen in browser Toegang intrekken voor mij -Uw weergavenaam -Uw profielfoto-URL -Uw persoon-ID -Uw thema +Jouw weergavenaam +Jouw profielfoto-URL +Jouw persoon-ID +Jouw thema Widget-ID Gespreks-ID Deze widget wil gebruik maken van de volgende bronnen: @@ -747,25 +747,25 @@Camera gebruiken Microfoon gebruiken DRM-beschermde media lezen -Om verder te gaan dient u de dienstvoorwaarden te aanvaarden. -Er bestaat al een back-up op uw server -Het lijkt erop dat u al een back-up van uw herstelsleutel heeft uit een andere sessie. Wilt u deze vervangen door degene die u nu aanmaakt\? +Om verder te gaan dien je de dienstvoorwaarden te aanvaarden. +Er bestaat al een back-up op je server +Het lijkt erop dat je al een back-up van je herstelsleutel heeft uit een andere sessie. Wilt je deze vervangen door degene die je nu aanmaakt\? Vervangen Stoppen Back-upstatus wordt gecontroleerd -U gebruikt geen identiteitsserver -Het lijkt er op dat u probeert verbinding te maken met een andere server. Wil je uitloggen\? +Je gebruikt geen identiteitsserver +Het lijkt er op dat je probeert verbinding te maken met een andere server. Wil je uitloggen\? Bewerken Beantwoorden Opnieuw proberen -Heeft u een uitnodiging gestuurd +Heeft je een uitnodiging gestuurd Uitgenodigd door %s -U bent helemaal bij! -U hebt geen ongelezen berichten meer +Je bent helemaal bij! +Je hebt geen ongelezen berichten meer Gesprekken -Uw directe gesprekken zullen hier worden weergegeven. Gebruik de + knop rechts onder om een gesprek te starten. +Jouw directe gesprekken zullen hier worden weergegeven. Gebruik de + knop rechts onder om een gesprek te starten. Kamers -Uw kamers zullen hier worden weergegeven. Gebruik de + knop rechtsonder om een bestaande kamer te openen of een nieuwe aan te maken. +Jouw kamers zullen hier worden weergegeven. Gebruik de + knop rechtsonder om een bestaande kamer te openen of een nieuwe aan te maken. Reacties Bevestigen Reactie Toevoegen @@ -775,7 +775,7 @@Gebeurtenis gemodereerd door gesprek beheerder Niet correcte gebeurtenis, kan niet weergeven Nieuwe kamer aanmaken -Geen netwerk. Controleer uw internet verbinding. +Geen netwerk. Controleer je internet verbinding. Wijzigen Netwerk wijzigen Even wachten… @@ -787,8 +787,8 @@Publiek Iedereen kan deelnemer worden van deze kamer Afspelen -U heeft het hoofdadres voor dit gesprek verwijderd. -U heeft %1$s uitgenodigd. Reden: %2$s +Je hebt het hoofdadres voor dit gesprek verwijderd. +Je hebt %1$s uitgenodigd. Reden: %2$s Jouw uitnodiging. Reden: %1$s Bericht verstuurd Initiële synchronisatie: @@ -797,36 +797,36 @@ \nAan het wachten op een antwoord van de server… Lege kamer (was %s) Moderator -U heeft %1$s uitgenodigd +Je hebt %1$s uitgenodigd %1$s nodigde %2$s uit Geen verandering. -U heeft toekomstige berichten zichtbaar gemaakt voor %1$s +Je hebt toekomstige berichten zichtbaar gemaakt voor %1$s %1$s heeft toekomstige berichten zichtbaar gemaakt voor %2$s -U hebt de oproep beëindigd. -U hebt de oproep beantwoord. -U heeft uw schermnaam gewijzigd van %1$s naar %2$s -U heeft uw schermnaam ingesteld op %1$s -U heeft uw avatar aangepast -U heeft de uitnodiging geweigerd -U heeft de kamer verlaten +Je hebt de oproep beëindigd. +Je hebt de oproep beantwoord. +Je hebt je schermnaam gewijzigd van %1$s naar %2$s +Je hebt je schermnaam ingesteld op %1$s +Je hebt je avatar aangepast +Je hebt de uitnodiging geweigerd +Je hebt de kamer verlaten %1$s heeft de kamer verlaten -U heeft de kamer verlaten -U heeft %1$s uitgenodigd -U heeft de discussie aangemaakt +Je hebt de kamer verlaten +Je hebt %1$s uitgenodigd +Je hebt de discussie aangemaakt %1$s heeft de discussie aangemaakt -U heeft de kamer aangemaakt +Je hebt de kamer aangemaakt %1$s heeft de kamer aangemaakt -Uw uitnodiging -U heeft %1$s verbannen. Reden: %2$s -U heeft de verbanning van %1$s opgeheven. Reden: %2$s -U heeft %1$s eruit getrapt. Reden: %2$s -U heeft de uitnodiging geweigerd. Reden: %1$s -U bent vertrokken. Reden: %1$s +Je uitnodiging +Je hebt %1$s verbannen. Reden: %2$s +Je hebt de verbanning van %1$s opgeheven. Reden: %2$s +Je hebt %1$s eruit getrapt. Reden: %2$s +Je hebt de uitnodiging geweigerd. Reden: %1$s +Je bent vertrokken. Reden: %1$s %1$s is vertrokken. Reden: %2$s -U heeft de kamer verlaten. Reden: %1$s -U heeft zich aangesloten. Reden: %1$s +Je hebt de kamer verlaten. Reden: %1$s +Je hebt je aangesloten. Reden: %1$s %1$s heeft zich aangesloten. Reden: %2$s -U heeft zich aangesloten bij de kamer. Reden: %1$s +Je hebt je aangesloten bij de kamer. Reden: %1$s - - %1$s, %2$s, %3$s en %4$d andere
- %1$s, %2$s, %3$s en %4$d anderen
@@ -835,75 +835,75 @@%1$s, %2$s en %3$s %1$s van %2$s naar %3$s %1$s heeft het machtigingsniveau van %2$s aangepast. -U heeft het machtigingsniveau van %1$s aangepast. +Je hebt het machtigingsniveau van %1$s aangepast. Speciaal Speciaal (%1$d) Standaardlid Beheerder -U heeft de widget %1$s aangepast +Je hebt de widget %1$s aangepast %1$s heeft de widget %2$s aangepast -U heeft de widget %1$s verwijderd +Je hebt de widget %1$s verwijderd %1$s heeft de widget %2$s verwijderd -U heeft de widget %1$s toegevoegd +Je hebt de widget %1$s toegevoegd %1$s heeft de widget %2$s toegevoegd -U heeft de uitnodiging voor %1$s geaccepteerd -U heeft de uitnodiging voor %1$s ingetrokken +Je hebt de uitnodiging voor %1$s geaccepteerd +Je hebt de uitnodiging voor %1$s ingetrokken %1$s heeft de uitnodiging voor %2$s ingetrokken -U heeft de uitnodiging voor %1$s ingetrokken om zich bij de kamer aan te sluiten -U heeft een uitnodiging gestuurd naar %1$s om zich bij de kamer aan te sluiten -U heeft de kameravatar verwijderd +Je hebt de uitnodiging voor %1$s ingetrokken om zich bij de kamer aan te sluiten +Je hebt een uitnodiging gestuurd naar %1$s om zich bij de kamer aan te sluiten +Je hebt de kameravatar verwijderd %1$s heeft de kameravatar verwijderd -U heeft het kameronderwerp verwijderd -U heeft de kamernaam verwijderd -U heeft de kamer geüpgraded. -U verstuurde data om het gesprek op te zetten. +Je hebt het kameronderwerp verwijderd +Je hebt de kamernaam verwijderd +Je hebt de kamer geüpgraded. +Je verstuurde data om het gesprek op te zetten. %s verstuurde data om het gesprek op te zetten. -U heeft een audiogesprek geopend. -U heeft een videogesprek geopend. -U heeft de kamernaam veranderd naar: %1$s -U heeft de kamerafbeelding aangepast +Je hebt een audiogesprek geopend. +Je hebt een videogesprek geopend. +Je hebt de kamernaam veranderd naar: %1$s +Je hebt de kamerafbeelding aangepast %1$s heeft de kamerafbeelding aangepast -U heeft het onderwerp gewijzigd naar: %1$s -U heeft uw weergavenaam verwijderd (voorheen %1$s) -U heeft de uitnodiging van %1$s ingetrokken -U heeft %1$s verbannen -U heeft de verbanning van %1$s opgeheven -U heeft %1$s eruit getrapt -U sloot zich aan +Je hebt het onderwerp gewijzigd naar: %1$s +Je hebt je weergavenaam verwijderd (voorheen %1$s) +Je hebt de uitnodiging van %1$s ingetrokken +Je hebt %1$s verbannen +Je hebt de verbanning van %1$s opgeheven +Je hebt %1$s verwijderd +Je sloot je aan %1$s sluit aan -U heeft de kamer betreden -Druk op uw opname om te stoppen of om te luisteren +Je hebt de kamer betreden +Druk op je opname om te stoppen of om te luisteren Houd ingedrukt om op te nemen, laat los om te versturen Verwijder opname Stembericht aan het opnemen Pauzeer stembericht Speel stembericht af -Iedereen in %s kan de ruimte vinden en betreden - het is niet nodig om iedereen handmatig uit te nodigen. U kunt dit op elk moment aanpassen in de kamer instellingen. +Iedereen in %s kan de ruimte vinden en betreden - het is niet nodig om iedereen handmatig uit te nodigen. Je kan dit op elk moment aanpassen in de kamer instellingen. Stembericht (%1$s) Kan niet antwoorden of aanpassen als stembericht actief is Kan stembericht niet opnemen Kan stembericht niet afspelen -U heeft gasten de toegang tot dit gesprek verleend. -U heeft het hoofdadres voor dit gesprek ingesteld op %1$s. -U heeft %1$s als gespreksadres toegevoegd en %2$s verwijderd. +Je hebt gasten de toegang tot dit gesprek verleend. +Je hebt het hoofdadres voor dit gesprek ingesteld op %1$s. +Je hebt %1$s als gespreksadres toegevoegd en %2$s verwijderd. - - U heeft %1$s als gespreksadres verwijderd.
-- U heeft %1$s als gespreksadressen verwijderd.
+- Je hebt %1$s als gespreksadres verwijderd.
+- Je hebt %1$s als gespreksadressen verwijderd.
- -- U heeft %1$s als kameradres toegevoegd.
-- U heeft %1$s als kameradressen toegevoegd.
+- Je hebt %1$s als kameradres toegevoegd.
+- Je hebt %1$s als kameradressen toegevoegd.
U heeft de uitnodiging van %1$s ingetrokken. Reden: %2$s -U heeft de uitnodiging voor %1$s aanvaard. Reden: %2$s +Je hebt de uitnodiging van %1$s ingetrokken. Reden: %2$s +Je hebt de uitnodiging voor %1$s aanvaard. Reden: %2$s 🎉 Alle servers zijn uitgesloten van deelname! Deze kamer kan niet meer gebruikt worden. • Servers die overeenkomen met IP-tekens zijn nu verbannen. • Servers die overeenkomen met IP-tekens zijn nu toegestaan. • Servers die overeenkomen met IP-letters zijn verbannen. • Servers die overeenkomen met IP-tekens zijn toegestaan. %s heeft de server ACL\'s voor deze kamer ingesteld. -U heeft de server ACL\'s voor deze kamer ingesteld. -U heeft de server ACL\'s voor deze kamer aangepast. +Je hebt de server ACL\'s voor deze kamer ingesteld. +Je hebt de server ACL\'s voor deze kamer aangepast. %s heeft de server ACL\'s voor deze kamer aangepast. • Servers die overeenkomen met %s zijn verwijderd uit de toegestane lijst. • Servers die overeenkomen met %s zijn nu toegestaan. @@ -911,9 +911,9 @@• Servers die overeenkomen met %s zijn nu verbannen. • Servers die overeenkomen met %s zijn toegestaan. • Servers die overeenkomen met %s zijn verbannen. -U heeft hier geüpgraded. +Je hebt hier geüpgraded. %s heeft hier geüpgraded. -U heeft toekomstige kamergeschiedenis zichtbaar gemaakt voor %1$s +Je hebt toekomstige kamergeschiedenis zichtbaar gemaakt voor %1$s %1$ds over %s is toegetreden. Conclusie Bevestiging @@ -958,7 +958,7 @@Klaar! Berichtsleutel Herstelwachtwoordzin -Bevestiging Geannuleerd +Verificatie geannuleerd Sleutelverzoeken Verwijderen Bevestigen Accountgegevens @@ -988,7 +988,7 @@%s heeft geannuleerd Jij hebt geaccepteerd %s heeft geaccepteerd -U heeft geannuleerd +Je hebt geannuleerd Niet beveiligd Ze komen overeen Versleuteling inschakelen @@ -1055,13 +1055,13 @@Overige Geen Persoon negeren -Uzelf degraderen\? +Jezelf degraderen\? Uitnodiging annuleren In de wacht zetten SSL-fout. Camera wisselen Draadloze Koptelefoon -Ruimten +Spaces Wisselen Opwaarderen Aanbevolen @@ -1169,10 +1169,10 @@Wachten… Formaat: Url: -sessie_naam: -app_weergave_naam: -push_key: -app_id: +Sessie weergavenaam: +App weergavenaam: +Push key: +App ID: Voorkeuren Algemeen BEKIJKEN @@ -1186,36 +1186,36 @@Succes Kopiëren Geef toestemming om de camera te gebruiken via de systeeminstellingen om deze actie uit te voeren. -Sommige rechten ontbreken om deze actie uit te voeren, geeft a.u.b. toestemming via de systeeminstellingen. +Sommige rechten ontbreken om deze actie uit te voeren, geeft toestemming via de systeeminstellingen. Ruimten Begin met chatten Herstellen Afwijzen Systeemstandaard -U heeft end-to-end-versleuteling ingeschakeld (onbekend algoritme %1$s). -U heeft end-to-end-versleuteling ingeschakeld. -U heeft gasten de toegang tot het gesprek verhinderd. +Je hebt eind-tot-eind-versleuteling ingeschakeld (onbekend algoritme %1$s). +Je hebt eind-tot-eind-versleuteling ingeschakeld. +Je hebt gasten de toegang tot het gesprek verhinderd. %1$s heeft gasten de toegang tot het gesprek verhinderd. -U heeft gasten de toegang tot het gesprek verhinderd. -U heeft hier gasten toegelaten. +Je hebt gasten de toegang tot het gesprek verhinderd. +Je hebt hier gasten toegelaten. %1$s heeft hier gasten toegelaten. -U heeft het gespreksadres gewijzigd. +Je hebt het gespreksadres gewijzigd. %1$s heeft het gespreksadres gewijzigd. -U heeft het hoofdadres en alternatieve gespreksadres gewijzigd. +Je hebt het hoofdadres en alternatieve gespreksadres gewijzigd. %1$s heeft het hoofdadres en alternatieve gespreksadres gewijzigd. -U heeft het alternatieve gespreksadres gewijzigd. +Je hebt het alternatieve gespreksadres gewijzigd. %1$s heeft het alternatieve gespreksadres gewijzigd. - - U heeft alternatief gespreksadres %1$s verwijderd.
-- U heeft alternatieve gespreksadressen %1$s verwijderd.
+- Je hebt alternatief gespreksadres %1$s verwijderd.
+- Je hebt alternatieve gespreksadressen %1$s verwijderd.
- %1$s heeft %2$s als alternatief gespreksadres verwijderd.
- %1$s heeft %2$s als alternatieve gespreksadressen verwijderd.
- - U heeft %1$s als alternatief gespreksadres toegevoegd.
-- U heeft %1$s als alternatieve gespreksadressen toegevoegd.
+- Je hebt %1$s als alternatief gespreksadres toegevoegd.
+- Je hebt %1$s als alternatieve gespreksadressen toegevoegd.
- %1$s heeft %2$s als alternatief gespreksadres toegevoegd.
@@ -1224,31 +1224,31 @@Aan de slag Spacerechten Gespreksrechten -Door de verbanning op te heffen kan deze gebruiker opnieuw deelnemer worden van de ruimte. -Door de verbanning op te heffen kan deze gebruiker opnieuw deelnemer worden van de kamer. -Door deze persoon te verbannen zal hij/zij verwijderd worden uit deze space en voorkomen dat hij/zij opnieuw toetreedt. +Door de verbanning op te heffen kan deze persoon opnieuw deelnemer worden van de space. +Door de verbanning op te heffen kan deze persoon opnieuw deelnemer worden van de kamer. +Door deze persoon te verbannen zullen ze verwijderd worden uit deze space en voorkomen dat ze opnieuw toetreden. Reden voor verbanning -De gebruiker zal worden verwijderd uit deze ruimte. + De persoon zal worden verwijderd uit deze space. \n -\nOm te voorkomen dat ze opnieuw toetreden, kunt u ze verbannen. -Door deze persoon te verwijderen zal hij/zij niet meer in dit gesprek zitten. +\nOm te voorkomen dat ze opnieuw toetreden, kan je ze verbannen. +De persoon zal worden verwijderd van deze kamer. \n -\nOm te voorkomen dat hij/zij opnieuw toetreedt, kun je hem/haar ook verbannen. +\nOm te voorkomen dat ze opnieuw toetreden, kan je ze verbannen.Reden voor verwijdering -Weet u zeker dat u uitnodiging voor deze persoon wilt annuleren\? -Als u deze persoon niet negeert, worden alle berichten van deze persoon opnieuw weergegeven. +Weet je zeker dat je de uitnodiging voor deze persoon wilt annuleren\? +Als je deze persoon niet negeert, worden alle berichten van deze persoon opnieuw weergegeven. Door deze persoon te negeren worden zijn/haar berichten verwijderd uit gesprekken die jullie delen. \n -\nU kunt deze actie op elk moment ongedaan maken in de algemene instellingen. -U kunt deze wijziging niet ongedaan maken omdat uzelf degradeert, als u de laatste persoon met rechten bent in het gesprek zal het onmogelijk zijn om opnieuw rechten te krijgen. -Deze kamer is niet publiek. U kunt niet opnieuw deelnemer worden zonder uitnodiging. -Toegang verlenen tot uw contactpersonen. -Om de QR-code te scannen moet u toegang verlenen tot de camera. +\nJe kan deze actie op elk moment ongedaan maken in de algemene instellingen. +Je kan deze wijziging niet ongedaan maken omdat je jezelf degradeert, als je de laatste persoon met rechten bent in het gesprek zal het onmogelijk zijn om opnieuw rechten te krijgen. +Deze kamer is niet publiek. Je kan niet opnieuw deelnemer worden zonder uitnodiging. +Toegang verlenen tot je contactpersonen. +Om de QR-code te scannen moet je toegang verlenen tot de camera. Oproep beëindigen… Geen antwoord -De persoon die u heeft gebeld is bezet. +De persoon die je hebt gebeld is bezet. Persoon bezet -U heeft de oproep in de wacht gezet +Je hebt de oproep in de wacht gezet %s heeft de oproep in de wacht gezet Bellen met %s Videobellen met %s @@ -1272,7 +1272,7 @@HD uitschakelen Geluidsapparaat Selecteren Kan geen realtime verbinding tot stand brengen. -\nVraag de beheerder van uw server om een TURN-server te configureren om gesprekken betrouwbaar te laten werken. +\nVraag de beheerder van jouw server om een TURN-server te configureren om gesprekken betrouwbaar te laten werken.${app_name} Oproep Mislukt Server API URL Sleutel deelverzoekgeschiedenis versturen @@ -1284,36 +1284,36 @@Nieuwe waarde Widget verwijderen mislukt Widget toevoegen mislukt -U kunt uzelf niet bellen, wacht totdat deelnemers de uitnodiging accepteren -U kunt niet met uzelf bellen -Vergaderingen gebruiken beveiligings- en toestemmingsbeleid van Jitsi. Alle huidige personen in het gesprek zullen een uitnodiging zien terwijl uw vergadering bezig is. +Je kunt jezelf niet bellen, wacht totdat deelnemers de uitnodiging accepteren +Je kunt niet met jezelf bellen +Vergaderingen gebruiken beveiligings- en toestemmingsbeleid van Jitsi. Alle huidige personen in het gesprek zullen een uitnodiging zien terwijl je vergadering bezig is. Geluidsvergadering starten Videoconferentie starten -U mist de rechten om een oproep te starten -U mist de rechten om een oproep in dit gesprek te starten -U mist de rechten om een vergadering te starten -U mist de rechten om een vergadering in dit gesprek te starten +Je mist de rechten om een oproep te starten +Je mist de rechten om een oproep in dit gesprek te starten +Je mist de rechten om een vergadering te starten +Je mist de rechten om een vergadering in dit gesprek te starten Ontbrekende rechten Geef toestemming om de microfoon te gebruiken om stemberichten te versturen. Alles herstellen -U bent toegetreden. +Je bent toegetreden. Er is een verificatie e-mail verzonden naar %1$s. Controleer je inbox Dit e-mailadres is niet aan een account gekoppeld -Als u uw wachtwoord wijzigt, worden alle end-to-end-versleutelingssleutels voor al uw sessies opnieuw ingesteld, waardoor de gecodeerde chatgeschiedenis onleesbaar wordt. Stel een back-up sleutel in of exporteer uw kamersleutels uit een andere sessie voordat u uw wachtwoord opnieuw instelt. -Er wordt een verificatie-e-mail naar uw inbox gestuurd om het instellen van uw nieuwe wachtwoord te bevestigen. +Als je jouw wachtwoord wijzigt, worden alle eind-tot-eind-versleutelingssleutels voor al je sessies opnieuw ingesteld, waardoor de gecodeerde chatgeschiedenis onleesbaar wordt. Stel een back-up sleutel in of exporteer je kamersleutels uit een andere sessie voordat je jouw wachtwoord opnieuw instelt. +Er wordt een verificatie-e-mail naar jouw inbox gestuurd om het instellen van je nieuwe wachtwoord te bevestigen. Wachtwoord opnieuw instellen op %1$s Dit e-mailadres is niet gekoppeld aan een account. De applicatie kan geen account aanmaken op deze server. \n -\nWilt u zich aanmelden met een webclient\? +\nWil je jezelf aanmelden met een webclient\?Sorry, deze server accepteert geen nieuwe accounts. De applicatie kan niet inloggen op deze server. De thuisserver ondersteunt de volgende aanmeldingstype(s): %1$s. \n \nWil je inloggen met een webclient\? Er is een fout opgetreden bij het laden van de pagina: %1$s (%2$d) -Voer het adres in van de server die u wilt gebruiken -Voer het adres in van de Modular Element of de server die u wilt gebruiken +Voer het adres in van de server die je wil gebruiken +Voer het adres in van de Modular Element of de server die je wil gebruiken Premium hosting voor organisaties Element Matrix Services-adres Geschiedenis wissen @@ -1331,21 +1331,21 @@Word gratis lid met miljoenen anderen op de grootste openbare server Net als e-mail hebben accounts één thuis, hoewel je met iedereen kunt praten Selecteer een server -Breid en pas uw ervaring aan +Breid uit en personaliseer je ervaring Houd gesprekken privé met versleuteling Chat direct met mensen of in groepen -Het is jouw gesprek. Bezet het. -U heeft deze op enkel uitnodiging gemaakt. +Het is jouw gesprek. Bezit het. +Je hebt deze op enkel uitnodiging gemaakt. %1$s heeft dit alleen op uitnodiging gemaakt. Je hebt de kamer alleen op uitnodiging gemaakt. %1$s heeft de kamer alleen voor uitnodigingen ingesteld. -U heeft de kamer openbaar gemaakt voor iedereen die de link kent. -U negeert geen enkele persoon +Je heb de kamer openbaar gemaakt voor iedereen die de link kent. +Je negeert geen enkele persoon %1$s heeft de kamer openbaar gemaakt voor iedereen die de link kent. Klik lang op een kamer om meer opties te zien Schrijf trefwoorden om een reactie te vinden. Stuurt het gegeven bericht als een spoiler -U heeft geen wijzigingen aangebracht +Je hebt geen wijzigingen aangebracht %1$s heeft geen wijzigingen aangebracht Kamer instellingen Verlaat de kamer @@ -1356,15 +1356,15 @@Alle belangrijke berichten Deze inhoud is als ongepast gerapporteerd. \n -\nAls u geen inhoud van deze persoon meer wilt zien, kunt u deze negeren om hun berichten te verbergen. +\nAls je geen inhoud van deze persoon meer wilt zien, kan je deze negeren om hun berichten te verbergen.Gemeld als ongepast Deze inhoud is gerapporteerd als spam. \n -\nAls u geen inhoud van deze persoon meer wilt zien, kunt u deze negeren om hun berichten te verbergen. +\nAls je geen inhoud van deze persoon meer wilt zien, kan je deze negeren om hun berichten te verbergen.Gerapporteerd als spam Deze inhoud is gemeld. \n -\nAls u geen inhoud van deze persoon meer wilt zien, kunt u deze negeren om hun berichten te verbergen. +\nAls je geen inhoud van deze persoon meer wilt zien, kan je deze negeren om hun berichten te verbergen.Reden voor het rapporteren van deze inhoud Deze inhoud rapporteren Er zijn geen bestanden in deze kamer @@ -1394,49 +1394,49 @@Open het menu kamer maken Open de navigatielade Het lijkt erop dat de server er te lang over doet om te reageren. Dit kan worden veroorzaakt door een slechte verbinding of een fout met de server. Probeer het over een tijdje opnieuw. -Probeer het opnieuw zodra u de algemene voorwaarden van uw homeserver hebt geaccepteerd. -Uitgebreide logboeken helpen ontwikkelaars door meer logboeken te verstrekken wanneer u een RageShake verzendt. Zelfs wanneer ingeschakeld, registreert de toepassing geen berichtinhoud of andere privégegevens. +Probeer het opnieuw zodra je de algemene voorwaarden van je homeserver hebt geaccepteerd. +Uitgebreide logboeken helpen ontwikkelaars door meer logboeken te verstrekken wanneer je een RageShake verzendt. Zelfs wanneer ingeschakeld, registreert de toepassing geen berichtinhoud of andere privégegevens. Uitgebreide logboeken inschakelen -Ga akkoord met de servicevoorwaarden van de identiteitsserver (%s), zodat u vindbaar bent op e-mailadres of telefoonnummer. -U deelt momenteel e-mailadressen of telefoonnummers op de identiteitsserver %1$s. U moet opnieuw verbinding maken met %2$s om ze niet meer te delen. +Ga akkoord met de servicevoorwaarden van de identiteitsserver (%s), zodat je vindbaar bent op e-mailadres of telefoonnummer. +Je deelt momenteel e-mailadressen of telefoonnummers op de identiteitsserver %1$s. Je moet opnieuw verbinding maken met %2$s om ze niet meer te delen. De verificatiecode is niet correct. Er is een sms-bericht verzonden naar %s. Voer de verificatiecode in die deze bevat. -De door u gekozen identiteitsserver heeft geen servicevoorwaarden. Ga alleen verder als je de eigenaar van de service vertrouwt +De door jouw gekozen identiteitsserver heeft geen servicevoorwaarden. Ga alleen verder als je de eigenaar van de service vertrouwt Identiteitsserver heeft geen servicevoorwaarden Voer de URL van de identiteitsserver in Kan geen verbinding maken met identiteitsserver Voer een identiteitsserver URL in -Gaat u akkoord met het versturen van deze informatie\? -Om bestaande contacten te ontdekken, moet u contactgegevens (e-mailadressen en telefoonnummers) naar uw identiteitsserver sturen. We hashen uw gegevens voordat ze worden verzonden vanwege privacy. +Ga je akkoord met het versturen van deze informatie\? +Om bestaande contacten te ontdekken, moet je contactgegevens (e-mailadressen en telefoonnummers) naar je identiteitsserver sturen. We hashen je gegevens voordat ze worden verzonden vanwege privacy. Stuur e-mailadressen en telefoonnummers naar %s Toestemming geven Mijn toestemming intrekken -Uw thuisserverbeleid -Kan geen server bereiken op de URL %s. Controleer uw link of kies handmatig een server. -Uw contacten zijn privé. Om personen van uw contacten te ontdekken, hebben we uw toestemming nodig om contactgegevens naar uw identiteitsserver te sturen. -We hebben u een bevestigingsmail gestuurd naar %s, controleer eerst uw e-mail en klik op de bevestigingslink -We hebben u een bevestigingsmail gestuurd naar %s, controleer uw e-mail en klik op de bevestigingslink -Ontdekkingsopties verschijnen zodra u een e-mail heeft toegevoegd. -U gebruikt momenteel %1$s om te ontdekken en vindbaar te zijn voor bestaande contacten die u kent. -U bekijkt deze kamer al! -Er kan geen voorbeeld van deze kamer worden bekeken. Wilt u deelnemen\? +Jouw thuisserverbeleid +Kan geen server bereiken op de URL %s. Controleer je link of kies handmatig een server. +Jouw contacten zijn privé. Om personen van je contacten te ontdekken, hebben we jouw toestemming nodig om contactgegevens naar je identiteitsserver te sturen. +We hebben een e-mail gestuurd naar %s, controleer eerst je e-mail en klik op de bevestigingslink +We hebben een e-mail gestuurd naar %s, controleer je e-mail en klik op de bevestigingslink +Ontdekkingsopties verschijnen zodra je een e-mailadres hebt toegevoegd. +Je gebruikt momenteel %1$s om te ontdekken en vindbaar te zijn voor bestaande contacten die je kent. +Je bekijkt deze kamer al! +Er kan geen voorbeeld van deze kamer worden bekeken. Wil je toetreden\? Deze kamer is op dit moment niet toegankelijk. -\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of u toegang heeft. -Verander uw avatar alleen in deze huidige kamer -Verander uw schermnaam alleen in de huidige kamer -Andere spaces of kamers die u misschien niet kent -Space die u kent die deze kamer bevat -Stel adressen in voor deze kamer zodat personen deze kamer kunnen vinden via uw server (%1$s) -U kunt dit op elk moment uitschakelen in de instellingen -U krijgt geen meldingen voor vermeldingen en trefwoorden in versleutelde kamers op uw mobiel. -Zorg ervoor dat u op de link heeft geklikt in de e-mail die we u hebben gestuurd. -U hebt uw toestemming gegeven om e-mails en telefoonnummers naar deze identiteitsserver te sturen om andere personen van uw contacten te ontdekken. +\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of je toegang hebt. +Verander je afbeelding alleen in deze kamer +Verander je weergavenaam alleen in de huidige kamer +Andere spaces of kamers die je misschien niet kent +Space die je kent die deze kamer bevat +Stel adressen in voor deze kamer zodat personen deze kamer kunnen vinden via jouw server (%1$s) +Je kan dit op elk moment uitschakelen in de instellingen +Je krijgt geen meldingen voor vermeldingen en trefwoorden in versleutelde kamers op je mobiel. +Zorg ervoor dat je op de link hebt geklikt in de e-mail die we je hebben gestuurd. +Je hebt toestemming gegeven om e-mails en telefoonnummers naar deze identiteitsserver te sturen om andere personen van je contacten te ontdekken. E-mailadressen en telefoonnummers versturen Vindbare telefoonnummers -Als u de verbinding met uw identiteitsserver verbreekt, betekent dit dat u niet door andere personen kan worden gevonden en dat u anderen niet per e-mail of telefoon kunt uitnodigen. -Ontdekkingsopties verschijnen zodra u een telefoonnummer heeft toegevoegd. +Als je de verbinding met je identiteitsserver verbreekt, betekent dit dat je niet door andere personen kan worden gevonden en dat je anderen niet per e-mail of telefoon kan uitnodigen. +Ontdekkingsopties verschijnen zodra je een telefoonnummer hebt toegevoegd. Vindbare e-mailadressen -U gebruikt momenteel geen identiteitsserver. Om te ontdekken en vindbaar te zijn door bestaande contacten die u kent, configureert u er een hieronder. +Je gebruikt momenteel geen identiteitsserver. Om te ontdekken en vindbaar te zijn door bestaande contacten die Je kent, configureer je er een hieronder. Geen beleid geleverd door de identiteitsserver Identiteitsserverbeleid verbergen Identiteitsserverbeleid weergeven @@ -1460,7 +1460,7 @@Bekijk de kamer directory Een nieuw privébericht versturen Nieuwe kamer aanmaken -Kunt u niet vinden wat u zoekt\? +Kan je niet vinden wat je zoekt\? Geen bewerkingen gevonden Bestand %1$s is gedownload! Video comprimeren %d%% @@ -1471,14 +1471,14 @@Verborgen gebeurtenissen op de tijdlijn weergeven Geef feedback De feedback kan niet worden verzonden (%s) -Bedankt, uw feedback is succesvol verzonden -U kunt contact met mij opnemen als u vervolgvragen heeft -U gebruikt een bètaversie van spaces. Uw feedback zal helpen bij het informeren van de volgende versies. Uw platform en inlognaam worden genoteerd om ons te helpen uw feedback zoveel mogelijk te gebruiken. +Bedankt, je feedback is succesvol verzonden +Je kan contact met mij opnemen als je vervolgvragen hebt +Je gebruikt een bètaversie van spaces. Jouw feedback zal helpen bij het informeren van de volgende versies. Jouw platform en inlognaam worden genoteerd om ons te helpen jouw feedback zoveel mogelijk te gebruiken. Spaces feedback De suggestie kan niet worden verzonden (%s) Bedankt, de suggestie is succesvol verzonden -Beschrijf hier uw suggestie -Schrijf hieronder uw suggestie. +Beschrijf hier jouw suggestie +Schrijf hieronder jouw suggestie. Een voorstel doen Systeeminstellingen Versies @@ -1500,7 +1500,7 @@ \n \n%sKameronderwerp (optioneel) -Nieuwe ruimte aanmaken +Nieuwe space aanmaken Geeft een plaatsvervangende melding weer voor verwijderde berichten. Verwijderde berichten weergeven Beveiligde back-up instellen @@ -1519,9 +1519,9 @@%1$s in %2$s en %3$s Deze server is al aanwezig in de lijst Kan deze server of de kamerlijst niet vinden -Voer de naam in van een nieuwe server die u wilt verkennen. +Voer de naam in van een nieuwe server die je wil verkennen. Een nieuwe server toevoegen -Uw server +Jouw server - Sleutel %1$d/%2$d geïmporteerd met succes.
- %1$d/%2$d sleutels met succes geïmporteerd.
@@ -1562,7 +1562,7 @@Een nieuw adres handmatig publiceren Andere gepubliceerde adressen: Dit is het hoofdadres -Gepubliceerde adressen kunnen door iedereen op elke server worden gebruikt om lid te worden van uw kamer. Om een adres te publiceren, moet het eerst als lokaal adres worden ingesteld. +Gepubliceerde adressen kunnen door iedereen op elke server worden gebruikt om lid te worden van jouw kamer. Om een adres te publiceren, moet het eerst als lokaal adres worden ingesteld. Gepubliceerde adressen Adressen van deze kamer bekijken en beheren. Ruimte-adressen @@ -1574,27 +1574,27 @@Wie heeft toegang\? Wijzigingen in wie geschiedenis kan lezen, zijn alleen van toepassing op toekomstige berichten in deze kamer. De zichtbaarheid van de bestaande historie blijft ongewijzigd. Account instellingen -U kunt meldingen beheren in %1$s. +Je kan meldingen beheren in %1$s. Houd er rekening mee dat vermeldingen en trefwoordmeldingen niet beschikbaar zijn in versleutelde kamers op mobiel. Informeer mij voor -Beheer e-mailadressen en telefoonnummers die aan uw Matrix-account zijn gekoppeld +Beheer e-mailadressen en telefoonnummers die aan je Matrix-account zijn gekoppeld E-mailadressen en telefoonnummers Schakel hiervoor \'Integraties toestaan\' in bij Instellingen. Integraties zijn uitgeschakeld Deze server biedt geen beleid. Bibliotheken van derden -Uw identiteitsserverbeleid +Jouw identiteitsserverbeleid ${app_name}-beleid We delen geen informatie met derden We registreren of profileren geen accountgegevens hier -Help ons problemen te identificeren en ${app_name} te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door uw apparaten wordt gedeeld. + Help ons problemen te identificeren en ${app_name} te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door jouw apparaten wordt gedeeld. \n -\nU kunt al onze voorwaarden %s lezen. +\nJe kan al onze voorwaarden %s lezen.Help ${app_name} verbeteren -Dit zal uw huidige sleutel of zin vervangen. -Genereer een nieuwe beveiligingssleutel of stel een nieuwe beveiligingszin in voor uw bestaande back-up. -Bescherm uzelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op uw server. +Dit zal jouw huidige sleutel of zin vervangen. +Genereer een nieuwe beveiligingssleutel of stel een nieuwe beveiligingszin in voor je bestaande back-up. +Bescherm jezelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op je server. Instellen op dit apparaat Beveiligde back-up resetten Beveiligde back-up instellen @@ -1616,23 +1616,23 @@Versleutelde berichten in groepsgesprekken Versleutelde berichten in één-op-één gesprekken Er is op de melding geklikt! -Klik op de melding. Als u de melding niet ziet, controleer dan de systeeminstellingen. -U bekijkt de melding! Klik hier! +Klik op de melding. Als je de melding niet ziet, controleer dan de systeeminstellingen. +Je bekijkt de melding! Klik hier! Kan push niet ontvangen. Oplossing zou kunnen zijn om de applicatie opnieuw te installeren. De applicatie ontvangt PUSH De applicatie wacht op de PUSH Trefwoorden mogen \'%s\' niet bevatten Trefwoorden mogen niet beginnen met \'.\' Nieuw trefwoord toevoegen -Uw trefwoorden +Jouw trefwoorden Breng me op de hoogte voor Vermeldingen en trefwoorden Standaardmeldingen E-mailmeldingen inschakelen voor %s -Om e-mail met melding te ontvangen, koppelt u een e-mail aan uw Matrix-account +Om e-mail met melding te ontvangen, koppel je een e-mailadres aan je Matrix-account E-mail notificatie -Er is geen e-mailadres toegevoegd aan uw account -Er is geen telefoonnummer toegevoegd aan uw account +Er is geen e-mailadres toegevoegd aan je account +Er is geen telefoonnummer toegevoegd aan je account De sessie is afgemeld! De kamer is verlaten! Alleen vermeldingen en trefwoorden @@ -1660,8 +1660,8 @@Personen uitnodigen Berichten sturen Standaardrol -U bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van deze space te wijzigen -U bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van de kamer te wijzigen +Je bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van deze space te wijzigen +Je bent niet gemachtigd om de rollen bij te werken die nodig zijn om verschillende delen van de kamer te wijzigen Selecteer de rollen die nodig zijn om verschillende delen van deze space te wijzigen Selecteer de rollen die nodig zijn om verschillende delen van de kamer te veranderen Bekijk en update de rollen die nodig zijn om verschillende delen van de kamer te veranderen. @@ -1669,8 +1669,8 @@Kies server Niet nu Inschakelen -Luisteren naar notificaties -U mag niet deelnemen aan deze kamer +Luisteren naar meldingen +Je mag niet toetreden tot deze kamer Gebeurtenis status verzonden! Gebeurtenis verzonden! Misvormde gebeurtenis @@ -1706,7 +1706,7 @@Sleutel importeren uit bestand Widgets openen Authenticatie mislukt -${app_name} vereist dat u uw inloggegevens invoert om deze actie uit te voeren. +${app_name} vereist dat je jouw inloggegevens invoert om deze actie uit te voeren. Opnieuw authenticatie nodig Schuif om het gesprek te beëindigen Onbekend persoon @@ -1737,20 +1737,20 @@Terugbellen Dit gesprek is beëindigd %1$s heeft dit gesprek geweigerd -U heeft deze oproep geweigerd +Je hebt deze oproep geweigerd Veranderingen ongedaan maken Er zijn niet opgeslagen wijzigingen. De wijzigingen negeren\? De kamer is nog niet aangemaakt. Het aanmaken van een kamer annuleren\? De link was verkeerd ingedeeld QR-code niet gescand! Ongeldige QR-code (ongeldige URI)! -U kunt uzelf niet DM\'en! +Je kan jezelf niet DM\'en! Deel via tekst Kan deze kamer niet vinden. Zorg ervoor dat het bestaat. -U kunt geen kamer openen waar u uit bent verbannen. -Wijzig uw huidige pincode +Je kan geen kamer openen waar je uit bent verbannen. +Wijzig je huidige pincode Verander pincode -Elke keer dat u ${app_name} opent, is een pincode vereist. +Elke keer dat je ${app_name} opent, is een pincode vereist. Pincode is vereist na 2 minuten ${app_name} niet te hebben gebruikt. Pincode vereist na 2 minuten Geef alleen het aantal ongelezen berichten weer in een eenvoudige melding. @@ -1759,16 +1759,16 @@Pincode is de enige manier om ${app_name} te ontgrendelen. Schakel apparaatspecifieke biometrische gegevens in, zoals vingerafdrukken en gezichtsherkenning. Biometrische gegevens inschakelen -Als u uw pincode opnieuw wilt instellen, tikt u op Pincode vergeten om uit te loggen en opnieuw in te stellen. +Als je jouw pincode opnieuw wilt instellen, tik je op Pincode vergeten om uit te loggen en opnieuw in te stellen. Pincode inschakelen Beveiliging configureren Beveilig de toegang met pincode en biometrie. Toegang beveiligen -Om uw pincode opnieuw in te stellen, moet u opnieuw inloggen en een nieuwe maken. -Voer uw pincode in +Om je pincode opnieuw in te stellen, moet je opnieuw inloggen en een nieuwe maken. +Voer je pincode in Kan pincode niet valideren. Tik voor een nieuwe. Kies een pincode voor beveiliging -Te veel fouten, u bent uitgelogd +Te veel fouten, je bent uitgelogd Waarschuwing! Laatste resterende poging voor uitloggen! - - %d invoer
@@ -1778,49 +1778,49 @@- Verkeerde code, %d resterende poging
- Verkeerde code, %d resterende pogingen
Controleer uw instellingen om pushmeldingen in te schakelen +Controleer je instellingen om pushmeldingen in te schakelen Pushmeldingen zijn uitgeschakeld Kan persoon verbanning niet opheffen Verbannen door %1$s Uitnodiging voor %1$s intrekken\? Zoeken naar contacten op Matrix -Uw contactenboek is leeg -Uw contacten ophalen… +Jouw contactenboek is leeg +Jouw contacten ophalen… Herstelsleutel opslaan in LEER MEER BEGREPEN -We zijn verheugd om aan te kondigen dat we van naam zijn veranderd! Uw app is up-to-date en u bent ingelogd op uw account. +We zijn verheugd om aan te kondigen dat we van naam zijn veranderd! Jouw app is up-to-date en je bent ingelogd op jouw account. Riot is nu Element! Wachten op versleutelingsgeschiedenis -U heeft geen toegang tot dit bericht omdat de afzender de sleutels met opzet niet heeft verzonden -U heeft geen toegang tot dit bericht omdat u bent geblokkeerd door de afzender -U heeft geen toegang tot dit bericht omdat uw sessie niet wordt vertrouwd door de afzender -Vanwege end-to-end-versleuteling moet u mogelijk wachten op het bericht van iemand omdat de versleutelingssleutels niet correct naar u zijn verzonden. +Je hebt geen toegang tot dit bericht omdat de afzender de sleutels met opzet niet heeft verzonden +Je hebt geen toegang tot dit bericht omdat je bent geblokkeerd door de afzender +Je hebt geen toegang tot dit bericht omdat jouw sessie niet wordt vertrouwd door de afzender +Vanwege eind-tot-eind-versleuteling moet je mogelijk wachten op het bericht van iemand omdat de versleutelingssleutels niet correct naar jou zijn verzonden. Wachten op dit bericht, dit kan even duren -U heeft geen toegang tot dit bericht +Je hebt geen toegang tot dit bericht Avatar instellen Je hebt de kamerinstellingen met succes gewijzigd -Voer uw beveiligingszin nogmaals in om deze te bevestigen. -Voer een beveiligingszin in die alleen u kent en die wordt gebruikt om geheimen op uw server te beveiligen. +Voer jouw beveiligingszin nogmaals in om deze te bevestigen. +Voer een beveiligingszin in die alleen jij kent en die wordt gebruikt om geheimen op jouw server te beveiligen. Stel een beveiligingszin in -Bewaar uw beveiligingssleutel ergens veilig, zoals een wachtwoordbeheerder of een kluis. -Bewaar uw beveiligingssleutel -Voer een geheime zin in die alleen u kent en genereer een sleutel voor back-up. +Bewaar jouw beveiligingssleutel ergens veilig, zoals een wachtwoordbeheerder of een kluis. +Bewaar jouw beveiligingssleutel +Voer een geheime zin in die alleen jij kent en genereer een sleutel voor back-up. Gebruik een beveiligingszin Genereer een beveiligingssleutel om ergens veilig op te slaan, zoals een wachtwoordbeheerder of een kluis. Een beveiligingssleutel gebruiken -Bescherm uzelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op uw server. +Bescherm jezelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op je server. Start de camera Stop de camera Dempen van de microfoon opheffen De microfoon dempen Voer de URL van een identiteitsserver in -U kunt ook een andere identiteitsserver URL invoeren -Uw server (%1$s) stelt voor om %2$s te gebruiken voor uw identiteitsserver +Je kan ook een andere identiteitsserver URL invoeren +Je server (%1$s) stelt voor om %2$s te gebruiken voor jouw identiteitsserver De toestemming van de persoon is niet gegeven. Er is geen huidige associatie met dit id. De associatie heeft gefaald. -Voor uw privacy ondersteunt ${app_name} alleen het versturen van gehashte e-mailadressen en telefoonnummers van personen. +Voor je privacy ondersteunt ${app_name} alleen het versturen van gehashte e-mailadressen en telefoonnummers van personen. Accepteer eerst de voorwaarden van de identiteitsserver in de instellingen. Configureer eerst een identiteitsserver. Deze operatie is niet mogelijk. De server is verouderd. @@ -1829,11 +1829,11 @@Open voorwaarden van %s Beschikbare talen laden… Andere beschikbare talen -Deel deze code met mensen zodat ze deze kunnen scannen om u toe te voegen en te beginnen met chatten. +Deel deze code met mensen zodat ze deze kunnen scannen om je toe te voegen en te beginnen met chatten. Mijn code Mijn code delen Een QR-code scannen -We kunnen geen personen uitnodigen. Controleer de personen die u wilt uitnodigen en probeer het opnieuw. +We kunnen geen personen uitnodigen. Controleer de personen die je wil uitnodigen en probeer het opnieuw. - - Uitnodigingen verzonden naar %1$s en nog één
- Uitnodigingen verzonden naar %1$s en %2$d meer
@@ -1845,19 +1845,19 @@Hé, praat met me op ${app_name}: %s Vrienden uitnodigen Mensen toevoegen -We kunnen je DM niet maken. Controleer de personen die u wilt uitnodigen en probeer het opnieuw. -De link %1$s brengt u naar een andere site: %2$s. + We kunnen je DM niet maken. Controleer de personen die je wilt uitnodigen en probeer het opnieuw. +De link %1$s brengt je naar een andere site: %2$s. \n -\nWeet u zeker dat u door wilt gaan\? +\nWeet je zeker dat je door wilt gaan\?Dubbelcheck deze link Kies een wachtwoord. Kies een inlognaam. Kan kruislingsondertekenen niet instellen -Bevestig uw identiteit door deze login te verifiëren en deze toegang te verlenen tot versleutelde berichten. -Bevestig uw identiteit door deze login van een van uw andere sessies te verifiëren en toegang te verlenen tot versleutelde berichten. +Bevestig je identiteit door deze login te verifiëren en deze toegang te verlenen tot versleutelde berichten. +Bevestig je identiteit door deze login van een van uw andere sessies te verifiëren en toegang te verlenen tot versleutelde berichten. Interactief verifiëren door Emoji Handmatig verifiëren via tekst -Verifieer de nieuwe login voor toegang tot uw account: %1$s +Verifieer de nieuwe login voor toegang tot je account: %1$s Verifieer al je sessies om ervoor te zorgen dat je account en berichten veilig zijn Bekijk waar je bent ingelogd Versleuteld door een niet-geverifieerd apparaat @@ -1866,34 +1866,34 @@Stuurt het gegeven bericht met sneeuwval Stuurt het gegeven bericht met confetti - -- Laat het apparaat zien waarmee u nu kunt verifiëren
-- %d apparaten weergeven waarmee u nu kunt verifiëren
+- Laat het apparaat zien waarmee je nu kan verifiëren
+- %d apparaten weergeven waarmee je nu kan verifiëren
U start opnieuw op zonder geschiedenis, geen berichten, vertrouwde apparaten of vertrouwde personen +Je start opnieuw op zonder geschiedenis, geen berichten, vertrouwde apparaten of vertrouwde personen Als je alles reset -Doe dit alleen als u geen ander apparaat heeft waarmee u dit apparaat kunt verifiëren. +Doe dit alleen als je geen ander apparaat hebt waarmee je dit apparaat kunt verifiëren. Alle herstelopties vergeten of verloren\? Alles resetten Kan geen toegang krijgen tot beveiligde opslag -Selecteer uw herstelsleutel of voer deze handmatig in door deze te typen of te plakken vanaf uw klembord +Selecteer je herstelsleutel of voer deze handmatig in door deze te typen of te plakken vanaf je klembord Herstelsleutel gebruiken -Gebruik uw %1$s of gebruik uw %2$s om door te gaan. +Gebruik je %1$s of gebruik je %2$s om door te gaan. Alleen ondersteund in versleutelde kamers Dwingt dat de huidige uitgaande groepssessie in een versleutelde kamer wordt weggegooid -Gebruik de nieuwste ${app_name} op uw andere apparaten: +Gebruik de nieuwste ${app_name} op je andere apparaten: of een andere Matrix client die kruislingsondetekenen ondersteunt ${app_name} iOS \n${app_name} Android ${app_name} Web \n${app_name} Desktop -Gebruik de nieuwste ${app_name} op uw andere apparaten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} voor Android of een andere Matrix-client die geschikt is voor kruislingsondertekenen +Gebruik de nieuwste ${app_name} op je andere apparaten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} voor Android of een andere Matrix-client die geschikt is voor kruislings ondertekenen Stel een nieuw accountwachtwoord in… Kan mediabestand niet opslaan -Als u deze instelling inschakelt, wordt de FLAG_SECURE aan alle activiteiten toegevoegd. Start de toepassing opnieuw om de wijziging door te voeren. +Als je deze instelling inschakelt, wordt de FLAG_SECURE aan alle activiteiten toegevoegd. Start de applicatie opnieuw om de wijziging door te voeren. Voorkom screenshots van de applicatie Sleutel Back-up herstelsleutel -Weet u uw Key Back-up wachtwoordzin niet, u kunt %s. -gebruik uw Backup-herstelsleutel -Voer uw Sleutel Back-up wachtwoordzin in om door te gaan. +Weet je jouw Key Back-up wachtwoordzin niet, je kan %s. +gebruik je Backup-herstelsleutel +Voer je Sleutel Back-up wachtwoordzin in om door te gaan. Sleutelback-up geheim opslaan in SSSS SSSS sleutel genereren uit herstelsleutel SSSS sleutel genereren op basis van wachtwoordzin (%s) @@ -1903,8 +1903,8 @@Back-upsleutel controleren Voer een herstelsleutel in Het is geen geldige herstelsleutel -Voer uw %s in om door te gaan -Verifieer uzelf en anderen om uw chats veilig te houden +Voer je %s in om door te gaan +Verifieer jezelf en anderen om jouw chats veilig te houden Encryptie upgrade beschikbaar Dit account is gedeactiveerd. Onjuiste inlognaam en/of wachtwoord. Het ingevoerde wachtwoord begint of eindigt met spaties, controleer dit alstublieft. @@ -1915,24 +1915,24 @@Bijna klaar! Toont het andere apparaat een vinkje\? Een onderwerp toevoegen %s om mensen te laten weten waar deze kamer over gaat. -Dit is het begin van uw privéberichtgeschiedenis met %s. +Dit is het begin van jouw privéberichtgeschiedenis met %s. Dit is het begin van dit gesprek. Dit is het begin van %s. -U hebt de kamer gemaakt en geconfigureerd. +Je hebt de kamer gemaakt en geconfigureerd. %s heeft de kamer gemaakt en geconfigureerd. De versleuteling die door deze kamer wordt gebruikt, wordt niet ondersteund Versleuteling niet ingeschakeld -Berichten in deze chat zijn end-to-end-versleuteld. +Berichten in deze chat zijn eind-tot-eind-versleuteld. Berichten in deze kamer zijn eind-tot-eind-versleuteld. Lees meer en verifieer persoon in hun profiel. -Als u nu annuleert, kunt u versleutelde berichten en gegevens kwijtraken als u de toegang tot uw aanmeldingen verliest. + +Als je nu annuleert, kan je versleutelde berichten en gegevens kwijtraken als je de toegang tot uw aanmeldingen verliest. \n -\nU kunt ook Veilige back-up instellen en uw sleutels beheren in Instellingen. -Kopieer het naar uw persoonlijke cloudopslag +\nJe kan ook Veilige back-up instellen en uw sleutels beheren in Instellingen.Kopieer het naar je persoonlijke cloudopslag Bewaar het op een USB-stick of back-upstation Print het uit en bewaar het ergens veilig -Uw %2$s en %1$s zijn nu ingesteld. + Jouw %2$s en %1$s zijn nu ingesteld. \n -\nHoud ze veilig! U heeft ze nodig om versleutelde berichten te ontgrendelen en informatie te beveiligen als u al uw actieve sessies verliest. +\nHoud ze veilig! Je hebt ze nodig om versleutelde berichten te ontgrendelen en informatie te beveiligen als je al jouw actieve sessies verliest.Sleutelback-up instellen Zelfondertekenende sleutel synchroniseren Persoonssleutel synchroniseren @@ -1943,25 +1943,25 @@Hou het veilig Herstel instellen. Dit kan enkele seconden duren, even geduld a.u.b. -Voer een beveiligingszin in die alleen u kent en die wordt gebruikt om geheimen op uw server te beveiligen. -Gebruik niet uw accountwachtwoord. -Voer uw %s in om door te gaan. -Verificatie is geannuleerd. U kunt de verificatie opnieuw starten. +Voer een beveiligingszin in die alleen jij kent en die wordt gebruikt om geheimen op jouw server te beveiligen. +Gebruik niet je accountwachtwoord. +Voer je %s in om door te gaan. +Verificatie is geannuleerd. Je kan de verificatie opnieuw starten. Een van de volgende zaken kan worden aangetast: \n -\n- Uw wachtwoord -\n- Uw server +\n- Jouw wachtwoord +\n- Jouw server \n- Dit apparaat, of het andere apparaat \n- De internetverbinding die elk apparaat gebruikt \n -\nWe raden u aan uw wachtwoord en herstelsleutel onmiddellijk in Instellingen te wijzigen. -U verifieert %1$s (%2$s) niet als u nu annuleert. Begin opnieuw in hun profiel. -Als u annuleert, kunt u geen versleutelde berichten op dit apparaat lezen en zullen andere personen het niet vertrouwen -Als u annuleert, kunt u geen versleutelde berichten lezen op je nieuwe apparaat en zullen andere personen het niet vertrouwen -Uw account is mogelijk gecompromitteerd +\nWe raden je aan jouw wachtwoord en herstelsleutel onmiddellijk in Instellingen te wijzigen. +Je verifieert %1$s (%2$s) niet als je nu annuleert. Begin opnieuw in hun profiel. +Als je annuleert, kan je geen versleutelde berichten op dit apparaat lezen en zullen andere personen het niet vertrouwen +Als je annuleert, kan je geen versleutelde berichten lezen op je nieuwe apparaat en zullen andere personen het niet vertrouwen +Jouw account is mogelijk gecompromitteerd Dit was ik niet -Gebruik deze sessie om uw nieuwe te verifiëren en deze toegang te verlenen tot versleutelde berichten. -Nieuwe login. Was u dit\? +Gebruik deze sessie om je nieuwe te verifiëren en deze toegang te verlenen tot versleutelde berichten. +Nieuwe login. Was jij dit\? Ontgrendel de geschiedenis van versleutelde berichten Exportcontrole ${app_name} Android @@ -1970,7 +1970,7 @@Gebeurtenis verwijderd door persoon, reden: %1$s Reden voor redigeren Geef een reden op -Weet u zeker dat u deze gebeurtenis wilt verwijderen (wissen)\? Houd er rekening mee dat als u een kamer naam of onderwerpwijziging verwijdert, de wijziging ongedaan kan worden gemaakt. +Weet je zeker dat je deze gebeurtenis wil verwijderen (wissen)\? Houd er rekening mee dat als je een kamer naam of onderwerpwijziging verwijdert, de wijziging ongedaan kan worden gemaakt. Media versturen in het originele formaat - - Stuur video in het originele formaat
@@ -1980,7 +1980,7 @@- Stuur afbeelding in het originele formaat
- Stuur afbeeldingen in het originele formaat
Wilt u deze bijlage naar %1$s sturen\? +Wil je deze bijlage naar %1$s sturen\? Kan geen geheimen vinden in opslag Als je geen toegang hebt tot een bestaande sessie Een herstelwachtwoordzin of -sleutel gebruiken @@ -1990,8 +1990,8 @@Vliegtuigmodus is ingeschakeld Verbinding met de server is verbroken Bijna klaar! Toont %s een vinkje\? -Totdat deze persoon deze sessie vertrouwt, worden berichten die van en naar de sessie worden verzonden, gelabeld met waarschuwingen. U kunt het ook handmatig verifiëren. -%1$s (%2$s) aangemeld met een nieuwe sessie: +Totdat deze persoon deze sessie vertrouwt, worden berichten die van en naar deze sessie worden verzonden, gelabeld met waarschuwingen. Je kan het ook handmatig verifiëren. +%1$s (%2$s) is aangemeld met een nieuwe sessie: Deze sessie wordt vertrouwd voor veilig berichtenverkeer omdat %1$s (%2$s) deze heeft geverifieerd: Kan geen sessies ophalen Gebruik een bestaande sessie om deze te verifiëren en deze toegang te verlenen tot versleutelde berichten. @@ -2000,39 +2000,39 @@- %d actieve sessie
- %d actieve sessies
Verifieer deze sessie om hem als vertrouwd te markeren en verleen hem toegang tot versleutelde berichten. Als u zich niet bij deze sessie hebt aangemeld, is uw account mogelijk gehackt: -Deze sessie wordt vertrouwd voor veilige berichten omdat u deze heeft geverifieerd: +Verifieer deze sessie om als vertrouwd te markeren en toegang te verlenen tot versleutelde berichten. Als jij je niet bij deze sessie hebt aangemeld, is jouw account mogelijk gehackt: +Deze sessie wordt vertrouwd voor veilige berichten omdat je deze hebt geverifieerd: Geen cryptografische informatie beschikbaar Standaardversie Kamerversies 👓 De limiet is onbekend. -Uw server accepteert bijlagen (bestanden, media, enz.) met een grootte tot %s. +Jouw server accepteert bijlagen (bestanden, media, enz.) met een grootte tot %s. Server limiet voor het uploaden van bestanden Serverversie Server naam Afmelden voor deze sessie Alle sessies tonen -Uw serverbeheerder heeft standaard end-to-end versleuteling uitgeschakeld in privékamers en privéberichten. +Jouw serverbeheerder heeft standaard eind-tot-eind versleuteling uitgeschakeld in privékamers en privéberichten. Kruisondertekenen is niet ingeschakeld -Kruisondertekenen is ingeschakeld. + +Kruislings ondertekenen is ingeschakeld. \nSleutels worden niet vertrouwd -Kruisondertekenen is ingeschakeld + Kruislings ondertekenen is ingeschakeld \nSleutels zijn vertrouwd. \nPrivésleutels zijn niet bekend Kruisondertekening is ingeschakeld \nPrivésleutels op het apparaat. -Uw nieuwe sessie is nu geverifieerd. Het heeft toegang tot uw gecodeerde berichten en andere personen zullen het als vertrouwd zien. -Berichten met deze persoon zijn end-to-end-versleuteld en kunnen niet door derden worden gelezen. +Jouw nieuwe sessie is nu geverifieerd. Het heeft toegang tot jouw gecodeerde berichten en andere personen zullen het als vertrouwd zien. +Berichten met deze persoon zijn eind-tot-eind-versleuteld en kunnen niet door derden worden gelezen. Vergelijk de code met die op het scherm van de andere persoon. Vergelijk de unieke emoji en zorg ervoor dat ze in dezelfde volgorde verschijnen. Doe dit voor de zekerheid persoonlijk of gebruik een andere manier om te communiceren. -Om veilig te zijn, verifieert u %s door een eenmalige code te controleren. +Om veilig te zijn, verifieer je %s door een eenmalige code te controleren. Eenmaal ingeschakeld, kan versleuteling voor een kamer niet worden uitgeschakeld. Berichten die in een versleutelde kamer worden verzonden, kunnen niet door de server worden gezien, alleen door de deelnemers van de kamer. Het inschakelen van versleuteling kan voorkomen dat veel bots en koppelingen correct werken. -U bent niet gemachtigd om versleuteling in deze kamer in te schakelen. -End-to-end-versleuteling inschakelen… +Je bent niet gemachtigd om versleuteling in deze kamer in te schakelen. +Eind-tot-eind-versleuteling inschakelen… Verzendt de gegeven emote gekleurd als een regenboog Stuurt het gegeven bericht gekleurd als een regenboog -Deze sessie kan deze verificatie niet delen met uw andere sessies. + Deze sessie kan deze verificatie niet delen met jouw andere sessies. \nDe verificatie wordt lokaal opgeslagen en gedeeld in een toekomstige versie van de app. ${app_name} heeft een probleem ondervonden bij het weergeven van de inhoud van het gebeurtenis met id \'%1$s\' ${app_name} verwerkt geen gebeurtenissen van het type \'%1$s\' @@ -2043,40 +2043,40 @@Moderator in %1$s Beheerder in %1$s De kamer verlaten… -Berichten hier zijn end-to-end-versleuteld. + +Berichten hier zijn eind-tot-eind-versleuteld. \n -\nUw berichten zijn beveiligd met sloten en alleen u en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. -Berichten in deze kamer zijn end-to-end-versleuteld. +\nJouw berichten zijn beveiligd met sloten en alleen jij en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. +Berichten in deze kamer zijn eind-tot-eind-versleuteld. \n -\nUw berichten zijn beveiligd met sloten en alleen u en de ontvanger hebben de unieke sleutels om ze te ontgrendelen. -Berichten hier zijn niet end-to-end-versleuteld. -Berichten in deze kamer zijn niet end-to-end-versleuteld. +\nJouw berichten zijn beveiligd met sloten en alleen jij en de ontvanger hebben de unieke sleutels om ze te ontgrendelen.Berichten hier zijn niet eind-tot-eind-versleuteld. +Berichten in deze kamer zijn niet eind-tot-eind-versleuteld. Wachten op %s… Verifieer door emoji\'s te vergelijken Verifieer door emoji te vergelijken -Als u niet persoonlijk aanwezig bent, vergelijk dan emoji\'s +Als je niet persoonlijk aanwezig bent, vergelijk dan emoji\'s Scannen met dit apparaat Scan hun code -Scan de code met uw ander apparaat of wissel en scan met dit apparaat +Scan de code met je andere apparaat of wissel en scan met dit apparaat Scan de code met het apparaat van de andere persoon om elkaar veilig te verifiëren Deze sessie verifiëren Gereageerd met: %s -"Een van de volgende zaken kan worden aangetast: + Een van de volgende zaken kan worden aangetast: \n -\n - Uw server -\n - De server waarmee de gebruiker die u verifieert is verbonden -\n - De internetverbinding van u of de andere personen -\n - Het apparaat van u of van andere personen" +\n - Jouw server +\n - De server waarmee de gebruiker die je verifieert is verbonden +\n - De internetverbinding van jou of de andere personen +\n - Het apparaat van jou of van andere personenZe komen niet overeen Niet-vertrouwd inloggen -Uw e-maildomein is niet geautoriseerd om op deze server te registreren +Jouw e-maildomein is niet geautoriseerd om op deze server te registreren Ruimte aanmaken… Kamer aanmaken… Sommige tekens zijn niet toegestaan Geef een kameradres op Dit adres is al in gebruik Ruimte-adres -U kunt dit inschakelen als de kamer alleen wordt gebruikt voor samenwerking met interne teams op uw server. Dit kan later niet meer worden gewijzigd. +Je kan dit inschakelen als de kamer alleen wordt gebruikt voor samenwerking met interne teams op jouw server. Dit kan later niet meer worden gewijzigd. Blokkeer iedereen die geen deel uitmaakt van %s om ooit deel te nemen aan deze kamer Verberg geavanceerd Geavanceerd weergeven @@ -2090,87 +2090,87 @@Schud je telefoon om de detectiedrempel te testen De ontwikkelaarsmodus activeert verborgen functies en kan de applicatie ook minder stabiel maken. Alleen voor ontwikkelaars! De beschrijving is te kort -Uw matrix.to link is onjuist opgemaakt -De huidige sessie is voor gebruiker %1$s en u geeft inloggegevens op voor persoon %2$s. Dit wordt niet ondersteund door ${app_name}. -\nWis eerst de gegevens en meld u vervolgens opnieuw aan met een ander account. -U raakt de toegang tot beveiligde berichten kwijt, tenzij u zich aanmeldt om uw versleutelingssleutels te herstellen. +Jouw matrix.to link is onjuist opgemaakt +De huidige sessie is voor gebruiker %1$s en je geeft inloggegevens op voor persoon %2$s. Dit wordt niet ondersteund door ${app_name}. +\nWis eerst de gegevens en meld je vervolgens opnieuw aan met een ander account. +Je raakt de toegang tot beveiligde berichten kwijt, tenzij je jezelf aanmeldt om jouw versleutelingssleutels te herstellen. Alle gegevens wissen die momenteel op dit apparaat zijn opgeslagen\? -\nMeld u opnieuw aan om toegang te krijgen tot uw accountgegevens en berichten. +\nMeld je opnieuw aan om toegang te krijgen tot je accountgegevens en berichten.Alle gegevens wissen -Waarschuwing: uw persoonlijke gegevens (inclusief versleutelingssleutels) zijn nog steeds opgeslagen op dit apparaat. + Waarschuwing: jouw persoonlijke gegevens (inclusief versleutelingssleutels) zijn nog steeds opgeslagen op dit apparaat. \n -\nWis het als u klaar bent met het gebruik van dit apparaat of als u zich wilt aanmelden bij een ander account. +\nWis het als je klaar bent met het gebruik van dit apparaat of als je jezelf wilt aanmelden bij een ander account.Persoonlijke gegevens wissen Log in om versleutelingssleutels te herstellen die exclusief op dit apparaat zijn opgeslagen. Je hebt ze nodig om al uw beveiligde berichten op elk apparaat te lezen. -Uw server (%1$s) beheerder heeft u uitgelogd van uw account %2$s (%3$s). +Je server (%1$s) beheerder heeft je uitgelogd van jouw account %2$s (%3$s). Je bent uitgelogd Opnieuw inloggen Het kan verschillende redenen hebben: \n -\n• U heeft uw wachtwoord bij een andere sessie gewijzigd. +\n• Je hebt je wachtwoord bij een andere sessie gewijzigd. \n -\n• U heeft deze sessie verwijderd uit een andere sessie. +\n• Je hebt deze sessie verwijderd uit een andere sessie. \n -\n• De beheerder van uw server heeft uw toegang om veiligheidsredenen ongeldig gemaakt. +\n• De beheerder van je server heeft jouw toegang om veiligheidsredenen ongeldig gemaakt.Je bent uitgelogd -Kan geen geldige server vinden. Controleer uw ID a.u.b. +Kan geen geldige server vinden. Controleer je ID Dit is geen geldige persoon-ID. Verwacht formaat: \'@persoon:server.org\' -Als u uw wachtwoord niet weet, gaat u terug om het opnieuw in te stellen. +Als je jouw wachtwoord niet weet, ga je terug om het opnieuw in te stellen. Als je een account aanmaakt op een server, gebruik dan je Matrix ID (bijv. @persoon:domein.nl) en wachtwoord hieronder. Aanmelden met Matrix ID Aanmelden met Matrix ID - -- Er zijn te veel verzoeken verzonden. Je kunt het over %1$d seconde opnieuw proberen…
-- Er zijn te veel verzoeken verzonden. Je kunt het over %1$d seconden opnieuw proberen…
+- Er zijn te veel verzoeken verzonden. Je kan het over %1$d seconde opnieuw proberen…
+- Er zijn te veel verzoeken verzonden. Je kan het over %1$d seconden opnieuw proberen…
Deze server draait op een oude versie. Vraag uw server beheerder om te upgraden. U kunt doorgaan, maar sommige functies werken mogelijk niet correct. +Deze server draait op een oude versie. Vraag je server beheerder om te upgraden. Je kan doorgaan, maar sommige functionaliteiten werken mogelijk niet correct. De ingevoerde code is niet correct. Gelieve dit te controleren. We hebben zojuist een e-mail gestuurd naar %1$s. \nKlik op de link die deze bevat om door te gaan met het aanmaken van een account. -Controleer uw e-mail +Controleer je e-mail Accepteer de voorwaarden om door te gaan Voer de captcha uitdaging uit Selecteer een aangepaste server Selecteer Element Matrix Services -Uw account is nog niet aangemaakt. Het registratieproces stoppen\? +Jouw account is nog niet aangemaakt. Het registratieproces stoppen\? Deze inlognaam is in gebruik Inlognaam of e-mailadres -Meld u aan bij %1$s +Meld je aan bij %1$s Telefoonnummer lijkt ongeldig. Controleer het alstublieft Internationale telefoonnummers moeten beginnen met \'+\' Gebruik het internationale formaat (telefoonnummer moet beginnen met \'+\') -We hebben zojuist een code naar %1$s gestuurd. Voer het hieronder in om te verifiëren dat u het bent. +We hebben zojuist een code naar %1$s gestuurd. Voer het hieronder in om te verifiëren dat jij het bent. Telefoonnummer bevestigen Telefoon nummer (optioneel) Gebruik het internationale formaat. -Stel een telefoonnummer in om optioneel toe te staan dat mensen die u kent u kunnen ontdekken. +Stel een telefoonnummer in om optioneel toe te staan dat mensen die je kent jou kunnen ontdekken. Telefoonnummer instellen Lijkt niet op een geldig e-mailadres -Stel een e-mail in om uw account te herstellen. Later kunt u optioneel toelaten dat mensen die u kent u via uw e-mail ontdekken. +Stel een e-mail in om je account te herstellen. Later kan je optioneel toestaan dat mensen die je kent jou via je e-mail ontdekken. E-mailadres instellen -Uw wachtwoord is nog niet gewijzigd. + Jouw wachtwoord is nog niet gewijzigd. \n \nHet proces voor het wijzigen van het wachtwoord stoppen\? Terug naar Inloggen -U bent bij alle sessies uitgelogd en ontvangt geen pushmeldingen meer. Log opnieuw in op elk apparaat om meldingen weer in te schakelen. +Je bent bij alle sessies uitgelogd en ontvangt geen pushmeldingen meer. Log opnieuw in op elk apparaat om meldingen weer in te schakelen. Je wachtwoord is gereset. Ik heb mijn e-mailadres geverifieerd -Tik op de link om uw nieuwe wachtwoord te bevestigen. Klik hieronder als u de link hebt gevolgd die erin staat. -Word eigenaar van uw gesprekken. -Ruimte aanmaken -Ruimte aanmaken… +Tik op de link om je nieuwe wachtwoord te bevestigen. Klik hieronder als je de link hebt gevolgd die erin staat. +Word eigenaar van jouw gesprekken. +Space aanmaken +Space aanmaken… Een ruimte aanmaken Gebeurtenis inhoud Houd er rekening mee dat bij het upgraden een nieuwe versie van de kamer wordt gemaakt. Alle huidige berichten blijven in deze gearchiveerde kamer. -Iedereen in een ouderkamer kan deze kamer vinden en er lid van worden. Het is niet nodig om iedereen handmatig uit te nodigen. U kunt dit op elk moment wijzigen in de kamer instellingen. +Iedereen in een ouderspace kan deze kamer vinden en er lid van worden. Het is niet nodig om iedereen handmatig uit te nodigen. Je kan dit op elk moment wijzigen in de kamer instellingen. Het upgraden van een kamer is een geavanceerde actie en wordt meestal aanbevolen wanneer een kamer onstabiel is vanwege bugs, ontbrekende functies of beveiligingsproblemen. \nDit heeft meestal alleen invloed op hoe de kamer op de server wordt verwerkt. Beheer kamers -U bent de enige beheerder van deze kamer. Als u het verlaat, betekent dit dat niemand er controle over heeft. +Je bent de enige beheerder van deze kamer. Als je het verlaat, betekent dit dat niemand er controle over heeft. Deze alias is momenteel niet toegankelijk. -\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of u toegang heeft. +\nProbeer het later opnieuw of vraag een kamerbeheerder om te controleren of je toegang hebt.Word lid van mijn kamer %1$s %2$s -Als u lid wilt worden van een bestaande kamer, heeft u een uitnodiging nodig. +Als je lid wil worden van een bestaande kamer, heb je een uitnodiging nodig. Weet je zeker dat je alle niet verzonden berichten in deze kamer wilt verwijderen\? Automatisch rapport versleutelingsfouten. Poll maken @@ -2179,7 +2179,7 @@Upload bestand Afbeeldingen en video\'s versturen Open camera -Weet u zeker dat u deze poll wilt verwijderen\? U kunt het niet meer herstellen nadat het is verwijderd. +Weet je zeker dat je deze poll wilt verwijderen\? Je kan het niet meer herstellen nadat het is verwijderd. Poll verwijderen Poll beëindigd Stem uitgebracht @@ -2216,65 +2216,65 @@Vraag of onderwerp Poll vraag of onderwerp Poll maken -Start de toepassing opnieuw om de wijziging door te voeren. +Start de applicatie opnieuw om de wijziging door te voeren. LaTeX wiskunde inschakelen %s in Instellingen om uitnodigingen rechtstreeks in ${app_name} te ontvangen. -Koppel deze e-mail aan uw account -Deze uitnodiging voor deze space is verzonden naar %s die niet is gekoppeld aan uw account -Deze uitnodiging voor deze kamer is verzonden naar %s die niet is gekoppeld aan uw account +Koppel deze e-mail aan je account +Deze uitnodiging voor deze space is verzonden naar %s die niet is gekoppeld aan jouw account +Deze uitnodiging voor deze kamer is verzonden naar %s die niet is gekoppeld aan jouw account Stop met opnemen Schuif om te annuleren Spraakbericht opnemen Sorry, er is een fout opgetreden bij het proberen deel te nemen aan: %s Upgrade naar de aanbevolen kamerversie In deze kamer wordt versie %s gebruikt, die door deze server als onstabiel is gemarkeerd. -U heeft toestemming nodig om een kamer te upgraden +Je hebt toestemming nodig om een kamer te upgraden Bovenliggende space automatisch bijwerken Personen automatisch uitnodigen -U update de kamer van %1$s naar %2$s. +Je update de kamer van %1$s naar %2$s. Upgrade privékamer Upgrade openbare kamer Upgrade vereist Even geduld, het kan even duren. Deelnemen aan vervangende kamer Naamloze kamer -Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en u een uitnodiging nodig heeft. -Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en u een uitnodiging nodig heeft. -\nU heeft geen rechten om kamers toe te voegen. +Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en je een uitnodiging nodig hebt. +Sommige kamers zijn mogelijk verborgen omdat ze privé zijn en je een uitnodiging nodig hebt. +\nJe hebt geen rechten om kamers toe te voegen. Deze space heeft geen kamers -Neem contact op met uw server beheerder voor meer informatie +Neem contact op met jouw serverbeheerder voor meer informatie Het lijkt erop dat je server nog geen Spaces ondersteunt Experimenteel voelen\? -\nU kunt bestaande spaces aan een space toevoegen. -Alle kamers waarin u deelneemt, worden weergegeven in Home. +\nJe kan bestaande spaces aan een space toevoegen.Alle kamers waarin je deelneemt, worden weergegeven in Home. Alle kamers op startscherm weergeven Kamers en spaces beheren Markeren als aanbevolen Markeren als niet aanbevolen Op zoek naar iemand die niet in %s zit\? -%s nodigt u uit -Uw systeem verzendt automatisch logboeken wanneer er een fout optreedt die niet kan worden ontsleuteld -U bent uitgenodigd +%s nodigt je uit +Jouw systeem verzendt automatisch logboeken wanneer er een fout optreedt die niet kan worden ontsleuteld +Je bent uitgenodigd Spaces zijn een nieuwe manier om kamers en mensen te groeperen. -Voeg een space toe aan elke space die u beheert. +Voeg een space toe aan elke space die jij beheert. Bestaande spaces toevoegen Bestaande kamers toevoegen Bestaande kamers en space toevoegen -U kunt pas weer deelnemen als u opnieuw wordt uitgenodigd. -U bent de enige persoon hier. Als u weggaat, kan niemand meer meedoen, ook u niet. -Weet u zeker dat u %s wilt verlaten\? +Je kan pas weer deelnemen als je opnieuw wordt uitgenodigd. +Je bent de enige persoon hier. Als je weggaat, kan niemand meer meedoen, ook jij niet. +Weet je zeker dat je %s wil verlaten\? Verlaat Kamers toevoegen Kamers ontdekken - - %d persoon die u kent is al lid geworden
-- %d mensen die u kent zijn al lid geworden
+- %d persoon die je kent is al lid geworden
+- %d mensen die je kent zijn al lid geworden
Ontdek (%s) Installatie voltooien Nodig uit via e-mail, vind contacten en meer… Voltooi het instellen van detectie. -U gebruikt momenteel geen identiteitsserver. Om teamgenoten uit te nodigen en door hen vindbaar te zijn, configureert u er hieronder een. +Je gebruikt momenteel geen identiteitsserver. Om teamgenoten uit te nodigen en door hen vindbaar te zijn, configureet je er hieronder een. Doe toch mee Space deelnemen Voor nu overslaan @@ -2285,37 +2285,37 @@Deel link Uitnodigen via inlognaam of e-mailadres uitnodiging via e-mail -Het is alleen u op dit moment. %s zal nog beter zijn met anderen. +Het is alleen jij op dit moment. %s zal nog beter zijn met anderen. Uitnodigen voor %s Mensen uitnodigen -Nodig mensen uit voor uw space -Laten we voor elk van hen een kamer maken. U kunt later ook meer toevoegen, inclusief reeds bestaande. -Aan welke dingen werkt u\? -Zorg ervoor dat de juiste mensen toegang hebben tot %s bedrijf. U kunt later meer uitnodigen. -Wie zijn uw teamgenoten\? -We zullen kamers voor hen maken. U kunt later ook meer toevoegen. -Wat zijn enkele discussies die u wilt voeren in %s\? +Nodig mensen uit voor jouw space +Laten we voor elk van hen een kamer maken. Je kan er later ook meer toevoegen, inclusief reeds bestaande. +Aan welke dingen werk jij\? +Zorg ervoor dat de juiste mensen toegang hebben tot %s bedrijf. Je kan er later meer uitnodigen. +Wie zijn jouw teamgenoten\? +We zullen kamers voor hen maken. Je kan later ook meer toevoegen. +Wat zijn enkele discussies die je wil voeren in %s\? Geef het een naam om door te gaan. -Voeg wat details toe om mensen te helpen het te identificeren. U kunt deze op elk moment wijzigen. -Voeg wat details toe om het te laten opvallen. U kunt deze op elk moment wijzigen. -Alleen op uitnodiging, het beste voor uzelf of teams +Voeg wat details toe om mensen te helpen het te identificeren. Je kan deze op elk moment wijzigen. +Voeg wat details toe om het te laten opvallen. Je kan deze op elk moment wijzigen. +Alleen op uitnodiging, het beste voor jezelf of teams Open voor iedereen, het beste voor gemeenschappen -Een privé-ruimte voor u en uw teamgenoten +Een privé-ruimte voor jou en jouw teamgenoten Ik en teamgenoten Een privé space om je kamers te organiseren Alleen ik Zorg ervoor dat de juiste mensen toegang hebben tot %s. -Met wie werkt u samen\? -U kunt dit later wijzigen -Wat voor soort ruimte wilt u aanmaken\? -Uw privé-ruimte -Uw openbare ruimte +Met wie werk je samen\? +Je kan dit later wijzigen +Wat voor soort ruimte wil je aanmaken\? +Jouw privé-ruimte +Jouw openbare ruimte Space toevoegen Privé space Openbare space Niet verzonden berichten verwijderen Berichten kunnen niet worden verzonden -Wilt u het versturen van een bericht annuleren\? +Wil je het versturen van een bericht annuleren\? Alle mislukte berichten verwijderen Upgrade een kamer naar een nieuwe versie Verlaat kamer met gegeven id (of huidige kamer indien leeg) @@ -2325,11 +2325,11 @@Kleur weergavenaam overschrijven Ik heb al een account Veilig berichtenverkeer. -U heeft de controle. +Jij hebt de controle. Deel locatie Open met -${app_name} kan geen toegang krijgen tot uw locatie. Probeer het later opnieuw. -${app_name} heeft geen toegang tot uw locatie +${app_name} kan geen toegang krijgen tot jouw locatie. Probeer het later opnieuw. +${app_name} heeft geen toegang tot jouw locatie Locatie Deel locatie Resultaten worden pas onthuld als je de poll beëindigt @@ -2345,36 +2345,36 @@Versleuteling is verkeerd geconfigureerd. Hun locatie gedeeld Account aanmaken -Berichten voor uw team. -End-to-end versleuteld en geen telefoonnummer vereist. Geen advertenties of dataverzameling. -Kies waar je gesprekken worden bewaard, zodat u controle en onafhankelijkheid heeft. Verbonden via Matrix. -Veilige en onafhankelijke communicatie die u dezelfde mate van privacy geeft als een persoonlijk gesprek in uw eigen huis. +Berichten voor jouw team. +Eind-tot-eind versleuteld en geen telefoonnummer vereist. Geen advertenties of dataverzameling. +Kies waar je gesprekken worden bewaard, zodat je controle en onafhankelijkheid hebt. Verbonden via Matrix. +Veilige en onafhankelijke communicatie die je dezelfde mate van privacy geeft als een persoonlijk gesprek in je eigen huis. Locatie -De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt versturen. Klik om instellingen te openen. -De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt versturen. Neem contact op met een beheerder om de versleuteling in een geldige staat te herstellen. +De versleuteling is verkeerd geconfigureerd, zodat je geen berichten kunt versturen. Klik om instellingen te openen. +De versleuteling is verkeerd geconfigureerd, zodat je geen berichten kunt versturen. Neem contact op met een beheerder om de versleuteling in een geldige staat te herstellen. Berichtbubbels weergeven Kan kaart niet laden Kaart Let op: app wordt opnieuw gestart Discussieberichten inschakelen Verbinding maken met server -Wilt u lid worden van een bestaande server\? +Wil je lid worden van een bestaande server\? Sla deze vraag over Nog niet zeker\? %s Gemeenschappen Teams Vrienden en familie -We helpen u om verbinding te maken -Met wie gaat u het meest chatten\? -U bekijkt deze discussie al! +We helpen je om verbinding te maken +Met wie ga je het meest chatten\? +Je bekijkt deze thread al! Bekijk in kamer -Reageren in discussie +Reageren in thread Het commando \"%s\" wordt herkend maar niet ondersteund in discussies. -Van een discussie +Van een thread Tip: Tik lang op een bericht en gebruik \"%s\". Discussies helpen je gesprekken on-topic te houden en gemakkelijk bij te houden. -Houd discussies georganiseerd met discussielijnen -Toont alle discussies waaraan u heeft deelgenomen +Houd discussies georganiseerd met threads +Toont alle discussies waaraan je hebt deelgenomen Mijn discussies Toont alle discussies van de huidige kamer Alle discussies @@ -2384,7 +2384,7 @@Discussies in de kamer filteren Kopieer link naar discussie Bekijk in kamer -Discussies bekijken +Thead bekijken Personen De server accepteert geen inlognaam met alleen cijfers. Kamer notificatie @@ -2408,24 +2408,24 @@Pin van geselecteerde locatie op kaart Sla deze stap over Opslaan en doorgaan -U kunt op elk moment bij de instellingen uw profiel bijwerken +Je kan op elk moment bij de instellingen jouw profiel bijwerken Ziet er goed uit! Laten we beginnen -Tijd om een gezicht bij uw naam te voegen +Tijd om een gezicht bij je naam te voegen Voeg een profielfoto toe -U kunt dit later wijzigen +Je kunt dit later wijzigen Weergavenaam Kies een weergavenaam -Uw account %s is aangemaakt +Jouw account %s is aangemaakt Gefeliciteerd! Breng me naar het begin Personaliseer profiel -We komen dichter bij het uitbrengen van een openbare bèta voor Discussies. + +We komen dichter bij het uitbrengen van een openbare bèta voor Threads. \n \nTerwijl we ons erop voorbereiden, moeten we enkele wijzigingen aanbrengen: discussies die vóór dit punt zijn gemaakt, worden weergegeven als gewone antwoorden. \n -\nDit zal een eenmalige overgang zijn, aangezien Discussies nu deel uitmaken van de Matrix-specificatie. -Discussies die bèta naderen 🎉 +\nDit zal een eenmalige overgang zijn, aangezien Threads nu deel uitmaken van de Matrix-specificatie.Threads benaderen bèta 🎉 %1$s, %2$s en anderen %1$s en %2$s Uitzetten @@ -2437,7 +2437,7 @@8 uur 1 uur 15 minuten -Deel uw live locatie voor +Deel jouw live locatie voor (%1$s) %1$s (%2$s) %1$s kan niet worden afgeluisterd @@ -2448,17 +2448,17 @@Hun live locatie gedeeld ${app_name} is ook geweldig voor op de werkplek. Het wordt vertrouwd door \'s werelds veiligste organisaties. BÈTA -Discussies zijn werk in uitvoering met nieuwe, opwindende aankomende functies, zoals verbeterde meldingen. We horen graag uw feedback! +Threads zijn werk in uitvoering met nieuwe, opwindende aankomende functies, zoals verbeterde meldingen. We horen graag jouw feedback! Discussies Beta-feedback Geef feedback BÈTA -Indien ingeschakeld, verschijnt u altijd offline voor andere personen, zelfs wanneer u de applicatie gebruikt. +Indien ingeschakeld, verschijn je altijd offline voor andere personen, zelfs wanneer je de applicatie gebruikt. Offline modus Aanwezigheid -Uw server ondersteunt momenteel geen discussies, dus deze functie kan onbetrouwbaar zijn. Sommige berichten in een discussie zijn mogelijk niet betrouwbaar beschikbaar. %sWilt u toch discussies inschakelen\? -Discussies bèta -Discussies helpen uw gesprekken on-topic te houden en gemakkelijk bij te houden. %s Als u discussies inschakelt, wordt de app vernieuwd. Bij sommige accounts kan dit langer duren. -Discussies bèta +Jouw server ondersteunt momenteel geen threads, dus deze functionaliteit kan onbetrouwbaar zijn. Sommige berichten in een thread zijn mogelijk niet betrouwbaar beschikbaar. %sWil je threads toch inschakelen\? +Threads bèta +Threads helpen je gesprekken on-topic te houden en gemakkelijk bij te houden. %s Als je threads inschakelt, wordt de app vernieuwd. Bij sommige accounts kan dit langer duren. +Threads bèta Leer meer Probeer het uit Scherm delen is bezig @@ -2482,7 +2482,7 @@Live tot %1$s Bekijk live locatie Live locatie beëindigd -Sommige resultaten zijn mogelijk verborgen omdat ze privé zijn en u hiervoor een uitnodiging nodig heeft. +Sommige resultaten zijn mogelijk verborgen omdat ze privé zijn en je hiervoor een uitnodiging nodig hebt. Geen resultaten gevonden Verlaat geen Verlaat alles @@ -2493,7 +2493,7 @@min u Locatie delen inschakelen -Let op: dit is een labfunctie met een tijdelijke implementatie. Dit betekent dat u uw locatiegeschiedenis niet kunt verwijderen en dat geavanceerde gebruikers uw locatiegeschiedenis kunnen zien, zelfs nadat u stopt met het delen van uw live locatie met deze ruimte. +Let op: dit is een labfunctie met een tijdelijke implementatie. Dit betekent dat je jouw locatiegeschiedenis niet kunt verwijderen en dat geavanceerde gebruikers jouw locatiegeschiedenis kunnen zien, zelfs nadat je stopt met het delen van je live locatie met deze ruimte. Live locatie delen Huidige gateway: %s Gateway @@ -2512,9 +2512,9 @@Meldingsmethode Achtergrondsynchronisatie Google Services -Kies hoe u meldingen wilt ontvangen +Kies hoe je meldingen wil ontvangen Kan biometrische authenticatie niet inschakelen. -Biometrische authenticatie is uitgeschakeld omdat er onlangs een nieuwe biometrische authenticatiemethode is toegevoegd. U kunt het weer inschakelen in Instellingen. +Biometrische authenticatie is uitgeschakeld omdat er onlangs een nieuwe biometrische authenticatiemethode is toegevoegd. Je kan het weer inschakelen in Instellingen. Meldingsmethode resetten Profieltag: Kan eindpunt token niet registreren op server: @@ -2525,7 +2525,7 @@ Resultaten zijn zichtbaar wanneer de poll is afgelopen Bij het uitnodigen in een versleutelde ruimte die geschiedenis deelt, is de versleutelde geschiedenis zichtbaar. MSC3061: Kamersleutels delen voor eerdere berichten -Stuur uw eerste bericht om %s uit te nodigen om te chatten +Stuur je eerste bericht om %s uit te nodigen om te chatten Berichten in deze chat worden eind-tot-eind versleuteld. Ga @@ -2533,49 +2533,49 @@ - %d berichten verwijderd
Deel locatie -U moet de juiste rechten hebben om de live locatie in deze kamer te delen. -U heeft geen toestemming om de live locatie te delen +Je moet de juiste rechten hebben om de live locatie in deze kamer te delen. +Je hebt geen toestemming om de live locatie te delen Kan deze link niet openen: communities zijn vervangen door spaces Gebruikersnaam / E-mailadres / Telefoonnummer -Bent u een mens\? +Ben je een mens\? Volg de instructies die naar %s zijn verstuurd Wachtwoord reset Wachtwoord vergeten -Email opnieuw verzenden +E-mail opnieuw verzenden Geen e-mail ontvangen\? Volg de instructies die naar %s zijn gestuurd -Verifieer uw e-mailadres +Verifieer je e-mailadres Code nogmaals versturen Er is een code verzonden naar %s -Bevestig uw telefoonnummer +Bevestig je telefoonnummer Alle apparaten uitloggen Reset wachtwoord Zorg ervoor dat het 8 tekens of meer zijn. Kies een nieuw wachtwoord Nieuw wachtwoord -Controleer uw e-mail. -%s stuurt u een verificatielink +Controleer je e-mail. +%s stuurt je een verificatielink Bevestigingscode Telefoonnummer -%s moet uw account verifiëren -Vul uw telefoonnummer in +%s moet je account verifiëren +Vul je telefoonnummer in %s moet uw account verifiëren -Vul uw e-mailadres in +%s moet jouw account verifiëren +Vul jouw e-mailadres in Lees de voorwaarden en het beleid van %s door Serverbeleiden Neem contact op Element Matrix Services (EMS) is een robuuste en betrouwbare hostingservice voor snelle, veilige en realtime communicatie. Ontdek hoe op element.io/ems -Wilt u uw eigen server hosten\? +Wil je jouw eigen server hosten\? Server URL -Wat is het adres van uw server\? Dit is als uw huis voor al uw data -Selecteer uw server +Wat is het adres van jouw server\? Dit is als een huis voor al jouw data +Selecteer je server Welkom terug! Bewerk Of -Waar uw gesprekken zijn opgeslagen +Waar jouw gesprekken worden opgeslagen Moet 8 tekens of meer zijn -Anderen kunnen u ontdekken %s +Anderen kunnen je ontdekken %s Maak een account aan Systeemstandaard gebruiken Handmatig kiezen @@ -2585,22 +2585,21 @@Snelkoppelingen voor Element Oproep machtigingen inschakelen Live locatie Deze QR-code lijkt misvormd. Probeer te verifiëren met een andere methode. -U hebt geen toegang tot de gecodeerde berichtgeschiedenis. Reset uw Veilige Berichten Back-up en verificatiesleutels om opnieuw te beginnen. +Je hebt geen toegang tot de gecodeerde berichtgeschiedenis. Reset je Veilige Berichten Back-up en verificatiesleutels om opnieuw te beginnen. Kan dit apparaat niet verifiëren -Wat is het adres van uw server\? -Waar uw conversaties leven -Uw gegevens bijwerken… +Wat is het adres van jouw server\? +Waar jouw gesprekken zijn opgeslagen +Jouw gegevens bijwerken… - %1$s en %2$d andere
- %1$s en %2$d andere
%1$s en %2$s E-mailadres niet geverifieerd, controleer je inbox -Alle sessies weergeven (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. +Voor de beste beveiliging verifieer je jouw sessies en meld je jezelf af bij elke sessie die je niet meer herkent of gebruikt. Andere sessies Sessies Lijst met publieke spaces @@ -2619,18 +2618,18 @@Kamer creëren Gesprek starten Alle gesprekken -U kunt feedback geven via het menu rechtsboven. -Krijg sneller en gemakkelijker toegang tot uw ruimten (rechtsonder). -Om ${app_name} te versimpelen zijn tabbladen nu optioneel. U kunt ze beheren in het menu rechtsboven. -Hier zullen uw ongelezen berichten verschijnen wanneer u deze heeft. +Je kan feedback geven via het menu rechtsboven. +Krijg sneller en gemakkelijker toegang tot jouw spaces (rechtsonder). +Om ${app_name} te versimpelen zijn tabbladen nu optioneel. Je kan ze beheren in het menu rechtsboven. +Hier zullen jouw ongelezen berichten verschijnen wanneer je deze hebt. De allesomvattende beveiligde chat-app voor teams, vrienden en organisaties. Maak een gesprek aan of word deelnemer van een bestaande kamer om te beginnen. -Ruimten zijn een nieuwe manier om kamers en personen te groeperen. Voeg een bestaande kamer toe, of maak een nieuwe aan via de knop rechtsonder. +Spaces zijn een nieuwe manier om kamers en personen te groeperen. Voeg een bestaande kamer toe, of maak een nieuwe aan via de knop rechtsonder. - - Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke u niet meer gebruikt.
-- Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke u niet meer gebruikt.
+- Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke je niet meer gebruikt.
+- Overweeg uit te loggen van oude sessies (%1$d of meer dagen) welke je niet meer gebruikt.
Verifieer of log uit van ongeverifieerde sessies. -Verbeter uw accountbeveiliging door deze aanbevelingen te volgen. +Verbeter je accountbeveiliging door deze aanbevelingen te volgen. - Al %1$d+ dag inactief (%2$s)
- Al %1$d+ dagen inactief (%2$s)
@@ -2654,8 +2653,8 @@Sessie verifiëren Sorry, deze kamer kon niet worden gevonden. \nProbeer het later opnieuw. %s -Dit is waar uw nieuwe verzoeken en uitnodigingen zullen verschijnen. -Ruimten zijn een nieuwe manier om kamers en personen te groeperen. Maak een ruimte aan om te beginnen. +Dit is waar jouw nieuwe verzoeken en uitnodigingen zullen verschijnen. +Spaces zijn een nieuwe manier om kamers en personen te groeperen. Maak een space aan om te beginnen. Ongeverifieerde sessie Geverifieerde sessie Onbekend apparaattype @@ -2664,8 +2663,177 @@Mobiel Niets nieuws. Uitnodigingen -Nog geen ruimten. +Nog geen spaces. %s subitems inklappen %s subitems uitvouwen -Ruimte aanpassen - +Space veranderen +Voeg (╯°□°)╯︵ ┻━┻ toe voor elk platte tekst bericht +Spraakuitzending +${app_name} heeft toestemming nodig om notificaties te laten zien. +\nGeef alsjeblieft toestemming. +Probeer de rich-text-editor (platte tekst-modus binnenkort beschikbaar) +Rich-text-editor inschakelen +Uitgestelde privéberichten inschakelen +Een vereenvoudigde Element met optionele tabs +Nieuwe layout inschakelen +Bevestigen +Opnieuw proberen +Je wordt ingelogd +Verbinden met apparaat +Scan QR-code +Inloggen op een mobiel apparaat\? +Toon QR-code op dit apparaat +Begin op het inlogscherm +Begin op het inlogscherm +Ga naar Instellingen -> Veiligheid & privacy +Open de app op je andere apparaat +Het inloggen is afgebroken op het andere apparaat. +Die QR-code is ongeldig. +Het andere apparaat moet ingelogd zijn. +Het andere apparaat is al ingelogd. +De aanvraag is mislukt. +De aanvraag is op het andere apparaat geweigerd. +De verbinding kon niet in de benodigde tijd tot stand worden gebracht. +Verbinden met dit apparaat wordt niet ondersteund. +Verbinding mislukt +Beveiligde verbinding tot stand gebracht +Scan de onderstaande QR-code met je uitgelogde apparaat. +Inloggen met QR-code +Gebruik de camera op dit apparaat om de op het andere apparaat getoonde QR-code te scannen: +Scan QR-code +Geverifieerde sessies +Niet-geverifieerde sessies +Inactieve sessies zijn sessies die je al een tijd niet gebruikt hebt, maar deze blijven encryptiesleutels ontvangen. +\n +\nVerwijder inactieve sessies om de veiligheid en prestaties te verbeteren. Het helpt je ook met het herkennen van mogelijk verdachte nieuwe sessies. +Inactieve sessies +Je kan dit apparaat gebruiken om in te loggen op een ander apparaat met een QR-code. Er zijn twee manier om dit te doen: +Log in met QR-code +Wees bewust dat sessienamen ook zichtbaar zijn voor personen met wie je communiceert. +Sessienaam +IP-adres +Besturingssysteem +Versie +Naam +Applicatie +Sessienaam +Ontvang pushnotificaties op deze sessie. +Pushnotificaties +Sessiedetails +Uitloggen voor deze sessie +Filter wissen +Geen inactieve sessies gevonden. +Geen niet-geverifieerde sessies gevonden. +Geen geverifieerde sessies gevonden. +Inactief +Niet geverifieerd +Geverifieerd ++ +- %1$d dag of langer inactief
+- %1$d dagen of langer inactief
+Inactief +Niet klaar voor veilige communicatie +Niet geverifieerd +Klaar voor veilige communicatie +Geverifieerd +Alle sessies +Apparaat +Sessie +Huidige Sessie +Niet geverifeerd · Jouw huidige sessie +Verifieer je huidige sessie voor verbeterde veilige communicatie. +Deze sessie is klaar voor veilige communicatie. +Je huidige sessie is klaar voor veilige communicatie. +Onbekende verificatiestatus +Bufferen +Live +De authenticiteit van dit versleutelde bericht kan niet worden gegarandeerd op dit apparaat. +Incognito toetsenbord +Scan QR-code +Ingeschakeld: +Sessie ID: +Er is iets fout gegaan. Controleer je netwerkverbinding en probeer het opnieuw. +⚠ Er zijn niet-geverifieerde apparaten in deze kamer. Deze zullen niet in staat zijn de door jouw verzonden berichten te ontsleutelen. +Stuur nooit versleutelde berichten naar niet-geverifieerde sessie in deze kamer. +Toestemming geven +${app_name} heeft toestemming nodig om notificaties te laten zien. Notificaties kunnen je berichten, uitnodigen, etc. tonen. +\n +\nGeeft toestemming bij de volgende pop-ups om notificaties te kunnen zien. +Begrepen +Onderstreep formaat toepassen +Doorhaal formaat toepassen +Cursief formaat toepassen +Vet formaat toepassen +Zorg ervoor dat je de herkomst van deze code kent. Door apparaten te koppelen, geef je iemand volledige toegang tot jouw account. +Geen match\? +Selecteer \'QR-code scannen\' +Selecteer \'Aanmelden met QR-code\' +Selecteer \'Toon QR-code\' +De server ondersteunt geen inloggen met QR-code. +Er is een beveiligingsprobleem opgetreden bij het instellen van beveiligde berichtenuitwisseling. Een van de volgende zaken kan in geschonden zijn: je server; je internetverbinding(en); je apparaat(en); +Controleer je aangemelde apparaat, de onderstaande code zou moeten worden weergegeven. Bevestig dat de onderstaande code overeenkomt met dat apparaat: +Gebruik je aangemelde apparaat om de onderstaande QR-code te scannen: +3 +2 +1 +In staat zijn om spraakuitzendingen op te nemen en te verzenden in de tijdlijn van de kamer. +Spraakuitzending inschakelen (in actieve ontwikkeling) +Noteer de naam, versie en url van de applicatie om sessies gemakkelijker te herkennen in sessiebeheer. +Opname van applicatie informatie inschakelen +Meer zichtbaarheid en controle over al je sessies. +Nieuwe sessiemanager inschakelen +Andere gebruikers in privéchats en chatruimten waaraan jij deelneemt, kunnen een volledige lijst van je sessies bekijken. +\n +\nDit geeft ze het vertrouwen dat ze echt met jou praten, maar het betekent ook dat ze de sessienaam kunnen zien die je hier invoert. +Sessies hernoemen +Geverifieerde sessies zijn ingelogd met jouw inloggegevens en vervolgens geverifieerd, hetzij met je veilige wachtwoordzin of door kruisverificatie. +\n +\nDit betekent dat ze coderingssleutels bevatten voor je eerdere berichten en bevestigen aan andere gebruikers waarmee je communiceert dat deze sessies echt van jou zijn. +Niet-geverifieerde sessies zijn sessies die zijn aangemeld met jouw inloggegevens, maar niet zijn geverifieerd. +\n +\nJe moet er vooral zeker van zijn dat je deze sessies herkent, omdat ze een ongeoorloofd gebruik van je account kunnen vertegenwoordigen. +Met aangepaste sessienamen kan je jouw apparaten gemakkelijker herkennen. +Sessie hernoemen +Model +Browser +URL +Laatste activiteit +Informatie over toepassing, apparaat en activiteit. +Sessies selecteren ++ +- Overweeg om je af te melden bij oude sessies (%1$d dag of meer) die je niet meer gebruikt.
+- Overweeg om je af te melden bij oude sessies (%1$d dagen of meer) die je niet meer gebruikt.
+Verifieer je sessies voor verbeterde beveiligde berichtenuitwisseling of meld je af bij sessies die je niet meer herkent of gebruikt. +Voor de beste beveiliging log je uit bij elke sessie die je niet meer herkent of gebruikt. +Filter +Filter +Laatste activiteit %1$s +Verifieer je huidige sessie om de verificatiestatus van deze sessie weer te geven. +Verifieer of meld je af bij deze sessie voor de beste beveiliging en betrouwbaarheid. +Contact +Camera +Locatie +Peilingen +Spraakuitzending +Bijlagen +Stikkers +Fotobibliotheek +Een spraakuitzending starten +Spraakuitzending pauzeren +Spraakuitzending afspelen of hervatten +Opname van spraakuitzending stoppen +Opname van spraakuitzending pauzeren +Opname van spraakuitzending hervatten +Verzoek dat het toetsenbord geen gepersonaliseerde gegevens, zoals typgeschiedenis en woordenboek, bijwerkt op basis van wat je in gesprekken hebt getypt. Opgelet dat sommige toetsenborden deze instelling mogelijk niet respecteren. +Open het scherm met ontwikkelaarstools +🔒 Je hebt de codering voor geverifieerde sessies enkel voor alle kamers ingeschakeld in Beveiligingsinstellingen. +Maak directe chat alleen aan bij een bericht +Deselecteer alles +Selecteer alles ++ + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml index c9bac8977b..1c01c82189 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -2688,7 +2688,6 @@- %1$d geselecteerd
+- %1$d geselecteerd
+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. @@ -2716,7 +2715,7 @@Preferencje interfejsu Przeglądaj pokoje Utwórz pokój -Zacznij rozmawiać +Rozpocznij czat Wszystkie rozmowy Nie zweryfikowano · Ostatnia aktywność %1$s Zweryfikowano · Ostatnia aktywność %1$s diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 108ecc7e38..8baba5df53 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -1007,10 +1007,10 @@Regras de Push Nenhuma regra de push definida Nenhum gateway de push registrado -app_id: -push_key: -app_display_name: -session_name: +ID do App: +Chave Push: +Nome de Exibição do App: +Nome de Exibição da Sessão: Url: Formato: Voz & Vídeo @@ -1053,12 +1053,12 @@Você está atualmente usando %1$s para descobrir e ser descobertável por contatos existentes que você conhece. Você não está atualmente usando um servidor de identidade. Para descobrir e ser descobertável por contatos existentes que você conhece, configure um abaixo. Endereços de email descobertáveis -Opções de descoberta vão aparecer uma vez que você tenha adicionado um email. +Opções de descoberta vão aparecer uma vez que você tenha adicionado um endereço de email. Opções de descoberta vão aparecer uma vez que você tenha adicionado um número de telefone. Desconectar-se de seu servidor de identidade vai significar que você não vai ser descobertável por outras(os) usuárias(os) e você não vai ser capaz de convidar outras(os) por email ou telefone. Números de telefone descobertáveis -Nós enviamos a você um email de confirmar para %s, cheque seu email e clique no link de confirmação -Nós enviamos a você um email de confirmar para %s, por favor primeiro cheque seu email e clique no link de confirmação +Nós enviamos um email para %s, cheque seu email e clique no link de confirmação +Nós enviamos um email para %s, por favor primeiro cheque seu email e clique no link de confirmação Entre um URL de servidor de identidade Não foi possível conectar-se a servidor de identidade Por favor entre o url de servidor de identidade @@ -1171,7 +1171,7 @@O aplicativo não é capaz de criar uma conta neste servidorcasa. \n \nVocê quer fazer signup usando um cliente web\? -Este email não está associado com nenhuma conta. +Este endereço de email não está associado a nenhuma conta. Resettar senha em %1$s Um email de verificação vai ser enviado para sua inbox para confirmar definição de sua nova senha. Próximo @@ -1180,7 +1180,7 @@Aviso! Mudar sua senha vai resettar quaisquer chaves de encriptação ponta-a-ponta em todas as suas sessões, fazendo histórico de chat encriptado ilegível. Configure Backup de Chave ou exporte suas chaves de sala de uma outra sessão antes de resettar sua senha. Continuar -Este email não está linkado a nenhuma conta +Este endereço de email não está linkado a nenhuma conta Cheque sua inbox Um email de verificação foi enviado para %1$s. Toque no link para confirmar sua nova senha. Uma vez que você tenha seguido o link que ele contém, clique abaixo. @@ -1194,7 +1194,7 @@ \n \nPara o processo de mudança de senha\?Definir endereço de email -Defina um email para recuperar sua conta. Mais tarde, você pode opcionalmente permitir pessoas que você conhece descobrirem você por seu email. +Defina um endereço de email para recuperar sua conta. Mais tarde, você pode opcionalmente permitir pessoas que você conhece descobrirem você por este endereço. Email (opcional) Próximo @@ -1560,7 +1560,7 @@Esta operação não é possível. O servidorcasa está desatualizado. Por favor primeiro configure um servidor de identidade. Por favor primeiro aceite os termos do servidor de identidade nas configurações. -Para sua privacidade, ${app_name} somente suporta enviar emails e números de telefone de usuária(o) hashados. +Para sua privacidade, ${app_name} somente suporta enviar endereços de email e números de telefone de usuária(o) hashados. A associação tem falhado. Não há nenhuma associação atual com este identificador. Seu servidorcasa (%1$s) propõe usar %2$s para seu servidor de identidade @@ -1654,7 +1654,7 @@Você não tem permissão para começar uma chamada nesta sala Nenhum número de telefone tem sido adicionado a sua conta Endereços de email -Nenhum email tem sido adicionado a sua conta +Nenhum endereço de email tem sido adicionado a sua conta Números de telefone Remover %s\? Assegure-se que você tem clicado no link no email que enviamos para você. @@ -1663,7 +1663,7 @@- %d segundos
Emails e números de telefone -Gerenciar emails e números de telefone linkados a sua conta Matrix +Gerenciar endereços de email e números de telefone linkados a sua conta Matrix Código Por favor use o formato internacional (número de telefone deve começar com \'+\') Confirme sua identidade ao verificar este login, concedendo-lhe acesso a mensagens encriptadas. @@ -1778,7 +1778,7 @@%1$d de %2$d Dar consentimento Revogar meu consentimento -Você tem dado seu consentimento para enviar emails e números de telefone para este servidor de identidade para descobrir outras(os) usuárias(os) de seus contatos. +Você tem dado seu consentimento para enviar endereços de email e números de telefone para este servidor de identidade para descobrir outras(os) usuárias(os) de seus contatos. Enviar emails e números de telefone Sugestões Usuárias(os) Conhecidas(os) @@ -2142,7 +2142,7 @@Menções e Palavrachaves Notificações Default %s em Configurações para receber convites diretamente em ${app_name}. -Linkar este email com sua conta +Linkar este endereço de email com sua conta Este convite para este espaço foi enviado para %s que não está associado com sua conta Este convite para esta sala foi enviado para %s que não está associado com sua conta Todas as salas em que você está vão ser mostradas em Home. @@ -2211,7 +2211,7 @@Acesso a espaço Quem pode acessar\? Habilitar notificações de email para %s -Para receber email com notificação, por favor associe um email a sua conta Matrix +Para receber email com notificação, por favor associe um endereço de email a sua conta Matrix Notificação de email Fazer upgrade do espaço Mudar nome de espaço @@ -2258,12 +2258,12 @@Sondar pergunta ou tópico Criar Sondagem Sondagem -Enviar emails e números de telefone para %s +Enviar endereços de email e números de telefone para %s Seus contatos são privados. Para descobrir usuárias(os) de seus contatos, você precisa de permissão para enviar info de contato a seu servidor de identidade. O signout desta sessão tem sido feito! Esta sala tem sido saída! Você concorda em enviar esta info\? -Para descobrir contatos existentes, você precisa enviar info de contato (emails e números de telefone) para seu servidor de identidade. Nós hashamos seus dados antes de enviar por privacidade. +Para descobrir contatos existentes, você precisa enviar info de contato (endereços de email e números de telefone) para seu servidor de identidade. Nós hashamos seus dados antes de enviar por privacidade. Não agora Você tem certeza que você quer remover esta sondagem\? Você não vai ser capaz de recuperá-la uma vez removida. Remover sondagem @@ -2600,7 +2600,6 @@ \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 @@ -2710,4 +2709,161 @@Habilitar DMs diferidas Um Element simplificado com abas opcionais Habilitar novo layout - +Outras(os) usuárias(os) em mensagens diretas e salas a que você se junta são capazes de visualizar uma lista completa de suas sessões. +\n +\nIsto as/os provê com confiança que elas(es) são estão realmente falando com você, mas também significa que elas(es) veem o nome da sessão que você entrar aqui. +Renomeando sessões +Sessões verificadas têm feito login com suas credenciais e então têm sido verificadas, ou usando sua frasepasse segura ou por verificação cruzada. +\n +\nIsto significa que elas mantêm chaves de encriptação para suas mensagens anteriores, e confirmam a outras(os) usuárias(os) com quem você está comunicando que estas sessões são realmente você. +Sessões verificadas +Sessões não-verificadas são sessões que você tem feito login com suas credenciais mas não têm sido verificadas cruzado. +\n +\nVocê devia especialmente se certificar que você reconhece estas sessões já que elas podiam representar um uso não-autorizado de sua conta. +Sessões não-verificadas +Sessões inativas são sessões que você não tem usado em algum tempo, mas elas continuam a receber chaves de encriptação. +\n +\nRemover sessões inativas melhora segurança e performance, e torna-o mais fácil para você identificar se uma nova sessão é suspeita. +Sessões inativas +Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica. +Nomes de sessões personalizadas podem ajudar você a reconhecer seus dispositivos mais facilmente. +Nome da sessão +Renomear sessão +Fazer signout desta sessão +Não-verificada · Sua sessão atual +Começar um broadcast de voz +A autenticidade desta mensagem encriptada não pode ser garantida neste dispositivo. +Requisitar que o teclado não devia atualizar quaisquer dados personalizados tais como histórico de digitação e dicionário baseado no que você tem digitado em conversas. Note que alguns teclados podem não respeitar esta configuração. +Teclado incognito +Prepende (╯°□°)╯︵ ┻━┻ a uma mensagem de texto puro +Broadcast de Voz +Abrir tela de ferramentas de desenvolvedor(a) +🔒 Você tem habilitado encriptar para sessões verificadas somente para todas as salas em Configurações de Segurança. +⚠ Existem dispositivos não-verificados nesta sala, eles não vão ser capazes de decriptar mensagens que você enviar. +Nunca enviar mensagens encriptadas a sessões não-verificadas nesta sala. +Entendido +Aplicar formato tachar +Aplicar formato sublinhar +Aplicar formato itálico +Aplicar formato negrito +Gravar o nome de cliente, versão, e url para reconhecer sessões mais facilmente em gerenciador de sessão. +Habilitar gravação de info de cliente +Tenha visibilidade e controle maiores sobre todas suas sessões. +Habilitar novo gerenciador de sessão +Sistema operativo +Modelo +Browser +URL +Versão +Nome +Aplicativo +Receber notificações push nesta sessão. +Notificações push +Verifique sua sessão atual para revelar o status de verificação desta sessão. +Status de verificação desconhecido +Habilitado: +ID da Sessão: +Algo deu errado. Por favor cheque sua conexão de rede e tente de novo. +Conceder Permissão +${app_name} precisa de permissão para mostrar notificações. +\nPor favor conceda a permissão. +${app_name} precisa de permissão para exibir notificações. Notificações podem exibir suas mensagens, seus convites, etc. +\n +\nPor favor permita acesso nos próximos pop-ups para ser capaz de visualizar notificação. +Experimente o editor de texto rico (modo de texto puro vindo em breve) +Habilitar editor de texto rico +Por favor assegure que você sabe a origem deste código. Ao linkar dispositivos, você vai prover alguém com acesso completo a sua conta. +Confirmar +Tentar de novo +Nenhuma correspondência\? +Fazendo-lhe signin +Conectando a dispositivo +Scannar QR code +Fazendo signin com um dispositivo móvel\? +Mostrar QR code neste dispositivo +Selecione \'Scannar QR code\' +Comece na tela de signin +Selecione \'Fazer signin com QR code\' +Comece na tela de signin +Selecione \'Mostrar QR code\' +Vá para Configurações -> Segurança & Privacidade +Abra o app em seu outro dispositivo +A requisição foi negada no outro dispositivo. +A linkagem não foi completada no tempo requerido. +Linkagem com este dispositivo não é suportado. +Conexão malsucedida +Cheque seu dispositivo feito signin, o código abaixo deveria ser exibido. Confirme que o código abaixo corresponde com esse dispositivo: +Conexão segura estabelecida +Scanne o QR code abaixo com seu dispositivo que está feito signout. +Use seu dispositivo feito signin para scannar o QR code abaixo: +Fazer signin com QR code +Use a câmera neste dispositivo para scannar o QR code mostrado em seu outro dispositivo: +Scannar QR code +3 +2 +1 +Você pode usar este dispositivo para fazer signin com um dispositivo móvel ou web com um QR code. Existem duas maneiras de fazer isto: +Fazer signin com QR Code +Scannar QR code +O servidorcasa não suporta sign in com QR code. +O sign in foi cancelado no outro dispositivo. +O QR code é inválido. +O outro dispositivo deve estar feito signin. +O outro dispositivo já está feito signin. +Um problema de segurança foi encontrado ao configurar mensageria segura. Um dos seguintes pode ter sido comprometido: Seu servidorcasa; Sua(s) conexão(ões) de internet; Seu(s) dispositivo(s); +A requisição falhou. +Seja capaz de gravar e enviar broadcast de voz em timeline de sala. +Broadcast de voz (sob desenvolvimento ativo) +Buffering +Pausar broadcast de voz +Tocar ou retomar broadcast de voz +Parar gravação de broadcast de voz +Pausar gravação de broadcast de voz +Retomar gravação de broadcast de voz +Ao vivo +Selecionar sessões +Contato +Câmera +Localização +Sondagens +Broadcast de voz +Anexos +Stickers +Biblioteca de fotos +Desselecionar todas(os) +Selecionar todas(os) ++ +- %1$d selecionada(o)
+- %1$d selecionadas(os)
+Alguma outra pessoa já está gravando um broadcast de voz. Espere que o broadcast de voz dela termine para começar um novo. +Alternar modo de tela cheia +Formatação de texto +Você já está gravando um broadcast de voz. Por favor termine seu broadcast de voz atual para começar um novo. +Você não tem as permissões requeridas para começar um broadcast de voz nesta sala. Contacte um/uma administrador(a) para fazer upgrade de suas permissões. +Não dá pra começar um novo broadcast de voz +Avançar rápido 30 segundos +Retroceder 30 segundos +Sessões verificadas são onde quer que você está usando esta conta depois de entrar sua frasepasse ou confirmar sua identidade com uma outra sessão verificada. +\n +\nIsto significa que você tem todas as chaves necessitadas para destrancar suas mensagens encriptadas e confirmar a outras(os) usuárias(os) que você confia nesta sessão. ++ +- Fazer signout de %1$d sessão
+- Fazer signout de %1$d sessões
+Fazer signout +%1$s restando +criou uma sondagem. +enviou um sticker. +enviou um vídeo. +enviou uma imagem. +enviou uma mensagem de voz. +enviou um arquivo de áudio. +enviou um arquivo. +Em resposta a +Esconder endereço de IP +Mostrar endereço de IP +Citando +Respondendo a %s +Editando + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index c8eee49d96..39d1c8de2b 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -367,8 +367,8 @@Добавить телефон Системные настройки приложения. Сведения о приложении -Включить уведомления для этой учетной записи -Включить уведомления для этой сессии +Уведомления для этой учётной записи +Уведомления для этой сессии В персональных чатах В групповых чатах Когда меня приглашают в комнату @@ -398,8 +398,8 @@Прикрепить комнаты с отключенными уведомлениями Прикрепить комнаты с непрочитанными сообщениями ID -Публичное имя -Обновить публичное имя +Публичное название +Обновить публичное название Недавно %1$s @ %2$s Аутентификация @@ -431,23 +431,23 @@Установить как основной адрес Сбросить основной адрес Ошибка дешифровки -Публичное имя +Публичное название ID сессии Ключ сессии -Экспорт E2E ключей комнаты -Экспорт ключей комнаты +Экспорт E2E ключей +Экспорт ключей Экспорт ключей в локальный файл Экспорт -Введите парольную фразу -Подтвердите парольную фразу -Импорт E2E ключей комнаты -Импорт ключей комнаты +Введите мнемоническую фразу +Подтвердите мнемоническую фразу +Импорт E2E ключей +Импорт ключей Импортировать ключи из локального файла Импорт Шифровать только для проверенных сессий Не отправлять зашифрованные сообщения непроверенным сессиям с этой сессии. -Не проверено -Проверено +Не заверено +Заверено Подтвердить Чтобы убедиться, что этой сессии можно доверять, обратитесь к ее владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии: Если они не совпадают, безопасность вашего общения может быть поставлена под угрозу. @@ -617,8 +617,8 @@Системные оповещения Ошибка -Создать парольную фразу -Парольные фразы не совпадают +Создать мнемоническую фразу +Мнемонические фразы не совпадают свяжитесь с вашим администратором Превышен один из ресурсных лимитов сервера, по этому некоторые пользователи не смогут авторизоваться. Превышен один из ресурсных лимитов сервера. @@ -637,7 +637,7 @@Мелодия входящего вызова Выберите мелодию звонка: Идёт видеозвонок … -Удалить из чата +Удалить из комнаты Поиск проблем с уведомлениями Оправлять уведомления о наборе текста Markdown форматирование @@ -663,7 +663,7 @@Включить Настройки сессии. Уведомления включены для этой сессии. -Уведомления не включено для этой сессии. + Уведомления не включены для этой сессии. \nПожалуйста, проверьте настройки ${app_name}. Включить Проверка сервисов Play @@ -725,26 +725,26 @@Управление криптографическими ключами Управление резервным копированием ключей Беззвучный -Пожалуйста, введите парольную фразу -Парольная фраза слишком простая -Пожалуйста, удалите парольную фразу, если хотите, чтобы ${app_name} сгенерировал ключ восстановления. +Введите мнемоническую фразу +Мнемоническая фраза слишком проста +Пожалуйста, удалите мнемоническую фразу, если хотите, чтобы ${app_name} сгенерировал бумажный ключ. Никогда не теряйте зашифрованных сообщений Сообщения в зашифрованных комнатах защищены сквозным шифрованием. Ключи для прочтения этих сообщений есть только у вас и получателя(ей). \n \nНадёжно сохраните резервную копию ключей, чтобы не потерять их. -Установите парольную фразу -Сохраните ключ восстановления +Задайте мнемоническую фразу +Сохранить бумажный ключ Готово Сохранить как файл Пожалуйста, сделайте копию -Поделиться ключом восстановления с… -Ключ для восстановления +Поделиться бумажным ключом с… +Бумажный ключ Непредвиденная ошибка Уверены? -Удалить резервную копию ключей шифрования с сервера? Вы больше не сможете использовать ключ восстановления для чтения истории зашифрованных сообщений. +Удалить резервную копию ключей шифрования с сервера\? Вы больше не сможете использовать бумажный ключ для чтения истории зашифрованных сообщений. Удалить резервную копию Удаление резервной копии… -Чтобы использовать резервную копию ключа в этой сессии, восстановите его с помощью своей парольной фразы или ключа восстановления. +Чтобы использовать резервное копирование ключей в этой сессии, восстановите их с помощью мнемонической фразы или бумажного ключа. Резервная копия имеет недействительную подпись из подтвержденной сессии %s Резервная копия имеет действительную подпись из неподтвержденной сессии %s Резервная копия имеет действительную подпись из подтверждённой сессии %s. @@ -755,17 +755,17 @@Резервное копирование ключей успешно настроено для этой сессии. Удалить резервную копию Восстановить из резервной копии -Пожалуйста, введите ключ восстановления +Пожалуйста, введите бумажный ключ Разблокировать историю Восстановление резервной копии: -Введите ключ восстановления -Используйте ключ восстановления для разблокировки истории зашифрованных сообщений -Если вы не знаете вашу парольную фразу для восстановления, вы можете %s. -используйте ключ восстановления +Введите бумажный ключ +Используйте бумажный ключ для разблокировки зашифрованных сообщений +Если забыли свою мнемоническую фразу, вы можете %s. +используйте бумажный ключ Вы можете потерять доступ к сообщениям, если выйдете из системы или потеряете это устройство. Получение версии резервной копии… -Используйте парольную фразу для разблокировки истории зашифрованных сообщений -Потеряли ключ восстановления? В настройках вы можете создать новый. +Используйте мнемоническую фразу для разблокировки зашифрованных сообщений +Потеряли бумажный ключ\? В настройках вы можете создать новый. Резервная копия восстановлена %s ! Резервная копия имеет недействительную подпись из неподтвержденной сессии %s Не удалось получить последнюю версию ключей восстановления (%s). @@ -780,9 +780,9 @@- Восстановлены резервные копии с %d ключами.
- Восстановлены резервные копии с %d ключами.
Невозможно расшифровать резервную копию с помощью этого ключа восстановления: убедитесь, что вы ввели правильный ключ. -Невозможно расшифровать резервную копию с помощью этого пароля: убедитесь, что вы ввели правильный пароль. -Генерация ключей восстановления с использованием парольной фразы может занять несколько секунд. +Невозможно расшифровать резервную копию с помощью этого бумажного ключа: пожалуйста, убедитесь, что вы ввели правильный бумажный ключ. +Невозможно расшифровать резервную копию с помощью этой мнемонической фразы: пожалуйста, убедитесь, что вы ввели правильную мнемоническую фразу. +Создание бумажного ключа с использованием мнемонической фразы может занять несколько секунд. [%1$s] \nЭта ошибка вне контроля ${app_name}. На телефоне нет учетной записи Google. Пожалуйста, добавьте аккаунт Google. [%1$s] @@ -815,9 +815,9 @@ Резервное копирование ключей… Никогда не теряйте зашифрованные сообщения Поделиться -Я сделал копию -Храните ключ восстановления в надежном месте, например, в диспетчере паролей (или в сейфе) -Защитите резервную копию парольной фразой. +Я сделал(а) копию +Храните бумажный ключ в очень надёжном месте, например, в менеджере паролей (или в сейфе) +Защитите резервную копию мнемонической фразой. Восстановление зашифрованных сообщений Начать использовать резервное копирование ключей Использовать резервное копирование ключей @@ -825,16 +825,16 @@Новые ключи зашифрованных сообщений Ваши ключи копируются. (Дополнительно) Настройка с ключом восстановления -Или защитите резервную копию с помощью ключа восстановления, сохранив его в безопасном месте. +Или защитите резервную копию бумажным ключом, сохранив его в надёжном месте. Безопасная резервная копия ключей должна быть активирована на всех ваших сессиях, чтобы не потерять доступ к зашифрованным сообщениям. -Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её парольной фразой. + +Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её мнемонической фразой. \n -\nДля максимальной безопасности парольная фраза должна отличаться от пароля вашей учётной записи. -Ключ восстановления — это страховка, вы можете использовать его для восстановления доступа к вашим зашифрованным сообщениям, если забудете вашу парольную фразу. -\nХраните ключ восстановления в надёжном месте, например, в диспетчере паролей (или в сейфе) +\nДля максимальной безопасности мнемоническая фраза должна отличаться от пароля вашей учётной записи.Бумажный ключ — это подстраховка: вы можете использовать его для восстановления доступа к своим зашифрованным сообщениям, если забудете свою мнемоническую фразу. +\nХраните свой бумажный ключ в очень надёжном месте, например, в менеджере паролей (или в сейфе) Импортирование ключей… Скачивание ключей… -Вычисление ключа восстановления… +Вычисление бумажного ключа… Игнорировать Отметить как прочитанное Войти с помощью единого входа @@ -929,10 +929,10 @@Предпочтения Безопасность Правила push-уведомлений -app_id: -push_key: -app_display_name: -session_name: +ID приложения: +Ключ Push: +Отображаемое название приложения: +Отображаемое название сессии: Url: Формат: Голос и видео @@ -1000,10 +1000,10 @@Не удалось подключиться к серверу обнаружения Пожалуйста, введите URL сервера обнаружения Сервер обнаружения не имеет условий использования -Параметры обнаружения появятся после добавления электронной почты. +Параметры обнаружения появятся после добавления адреса электронной почты. Параметры поиска появятся после добавления номера телефона. Отключение от сервера обнаружения будет означать, что другие пользователи не смогут обнаружить вас, и вы не сможете приглашать других по электронной почте или по телефону. -Мы отправили вам электронное письмо с подтверждением на %s, проверьте вашу электронную почту и нажмите на ссылку для подтверждения +Мы отправили вам электронное письмо на %s, проверьте вашу электронную почту и нажмите на ссылку для подтверждения Выбранный сервер обнаружения не имеет условий использования. Продолжайте, только если вы доверяете его владельцу Текстовое сообщение отправлено %s. Введите код проверки, который он содержит. В настоящее время вы делитесь адресами электронной почты или телефонными номерами на сервере обнаружения %1$s. Вам нужно повторно подключиться к %2$s, чтобы прекратить делиться ими. @@ -1102,7 +1102,7 @@Предупреждение! Смена пароля приведёт к сбросу всех сквозных ключей шифрования во всех ваших сессиях, что сделает зашифрованную историю разговоров нечитаемой. Настройте резервное копирование ключей или экспортируйте ключи от комнаты из другой сессии, прежде чем сбрасывать пароль. Продолжить -Данный электронный ящик не связан ни с одним аккаунтом +Данный адрес электронной почты не связан ни с одним аккаунтом Проверьте свою почту Письмо с подтверждением было отправлено на %1$s. Нажмите на ссылку, чтобы подтвердить свой новый пароль. Как только вы перейдете по ссылке нажмите ниже. @@ -1219,10 +1219,10 @@Ещё QR-код Соединение с сервером потеряно -Используйте пароль восстановления или ключ +Используйте мнемоническую фразу или бумажный ключ Разблокировать историю зашифрованных сообщений Проверка была отменена. Вы можете начать проверку снова. -Парольная фраза для восстановления +Мнемоническая фраза Введите %s, чтобы продолжить. Не переиспользуйте пароль учётной записи. Это может занять несколько секунд, пожалуйста, наберитесь терпения. @@ -1241,7 +1241,7 @@%1$s: %2$s %3$s Удалённые сообщения Показывать заглушку на месте удалённых сообщений -Мы отправили письмо для подтверждения на %s, проверьте почту и нажмите на ссылку для подтверждения +Мы отправили письмо на %s, пожалуйста проверьте почту и нажмите на ссылку для подтверждения Код подтверждения неверный. Попробуйте снова после принятия условий обслуживания на вашем домашнем сервере. Похоже, сервер долгое время не отвечает, что может быть вызвано плохим соединением или ошибкой на сервере. Попробуйте снова через некоторое время. @@ -1314,7 +1314,7 @@Сброс безопасного резервного копирования Настроить на этом устройстве Защитите себя от потери доступа к зашифрованным сообщениям и данным, создав резервные копии ключей шифрования на вашем сервере. -Создайте новый ключ безопасности или задайте новую секретную фразу для существующей резервной копии. +Создайте новый бумажный ключ или задайте новую мнемоническую фразу для существующей резервной копии. Это заменит ваш текущий ключ или фразу. Интеграции отключены Включите «Управление интеграциями» в настройках, чтобы сделать это. @@ -1326,7 +1326,7 @@Ключи успешно экспортированы ОБЗОР Активные виджеты -Ключ восстановления был сохранён. +Бумажный ключ сохранён. Безопасное резервное копирование Защита от потери доступа к зашифрованным сообщениям и данным Настроить безопасное резервное копирование @@ -1351,7 +1351,7 @@Введите адрес сервера, который вы хотите использовать На ваш почтовый ящик будет отправлено письмо для подтверждения установки нового пароля. Я подтвердил свою электронную почту -Установите адрес электронной почты для восстановления вашей учетной записи. Позже вы можете дополнительно разрешить людям, которых вы знаете, обнаружить вас по электронной почте. +Укажите адрес электронной почты для восстановления вашей учетной записи. Потом вы сможете, при желании, разрешить людям, которых вы знаете, обнаружить вас по адресу электронной почты. Введенный код неверен. Пожалуйста, проверьте. Войти с Matrix ID Войти с Matrix ID @@ -1368,7 +1368,7 @@Показываем только первые результаты, наберите больше букв… Раннее падение ${app_name} может падать чаще, когда происходит непредвиденная ошибка -Добавляет смайл ¯\\_(ツ)_/¯ в начало сообщения +Добавляет ¯\\_(ツ)_/¯ в начало сообщения После включения шифрования его нельзя отключить. Ваш почтовый домен не имеет права регистрироваться на этом сервере Не безопасно @@ -1429,9 +1429,9 @@Сравните уникальные эмодзи, убедившись, что они появились в том же порядке. Сравните код с тем, который отображается на экране другого пользователя. Сообщения от этого пользователя зашифрованы сквозным шифрованием и не смогут быть прочитаны третьими лицами. -Ваша новая сессия подтверждена. У нее есть доступ к вашим зашифрованным сообщениям, а другие пользователи увидят его как доверенное. -Перекрестная подпись -Перекрестная подпись включена + Ваша новая сессия подтверждена. Она имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут воспринимать её как заверенную. +Перекрёстная подпись +Перекрёстная подпись включена \nЛичные ключи хранятся на устройстве. Перекрестная подпись включена \nКлючи являются доверенными. @@ -1482,7 +1482,7 @@ Незаверенная Эта сессия является доверенной для безопасного обмена сообщениями, так как %1$s (%2$s) проверил(а) его: %1$s (%2$s) вошел(ла), используя новую сессию: -Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Кроме того, вы можете подтвердить сессию вручную. +Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Вы также можете подтвердить эту сессию вручную. Начать перекрестную подпись Сбросить ключи Почти готово! Показывает ли %s галочку\? @@ -1520,16 +1520,16 @@ \n \nМы рекомендуем вам немедленно изменить свой пароль и ключ восстановления в настройках.Подтверждение отменено -Генерация ключа безопасности из парольной фразы -Генерация ключа SSSS из парольной фразы -Генерация ключа SSSS из парольной фразы (%s) +Создание бумажного ключа из мнемонической фразы +Создание ключа SSSS из мнемонической фразы +Создание ключа SSSS из мнемонической фразы (%s) Чтобы продолжить работу, введите парольную фразу для резервного копирования ключа. Если вы не знаете вашу парольную фразу для резервного копирования ключей, вы можете %s. Задать роль -Введите секретную фразу, известную только вам, для защиты данных на вашем сервере. +Введите мнемоническую фразу, известную только вам, которая используется для защиты данных на вашем сервере. Настройка восстановления. Готово! -Храните его в безопасности +Храните его в надёжном месте Завершить Публикация созданных ключей идентификации Определение ключа SSSS по умолчанию @@ -1553,12 +1553,12 @@Эта учётная запись была деактивирована. Введите %s, чтобы продолжить Использовать файл -Это недействительный ключ восстановления -Пожалуйста, введите ключ восстановления +Этот бумажный ключ недействителен +Пожалуйста, введите бумажный ключ Проверка ключа резервного копирования Проверка ключа резервного копирования (%s) Получение кривой ключа -Генерация ключа SSSS из ключа восстановления +Генерация ключа SSSS из бумажного ключа Сохранение резервной копии ключа в SSSS используйте ваш ключ восстановления ключа резервной копии Ключ восстановления ключа резервной копии @@ -1571,9 +1571,9 @@ \n${app_name} для Androidили другой клиент Matrix поддерживающий перекрестную подпись Принудительно отбрасывает текущую групповую сессию для отправки сообщений в зашифрованную комнату -Чтобы продолжить, используйте ваш %1$s или используйте ваш %2$s. -Используйте ключ восстановления -Выберите ключ восстановления или введите его вручную, введя или вставив из буфера обмена +Чтобы продолжить, используйте %1$s или %2$s. +Используйте бумажный ключ +Выберите бумажный ключ или введите его вручную, введя или вставив из буфера обмена Не удалось получить доступ к защищенному хранилищу данных Не зашифровано Зашифровано неподтверждённой сессией @@ -1606,7 +1606,7 @@Эта операция невозможна. Домашний сервер устарел. Пожалуйста, настройте сначала сервер идентификации. Пожалуйста, примите сначала условия сервера идентификации в настройках. -Для вашей приватности, ${app_name} поддерживает отправку адреса электронной почты и номера телефона только в хэшированном виде. +Для вашей приватности, ${app_name} поддерживает отправку адреса электронной почты и номеров телефонов только в хэшированном виде. Привязка не удалась. Текущая взаимосвязь с этим идентификатором отсутствует. Ваш домашний сервер (%1$s) предлагает использовать %2$s для вашего сервера обнаружения @@ -1624,14 +1624,14 @@Настроить Используйте ключ безопасности Создайте ключ безопасности для хранения в надежном месте, например в менеджере паролей или сейфе. -Использовать секретную фразу -Введите секретную фразу, известную только вам, и создайте ключ для резервного копирования. +Использовать мнемоническую фразу +Введите мнемоническую фразу, известную только вам, и создайте ключ для резервного копирования. Сохраните свой ключ безопасности -Храните ключ безопасности в надежном месте, например в менеджере паролей или сейфе. -Задайте секретную фразу -Введите секретную фразу, известную только вам, для защиты данных на вашем сервере. -Секретная фраза -Для подтверждения введите вашу секретную фразу ещё раз. +Храните бумажный ключ в надёжном месте, например, в менеджере паролей или в сейфе. +Задайте мнемоническую фразу +Введите мнемоническую фразу, известную только вам, которая используется для защиты данных на вашем сервере. +Мнемоническая фраза +Введите мнемоническую фразу ещё раз, чтобы подтвердить её. Название комнаты Тема Вы успешно изменили настройки комнаты @@ -1646,7 +1646,7 @@Мы рады сообщить, что сменили имя! Ваше приложение обновлено, и вы вошли в свою учетную запись. ПОНЯТНО УЗНАТЬ БОЛЬШЕ -Сохранить ключ восстановления в +Сохранить бумажный ключ в Получаем ваши контакты… Ваша контактная книга пуста Книга контактов @@ -1679,7 +1679,8 @@- %1$d/%2$d ключ успешно импортирован.
- %1$d/%2$d ключа успешно импортированы.
-- %1$d/%2$d ключей успешно импортировано.
+- %1$d/%2$d ключей успешно импортированы.
+- %1$d/%2$d ключей успешно импортированы.
Управление интеграциями Нет активных виджетов @@ -1701,12 +1702,12 @@Этот номер телефона уже используется. В ваш аккаунт не добавлен номер телефона Адрес электронной почты -В ваш аккаунт не добавлен адрес электронной почты +В вашу учётную запись не добавлен адрес электронной почты Телефонные номера Удалить %s\? Убедитесь, что вы перешли по ссылке в электронном письме, которое мы вам отправили. Электронная почта и номера телефонов -Управляйте электронной почтой и номерами телефонов, привязанными к вашей учетной записи Matrix +Управляйте адресами электронной почты и номерами телефонов, привязанными к вашей учётной записи Matrix Код Используйте международный формат (номер телефона должен начинаться с \'+\') Подтвердите свою личность, проверив этот логин, предоставив ему доступ к зашифрованным сообщениям. @@ -1792,7 +1793,7 @@Добавить изображение из Тема Название комнаты -Вы дали свое согласие на отправку электронных писем и телефонных номеров на этот сервер обнаружения для обнаружения других пользователей из ваших контактов. +Вы дали свое согласие на отправку адресов электронных почт и телефонных номеров на этот сервер идентификации для обнаружения других пользователей из ваших контактов. Добавить по QR-коду Разрешить доступ к вашим контактам. Чтобы отсканировать QR-код, вам нужно разрешить доступ к камере. @@ -2275,7 +2276,7 @@Доступ к пространству Кто имеет к этому доступ\? Включить уведомления по электронной почте для %s -Чтобы получать уведомления по электронной почте, пожалуйста, привяжите электронную почту к вашей учетной записи Matrix +Чтобы получать уведомления по электронной почте, пожалуйста, привяжите адрес электронной почты к своей учётной записи Matrix Уведомление по эл. почте Обновить пространство Изменить название пространства @@ -2395,7 +2396,7 @@Местоположение Вы согласны отправить эту информацию\? Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер обнаружения. Мы хешируем ваши данные перед отправкой для обеспечения конфиденциальности. -Отправить электронные адреса и номера телефонов %s +Отправить адреса электронных почт и номера телефонов %s Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер обнаружения. Системные настройки Версии @@ -2633,7 +2634,7 @@Где хранятся ваши переписки Где будут храниться ваши переписки Должно быть 8 или более символов -Не удалось подтвердить это устройство +Не удалось подтвердить эту сессию Невозможно открыть эту ссылку: сообщества были заменены пространствами Имя пользователя / Почта / Телефон Следуйте инструкциям, отправленным на %s @@ -2664,7 +2665,6 @@Другие сессии Сессии Создать беседу или комнату -Показать все сессии (V2, в разработке) ЛС Настройки вида Фильтры @@ -2721,7 +2721,7 @@%s \nвыглядит слегка пустовато. Попробовать -Сведения о приложении, устройстве и активности. +Информация о приложении, устройстве и активности. Подтвердите текущую сессию для более безопасного обмена сообщениями. Пока нет пространств. Подтвердите свои сессии для более безопасного обмена сообщениями или выйдите из тех, которые более не признаёте или не используете. @@ -2742,4 +2742,225 @@Незаверенные Фильтр +Незаверенная · Текущая сессия +Переименовать сессию +Название сессии +Заверенные +Выйти из этой сессии +Неактивные +Незаверенные +Пожалуйста, имейте в виду, что названия сессий также видны людям, с которыми вы общаетесь. +Заверенные сессии +Незаверенные сессии +Неактивные сессии +Добавляет (╯°□°)╯︵ ┻━┻ в начало сообщения +Приватная клавиатура +Запрещает клавиатуре обновлять персональные данные, такие как история набора текста и словарь, на основе того, что вы набрали при общении. Обратите внимание, что некоторые клавиатуры могут не соблюдать эту настройку. +Понятно +🔒 В настройках безопасности вы включили шифрование только для заверенных сессий во всех комнатах. +Не отправлять зашифрованные сообщения незаверенным сессиям в этой комнате. +Неактивные сессии — это сессии, которыми вы не пользовались определённое время, но они продолжают получать ключи шифрования. +\n +\nУдаление неактивных сессий повышает безопасность и производительность, а также облегчает выявление подозрительных новых сессий. +Переименование сессий +Другие пользователи в личных сообщениях и комнатах, к которым вы присоединились, могут просматривать весь список ваших сессий. +\n +\nЭто даёт им уверенность в том, что они действительно общаются с вами, но это также означает, что они могут видеть название сессии, которое вы ввели здесь. +Наглядный текстовый редактор +ID сессии: +Уведомления +Получать push-уведомления в этой сессии. +URL-адрес +Приложение +Название +Версия +Веб-браузер +Модель +Операционная система +Новый менеджер сессий ++ +- Рассмотрите возможность выхода из старых сессий (%1$d день или дольше), которые вы более не используете.
+- Рассмотрите возможность выхода из старых сессий (%1$d дня или дольше), которые вы более не используете.
+- Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+- Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+Результаты будут видны после завершения опроса +Доступ к пространствам (внизу справа) быстрее и проще, чем когда-либо прежде. +Доступ к пространствам ++ +- Рассмотрите возможность выхода из старых сессий (%1$d день или дольше), которые вы более не используете.
+- Рассмотрите возможность выхода из старых сессий (%1$d дня или дольше), которые вы более не используете.
+- Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+- Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+Голосовая трансляция +Голосовые трансляции (в активной разработке) +Записывает название клиента, версию и URL-адрес для более лёгкого распознавания сессий в менеджере сессий. +Записывать информацию о клиенте +Галерея +Наклейки +Вложения +Голосовая трансляция +Опрос +Местоположение +Камера +Контакт +${app_name} нуждается в разрешении для отображения оповещений. +\nПожалуйста, дайте разрешение. ++ +- %1$s и %2$d другой
+- %1$s и %2$d другие
+- %1$s и %2$d других
+- %1$s и %2$d других
+${app_name} нуждается в резрешении для отображения оповещений. Оповещения могут показывать ваши сообщения, приглашения и тому подобное. +\n +\nПожалуйста разрешите доступ при следующем всплывающем сообщении, чтобы иметь возможность видеть оповещения. +Здесь будут появляться новые запросы и приглашения. +Приглашения +Попробуйте расширенный текстовый редактор (режим набора обычного текста скоро появится) +Создавать личные сообщения только при отправке первого сообщения +Включить отложенные личные сообщения +Отменить выбор всего +Выбрать всё +Свернуть дочерние элементы %s +Развернуть дочерние элементы %s ++ +- Выбрано %1$d
+- Выбрано %1$d
+- Выбрано %1$d
+- Выбрано %1$d
+Войти в полноэкранный режим +Применить форматирование подчёркиванием +Применить форматирование перечёркиванием +Применить форматирование курсивом +Применить форматирование жирным +Пожалуйста удостоверьтесь в том, что вы знаете откуда этот код. При соединении устройств, вы даёте кому-то полный доступ к вашей учётной записи. +Подтвердить +Попробовать снова +Не сходится\? +Вход +Соединение с устройством +Сканировать QR-код +Входите с мобильного устройства\? +Показать QR-код на этом устройстве +Выберите «Сканировать QR-код» +Начните с экрана входа +Выберите «Войти при помощи QR-кода» +Начните с экрана входа +Выберите «Показать QR-код» +Зайдите в Настройки -> Безопасность и Приватность +Откройте приложение с другого устройства +Домашний сервер не поддерживает вход при помощи QR-кода. +Вход был отменён с другого устройства. +Этот QR-код не работает. +Другое устройство должно войти в учётную запись. +Другое устройство уже выполнило вход. +Во время установки безопасной переписки возникла проблема с безопасностью. Одно из следующего является скомпроментированным: Ваш домашний сервер; Ваше интернет-соединение; Ваше устройство; +Запрос не выполнен. +Запрос был отклонён на другом устройстве. +Соединение не было выполнено за нужное время. +Соединение с этим устройством не поддерживается. +Неудачное соединение +Проверьте устройство, с которого вы вошли в учётную запись. На его экране должен появиться код снизу. Подтвердите, что код снизу такой же, как и на том устройстве: +Безопасное соединение установлено +Сканируйте QR-код снизу при помощи устройства, с которого вы вышли с учётной записи. +Используйте устройство, с которого вы вошли в учётную запись, чтобы сканировать QR-код снизу: +Войти при помощи QR-кода +Используйте камеру на этом устройстве, чтобы сканировать QR-код, отображённый на вашем другом устройстве: +Сканировать QR-код +3 +2 +1 +Нажмите слева сверху, чтобы увидеть опцию отзыва. +Чтобы упростить ${app_name}, вкладки теперь опциональные. Управляйте ими при помощи меню справа сверху. +Универсальное безопасное приложение для переписок с командами, друзьями и организациями. Создайте переписку или присоеденитесь к уже существующей, чтобы начать. +Пространства — новый способ групировать комнаты и людей. Добавьте существующую комнату или создайте новую, используя кнопку слева снизу. +Возможность записывать и отправлять голосовые трансляции в ленту комнаты. +Получите лучший надзор и контроль над всеми вашими сессиями. +Подтверждённые сессии есть везде, где вы используете эту учётную запись, после введения вашего пароля или подтверждения вашей личности при помощи другой подтверждённой сессии. +\n +\nЭто значит, что у вас есть все нужные ключи, чтобы разблокировать зашифрованные сообщения и даёте другим пользователям знать, что вы доверяете этой сессии. +Подтверждённые сессии вошли при помощи ваших учётных данных и были подтверждены, либо при помощи вашего безопасного пароля, либо при помощи подтверждения с другого устройства. +\n +\nЭто значит, что на них находятся ключи шифрования для ваших предыдущих сообщений и дают другим пользователям знать, что эти сессии действительно принадлежат вам. +Неподтверждённые сессии — это сессии, которые вошли при помощи ваших учётных данных, но не были подтверждены. +\n +\nВы должны удостовериться, что узнаёте эти сессии, так как они могут быть несанкционированным входом в вашу учётную запись. +Вы можете использовать это устройство для входа с телефона или веб-устройства при помощи QR-кода. Для этого есть два способа: +Войти при помощи QR-кода +Собственные названия сессий помогут вам легче распознать свои девайсы. ++ +- Выйти из %1$d сессии
+- Выйти из %1$d сессий
+- Выйти из %1$d сессий
+- Выйти из %1$d сессий
+Выйти +Выбрать сессии +Фильтр ++ +- Неактивен %1$d+ день (%2$s)
+- Неактивен %1$d+ дней (%2$s)
+- Неактивен %1$d+ дня (%2$s)
+- Неактивен %1$d+ дня (%2$s)
+Подтвердите текущую сессию, чтобы посмотреть её состояние подтверждения. +Неизвестное состояние проверки +Автоматически принимать виджеты Element Call и давать доступ к микрофону/камере +Включить ярлыки разрешений Element Call +Форматирование текста +Начать новую голосовую трансляцию +Вам необходимо иметь нужные разрешения, чтобы делиться местоположением в реальном времени в этой комнате. +У вас нет разрешения делиться местоположением в реальном времени +При приглашении кого-то в зашифрованную комнату, которая делится историей, зашифрованная история будет видимой. +Вы уже записываете голосовую трансляцию. Пожалуйста закончите текущую голосовую трансляцию, чтобы начать новую. +Кто-то другой уже записывает голосовую трансляцию. Подождите пока их голосовая трансляция закончится, чтобы начать новую. +У вас нет необходимых разрешений для начала голосовой трансляции в этой комнате. Свяжитесь с администратором комнаты, чтобы получить разрешения. +Не получилось начать новую голосовую трансляцию +Перемотать вперёд на 30 секунд +Перемотать назад на 30 секунд +Буферизация +Приостановить голосовую трансляцию +Проиграть или продолжить голосовую трансляцию +Остановить запись голосовой трансляции +Приостановить запись голосовой трансляции +Продолжить запись голосовой трансляции +Прямая трансляция +Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве. +Сканировать QR-код +Отправьте ваше первое сообщение, чтобы пригласить %s в переписку +Этот QR-код выглядит неправильно. Пожалуйста, попробуйте подтвердить другим способом. +Вы не сможете получить доступ к истории зашифрованных сообщений. Сбросьте вашу защищённую резевную копию и ключи подтверждения, чтобы начать заново. +Сброс пароля +Выберите новый пароль +%s пришлёт вам ссылку для подтверждения +%s нуждается в подтверждении вашей учётной записи +%s нуждается в подтверждении вашей учётной записи +Связаться +Element Matrix Services (EMS) — надёжная хостинговая служба для быстрой и безопасной связи в режиме реального времени. Узнайте больше на <a href=\"${ftue_ems_url}\">element.io/ems</a> +Открыть список пространств +Включено: +Что-то пошло не так. Пожалуйста, проверьте соединение и попробуйте ещё раз. +Открыть экран инструментов для разработчика +Простите, эта комната не была найдена. +\nПожалуйста, попробуйте снова позже.%s +⚠ В этой комнате есть неподтверждённые устройства, они не смогут расшифровывать сообщения, отправленные вами. +Дать разрешение +Другие пользователи могут найти вас по %s +Осталось %1$s +создал опрос. +отправил наклейку. +отправил видео. +отправил изображение. +отправил голосовое сообщение. +отправил аудиофайл. +отправил файл. +В ответ на +Скрыть IP-адрес +Показать IP-адрес +Цитируя +В ответ на %s +Редактирование \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index f37af1a654..078ffc44eb 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -1040,8 +1040,8 @@Možnosti objavovania sa zobrazia, až keď pridáte telefónne číslo. Odpojením sa od servera totožností znemožníte ostatným, aby vás našli a tiež nebudete môcť kontakty pozývať zadaním emailovej adresy alebo telefónneho čísla. Telefónne čísla, podľa ktorých je vás možné nájsť -Odoslali sme vám potvrdzujúci e-mail na adresu %s, skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz -Odoslali sme vám potvrdzujúci e-mail na adresu %s, najskôr si prosím skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz +Odoslali sme vám e-mail na adresu %s, skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz +Odoslali sme vám e-mail na adresu %s, najskôr si prosím skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz Zadajte URL adresu servera totožností Nie je možné sa pripojiť k serveru totožností Prosím, zadajte URL adresu servera totožností @@ -1700,7 +1700,7 @@Prosím, použite medzinárodný formát. Nastavte si telefónne číslo, aby ste voliteľne umožnili ľuďom, ktorých poznáte, aby vás objavili. Toto nevyzerá ako platná e-mailová adresa -Nastavte si e-mail na obnovenie konta. Neskôr môžete voliteľne povoliť známym, aby vás objavili podľa vášho e-mailu. +Nastavte si e-mail na obnovenie konta. Neskôr môžete voliteľne povoliť svojim známym, aby vás objavili podľa tohto e-mailu. Nastaviť e-mailovú adresu Späť na prihlásenie Vaše heslo bolo obnovené. @@ -1846,7 +1846,7 @@Kľúčové slová nemôžu začínať na \".\" Pridať nové kľúčové slovo Povoliť e-mailové oznámenia pre %s -Ak chcete dostávať e-mail s upozornením, priraďte e-mail k svojmu kontu Matrix +Ak chcete dostávať e-mail s upozornením, priraďte e-mail k svojmu Matrix účtu E-mailové oznámenie Upozorniť ma na Upozorniť ma na @@ -1856,10 +1856,10 @@Iba zmienky a kľúčové slová Medzinárodné telefónne čísla musia začínať znakom \"+\" Ak chcete zistiť existujúce kontakty, potrebujete odoslať kontaktné informácie (e-maily a telefónne čísla) na server totožností. Pred odoslaním vaše údaje zahašujeme kvôli ochrane osobných údajov. -Odoslať e-maily a telefónne čísla na %s +Odoslať emailové adresy a telefónne čísla na %s Dali ste súhlas na odosielanie e-mailov a telefónnych čísel na tento server totožností na objavenie ďalších používateľov z vašich kontaktov. Odoslať e-maily a telefónne čísla -Spravovať e-maily a telefónne čísla prepojené s vaším účtom Matrix +Spravujte e-maily a telefónne čísla prepojené s vaším účtom Matrix Emaily a telefónne čísla Nastaviť nové heslo k účtu… Nepoužívajte heslo k svojmu účtu. @@ -2650,11 +2650,10 @@ \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. +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 +Zoznam priestorov Vytvoriť novú konverzáciu alebo miestnosť Ľudia Obľúbené @@ -2693,7 +2692,7 @@Tu sa zobrazia neprečítané správy, ak nejaké máte. Nič, o čom by bolo potrebné podať správu. Kompletná zabezpečená aplikácia na komunikáciu pre tímy, priateľov a organizácie. Začnite konverzáciu alebo sa pridajte k existujúcej miestnosti. -Vitajte v aplikácii ${názov_aplikácie}, + Vitajte v aplikácii ${app_name}, \n%s. Priestory sú novým spôsobom zoskupovania miestností a ľudí. Pomocou tlačidla vpravo dole môžete pridať existujúcu miestnosť alebo vytvoriť novú. %s @@ -2764,4 +2763,163 @@ Povoliť odložené priame správy Zjednodušený Element s voliteľnými kartami Zapnúť nové usporiadanie +Ostatní používatelia v priamych správach a miestnostiach, do ktorých sa pripojíte, si môžu pozrieť úplný zoznam vašich relácií. +\n +\nTo im poskytuje istotu, že sa komunikujú naozaj s vami, ale zároveň to znamená, že vidia názov relácie, ktorý sem zadáte. +Premenovanie relácií +Overené relácie, do ktorých ste sa prihlásili pomocou svojich prihlasovacích údajov a ktoré boli následne overené buď pomocou vašej bezpečnostnej prístupovej frázy, alebo krížovým overením. +\n +\nTo znamená, že majú šifrovacie kľúče pre vaše predchádzajúce správy a potvrdzujú ostatným používateľom, s ktorými komunikujete, že tieto relácie ste skutočne vy. +Overené relácie +Neoverené relácie sú relácie, do ktorých ste sa prihlásili pomocou svojich prístupových údajov, ale ktoré neboli krížovo overené. +\n +\nMali by ste si byť obzvlášť istí, že tieto relácie poznáte, pretože by mohli predstavovať neoprávnené použitie vášho konta. +Neoverené relácie +Neaktívne relácie sú relácie, ktoré ste určitý čas nepoužívali, ale naďalej dostávajú šifrovacie kľúče. +\n +\nOdstránenie neaktívnych relácií zvyšuje bezpečnosť a výkon a uľahčuje identifikáciu podozrivých nových relácií. +Neaktívne relácie +Uvedomte si, že názvy relácií sú viditeľné aj pre ľudí, s ktorými komunikujete. +Vlastné názvy relácií vám pomôžu ľahšie rozpoznať vaše zariadenia. +Názov relácie +Premenovať reláciu +Odhlásiť sa z tejto relácie +Neoverená - Vaša aktuálna relácia +Spustiť hlasové vysielanie +Vierohodnosť tejto zašifrovanej správy nie je možné zaručiť na tomto zariadení. +Požiadajte, aby klávesnica neaktualizovala žiadne personalizované údaje, napríklad históriu písania a slovník, na základe toho, čo ste napísali v konverzáciách. Upozorňujeme, že niektoré klávesnice nemusia toto nastavenie rešpektovať. +Inkognito klávesnica +Pridá znaky (╯°□°)╯︵ ┻━┻ pred správy vo formáte obyčajného textu +Hlasové vysielanie +Otvoriť obrazovku vývojárskych nástrojov +🔒 V Nastaveniach zabezpečenia ste povolili šifrovanie len pre overené relácie pre všetky miestnosti. +⚠ V tejto miestnosti sa nachádzajú neoverené zariadenia, ktoré nebudú schopné dešifrovať odoslané správy. +Nikdy neposielať šifrované správy do neoverených relácií v tejto miestnosti. +Rozumiem +Použiť formát podčiarknutia +Použiť formát prečiarknutia +Použiť formát kurzívou +Použiť tučný formát +Zaznamenať názov klienta, verziu a url, aby bolo možné ľahšie rozpoznať relácie v správcovi relácií. +Povoliť zaznamenanie informácií o klientovi +Majte lepší prehľad a kontrolu nad všetkými reláciami. +Použiť nového správcu relácií +Operačný systém +Model +Prehliadač +URL +Verzia +Názov +Aplikácia +Prijímať push oznámenia v tejto relácii. +Push oznámenia +Overením aktuálnej relácie zistíte stav overenia tejto relácie. +Neznámy stav overenia +Zapnuté: +ID relácie: +Niečo sa pokazilo. Skontrolujte, prosím, svoje sieťové pripojenie a skúste to znova. +Udeliť oprávnenie +${app_name} potrebuje povolenie na zobrazovanie oznámení. +\nProsím, udeľte toto povolenie. +${app_name} potrebuje povolenie na zobrazovanie oznámení. Oznámenia môžu zobrazovať vaše správy, pozvánky atď. +\n +\nPovoľte prístup na ďalších vyskakovacích oknách, aby ste mohli zobrazovať oznámenia. +Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro) +Povoliť rozšírený textový editor +Uistite sa prosím, že poznáte pôvod tohto kódu. Prepojením zariadení poskytnete niekomu plný prístup k svojmu účtu. +Potvrdiť +Skúste to znova +Nezhoduje sa\? +Prebieha prihlasovanie +Pripájanie k zariadeniu +Skenovať QR kód +Prihlasovanie do mobilného zariadenia\? +Zobraziť QR kód na tomto zariadení +Vyberte možnosť \"Skenovať QR kód\" +Začnite na prihlasovacej obrazovke +Vyberte možnosť \"Prihlásiť sa pomocou QR kódu\" +Začnite na prihlasovacej obrazovke +Vyberte možnosť \"Zobraziť QR kód\" +Prejdite do Nastavenia -> Zabezpečenie a súkromie +Otvorte aplikáciu na vašom druhom zariadení +Žiadosť bola na druhom zariadení zamietnutá. +Prepojenie nebolo dokončené v požadovanom čase. +Prepojenie s týmto zariadením nie je podporované. +Neúspešné pripojenie +Skontrolujte svoje prihlásené zariadenie, mal by sa zobraziť nasledujúci kód. Skontrolujte, či sa nižšie uvedený kód zhoduje s daným zariadením: +Zabezpečené pripojenie bolo vytvorené +Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené. +Pomocou prihláseného zariadenia naskenujte nižšie uvedený QR kód: +Prihlásiť sa pomocou QR kódu +Pomocou fotoaparátu na tomto zariadení naskenujte QR kód zobrazený na vašom druhom zariadení: +Skenovať QR kód +3 +2 +1 +Pomocou tohto zariadenia sa môžete prihlásiť do mobilného alebo webového zariadenia pomocou QR kódu. Môžete to urobiť dvoma spôsobmi: +Prihlásiť sa pomocou QR kódu +Skenovať QR kód +Domovský server nepodporuje prihlásenie pomocou QR kódu. +Prihlasovanie bolo zrušené na druhom zariadení. +QR kód nie je platný. +Druhé zariadenie musí byť prihlásené. +Druhé zariadenie je už prihlásené. +Pri nastavovaní zabezpečeného zasielania správ sa vyskytol bezpečnostný problém. Jedna z nasledujúcich možností môže byť kompromitovaná: Váš domovský server; Vaše internetové pripojenie (pripojenia); Vaše zariadenie (zariadenia); +Žiadosť zlyhala. +Možnosť nahrávania a odosielania hlasového vysielania v časovej osi miestnosti. +Zapnúť hlasové vysielanie (v štádiu aktívneho vývoja) +Načítavanie do vyrovnávacej pamäte +Pozastaviť hlasové vysielanie +Prehrať alebo pokračovať v nahrávaní hlasového vysielania +Zastaviť nahrávanie hlasového vysielania +Pozastaviť nahrávanie hlasového vysielania +Pokračovať v nahrávaní hlasového vysielania +Naživo +Vyberte relácie +Kontakt +Kamera +Poloha +Ankety +Hlasové vysielanie +Prílohy +Nálepky +Knižnica fotografií +Zrušiť výber všetkých +Vybrať všetko ++ +- %1$d vybraté
+- %1$d vybraté
+- %1$d vybraných
+Prepnutie režimu na celú obrazovku +Formátovanie textu +Už nahrávate hlasové vysielanie. Ukončite aktuálne hlasové vysielanie a spustite nové. +Niekto iný už nahráva hlasové vysielanie. Počkajte, kým sa skončí jeho hlasové vysielanie, a potom spustite nové. +Nemáte požadované oprávnenia na spustenie hlasového vysielania v tejto miestnosti. Obráťte sa na správcu miestnosti, aby vám rozšíril oprávnenia. +Nie je možné spustiť nové hlasové vysielanie +Rýchle posunutie dozadu o 30 sekúnd +Rýchle posunutie dopredu o 30 sekúnd +Overené relácie sú všade tam, kde používate toto konto po zadaní svojho prístupového hesla alebo po potvrdení svojej totožnosti inou overenou reláciou. +\n +\nTo znamená, že máte všetky kľúče potrebné na odomknutie zašifrovaných správ a potvrdenie pre ostatných používateľov, že tejto relácii dôverujete. ++ +- Odhlásiť sa z %1$d relácie
+- Odhlásiť sa z %1$d relácií
+- Odhlásiť sa z %1$d relácií
+Odhlásiť sa +Ostáva %1$s +Cituje +vytvoril/a anketu. +poslal/a nálepku. +poslal/a video. +poslal/a obrázok. +poslal/a zvukovú správu. +poslal/a zvukový súbor. +poslal súbor. +V odpovedi na +Skryť IP adresu +Zobraziť IP adresu +Odpoveď na %s +Úprava \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index a6af0a4921..800ec17dcf 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -601,9 +601,7 @@Formatojini mesazhet duke përdorur sintaksën Markdown përpara se të dërgohen. Kjo lejon formatim të thelluar, f.v., përdorimi i yllthit për ta shfaqur tekstin me të pjerrëta. Nuk prek ftesat, heqjet dhe dëbimet. ${app_name}-i grumbullon të dhëna analitike anonime që të na lejojë ta përmirësojmë aplikacionin. -Të shfaqen krejt mesazhet prej %s\? -\n -\nKini parasysh që ky veprim do të sjellë rinisjen e aplikacionit dhe mund të hajë ca kohë. +Të shfaqen krejt mesazhet prej %s\? Nis kamerën e sistemit, në vend se skenën e kamerës vetjake. Shfaq veprimin On/Off sintakse Markdown @@ -897,10 +895,10 @@S’u arrit të dërgohej sugjerimi (%s) Shfaq te rrjedha kohore akte të fshehura Përgjegjës integrimesh -app_id: -push_key: -app_display_name: -emër_sesioni: +ID Aplikacioni: ++ Emër Aplikacioni Në Ekran: +Emër Sesioni Në Ekran: Mesazhe të Drejtpërdrejtë Po pritet… Po fshehtëzohet miniatura… @@ -949,11 +947,11 @@Po përdorni %1$s për të zbuluar dhe për të qenë i zbulueshëm nga kontakte ekzistues që njihni. S’po përdorni ndonjë shërbyes identitetesh. Që të zbuloni dhe të jini i zbulueshëm nga kontakte ekzistuese që njihni, formësoni një të tillë më poshtë. Adresa email të zbulueshme -Mundësitë rreth zbulimesh do të shfaqen sapo të keni shtuar një email. +Mundësitë e zbulimit do të shfaqen sapo të keni shtuar një adresë email. Mundësi zbulimesh do të shfaqen sapo të keni shtuar një numër telefoni. Shkëputja prej shërbyesit tuaj të identiteteve do të thotë se s’do të jeni i zbulueshëm prej përdoruesish të tjerë dhe s’do të jeni në gjendje të ftoni të tjerë me email ose telefon. Numra telefoni të zbulueshëm -Ju dërguam një email ripohimi te %s, hapeni dhe klikoni mbi lidhjen e ripohimit +Ju dërguam një email te %s, hapeni dhe klikoni mbi lidhjen e ripohimit Jepni një URL shërbyesi identitetesh S’u lidh dot te shërbyes identitetesh Ju lutemi, jepni URL-në e shërbyesit të identiteteve @@ -1080,7 +1078,7 @@Aplikacioni s’është në gjendje të krijojë llogari në këtë shërbyes Home. \n \nDoni të regjistroheni duke përdorur një klient web\? -Ky emai s’është përshoqëruar me ndonjë llogari. +Kjo adresë email s’është e përshoqëruar me ndonjë llogari. Ricaktoni fjalëkalimin në %1$s Te mesazhet tuaj do të dërgohet një email verifikimi, për të ripohuar caktimin e fjalëkalimit tuaj të ri. Pasuesi @@ -1089,7 +1087,7 @@Kujdes! Ndryshimi i fjalëkalimit tuaj do të sjellë zerim të çfarëdo kyçesh fshehtëzimi skaj-më-skaj në krejt sesionet tuaj, duke e bërë të palexueshëm historikun e bisedave të fshehtëzuara. Ujdisni një Kopjeruajtje Kyçesh ose eksportoni kyçet e dhomës tuaj prej një tjetër sesioni, përpara se të ricaktoni fjalëkalimin tuaj. Vazhdo -Ky email s’është i lidhur me ndonjë llogari +Kjo adresë email s’është e lidhur me ndonjë llogari Kontrolloni te mesazhet tuaj të marrë Një email verifikimi u dërgua te %1$s. Prekni mbi lidhjen që të ripohohet fjalëkalimi juaj i ri. Pasi të keni ndjekur lidhjen që përmban, klikoni më poshtë. @@ -1103,7 +1101,7 @@ \n \nTë ndalet procesi i ndryshimit të fjalëkalimit\?Caktoni adresë email -Caktoni një email për rimarrje të llogarisë tuaj. Më vonë, mundeni të lejoni persona që njihni t’ju zbulojnë përmes email-it tuaj. +Caktoni një adresë email për rimarrje të llogarisë tuaj. Më vonë, mundeni të lejoni persona që njihni t’ju zbulojnë përmes kësaj adrese. Email (në daçi) Pasuesi @@ -1445,7 +1443,7 @@Mesazhi u fshi Shfaq mesazhe të hequr Shfaq një vendmbajtëse për mesazhe të hequr -Ju dërguam një email ripohimi te %s, ju lutemi, së pari, shihni email-in tuaj dhe klikoni mbi lidhjen e ripohimit +Ju dërguam një email te %s, ju lutemi, së pari, shihni email-in tuaj dhe klikoni mbi lidhjen e ripohimit Kodi i verifikimit s’është i saktë. MEDIA S’ka media në këtë dhomë @@ -1518,9 +1516,7 @@ \n \nKëtë veprim mund ta zhbëni në çfarëdo kohe, te rregullimet e përgjithshme.Hiqe shpërfilljen e përdoruesit -Heqja e shpërfilljes së këtij përdoruesi do të shfaqë sërish krejt mesazhet prej tij. -\n -\nKini parasysh se ky veprim do të sjellë rinisjen e aplikacionit dhe do të hajë ca kohë. +Heqja e shpërfilljes së këtij përdoruesi do të shfaqë sërish krejt mesazhet prej tij. Anuloje ftesën Jeni i sigurt se doni të anulohet ftesa për këtë përdorues\? Përzëre përdoruesin @@ -1534,7 +1530,7 @@Heqja e dëbimit përdoruesit do t’i lejojë të marrë pjesë sërish në dhomë. Te llogaria juaj s’është shtuar ndonjë numër telefoni Adresa email -Te llogaria juaj s’është shtuar ndonjë email +Te llogaria juaj s’është shtuar ndonjë adresë email Numra telefoni Të hiqet %s\? Sigurohuni që keni klikuar te lidhja në email-in që ju kemi dërguar. @@ -1552,7 +1548,7 @@Integrimet janë të çaktivizuara Që të bëhet kjo, aktivizoni “Lejo integrime”, te Rregullimet. Email-e dhe numra telefonash -Administroni email-e dhe numra telefonash të lidhur me llogarinë tuaj Matrix +Administroni adresa email dhe numra telefonash të lidhur me llogarinë tuaj Matrix - %d përdorues i dëbuar
- %d përdorues të dëbuar
@@ -1605,7 +1601,7 @@Kjo llogari është çaktivizuar. S’u ruajt dot kartelë media Ripohoni identitetin tuaj duke verifikuar këto kredenciale hyrjeje, duke i akorduar hyrje te mesazhe të fshehtëzuar. -Për privatësinë tuaj, ${app_name}-i mbulon vetëm dërgim email-esh dhe numrash telefoni përdoruesi të koduar. +Për privatësinë tuaj, ${app_name}-i mbulon vetëm dërgim adresash email dhe numrash telefoni përdoruesi të koduar. Caktoni rol Rol Hapni fjalosje @@ -1769,7 +1765,7 @@%1$d nga %2$d Jepe pranimin Shfuqizoje pranimin tim -Keni dhënë pranimin tuaj për të dërguar email-e dhe numra telefonash te ky shërbyes identitetesh që të zbulojë përdorues të tjerë prej kontakteve tuaj. +Keni dhënë pranimin tuaj për të dërguar adresa email-e dhe numra telefonash te ky shërbyes identitetesh që të zbulojë përdorues të tjerë prej kontakteve tuaj. Dërgo email-e dhe numra telefonash Sugjerime Përdorues të Ditur @@ -2135,7 +2131,7 @@Përmendje dhe Fjalëkyçe Njoftime Parazgjedhje %s te Rregullimet, që të merrni ftesa drejt e në ${app_name}. -Lidheni këtë email me llogarinë tuaj +Lidheni këtë adresë email me llogarinë tuaj Kjo ftesë për te kjo hapësirë u dërgua te %s që s’është i përshoqëruar me llogarinë tuaj Kjo ftesë për te kjo dhomë qe dërguar për %s që s’është i përshoqëruar me llogarinë tuaj Krejt dhomat ku gjendeni do të shfaqen te Home. @@ -2203,7 +2199,7 @@Hyrje në hapësirë Kush mund të hyjë\? Aktivizo njoftime me email për %s -Që të merrni email me njoftim, ju lutemi, përshoqërojini llogarisë tuaj Matrix një email +Që të merrni email me njoftim, ju lutemi, përshoqërojini llogarisë tuaj Matrix një adresë email Njoftim me email Të përmirësojë hapësirën Të ndryshojë emrin e hapësirës @@ -2249,8 +2245,8 @@Pyetje ose temë pyetësori Krijoni Pyetësor A pranoni të dërgohen këto hollësi\? -Për të zbuluar kontakte ekzistuese, duhet të dërgoni hollësi kontakti (email-e dhe numra telefonash) te shërbyesi juaj i identiteteve. Para dërgimit, i fshehtëzojmë të dhënat tuaja, për privatësi. -Dërgo email-e dhe numra telefonash te %s +Për të zbuluar kontakte ekzistuese, duhet të dërgoni hollësi kontakti (adresa email dhe numra telefonash) te shërbyesi juaj i identiteteve. Para dërgimit, i fshehtëzojmë të dhënat tuaja, për privatësi. +Dërgo adresa email dhe numra telefonash te %s Kontaktet tuaja janë private. Për të zbuluar përdorues prej kontakteve tuaja, na duhet leja juaj për të dërguar hollësi kontakti te shërbyesi juaj i identiteteve. Është bërë dalja nga sesioni! U dol nga dhoma! @@ -2355,7 +2351,7 @@Bashkësi Ekipe Shokë dhe familje -Do t’ju ndihmojmë të lidheni. +Do t’ju ndihmojmë të lidheni Me kë do të bisedoni më shumë\? Po e shihni tashmë këtë rrjedhë! Shiheni në Dhomë @@ -2411,15 +2407,15 @@Shërbyesi Home s’pranon emër përdorues vetëm me shifra. Anashkalojeni këtë hap Ruajeni dhe vazhdoni -Parapëlqimet tuaja u ruajtën. +Kaloni te rregullimet, kur të doni, që të përditësoni profilin tuaj Kaq qe! Shkojmë -Këtë mund ta ndryshoni kurdo. +Erdh koha t’i jepet surrat emrit Shtoni një foto profili Këtë mund ta ndryshoni më vonë Emër Në Ekran Zgjidhni një emër për në ekran -Llogaria juaj %s u krijua. +Llogaria juaj %s u krijua Përgëzime! Shpjemëni në shtëpi Personalizoni profil @@ -2450,4 +2446,409 @@Prani Mësoni më tepër Provojeni - +Aktivizo shkurtore lejesh për Thirrje Element +S’u gjet metodë tjetër veç njëkohësimit në prapaskenë. +${app_name}-it i duhet një fshehtinë e pastër, për të qenë i përditësuar, për arsyen vijuese: +\n%s +\n +\nKini parasysh se ky veprim do të sjellë rinisjen e aplikacionit dhe mund të dojë ca kohë. +Regjistro emrin, versionin dhe URL-në e klientit, për të dalluar më kollaj sesionet te përgjegjës sesionesh. +Veprimtaria e fundit më %1$s +Apliko format me të nënvizuara +Apliko format me të hequravije +Apliko format me të pjerrta +Apliko format me të trasha +Ju lutemi, sigurohuni se e dini origjinën e këtij kodi. Duke lidhur pajisje, do t’i jepni dikujt hyrje të plotë në llogarinë tuaj. +Ripohojeni +Riprovoni +Pa përputhje\? +Po bëhet hyrja juaj +Po lidhet me pajisjen +Skanoni kodin QR +Po bëhet hyrja te një pajisje celulare\? +Shfaq kod QR te kjo pajisje +Përzgjidhni “Skanoni kod QR” +Filloja në skenën e hyrjes +Përzgjidhni “Hyni me kod QR” +Filloja në skenën e hyrjes +Përzgjidhni “Shfaq kod QR” +Kaloni te Rregullime -> Siguri & Privatësi +Hapeni aplikacionin në pajisjen tuaj tjetër +Hyrja u anulua në pajisjen tuaj tjetër. +Ai kod QR është i pavlefshëm. +Duhet bërë hyrja te pajisja tjetër. +Nga pajisja tjetër është bërë tashmë hyrja. +Kërkesa dështoi. +Kërkesa u hodh poshtë në pajisjen tjetër. +Lidhja me këtë pajisje nuk mbulohet. +Lidhje e pasuksesshme +U vendos lidhje e siguruar +Hyni me kod QR +Skanoni kodin QR +3 +2 +1 +Provojeni +Prekeni djathtas në krye që të shihni mundësinë për dhënie përshtypjesh. +Jepni Përshtypje +Hyni në Hapësirat tuaja (poshtë djathtas) më shpejt dhe më kollaj se kurrë më parë. +Hyni Në Hapësira +Që të thjeshtohet ${app_name} juaj, skedat tanimë janë opsionale. Administrojini duke përdorur menunë djathtas në krye. +Mirë se vini te një pamje e re! +Ky është vendi ku do të shfaqen mesazhet tuaj të palexuar, kur të ketë të tillë. +S’ka gjë për ta raportuar. +Aplikacioni “all-in-one” i fjalosjeve të siguruara, për ekipe, shokë dhe ente. Që t’ia filloni, krijoni një fjalosje, ose hyni në një dhomë ekzistuese. +Mirë se vini te ${app_name}, +\n%s. +Hapësirat janë një mënyrë e re për të grupuar dhoma dhe persona. Shtoni një dhomë ekzistuese, ose krijoni një të re, duke përdorur butonin poshtë djathtas. +%s +\nduket paksa si i zbrazët. +Jini në gjendje të incizoni dhe dërgoni transmetim zanor në rrjedhën kohore të dhomës. +Aktivizoni transmetim zanor (nën zhvillim aktiv) +Aktivizo regjistrim hollësish klienti +Shihini më qartë dhe kontrolloni më mirë krejt sesionet tuaj. +Aktivizo përgjegjës të ri sesionesh +Përdorues të tjerë në mesazhe të drejtpërdrejtë dhe dhoma ku hyni janë në gjendje të shohin një listë të plotë të sesioneve tuaj. +\n +\nKjo u jep atyre besim se po flasin vërtet me ju, por do të thotë gjithashtu që mund shohin emrin e sesionit që jepni këtu. +Riemërtim sesionesh +Sesionet e verifikuar përfaqësojnë sesione ku është bërë hyrja dhe janë verifikuar, ose duke përdorur togfjalëshin tuaj të sigurt, ose me verifikim. +\n +\nKjo do të thotë se zotërojnë kyçe fshehtëzimi për mesazhe tuajt të mëparshëm dhe u ripohojnë përdoruesve të tjerë, me të cilët po komunikoni, se këto sesione ju takojnë juve. +Sesione të verifikuar +Sesionet e paverifikuar janë sesione në të cilët është bërë hyrja me kredencialet tuaja, por pa u bërë verifikim. +\n +\nDuhet të jeni posaçërisht të qartë se i njihni këto sesione, ngaqë mund të përbëjnë përdorim të paautorizuar të llogarisë tuaj. +Sesione të paverifikuar +Sesioni joaktive janë sesione që keni ca kohë që s’i përdorni, por që vazhdojnë të marrin kyçe fshehtëzimi. +\n +\nHeqja e sesioneve joaktive përmirëson sigurinë dhe punimin dhe e bën më të lehtë për ju të pikasni nëse një sesion i ri është i dyshimtë. +Sesione joaktive +Mund të përdorni këtë pajisje për të bërë hyrjen në një pajisje celulare apo web me një kod QR. Për ta bërë këtë ka dy mënyra: +Hyni me Kod QR +Ju lutemi, kini parasysh se emrat e sesioneve janë të dukshëm edhe për personat me të cilët komunikoni. +Emra vetjakë sesionesh mund t’ju ndihmojnë të njihni më kollaj pajisjet tuaja. +Emër sesioni +Riemërtoni sesionin +Adresë IP +Sistem operativ +Model +Shfletues +URL +Version +Ëmër +Aplikacion +Veprimtaria e fundit +Emër sesioni +Merrni njoftime push për këtë sesion. +Njoftime Push +Hollësi aplikacioni, pajisjeje dhe veprimtarie. +Hollësi sesioni +Dilni nga ky sesion +Përzgjidhni sesione +Spastroje Filtrin +S’u gjetën sesione joaktive. +S’u gjetën seanca të paverifikuara. +S’u gjetën sesione të verifikuara. ++ +- Shihni mundësinë e daljes nga sesione të vjetër (%1$d ditë ose më tepër) të cilët s’i përdorni më.
+- Shihni mundësinë e daljes nga sesione të vjetër (%1$d ditë ose më tepër) të cilët s’i përdorni më.
+Joaktive +Verifikoni sesionet tuaj, për shkëmbim më të sigurt mesazhesh, ose dilni prej atyre që nuk i njihni, apo përdorni më. +Të paverifikuar +Për sigurinë më të mirë, dilni nga çfarëdo sesioni që nuk e njihni apo përdorni më. +Të verifikuar +Filtroji ++ +- Joaktiv për %1$d ditë, ose më gjatë
+- Joaktiv për %1$d ditë, ose më gjatë
+Jo aktiv +Jo gati për shkëmbim të sigurt mesazhesh +E paverifikuar +Gati për shkëmbim të sigurt mesazhesh +E verifikuar +Krejt sesionet +Filtroji +Pajisje +Sesion +Sesioni i Tanishëm ++ +- Shihni mundësinë e daljes nga sesione të vjetër (%1$d ditë ose më tepër) të cilët s’i përdorni më.
+- Shihni mundësinë e daljes nga sesione të vjetër (%1$d ditë ose më tepër) të cilët s’i përdorni më.
+Sesione joaktive +Verifikojini, ose dilni nga sesione të paverifikuar. +Sesione të paverifikuar +Përmirësoni sigurinë e llogarisë tuaj duke ndjekur këto rekomandime. +Rekomandime sigurie ++ +- Joaktiv për %1$d+ ditë (%2$s)
+- Joaktiv për %1$d+ ditë (%2$s)
+I paverifikuar · Sesioni juaj i tanishëm +I paverifikuar · Veprimtari së fundi më %1$s +I verifikuar · Veprimtaria e fundit më %1$s +Shihni Krejt (%1$d) +Shihni Hollësitë +Verifiko Sesion +Verifikoni sesionin tuaj të tanishëm, që të shfaqni gjendjen e verifikimit të këtij sesioni. +Për sigurinë dhe besueshmërinë më të mirë, verifikojeni, ose dilni nga ky sesion. +Verifikoni sesionin tuaj të tanishëm, për shkëmbim më të sigurt të mesazheve. +Ky sesion është gati për shkëmbim të sigurt mesazhesh. +Sesioni juaj i tanishëm është gati për shkëmbim të sigurt mesazhesh. +Gjendje e panjohur verifikimi +Sesion i paverifikuar +Sesion i verifikuar +Lloj i panjohur pajisjeje +Desktop +Web +Celular +Për sigurinë më të mirë, verifikoni sesionet tuaja dhe dilni nga çfarëdo sesioni që s’e njihni, ose s’e përdorni më. +Sesione të tjera ++ +- U hoq %d mesazh
+- U hoqë %d mesazhe
+Aktivizoni tregim vendndodhjeje +Ju lutemi, kini parasysh: kjo është një veçori në zhvillim, që përdor një sendërtim të përkohshëm. Kjo do të thotë se s’do të jeni në gjendje të fshini historikun e vendndodhjeve tuaja dhe përdoruesit e përparuar do të jenë në gjendje të shohin historikun e vendndodhjeve tuaja, edhe pasi të keni ndalur dhënien “live” për këtë dhomë të vendndodhjes tuaj. +Tregim “live” vendndodhjeje +Kanal i tanishëm: %s +Kanal +S’gjendet pikëmbarimi. +Pikëmbarim i tanishëm: %s +Pikëmbarim +Hëpërhë po përdoret %s. +Metodë ++ +- U gjet %d metodë.
+- U gjetën %d metoda.
+S’u gjet metodë tjetër veç Google Play Service. +Metoda të gatshme +Metodë njoftimi +Njëkohësim në prapaskenë +Shërbime Google +Zgjidhni si të merren njoftime +Tregimi i ekranit është në punë e sipër +Tregim Ekrani ${app_name} +Kontakt +Kamerë +Vendndodhje +Pyetësorë +Transmetim zanor +Bashkëngjitje +Ngjitës +Fototekë +Nisni një transmetim zanor +Vendndodhje drejtpërsëdrejti +Jepe vendndodhjen +Që të mund të ndani drejtpërsëdrejti vendndodhje me të tjerë në këtë dhomë, lypset të keni lejet e duhura. +S’keni leje të tregoni vendndodhje drejtpërsëdrejti +Përditësuar %1$s më parë +Sendërtim i përkohshëm: vendndodhjet mbeten në historikun e dhomës +Aktivizo Tregim Vendndodhjeje “Live” +Vendndodhje Drejtpërsëdrejti ${app_name} +Edhe %1$s +“Live” deri më %1$s +Shihni vendndodhje “live” +Tregimi “live” i vendndodhjes përfundoi +Po ngarkohet vendndodhje “live”… +S’arrihet të ngarkohet hartë +\nKy shërbyes Home mund të mos jetë formësuar të shfaqë harta. +Përfundimet do të jenë të dukshme pasi të ketë përfunduar pyetësori +Kur bëhet ftesë në një dhomë të fshehtëzuar që ka historik ndarjesh me të tjerët, historiku i fshehtëzuar do të jetë i dukshëm. +Përdo +Ndal transmetim zanor +Luani ose vazhdoni luajtje transmetimi zanor +Ndal incizim transmetimi zanor +Ndal incizim transmetimi zanor +Vazhdo incizim transmetimi zanor +Drejtpërdrejt +Shfaq hollësitë më të reja të përdoruesit +Disa përfundime mund të jenë të fshehura, ngaqë janë private dhe ju duhet një ftesë për to. +S’u gjetën përfundime +Mos braktis ndonjë +Braktisi krejt +Gjëra në këtë hapësirë +I zënë +Hap rregullimet +S’u aktivizua dot mirëfilltësim biometrik. +Mirëfilltësimi biometrik qe çaktivizuar ngaqë tani së fundi është shtuar një metodë e re mirëfilltësimi biometrik. Mund ta riaktivizoni që nga Rregullimet. +S’mund të garantohet mirëfilltësia e këtij mesazhi të fshehtëzuar në këtë pajisje. +Tastierë inkonjito +Dërgoni mesazhin tuaj të parë për të ftuar në fjalosje %s +Mesazhet në këtë fjalosje do të jenë të fshehtëzuar skaj-më-skaj. +S’do të jeni në gjendje të shihni historikun e mesazheve të fshehtëzuara. Që t’ia rifilloni nga e para, ricaktoni kyçet tuaja për Kopjeruajtje të Sigurt Mesazhesh dhe kyçe verifikimi. +S’arrihet të verifikohet kjo pajisje +Sesione +Tregoi vendndodhjen e vet drejtpërsëdrejti +E paraprin një mesazh tekst i thjeshtë me (╯°□°)╯︵ ┻━┻ +S’hapet dot kjo lidhje: bashkësitë janë zëvendësuar nga hapësirat +Skanoni kodin QR +Emër përdoruesi / Email / Telefon +Jeni qenie njerëzore\? +Ndiqni udhëzimet e dërguara te %s +Ricaktim fjalëkalimi +Harrova fjalëkalimin +Ridërgo email +S’morët email\? +Ndiqni udhëzimet e dërguara te %s +Verifikoni email-in tuaj +Ridërgomëni kodin +Te %s u dërgua një kod +Ripohoni numrin e telefonit tuaj +Dil nga krejt pajisjet +Ricaktoni fjalëkalimin +Sigurohuni të jetë 8 ose më shumë shenja. +Zgjidhni një fjalëkalim të ri +Fjalëkalim i Ri +Kontrolloni email-in tuaj. +%s do t’ju dërgojë një lidhje verifikimi +Kod ripohimi +Numër Telefoni +%s lyp verifikimin e llogarisë tuaj +Jepni numrin e telefonit tuaj +%s lyp verifikimin e llogarisë tuaj +Jepni email-in tuaj +Ju lutemi, lexoni kushte dhe rregulla të %s +Rregulla shërbyesi +Lidhuni +Element Matrix Services (EMS) është një shërbim strehimi i fuqishëm dhe i besueshëm, për komunikim të shpejtë, të sigurt dhe të atypëratyshëm. Shihni më tepër se si, teelement.io/ems +Doni të strehoni shërbyesin tuaj\? +URL Shërbyesi +Cila është adresa e shërbyesit tuaj\? +Cila është adresa e shërbyesit tuaj\? Kjo është si një shtëpi për krejt të dhënat tuaja +Përzgjidhni shërbyesin tuaj +Mirë se u kthyet! +Përpunojeni +Ose +Ku gjenden bisedat tuaja +Ku do të gjenden bisedat tuaja +Duhet të jetë 8 ose më shumë shenja +Të tjerët mund t’ju zbulojnë %s +Krijoni llogarinë tuaj +Transmetim Zanor +Hap listë hapësirash +Krijoni një bisedë ose dhomë të re +Ricaktoni metodë njoftimesh +Të aktivizuara: +Etiketë profili: +ID sesioni: +Jepi +Po përditësohen të dhënat tuaja… +Diç shkoi ters. Ju lutemi, kontrolloni lidhjen tuaj në rrjet dhe riprovoni. +Persona +Të parapëlqyera +Të palexuara +Krejt +Kopjeruajtja ka një nënshkrim të vlefshëm prej këtij përdoruesi. +Hap skenën e mjeteve të zhvilluesit +Na ndjeni, kjo dhomë s’u gjet. +\nJu lutemi, riprovoni më vonë.%s +Përdor parazgjedhje sistemi +Zgjidheni dorazi +Caktoje vetvetiu +Zgjidhni madhësi shkronjash +⚠ Në këtë dhomë ka pajisje të paverifikuara, ato s’do të jenë në gjendje të shfshehtëzojnë mesazhet që dërgoni. +Mos dërgo kurrë prej këtij sesioni mesazhe të fshehtëzuar te sesione të paverifikuar në këtë dhomë. +Figurat e animuara vetëluaji +S’u arrit të regjistrohej token pikëmbarimi te shërbyesi Home: +\n%1$s +Pikëmbarim i regjistruar me sukses te shërbyesi Home. +Regjistrim Pikëmbarimi +Akordojini Leje +${app_name} lyp lejen për shfaqje njoftimesh. +\nJu lutemi, akordoni lejen. ++ +- %1$s dhe %2$d tjetër
+- %1$s dhe %2$d të tjerë
+%1$s dhe %2$s +${app_name} lyp leje të shfaqë njoftime. Njoftimet mund të shfaqin mesazhet tuaja, ftesa tuajat, etj. +\n +\nJu lutemi, lejoni përdorimin e tyre te flluska pasuese, që të jeni në gjendje të shihni njoftime. +Email jo i verifikuar, kontrolloni te Të marrët tuaj +Reshtni tregimin e ekranit tuaj +Tregojuani ekranin të tjerëve +Ky është vendi ku do të gjenden kërkesat dhe ftesat tuaja të reja. +S’ka gjë të re. +Ftesa +Hapësirat janë një mënyrë e re për të grupuar dhoma dhe njerëz. Që t’ia filloni, krijoni një hapësirë. +Ende pa hapësira. +Provoni përpunuesin e teksteve të pasur (për tekst të thjeshtë vjen së shpejti) +Aktivizo përpunues teksti të pasur +Krijo MD vetëm për mesazhin e parë +Një Element i thjeshtuar, me skeda opsionale +Aktivizo skemë të re +A - Z +Veprimtari +Renditi sipas +Shfaq të freskëta +Shfaq filtra +Parapëlqime skeme grafike +Shpërzgjidhi krejt +Përzgjidhi krejt +E mora +Më pas +Rifillo +sek +min +h +- Për disa përdorues u hoq shpërfillja +Kërkesë njëkohësimi fillestar +Eksploroni Dhoma +Ndërroni Hapësire +Krijo Dhomë +Filloni Fjalosje +Krejt Fjalosjet ++ +- %1$d i përzgjedhura
+- %1$d të përzgjedhura
+Shërbyesi Home nuk mbulon hyrje me kod QR. +U has një problem sigurie, kur ujdisej shkëmbim i siguruar mesazhesh. Mund të jetë komprometuar një nga sa vijon: shërbyesi juaj Home; lidhja(et) tuaja internet; pajisja(et) tuaja; +Lidhja s’u plotësua në kohën e duhur. +Kontrolloni pajisjen ku jeni i futur, duhet të shfaqet kodi më poshtë. Sigurohuni se kodi më poshtë përputhet me atë pajisje: +Skanoni kodin QR më poshtë me pajisjen tuaj prej nga është dalë nga llogaria. +Përdorni pajisjen tuaj ku jeni brenda llogarisë që të skanoni kodin QR më poshtë: +Përdorni kamerën në këtë pajisje që të skanoni kodin QR të shfaqur në pajisjen tuaj tjetër: +Mirato vetvetiu widget-e Thirrjesh Element Call dhe akordo përdorim kamere / mikfrofoni +MSC3061: Po jepen kyçe dhome për mesazhe të dikurshëm +Shfaq hollësitë më të reja të profileve (avatar dhe emër në ekran) për krejt mesazhet. +Kërko doemos që tastiera të mos përditësojë ndonjë të dhënë të personalizuar, bie fjala, historik shtypjeje në të dhe fjalor bazuar në ç’keni shtypur në biseda. Kini parasysh se disa tastiera mund të mos e respektojnë këtë rregullim. +Ky kod QR duket i formuar keq. Ju lutemi, provoni ta verifikoni me tjetër metodë. +🔒 Keni aktivizuar fshehtëzim për sesionie të verifikuar vetëm për krejt dhomat, që nga Rregullime Sigurie. +Luaj figura të animuara te rrjedha kohora sapo zënë të duken +krijoi një pyetësor. +dërgoi një ngjitës. +dërgoi një video. +dërgoi një figurë. +dërgoi një mesazh zanor. +dërgoi një kartelë audio. +dërgoi një kartelë. +Në përgjigje të +Hyni/Dilni nga mënyra “Sa krejt ekrani” +Sesionet e verifikuar janë kudo ku përdorni këtë llogari pas dhënies së frazëkalimit tuaj, apo ripohimit të identitetit tuaj me një sesion tjetër të verifikuar. +\n +\nKjo do të thotë se keni krejt kyçet e nevojshëm për të shkyçur mesazhet tuaj të fshehtëzuar dhe për të ripohuar se e besoni këtë sesion. +Fshihe adresën IP +Shfaq adresë IP ++ +- Dilni nga %1$d sesion
+- Dilni nga %1$d sesione
+Dilni +Formatim teksti +Edhe %1$s +Jeni duke incizuar tashmë një transmetim zanor. Ju lutemi, që të nisni një të ri, përfundoni transmetimin tuaj aktual zanor. +Dikush tjetër është tashmë duke incizuar një transmetim zanor. Prisni që të përfundojë transmetimi zanor i tij, pa të filloni një të ri. +S’keni lejet e domosdoshme për të nisur një transmetim zanor në këtë dhomë. Lidhuni me një përgjegjës dhome që të përmirësojë lejet tuaja. +S’mund të niset një transmetim i ri zanor +Shtyrje përpara 30 sekonda +Kthim prapa 30 sekonda +Si përgjigje për %s +Aktivizo MD të lënë për më vonë + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index 30b63c213c..65318096c7 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -570,8 +570,8 @@Upptäckbarhetsalternativ kommer att synas när du har lagt till ett telefonnummer. Om du kopplar bort från din identitetsserver så kommer du inte att vara upptäckbar av andra användare och du kommer inte kunna bjuda in folk med hjälp av deras e-postadresser eller telefonnummer. Upptäckbara telefonnummer -Vi skickade ett bekräftelse-e-brev till %s, kolla din e-post och klicka på bekräftelselänken -Vi skickade ett bekräftelse-e-brev till %s, vänligen kolla din e-post och klicka på bekräftelselänken +Vi skickade ett e-brev till %s, kolla din e-post och klicka på bekräftelselänken +Vi skickade ett e-brev till %s, vänligen kolla din e-post och klicka på bekräftelselänken Skriv in en identitetsserver-URL Kunde inte ansluta till identitetsserver Vänligen ange en identitetsserver-URL @@ -615,7 +615,7 @@Jag har verifierat min e-postadress Du har blivit utloggad ur alla sessioner och kommer inte längre motta pushnotiser. För att återaktivera pushnotiser, logga in igen på varje enhet. Sätt e-postadress -Sätt en e-postadress för att kunna återförva ditt konto. Senare kan du valfritt låta personer du känner upptäcka dig med din e-postadress. +Sätt en e-postadress för att kunna återförvärva ditt konto. Senare kan du valfritt låta personer du känner upptäcka dig med den här e-postadressen. E-post E-post (valfritt) Sätt ett telefonnummer som valfritt kan användas för att vara upptäckbar av folk som känner dig. @@ -1312,10 +1312,10 @@Ett fel inträffade vid hämtning av nyckelsäkerhetskopia Du tittar redan på det här rummet! Inga registrerade pushgateways -app_id: -push_key: -app_display_name: -session_name: +App-ID: +Pushnyckel: +Appens visningsnamn: +Sessionens visningsnamn: Url: Format: Registrera token @@ -2544,7 +2544,7 @@Skicka e-brev igen Fick du inget e-brev\? För att bekräfta din e-post, tryck på knappen i e-brevet vi just skickade till %s -Kolla din e-post för att verifiera. +Verifiera din e-post Skicka kod igen En kod skickades till %s Bekräfta ditt telefonnummer @@ -2581,4 +2581,275 @@Välj manuellt Ställ in automatiskt Välj teckenstorlek - +Öppna utvecklingsverktygsskärmen +Tyvärr hittades inte det här rummet. +\nVänligen pröva igen senare.%s +🔒 Du har aktiverat kryptering endast till verifierade sessioner för alla rum i säkerhetsinställningarna. +⚠ Det finns overifierade enheter i det här rummet, de kommer inte kunna avkryptera meddelanden du skickar. +Skicka aldrig krypterade meddelanden till overifierade sessioner i det här rummet. ++ +- %1$s och %2$d till
+- %1$s och %2$d till
+%1$s och %2$s +E-post inte verifierad, kolla din inkorg +Det här är var dina nya förfrågningar och inbjudningar kommer att vara. +Inget nytt. +Inbjudningar +Utrymmen är ett nytt sätt att gruppera rum och personer. Skapa ett utrymme för att komma igång. +Inga utrymmen än. +Skapa DM först när du skickar första meddelandet +Aktivera avvaktade DM:er +En förenklad Element med valfria flikar +Aktivera ny layout +A - Ö +Aktivitet +Sortera efter +Visa nyliga +Visa filter +Gränssnittsinställningar +Förstått +Kollapsa underutrymmen för %s +Expandera underutrymmen för %s +Utforska rum +Byt utrymme +Skapa rum +Starta chatt +Alla chattar +Den här sessionen är redo för säkra meddelanden. +Din nuvarande session är redo för säkra meddelanden. +Overifierad session +Verifierad session +Okänd enhetstyp +Skrivbord +Webb +Mobil +För bäst säkerhet, verifiera dina sessioner och logga ut från alla sessioner du inte känner igen eller använder längre. +Andra sessioner +Godkänn automatiskt Element Call-widgets och ge kamera-/mikrofonåtkomst +Aktivera Element Call-behörighetsgenvägar +Starta en röstsändning +Realtidsplats +Kunde inte ladda kartan. +\nDen här hemservern kanske inte är konfigurerad för at visa kartor. +Öppna inställningar +Autenticiteten för det krypterade meddelanden kan inte garanteras på den här enheten. +Begär att tangentbordet inte ska uppdatera någon personanpassad data som skrivhistorik och ordlista baserat på vad du har skrivit i konversationer. Observera att vissa tangentbord inte respekterar den här inställningen. +Inkognitotangentbord +Det här QR-koden ser ogiltig ut. Vänligen pröva igen för att verifiera med en annan metod. +Du kommer inte komma åt krypterad meddelandehistorik. Återställ din säkra meddelandesäkerhetskopia och verifieringsnycklar för att börja om. +Kunde inte verifiera den här enheten +Sessioner +Lägger till (╯°□°)╯︵ ┻━┻ till början av ett textmeddelande +Vad är din servers adress\? +Där dina konversationer bor +Röstsändning +Öppna utrymmeslista +Skapa den ny konversation eller ett nytt rum +Uppdaterar din data… +Personer +Favoriter +Olästa +Alla +Pushnotiser +Applikations-, enhets- och aktivitetsinformation. +Sessionsdetaljer +Logga ut ur den här sessionen +Rensa filter +Inga inaktiva sessioner hittade. +Inga overifierade sessioner hittade. +Inga verifierade sessioner hittade. ++ +- Överväg att logga ut ur gamla sessioner (%1$d dag eller längre) du inte använder längre.
+- Överväg att logga ut ur gamla sessioner (%1$d dagar eller längre) du inte använder längre.
+Inaktiv +Verifiera dina sessioner för förbättrad säker meddelandehantering eller logga ut ur de du inte känner igen eller använder längre. +Overifierad +För bäst säkerhet, logga ut från sessioner du inte känner igen eller använder längre. +Verifierad +Filter ++ +- Överväg att logga ut ur gamla sessioner (%1$d dag eller längre) som du inte använder längre.
+- Överväg att logga ut ur gamla sessioner (%1$d dagar eller längre) som du inte använder längre.
++ +- Inaktiv %1$d dag eller längre
+- Inaktiv %1$d dagar eller längre
+Inaktiv +Inte redo för säkra meddelanden +Overifierad +Redo för säkra meddelanden +Verifierade +Alla sessioner +Filter +Senast aktiv %1$s +Enhet +Session +Nuvarande session +Inaktiva sessioner +Verifiera eller logga ut ur overifierade sessioner. +Overifierade sessioner +Förbättra din kontosäkerhet genom att följa dessa rekommendationer. +Säkerhetsrekommendationer ++ +- Inaktiv %1$d+ dag (%2$s)
+- Inaktiv %1$d+ dagar (%2$s)
+Overifierad · Din nuvarande session +Overifierad · Senast aktiv %1$s +Verifierad · Senast aktiv %1$s +Visa alla (%1$d) +Visa detaljer +Verifiera session +Verifiera din nuvarande session för att visa den här sessionens verifieringsstatus. +Verifiera eller logga ut från den här sessionen för bäst säkerhet och pålitlighet. +Verifiera din nuvarande session för förbättrad säker meddelandehantering. +Okänd verifieringsstatus +Aktiverad: +Sessions-ID: +Nåt gick fel. Kolla din nätverksanslutning och pröva igen. +Ge åtkomst +${app_name} behöver behörighet att visa aviseringar. +\nVänligen ge åtkomst. +${app_name} behöver behörighet att visa aviseringar. Aviseringar kan visa dina meddelanden, dina inbjudningar, o.s.v. +\n +\nVänligen ge åtkomst på nästa pop-uper för att kunna se aviseringar. +Aktivera rik-text-redigerare +Testa den nya rik-text-redigeraren +Välkommen till en ny vy! +Det här är vart dina olästa meddelanden hamnar, när du har några. +Inget att rapportera. +Den säkra allt-i-ett-chattappen för teams, vänner och organisationer. Skapa en chatt eller gå med i ett existerande rum för att komma igång. +Välkommen till ${app_name}, +\n%s. +Utrymmen är ett nytt sätt att gruppera rum och personer. Lägg till ett existerande rum, eller skapa ett nytt, med knappen nere till höger. +%s +\nser lite tom ut. +Möjliggör att spela in och skicka röstsändning i rummets tidslinje. +Aktivera röstsändning (under aktiv utveckling) +Spara klientnamnet, versionen, och URL:en för att enklare känna igen sessioner i sessionehanteraren. +Aktivera klientinforapportering +Ha bättre insyn i och kontroll över alla dina sessioner. +Aktivera den nya sessionshanteraren +Andra användare i direktmeddelanden och rum du går med in kan se en full lista över dina sessioner. +\n +\nDet försäkrar dem om att de verkligen pratar med dig, men det betyder också att de kan se sessionsnamnet du anger här. +Döper om sessioner +Verifierade sessioner har loggat in med dina uppgifter och har sedan verifierats, antingen med din säkra lösenfras eller genom att kors-verifiera. +\n +\nDet betyder att det har krypteringsnycklar för dina tidigare meddelanden, bekräftar för andra användare du kommunicerar med att dessa sessioner verkligen är du. +Verifierade sessioner +Overifierade sessioner är sessioner som har loggat in med dina uppgifter men som inte har kors-verifierats. +\n +\nDu bör speciellt försäkra dig om att du känner igen dessa sessioner eftersom att de kan utgöra otillåten användning av ditt konto. +Overifierade sessioner +Inaktiva sessioner är sessioner du inte har använt på länge, men de tar fortfarande emot krypteringsnycklar. +\n +\nBorttagning av inaktiva sessioner förbättrar säkerhet och prestanda, och gör det lättare för dig att se om en ny session ser misstänkt ut. +Inaktiva sessioner +Du kan använda den här enheten för att logga in på en mobil- eller webbenhet med en QR-kod. Det finns två sätt att göra detta: +Logga in med QR-kod +Observera att sessionsnamnen också kan ses av folk du kommunicerar med. +Anpassade namn kan hjälpa dig att känna igen dina enheter lättare. +Sessionsnamn +Döp om session +IP-adress +Operativsystem +Modell +Webbläsare +URL +Version +Namn +Applikation +Senaste aktiviteten +Sessionsnamn +Ta emot pushnotiser i den här sessionen. +Skanna QR-kod +Använd understrykning +Använd överstrykning +Använd kursiv stil +Använd fetstil +Se till att du känner till ursprunget till denna kod. Genom att länka enheter ger du någon full åtkomst till ditt konto. +Bekräfta +Pröva igen +Ingen match\? +Loggar in dig +Ansluter till enhet +Skanna QR-kod +Loggar du in en mobil\? +Visa QR-kod på den här enheten +Välj \'Skanna QR-kod\' +Börja på inloggningsskärmen +Välj \'Logga in med QR-kod\' +Börja på inloggningsskärmen +Välj \'Visa QR-kod\' +Gå till Inställningar -> Säkerhet och sekretess +Öppna appen på din andra enhet +Hemservern stöder inte inloggning med QR-kod. +Inloggningen avbröts på den andra enheten. +Den QR-koden är ogiltig. +Den andra enheten måste vara inloggad. +Den andra enheten är redan inloggad. +Ett säkerhetsproblem påträffades vid konfigurering av säker meddelandehantering. En av följande kan vara äventyrad: Din hemserver; Din internetuppkoppling Din enhet; +Begäran misslyckades. +Begäran nekades på den andra enheten. +Länkningen slutfördes inte inom den krävda tiden. +Länkning med den här enheten stöds inte. +Misslyckad anslutning +Kolla din inloggade enhet, koden nedan borde visas. Bekräfta att koden nedan matchar den enheten: +Säker anslutning etablerad +Skanna QR-koden nedan med din utloggade enhet. +Använd din inloggade enhet för att skanna QR-koden nedan: +Logga in med QR-kod +Använd den här enhetens kamera för att skanna QR-koden på din andra enhet: +Buffrar +Pausa röstsändning +Spela eller återuppta röstsändning +Avsluta inspelning av röstsändning +Pausa inspelning av röstsändning +Återuppta inspelning av röstsändning +Live +Skanna QR-kod +3 +2 +1 +Pröva +Tryck uppe till höger för att se alternativet att ge återkoppling. +Ge återkoppling +Kom åt dina utrymmen (nere till höger) snabbare och enklare än någonsin förut. +Kom åt utrymmen +För att förenkla din ${app_name} så är flikar nu valfria. Hantera dem i menyn uppe till höger. +Välj sessioner +Kontakt +Kamera +Plats +Omröstningar +Röstsändning +Bilagor +Dekaler +Fotobibliotek +Avmarkera alla +Välj alla ++ +- %1$d vald
+- %1$d valda
+Växla fullskärmsläge +Verifierade sessioner är alla ställen där du använder det här kontot efter att ha angett din lösenfras eller bekräftat din identitet med en annan verifierad session. +\n +\nDetta betyder att du har alla nycklar som krävs för att låsa upp dina krypterade meddelanden att bekräfta för andra användare att du litar på den här sessionen. ++ +- Logga ut ur %1$d session
+- Logga ut ur %1$d sessioner
+Logga ut +Textformatering +Du spelar redan in en röstsändning. Avsluta din nuvarande röstsändning för att starta en ny. +Någon annan spelar redan in en röstsändning. Vänta på att deras röstsändning avslutas för att starta en ny. +Du är inte behörig att starta en ny röstsändning i det här rummet. Kontakta en rumsadministratör för att uppgradera dina behörigheter. +Kan inte starta en ny röstsändning +Spola framåt 30 sekunder +Spola tillbaka 30 sekunder + \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index c4f1658f6b..19889892ff 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -675,8 +675,8 @@У цій кімнаті немає файлів %1$s о %2$s Жодного номера телефону не додано до вашого облікового запису -У ваш обліковий запис не додано жодної електронної адреси -Для вашої приватності ${app_name} підтримує лише надсилання хешованих електронних адрес користувачів та номера телефону. +У ваш обліковий запис не додано жодної адреси електронної адреси +Для вашої приватності ${app_name} підтримує лише надсилання хешованих електронних адрес користувачів та номерів телефону. Ви погодилися надіслати електронні адреси та телефонні номери на цей сервер ідентифікації для виявлення інших користувачів із ваших контактів. Надіслати електронні адреси та номери телефонів Керування електронними адресами та номерами телефонів, пов’язаними з вашим обліковим записом Matrix @@ -1740,10 +1740,10 @@Відгук Формат: Url: -session_name: -app_display_name: -push_key: -app_id: +Показувана назва сеансу: +Показувана назва застосунку: +Ключ Push: +ID застосунку: Версія Matrix SDK Кімнату створено, але деякі запрошення не надіслано з такої причини: \n @@ -1814,8 +1814,8 @@ Схоже, що відповідь сервера надто тривала, це може бути спричинено або поганим з’єднанням, або помилкою сервера. Повторіть спробу через деякий час. Повторіть спробу, коли погодитесь з умовами свого домашнього сервера. Текстове повідомлення надіслано на %s. Введіть код підтвердження, який воно містить. -Ми надіслали вам електронний лист для підтвердження на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження -Ми надіслали вам електронний лист для підтвердження на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження +Ми надіслали вам електронний лист на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження +Ми надіслали вам електронний лист на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження Опитування Файл Голосове @@ -1877,7 +1877,7 @@Далі Далі Скинути пароль на %1$s -Ця е-пошта не пов\'язана з жодним обліковим записом. +Ця адреса е-пошти не пов\'язана з жодним обліковим записом. Перепрошуємо, сервер не приймає нових облікових записів. Сталася помилка під час завантаження сторінки: %1$s (%2$d) Введіть адресу сервера, який ви хочете використовувати @@ -1895,7 +1895,7 @@Під\'єднатися до Element Matrix Services Під\'єднатися до %1$s Перевірти вхідні -Ця е-пошта не пов\'язана з жодним обліковим записом +Ця адреса е-пошти не пов\'язана з жодним обліковим записом Продовжити Продовжити єдиний вхід @@ -1948,7 +1948,7 @@Завершити налаштування виявності. Виявні номери телефону Опції виявності з\'являться після додавання номера телефону. -Опції виявності з\'являться після додавання е-пошти. +Опції виявності з\'являться після додавання адреси е-пошти. Відкрити налаштування виявності Пошук за іменем, ID або е-поштою Створити новий простір @@ -1956,7 +1956,7 @@Доступ до простору Хто може мати доступ\? Увімкнути сповіщення е-поштою для %s -Щоб отримувати сповіщення е-поштою, пов’яжіть її зі своїм обліковим записом Matrix +Щоб отримувати сповіщення е-поштою, пов’яжіть її адресу зі своїм обліковим записом Matrix Сповіщення е-поштою Оновити простір Змінити назву простору @@ -2197,7 +2197,7 @@Над чим ви працюєте\? Хто учасники вашої команди\? Назвіть його, щоб продовжити. -Приватний простір для організації ваших кімнат +Приватний простір для впорядкування ваших кімнат Не вдалося знайти таку кімнату. Переконайтеся, що вона існує. Показувати вміст у сповіщеннях @@ -2233,7 +2233,7 @@ -- Надіслано забагато запитів. Спробуйте знову за %1$d секунд…
- Надіслано забагато запитів. Спробуйте знову за %1$d секунд…
Вкажіть е-пошту для відновлення облікового запису. Згодом ви зможете дозволити знайомим знаходити вас за е-поштою. +Вкажіть адресу е-пошти для відновлення облікового запису. Згодом ви зможете дозволити знайомим знаходити вас за цією адресою. Ви вийшли з усіх сеансів і більше не отримуватимете сповіщень. Щоб отримувати сповіщення знову, ввійдіть на кожному пристрої заново. Зміна пароля скине всі ключі наскрізного шифрування всіх ваших сеансів, унеможливлюючи читання історії шифрованих чатів. Налаштуйте резервне копіювання ключів чи експортуйте ключі кімнат з іншого сеансу, перш ніж скинути пароль. Застосунку не вдалося створити обліковий запис на цьому домашньому сервері. @@ -2242,7 +2242,7 @@ Застосунку не вдається зайти до вашого домашнього сервера. Домашній сервер підтримує такі типи входу: %1$s. \n \nБажаєте зайти через вебклієнт\? -Щоб знайти наявні контакти, надішліть дані контактів (е-пошти й номери телефонів) серверу ідентифікації. Ми хешуємо ваші дані перед надсиланням для приватності. +Щоб знайти наявні контакти, надішліть дані контактів (адреси е-пошти й номери телефонів) серверу ідентифікації. Ми хешуємо ваші дані перед надсиланням для приватності. Ваші контакти приватні. Щоб дізнаватись про користувачів, відповідних вашим контактам, дозвольте нам надсилати дані ваших контактів серверу ідентифікації. Надіслати електронні адреси та номери телефонів %s Сеанс завершено! @@ -2340,7 +2340,7 @@ \nВвімкнути захищене резервне копіювання й керувати своїми ключами можна в налаштуваннях.Скасування залишить %1$s (%2$s) без звірки. У їхньому користувацькому профілі можна почати заново. Звірте цим сеансом свій новий. Це надасть йому доступ до зашифрованих повідомлень. -Надіслані цьому сеансу й цим сеансом повідомлення позначатимуться застереженнями, поки цей користувач йому не довірить. Або ви можете власноруч звірити сеанс. +Поки цей користувач не довіряє цьому сеансу, повідомлення, що надсилаються до нього і від нього, позначаються попередженнями. Крім того, ви можете звірити його вручну. Якщо ви увімкнете шифрування для кімнати, його неможливо буде вимкнути. Надіслані у зашифровану кімнату повідомлення будуть прочитними тільки для учасників кімнати, натомість для сервера вони будуть непрочитними. Увімкнення шифрування може унеможливити роботу ботів та мостів. Не вдалося поширити звірку цього сеансу з вашими іншими. \nЗвірка збережеться локально, її поширить майбутня версія застосунку. @@ -2700,7 +2700,6 @@ \nМожливо, цей домашній сервер не налаштовано для показу карт.Відкрити налаштування Усі бесіди -Показати всі сеанси (V2, WIP) Звірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте для кращої безпеки. Інші сеанси Сеанси @@ -2756,7 +2755,7 @@Неактивні сеанси Звірити або вийти з не звірених сеансів. -Не звірений сеанс +Не звірені сеанси Удоскональте безпеку свого облікового запису, дотримуючись цих порад. Поради щодо безпеки @@ -2818,4 +2817,165 @@ Увімкнути відкладені приватні повідомлення Спрощений Element з опціональними вкладками Увімкнути новий вигляд +Інші користувачі в особистих повідомленнях і кімнатах, до яких ви приєдналися, можуть переглядати повний список ваших сеансів. +\n +\nЦе дає їм впевненість у тому, що вони дійсно розмовляють з вами, а також означає, що вони можуть бачити назву сеансу, яку ви ввели тут. +Перейменування сеансів +Звірені сеанси — ті, до яких ви ввійшли за допомогою своїх облікових даних, а потім пройшли перевірку, використовуючи вашу захищену парольну фразу або шляхом перехресної перевірки. +\n +\nЦе означає, що вони мають ключі шифрування для ваших попередніх повідомлень і підтверджують іншим користувачам, з якими ви спілкуєтеся, що ці сеанси — це дійсно ви. +Звірені сеанси +Не звірені сеанси — це сеанси, до яких ви ввійшли в за допомогою своїх облікових даних, але не пройшли перехресну перевірку. +\n +\nВи повинні бути особливо впевнені, що розпізнаєте ці сеанси, оскільки вони можуть означати несанкціоноване використання вашого облікового запису. +Не звірені сеанси +Неактивні сеанси — це сеанси, які ви не використовували протягом певного часу, але вони продовжують отримувати ключі шифрування. +\n +\nВилучення неактивних сеансів поліпшує безпеку і швидкодію, а також полегшує визначення підозрілих нових сеансів. +Неактивні сеанси +Зауважте, що назви сеансів також видно людям, з якими ви спілкуєтесь. +Власні назви сеансів допоможуть вам легше розпізнавати ваші пристрої. +Назва сеансу +Перейменувати сеанс +Вийти з цього сеансу +Не звірений - Ваш поточний сеанс +Розпочати голосову трансляцію +Справжність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої. +Заборонити клавіатурі оновлювати будь-які персоналізовані дані, як-от історію набору тексту та словник, на основі того, що ви набрали в розмовах. Зверніть увагу, що деякі клавіатури можуть не дотримуватися цього налаштування. +Клавіатура інкогніто +Надсилає (╯°□°)╯︵ ┻━┻ на початку текстового повідомлення +Голосові трансляції +Відкрийте інструменти розробника +🔒 Ви увімкнули шифрування лише для перевірених сеансів для всіх кімнат у налаштуваннях безпеки. +⚠ У цій кімнаті є неперевірені пристрої, вони не зможуть розшифрувати повідомлення, які ви надсилаєте. +Ніколи не надсилати зашифровані повідомлення на неперевірені сеанси в цій кімнаті. +Зрозуміло +Застосувати форматування підкресленим +Застосувати форматування перекресленим +Застосувати форматування курсивом +Застосувати форматування жирним +Записуйте назву клієнта, версію та URL-адресу, щоб легше розпізнавати сеанси в менеджері сеансів. +Увімкнути запис відомостей про клієнт +Отримайте кращу видимість і контроль над усіма вашими сеансами. +Увімкнути новий менеджер сеансів +Операційна система +Модель +Браузер +URL +Версія +Назва +Застосунок +Отримувати push-сповіщення про цей сеанс. +Push-сповіщення +Звірте свій поточний сеанс, щоб побачити стан перевірки цього сеансу. +Невідомий стан перевірки +Увімкнено: +ID сеансу: +Щось пішло не так. Будь ласка, перевірте мережеве з\'єднання та спробуйте ще раз. +Надати дозвіл +${app_name} потребує дозволу на показ сповіщень. +\nНадайте дозвіл. +Для показу сповіщень ${app_name} потрібен дозвіл. Сповіщення можуть показувати ваші повідомлення, запрошення тощо. +\n +\nДозвольте доступ до наступних спливних вікон, щоб мати змогу переглядати сповіщення. +Спробуйте розширений текстовий редактор (незабаром з\'явиться режим звичайного тексту) +Увімкнути розширений текстовий редактор +Переконайтеся, що ви знаєте походження цього коду. Пов\'язавши пристрої, ви надасте будь-кому повний доступ до свого облікового запису. +Підтвердити +Повторити спробу +Не збігається\? +Вхід +Під\'єднання до пристрою +Входите на мобільному пристрої\? +Показати QR-код на цьому пристрої +Виберіть «Сканувати QR-код» +Виберіть «Увійти за допомогою QR-коду» +Почніть з екрана входу +Почніть з екрана входу +Виберіть «Показати QR-код» +Перейдіть до Налаштування -> Безпека й приватність +Відкрийте застосунок на іншому своєму пристрої +Запит на іншому пристрої було відхилено. +Пов\'язування не було завершено у встановлені терміни. +Пов\'язування з цим пристроєм не підтримується. +Невдале з\'єднання +Перевірте свій пристрій, на якому ви ввійшли. На екрані повинен з\'явитися код, наведений нижче. Переконайтеся, що наведений код збігається з кодом на вашому пристрої: +Безпечне з\'єднання встановлено +Зіскануйте QR-код нижче своїм пристроєм, з якого ви вийшли. +Скануйте QR-код нижче за допомогою свого пристрою для входу: +Увійти за допомогою QR-коду +Використовуйте камеру цього пристрою, щоб зісканувати QR-код, показаний на іншому пристрої: +3 +2 +1 +За допомогою цього пристрою ви можете ввійти на мобільному або вебпристрої за допомогою QR-коду. Зробити це можна двома способами: +Увійти за допомогою QR-коду +Сканувати QR-код +Сканувати QR-код +Сканувати QR-код +Домашній сервер не підтримує вхід за допомогою QR-коду. +Вхід на іншому пристрої було скасовано. +Цей QR-код недійсний. +Повинен бути виконаний вхід з іншого пристрою. +Вхід з іншого пристрою вже виконано. +Під час налаштування захищеного обміну повідомленнями виникла проблема з безпекою. Можливо, порушено одне з таких налаштувань: Ваш домашній сервер; Ваше інтернет-з\'єднання; Ваш пристрій; +Запит не виконаний. +Можливість записувати та надсилати голосові трансляції до стрічки кімнати. +Увімкнути голосові трансляції (в активній розробці) +Буферизація +Призупинити голосову трансляцію +Відтворити або поновити відтворення голосової трансляції +Припинити запис голосової трансляції +Призупинити запис голосової трансляції +Відновити запис голосової трансляції +Наживо +Вибрати сеанси +Контакт +Камера +Місце перебування +Опитування +Голосові трансляції +Вкладення +Наліпки +Фотобібліотека +Скасувати вибір усіх ++ +- Вибрано %1$d
+- Вибрано %1$d
+- Вибрано %1$d
+- Вибрано %1$d
+Вибрати все +Перемкнути повноекранний режим +Форматування тексту +Ви вже записуєте голосову трансляцію. Завершіть поточну трансляцію, щоб розпочати нову. +Хтось інший вже записує голосову трансляцію. Зачекайте, поки вона завершиться, щоб розпочати нову. +Ви не маєте необхідних дозволів для початку голосової трансляції в цю кімнату. Зверніться до адміністратора кімнати, щоб оновити ваші дозволи. +Не вдалося розпочати нову голосову трансляцію +Перемотати вперед на 30 секунд +Перемотати назад на 30 секунд +Звірені сеанси — це будь-який пристрій, на якому ви використовуєте цей обліковий запис після введення парольної фрази або підтвердження вашої особи за допомогою іншого звіреного сеансу. +\n +\nЦе означає, що ви маєте всі ключі, необхідні для розблокування ваших зашифрованих повідомлень і підтвердження іншим користувачам, що ви довіряєте цьому сеансу. ++ +- Вийти з %1$d сеансу
+- Вийти з %1$d сеансів
+- Вийти з %1$d сеансів
+- Вийти з %1$d сеансів
+Вийти +Залишилося %1$s +надсилає аудіофайл. +відправив файл. +У відповідь на +Сховати IP-адресу +створив голосування. +відправив наліпку. +відправив відео. +відправив зображення. +відправив голосове повідомлення. +Показати IP-адресу +Цитуючи +У відповідь на %s +Редагування \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index eba96e82c3..5ab8a351d1 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -11,10 +11,10 @@%1$s 封禁了 %2$s %1$s 更换了他们的头像 %1$s 将他们的显示名称设置为 %2$s -%1$s 把他们的显示名称从 %2$s 改为 %3$s -%1$s 移除了他们的显示名称 (%2$s) -%1$s 把主题改为: %2$s -%1$s 把房间名称改为: %2$s +%1$s 将其显示名称从 %2$s 更改为 %3$s +%1$s 移除了他们的显示名称(%2$s) +%1$s 将话题更改为:%2$s +%1$s 将房间名称更改为:%2$s %s 发起了一次视频通话。 %s 发起了一次语音通话。 %s 已接听通话。 @@ -25,12 +25,12 @@任何人。 (头像也被更改) %1$s 移除了房间名称 -%1$s 移除了房间主题 +%1$s 移除了房间话题 ** 无法解密:%s ** 发送者的设备没有向我们发送此消息的密钥。 无法发送消息 Matrix 错误 -电子邮箱地址 +电子邮件地址 手机号码 %1$s 撤回了对 %2$s 的邀请 %1$s 让未来的房间历史记录对 %2$s 可见 @@ -79,11 +79,11 @@%1$s 为此房间移除了主要地址。 %1$s 已允许访客加入房间。 %1$s 已禁止访客加入房间。 -%1$s已开启端到端加密。 -%1$s已开启端到端加密(无法识别的算法%2$s)。 -%1$s 创建了这个房间 +%1$s 已开启端到端加密。 +%1$s 已开启端到端加密(无法识别的算法 %2$s)。 +%1$s 创建了房间 你的邀请 -你创建了这个房间 +你创建了房间 你邀请了 %1$s 你加入了房间 你离开了房间 @@ -94,40 +94,40 @@你撤回了对 %1$s 的邀请 你更换了你的头像 你将你的显示名称设置为 %1$s -你将你的显示名称从 %1$s 改为 %2$s -你移除了你的显示名称 (%1$s) -你把主题改为:%1$s +您将显示名称从 %1$s 更改为 %2$s +您移除了您的显示名称(%1$s) +您将话题更改为:%1$s %1$s 更改了房间头像 你更改了房间头像 -你把房间名称改为:%1$s +您将房间名称更改为:%1$s 你发起了一次视频通话。 你发起了一次语音通话。 %s 发送了数据以建立通话。 你发送了数据以建立通话。 -你接听了通话。 -你结束了通话。 -你已让未来的房间历史对%1$s可见 +你已接听通话。 +你已结束通话。 +你已让未来的房间历史对 %1$s 可见 你升级了此房间。 你移除了房间名称 -你移除了房间主题 +你移除了房间话题 %1$s 移除了房间头像 你移除了房间头像 你向 %1$s 发送了加入房间的邀请 你已撤回了对 %1$s 加入房间的邀请 你接受了 %1$s 的邀请 -%1$s 添加了 %2$s 挂件 -你添加了 %1$s 挂件 -%1$s 移除了 %2$s 挂件 -你移除了 %1$s 挂件 -%1$s 修改了 %2$s 挂件 -你修改了 %1$s 挂件 +%1$s 添加了 %2$s 小部件 +你添加了 %1$s 小部件 +%1$s 移除了 %2$s 小部件 +你移除了 %1$s 小部件 +%1$s 修改了 %2$s 小部件 +你修改了 %1$s 小部件 管理员 协管员 默认 自定义(%1$d) 自定义 -你更改了%1$s的权力级别。 -%1$s更改了%2$s的权力级别。 +您更改了 %1$s 的权限等级。 +%1$s 更改了 %2$s 的权限等级。 %1$s 从 %2$s 到 %3$s 你的邀请。理由:%1$s 你邀请了 %1$s。理由:%2$s @@ -151,7 +151,7 @@你已允许访客加入房间。 你已禁止访客加入房间。 你已开启端到端加密。 -你已开启端到端加密(无法识别的算法%1$s)。 +你已开启端到端加密(无法识别的算法 %1$s)。 你已离开。理由:%1$s %1$s 已离开。理由:%2$s 你已加入。理由:%1$s @@ -170,15 +170,15 @@%1$s 已加入 你创建了讨论 %1$s 创建了讨论 -你已阻止客人加入房间。 -%1$s已阻止客人加入房间。 -你已允许客人加入这里。 -%1$s 已允许客人加入这里。 +你已阻止访客加入房间。 +%1$s 已阻止访客加入房间。 +你已允许访客加入这里。 +%1$s 已允许访客加入这里。 接受 拒绝 挂断 引用 -分享 +共享 语音通话 视频通话 全部标记为已读 @@ -214,8 +214,8 @@登录 提交 错误的用户名和/或密码 -此电子邮箱地址似乎无效 -此电子邮箱地址已被使用。 +此电子邮件地址似乎无效 +此电子邮件地址已被使用。 忘记密码? 请输入有效的 URL 没有包含有效的 JSON @@ -228,7 +228,7 @@搜索 过滤房间成员 没有结果 -添加电子邮箱地址 +添加电子邮件地址 添加手机号码 版本 olm 版本 @@ -257,7 +257,7 @@开始视频通话 拍摄照片或视频 此主服务器想确认你不是机器人 -电子邮箱地址验证失败:请确保你已点击邮件中的链接 +电子邮件地址验证失败:请确保你已点击邮件中的链接 原始 通话正在连接…… ${app_name} 需要权限以访问你的麦克风来进行语音通话。 @@ -298,7 +298,7 @@当前密码 显示所有来自 %s 的消息? 选择国家 -主题 +话题 房间历史可见性 谁可以阅读历史消息? 任何人 @@ -340,7 +340,7 @@ \n \n请在接下来的弹出窗口中授权允许访问,以便进行通话。移除 -你将不能撤销这个修改,因为你正在让这个用户和你拥有相同的权力级别。 + -您将无法撤消此更改,因为您正在将用户提升为与您相同的权限级别。 \n你确定吗? 这可能意味着有人正在恶意劫持你的流量,或者你的手机不信任远程服务器提供的数字证书。 如果服务器管理员说这是预期的情况,请确保下面的指纹与管理员提供的指纹相匹配。 @@ -348,11 +348,11 @@显示系统设置中的应用程序信息。 通话请求 使用条款 -其他 +其它 通知目标 登录为 -请检查你的电子邮箱并点击里面包含的链接。完成时请点击继续。 -此电子邮箱地址已被使用。 +请检查你的电子邮件并点击里面包含的链接。完成时请点击继续。 +此电子邮件地址已被使用。 此手机号码已被使用。 设置为主要地址 取消设置为主要地址 @@ -416,11 +416,11 @@深色主题 黑色主题 通知声音 -使用12小时制显示时间戳 -确定要从此房间删除此挂件吗? -无法创建挂件。 +使用 12 小时制显示时间戳 +确定要从此房间中删除小部件吗? +无法创建小部件。 发送请求失败。 -权力级别必须是正整数。 +权限等级必须是正整数。 你不在这个房间。 你没权限在当前房间执行此操作。 请求中缺失 room_id。 @@ -434,7 +434,7 @@你添加了一个新会话“%s”,它正在请求加密密钥。 你的未验证会话“%s”正在请求加密密钥。 开始验证 -bug报告 +错误报告 拍摄照片 拍摄视频 使用原生相机 @@ -452,7 +452,7 @@封禁用户会把他们移出此房间并阻止他们再次加入。 全部消息 添加到主屏幕 -行内URL预览 +内联网址预览 提及用户时震动 创建 @@ -463,7 +463,7 @@ - %d个成员状态变动
- - %d个成员
+- %d 个成员
- %d条未读的已通知消息
@@ -473,7 +473,7 @@- %d 个房间
- - %d个启用的挂件
+- %d 个活动的小部件
主页 房间 @@ -506,25 +506,25 @@下载 发送语音消息 对不起,没有可完成此操作的外部应用。 -从你的其他会话上重新请求加密密钥。 -请在其他可解密此消息的设备上启动 ${app_name},以便其将密钥发送至当前会话。 +从你的其它会话上重新请求加密密钥。 +请在其它可解密此消息的设备上启动 ${app_name},以便其将密钥发送至当前会话。 请输入你的密码。 如果可能的话,请使用英文撰写问题描述。 发送前预览媒体文件 显示动作 按照 ID 封禁用户 按照 ID 解禁用户 -定义用户的权力级别 +定义用户的权限等级 按照 ID 取消用户管理员权限 按照 ID 邀请用户进入当前房间 用给定地址加入房间 离开房间 -设置房间主题 +设置房间话题 从此房间移除指定ID的用户 更改你显示的显示名称 打开/关闭 markdown 修复 Matrix Apps 管理 -这个房间已经被替换并且不再活跃。 +此房间已被替换,不再处于活动状态。 对话在此继续 这个房间是另一个对话的延续 点击此处查看更早的消息 @@ -535,8 +535,8 @@联系你的服务管理员 本服务器其中一项资源已超出限制,部分用户将无法登录。 本服务器其中一项资源已超出限制。 -本服务器已达到每月活跃用户限制,部分用户将无法登录。 -本服务器已达到每月活跃用户限制。 +" 此主服务器已达到其每月活跃用户限制,因此<b>某些用户将无法登录</b>。" +此主服务器已达到其每月活跃用户限制。 请 %s 以继续使用本服务。 请 %s 以增加此限制的额度。 接受 @@ -555,7 +555,7 @@一个或多个测试没有通过,请尝试建议的修复方法。 一个或多个测试没有通过,请提交错误反馈以协助我们调查此问题。 系统设置。 -通知已在系统设置中启用。 +已在系统设置中启用通知。 通知已在系统设置中禁用。 \n请检查系统设置。 打开设置 @@ -593,8 +593,8 @@让房间中的其他用户知道你正在输入。 Markdown 格式化 在消息发出之前使用 Markdown 语法格式化消息。这允许你使用高级的文字格式,例如使用星号显示斜体文字。 -显示已阅回执 -点击已阅回执以显示所有已经阅读过某条消息的用户。 +显示已读回执 +单击已读回执以获取详细列表。 显示加入与离开事件 邀请、移除与封禁不受影响。 显示账户变动事件 @@ -606,7 +606,7 @@展开 抱歉,发生了一个错误 Markdown 已禁用。 -Markdown 已启用。 +已启用 Markdown。 视频通话中…… 服务将在设备重启后启动。 服务不会在设备重启后启动,在你打开 ${app_name} 一次之前你将不会收到消息通知。 @@ -628,7 +628,7 @@使用密钥备份 如果你此时登出账户,你将会失去你的已加密消息 密钥备份进行中。如果你此时登出账户将无法再访问你的已加密消息。 -你的所有会话都应当启用安全密钥备份以避免失去对你的已加密消息的访问权。 +安全密钥备份应该在您的所有会话中都处于活动状态,以避免失去对加密消息的访问权限。 我不想要我的已加密消息 正在备份密钥…… 确定吗? @@ -684,10 +684,10 @@完成 我已经制作了一份拷贝 保存恢复密钥 -分享 +共享 保存为文件 请制作一份拷贝 -分享恢复密钥… +与…共享恢复密钥 正在使用口令词组来生成恢复密钥,此过程可能会花费几秒钟。 恢复密钥 意外错误 @@ -777,8 +777,8 @@ \n会话名称:%1$s \n最近上线于:%2$s \n若你未曾在另一个会话上登录,则忽略此请求。分享 -密钥分享请求 +共享 +密钥共享请求 忽略 替换 终止 @@ -786,7 +786,7 @@已验证! 了解了 验证请求 -%s 想验证你的装置 +%s 想验证您的会话 未知错误 编辑 回复 @@ -795,7 +795,7 @@由 %s 邀请 对话 房间 -反应 +回应 同意 添加反应 查看反应 @@ -815,10 +815,10 @@推送规则 尚未定义任何推送规则 没有已注册的推送通道 -app_id: -push_key: -app_display_name: -device_name: +应用ID: +推送密钥: +应用显示名称: +会话显示名称: URL: 格式: 音频与视频 @@ -828,7 +828,7 @@撤消 断开连接 拒绝 -这不是有效的Matrix服务器地址 +这不是有效的 Matrix 服务器地址 无法在此 URL 找到主服务器,请检查 播放 忽略 @@ -837,7 +837,7 @@通知 ${app_name} 呼叫失败 无法建立实时连接。 -\n请要求你的主服务器管理员配置 TURN 服务器以使通话可靠工作。 +\n请让您的主服务器的管理员配置一个 TURN 服务器,以便呼叫能够可靠地工作。选择声音设备 电话 扬声器 @@ -881,8 +881,8 @@无后台同步 应用在后台时你不会收到消息通知。 集成 -使用集成管理器管理机器人、桥接、部件和贴纸包。 -\n集成管理器接收配置数据,可以代表你修改部件、发送房间邀请及设置权力级别。 +使用集成管理器来管理机器人、桥接、小部件和贴纸包。 +\n集成管理器接收配置数据,并可以代表您修改小部件、发送房间邀请和设置权限等级。 安全备份 设置安全备份 重置安全备份 @@ -903,24 +903,24 @@%1$s:%2$s %1$s:%2$s %3$s 查看 -活动挂件 -挂件 -载入挂件 -此挂件添加者: -使用它可能会设置cookie并与%s分享数据: -使用它可能会与%s分享数据: -载入挂件失败。 + 活动小部件 +小部件 +加载小部件 +此小部件由以下人员添加: +使用它可能会设置 cookie 并与 %s 共享数据: +使用它可能会与 %s 共享数据: +加载小部件失败。 \n%s -重载挂件 +重新加载小部件 在浏览器中打开 撤消我的访问权限 你的显示名称 你的头像 URL 你的用户 ID 你的主题 -挂件 ID +小部件 ID 房间 ID -挂件想使用以下资源: +这个小部件想要使用以下资源: 允许 阻止全部 使用相机 @@ -952,7 +952,7 @@获取信任信息时发生错误 获取密钥备份数据时发生错误 从文件“%1$s”导入端到端密钥。 -其他第三方通知 +其它第三方通知 你已经在查看此房间! 注册令牌 提出建议 @@ -970,7 +970,7 @@文件%1$s 已被下载! 消息编辑 未找到编辑 -过滤对话… +过滤对话…… 找不到你要找的? 创建新房间 发送新私聊消息 @@ -983,20 +983,20 @@查看编辑历史 服务条款 可被其他人发现 -使用机器人,挂件和贴纸包 +使用机器人、桥接、小部件和贴纸包 身份服务器 断开身份服务器 配置身份服务器 更改身份服务器 你正在使用 %1$s 与你知道的现有联系人相互发现。 你当前未使用身份服务器。若要与你知道的现有联系人相互发现,请在下方配置。 -可发现电子邮件地址 -发现选项将在你添加电子邮件后出现。 +可发现的电子邮件地址 +发现选项将在你添加电子邮件地址后出现。 发现选项将在你添加电话号码后出现。 -与你的身份服务器断开意味着你将无法被其它用户发现并且无法通过电子邮件和电话邀请他人。 +与您的身份服务器断开连接意味着您将不会被其他用户发现,并且您将无法通过电子邮件或电话邀请其他人。 可发现电话号码 -我们向 %s 给你发送了确认电子邮件,检查你的电子邮件并点击确认链接 -我们向 %s 给你发送了确认电子邮件,请先检查你的电子邮件并点击确认链接 +我们向%s发送了一封电子邮件,请检查你的电子邮件并点击确认链接 +我们向 %s 发送了一封电子邮件,请先检查您的电子邮件并点击确认链接 输入身份服务器 URL 无法连接到身份服务器 请输入身份服务器 url @@ -1004,10 +1004,10 @@你选择的身份服务器无任何服务条款。仅在你信任服务所有者时继续 已向 %s 发送文字消息。请输入它包含的验证码。 验证码不正确。 -你当前在身份服务器 %1$s 上分享电子邮件地址或电话号码。你需要重连接 %2$s 已停止分享。 +您当前在身份服务器 %1$s 上共享电子邮件地址或电话号码。您需要重新连接到 %2$s 才能停止共享它们。 同意身份服务器 (%s) 服务条款使你可以通过电子邮件地址或电话号码被发现。 启用详细日志。 -当你发送 RageShake 时详细日志将帮助开发者提供更多日志。即使启用,应用也不会记录消息内容或任何其他私有数据。 +详细日志将通过在您发送 RageShake 时提供更多日志来帮助开发人员。即使启用,应用程序也不会记录消息内容或任何其他私人数据。 接收你的主服务器条款和条件后请重试。 服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。请稍后再试。 发送附件 @@ -1105,16 +1105,16 @@应用无法在此服务器上创建账户。 \n \n你想要通过网页客户端注册吗? -电子邮件未关联到任何账户。 +此电子邮件地址未关联到任何账户。 在 %1$s 上重置密码 验证邮件将发送到你的收件箱以确认设置你的新密码。 下一个 电子邮件 新密码 -注意! +警告! 更改你的密码将重置所有会话上的端到端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出房间密钥。 继续 -电子邮件未链接到任何账户 +此电子邮件地址未链接到任何账户 检查你的收件箱 验证电子邮件已发送到 %1$s。 点击链接以确认你的新密码。跟随包含的链接验证后,请点击下方。 @@ -1123,12 +1123,12 @@你的密码已重置。 你已登出全部会话,不会再接收到推送通知。若要重新启用通知,请在每个设备上再次登录。 返回登录 -注意 +警告 你的密码尚未更改。 \n \n是否中止密码更改过程? 设置电子邮件地址 -设置电子邮件用于恢复你的账户。之后,你可以选择允许你认识的人通过电子邮件发现你。 +设置电子邮件地址以用于恢复你的账户。之后,你可以选择允许你认识的人通过此地址发现你。 电子邮件 电子邮件(可选) 下一个 @@ -1143,15 +1143,15 @@输入验证码 重新发送 下一个 -国际电话号码必须以 ‘+’ 开头 +国际电话号码必须以“+”开头 电话号码似乎无效。请检查 在 %1$s 上注册 用户名或电子邮件 用户名 密码 下一个 -用户名已占用 -注意 +该用户名已被使用 +警告 你的账户尚未创建。是否中止注册过程? 选择 matrix.org 选择 Element Matrix Services @@ -1171,14 +1171,14 @@如果你在主服务器上设置了账户,在下方使用你的 Matrix ID(例 @user:domain.com)和密码。 Matrix ID 如果你不知道你的密码,返回并重置。 -这不是一个有效的用户标识符。期望的格式:\'@user:homeserver.org\' +这不是有效的用户标识符。预期格式:\'@user:homeserver.org\' 无法找到有效的主服务器。请检查你的标识符 你已登出 这可能由于多种原因: \n -\n• 你已在其他会话中更改了你的密码。 +\n• 你已在其它会话中更改了你的密码。 \n -\n• 你已从其他会话删除了此会话。 +\n• 你已从其它会话删除了此会话。 \n \n• 你的服务器管理员出于安全原因已取消你的访问权限。 重新登录 @@ -1189,7 +1189,7 @@登录 密码 清除个人数据 -注意:你的个人数据(包括加密密钥)仍存储在此设备上。 + 警告:你的个人数据(包括加密密钥)仍存储在此设备上。 \n \n如果你不再使用此设备,或想登录另一个账户,请清除它。 清除全部数据 @@ -1199,7 +1199,7 @@除非你登录以恢复加密密钥,否则你将无法访问安全消息。 当前会话用于用户 %1$s 而你提供了用户 %2$s 的凭证。${app_name} 不支持此功能。 \n请先清除数据,然后重新登录另一个账户。 -你的 matrix.to 链接更是不正确 +您的 matrix.to 链接格式错误 描述太短 初始同步… 高级设置 @@ -1211,13 +1211,13 @@检测到摇动! 设置 当前会话 -其他会话 +其它会话 仅显示第一个结果,请输入更多字符… 快速失败 发生意外错误时,${app_name} 可能更经常崩溃 在明文消息前添加 ¯\\_(ツ)_/¯ 启用加密 -加密一经启用,便无法禁用。 +启用后,无法禁用加密。 你的电子邮件域无权注册此服务器 未信任的登录 匹配 @@ -1227,7 +1227,7 @@ \n \n - 你的主服务器 \n - 你验证的用户连接到的主服务器 -\n - 你或其它用户的网络连接 +\n - 你或其他用户的网络连接 \n - 你或其他用户的设备视频。 图片。 @@ -1235,7 +1235,7 @@文件 贴纸 正在等待…… -%s 已取消 +%s已取消 你已取消 %s 已接受 你已接受 @@ -1276,9 +1276,9 @@%1$s里的默认 %2$s里的自定义(%1$d) ${app_name} 无法处理类型为 \'%1$s\' 的事件 -${app_name} 在渲染 id 为 \'%1$s\' 的事件内容时遇到了一个问题 +${app_name} 在呈现 ID 为“%1$s”的事件内容时遇到问题 取消忽略 -该会话无法与你的其他会话共享此验证。 + 该会话无法与你的其它会话共享此验证。 \n验证将保存在本地,并在此应用的未来版本中共享。 给给定的消息和彩虹一样上色后发送 和彩虹一样给给定的表情上色后发送 @@ -1289,22 +1289,22 @@房间加密一经启用,便无法禁用。在加密房间中,发送的消息无法被服务器看到,只能被房间的参与者看到。启用加密可能会使许多机器人和桥接无法正常运作。 启用加密 为保证安全,请核对一次性代码以验证 %s。 -为保证安全,请当面验证,或者使用其他通讯方式验证。 +为保证安全,请当面验证,或者使用其它通讯方式验证。 比较独特表情,确保它们以相同顺序出现。 与其他用户设备上显示的代码比较。 与此用户的消息是端到端加密的,无法被第三方读取。 你的新会话已验证。它可以访问你的加密消息,其他用户会将其视为可信任。 交叉签名 -交叉签名已启用 + 已启用交叉签名 \n设备上的私钥。 -交叉签名已启用 + 已启用交叉签名 \n密钥可信任。 \n私钥未知 -交叉签名已启用。 + 已启用交叉签名。 \n密钥未信任 -交叉签名未启用 +未启用交叉签名 你的服务器管理员已默认禁用私有房间和私聊消息端到端加密。 -活跃的会话 +可用会话 显示全部会话 管理会话 登出此会话 @@ -1318,7 +1318,7 @@使用现有会话来验证此会话,并授予其访问加密消息的权限。 验证 已验证 -注意 +警告 无法获取会话 会话 可信任 @@ -1394,7 +1394,7 @@将给定信息作为剧透发送 剧透 输入关键字以查找反应。 -已阅 +已读 跳至已读回执 事件被房间管理员调整,理由:%1$s 密钥已是最新! @@ -1441,16 +1441,16 @@不知道你的密钥备份口令词组,你可以 %s。 密钥备份恢复密钥 阻止应用内屏幕截图 -启用此设置添加 FLAG_SECURE 到所有活动。重启应用使更改生效。 +启用此设置会将 FLAG_SECURE 添加到所有活动项。重新启动应用程序以使更改生效。 无法保存媒体文件 设置新账户密码…… -在你的其他设备上使用最新的${app_name} 网页版、${app_name} 桌面版、${app_name} iOS 版、${app_name} 安卓版,或其他能够交叉签名的 Matrix 客户端 +在您的其它设备上使用最新的 ${app_name}、${app_name} Web、${app_name} Desktop、${app_name} iOS、${app_name} for Android 或其他支持交叉签名的 Matrix 客户端 ${app_name} Web \n${app_name} Desktop ${app_name} iOS \n${app_name} Android -或其他能够交叉签名的 Matrix 客户端 -在你的其他设备上使用最新的 ${app_name}: +或其它支持交叉签名的 Matrix 客户端 +在你的其它设备上使用最新的 ${app_name}: 强制丢弃加密房间中的当前出站群组会话 仅在加密房间中支持 使用你的 %1$s 或使用你的 %2$s 继续。 @@ -1465,7 +1465,7 @@使用文本手动验证 验证登录 使用表情交互式验证 -通过从你的其他会话验证此登录确认你的身份,授权它访问你的加密消息。 +通过从你的其它会话验证此登录确认你的身份,授权它访问你的加密消息。 请选择用户名。 请选择密码。 仔细检查此链接 @@ -1484,7 +1484,7 @@我们无法邀请用户,请检查你想要邀请的用户并重试。 当前语言 -其他可用语言 +其它可用语言 正在载入可用语言… 打开 %s 条款 是否从身份服务器 %s 断开? @@ -1492,12 +1492,12 @@无法执行此操作。主服务器已过期。 请先配置身份服务器。 请先在设置中接受身份服务器的条款。 -为了你的隐私,${app_name} 仅支持发送用户电子邮件和电话号码的哈希值。 +为了你的隐私,${app_name}仅支持发送经过哈希处理的用户电子邮件的和电话号码。 关联失败。 当前与此标识符没有关联。 你的主服务器(%1$s)建议使用 %2$s 作为你的身份服务器 使用 %1$s -或者,你可以输入任何其他身份服务器 URL +或者,你可以输入任何其它身份服务器网址 输入身份服务器 URL 提交 设置角色 @@ -1511,7 +1511,7 @@通过在你的服务器上备份加密密钥,防止失去对加密消息和数据的访问。 设置 使用安全密钥 -生成安全密钥存储在安全的地方如密码管理器或保险箱。 +生成安全密钥以存储在密码管理器或保险箱等安全位置。 使用安全短语 输入仅有你知道的秘密短语,生成备份用的密钥。 保存你的安全密钥 @@ -1521,7 +1521,7 @@安全短语 再次输入你的安全短语以确认。 房间名称 -主题 +话题 你已成功更改房间设置 你无法访问此消息 正在等待此消息,可能会花费一些时间 @@ -1531,7 +1531,7 @@你无法访问此消息因为发送者有意不发送密钥 正在等待加密历史 Riot 现已成为 Element! -我们很高兴地宣布我们改名了!你的应用已经更新到最新版本,并且你已登录你的账户。 +我们很高兴地宣布我们已经更名了!您的应用程序是最新的,并且您已登录到您的帐户。 明白了 了解更多 将恢复密钥保存到 @@ -1555,20 +1555,20 @@启用 PIN 如果你想要重置你的 PIN,点按忘记 PIN 登出并重置。 防止意外通话 -发起通话之前要求确认 +在开始通话之前要求确认 你没有权限在此房间发起会议通话 发起视频会议 发起音频会议 -会议使用 Jitsi 安全与许可政策。你的会议进行期间当前房间内的所有人将看到加入邀请。 +会议使用 Jitsi 安全和权限策略。 当前在会议室中的所有人都会在会议进行期间看到加入邀请。 你无法呼叫你自己 你无法与自己通话,请等待参与者接受邀请 -添加挂件失败 -移除挂件失败 +添加小部件失败 +移除小部件失败 - 成功导入 %1$d/%2$d 个密钥。
管理集成 -无活动挂件 +没有活动的小部件 房间已创建,但由于以下原因一些邀请尚未发送: \n \n%s @@ -1583,14 +1583,14 @@此电话号码已定义。 你的账户尚未添加电话号码 电子邮件地址 -你的账户尚未添加电子邮件 +你的账户尚未添加电子邮件地址 电话号码 移除 %s? 请确认你已点击我们向你发送的电子邮件中的链接。 电子邮件和电话号码 -管理链接到你的 Matrix 账户的电子邮件和电话号码 +管理与您的 Matrix 帐户链接的电子邮件地址和电话号码 代码 -请使用国际格式(电话号码必须以“+”开始) +请使用国际格式(电话号码必须以“+”开头) 验证此登录来确认你的身份,授权其访问加密消息。 无法打开你被封禁的房间。 无法找到此房间。请确认它存在。 @@ -1605,9 +1605,9 @@ \n \n小心使用,它可能导致意外行为。链接格式不正确 -每次打开 ${app_name} 都要求 PIN 码。 -在 2 分钟未使用 ${app_name} 后要求 PIN 码。 -2 分钟后要求 PIN +每次打开 ${app_name} 时都需要 PIN 码。 +未使用 ${app_name} 2 分钟后需要 PIN 码。 +2 分钟后需要 PIN 码 仅在一个简单的通知中显示未读消息的数量。 显示详情,如房间名称和消息内容。 在通知中显示内容 @@ -1622,7 +1622,7 @@你将重新启动,没有历史记录,消息,受信任的设备或受信任的用户 如果你重置一切 -仅当没有其他设备可用来验证此设备时,才执行此操作。 +仅当没有其它设备可用来验证此设备时,才执行此操作。 全部重置 忘记或丢失了所有的恢复选项?重置一切 你已加入。 @@ -1634,7 +1634,7 @@ \n \n你的消息受加密保护,并且只有你和消息接收者拥有唯一解密密钥。此处的消息未经端到端加密。 -此主服务器正在运行较旧版本。要求你的主服务器管理员升级。你可以继续,但一些功能可能无法正确工作。 +此主服务器正在运行旧版本。 请让您的主服务器管理员升级。 您可以继续,但某些功能可能无法正常工作。 你将此房间设为仅邀请。 %1$s 仅发出此邀请。 在加密房间显示完整历史 @@ -1658,7 +1658,7 @@允许访问你的联系人。 如需扫描二维码,你须允许相机访问权限。 没有更多结果 -开始畅聊 +开始聊天 删除地址 \"%1$s\"? 取消发布地址 \"%1$s\"? 发布 @@ -1709,14 +1709,14 @@添加图像自 授予许可 撤销我的许可 -你已同意发送电子邮件和电话号码到身份服务器以从你的联系人发现其他用户。 +你已同意发送电子邮件地址和电话号码到身份服务器以从你的联系人发现其他用户。 发送电子邮件和电话号码 建议 已知用户 二维码 通过二维码添加 房间设置 -主题 +话题 房间话题(可选) 房间名称 此房间无法预览。你想加入吗? @@ -1731,10 +1731,10 @@为此房间设置地址以便用户通过你的主服务器(%1$s)找到此房间 本地地址 新的发布的地址(例如 #alias:server) -尚无其他已发布地址。 +尚无其它已发布地址。 还没有别的发布的地址,可在下方添加。 -在消息框添加打开emoji键盘的按钮 -显示emoji键盘 +在消息框添加打开 emoji 键盘的按钮 +显示 emoji 键盘 使用 /confetti 命令或发送包含 ❄️ 或 🎉 的消息 显示聊天效果 更改话题 @@ -1746,7 +1746,7 @@启用房间加密 更改房间主要地址 更改房间头像 -修改挂件 +修改小部件 通知每个人 移除其他人发送的消息 封禁用户 @@ -1774,7 +1774,7 @@• 已允许匹配 %s 的服务器。 已勾选 已选中 -活跃通话(%1$s) +可用通话(%1$s) 需要重新验证 删除失败的消息 你确定要取消发送消息吗? @@ -1804,7 +1804,7 @@- - %d 个条目
不是有效的 Matrix 二维码 +这不是有效的 Matrix 二维码 扫描二维码 添加人员 邀请朋友 @@ -1842,9 +1842,9 @@你为此房间设置了服务器访问控制列表。 在 Matrix 上查找联系人 用户尚未同意条款。 -分享此二维码,其他人扫描后即可添加你,并开始聊天。 +共享此二维码,其他人扫描后即可添加你,并开始聊天。 我的二维码 -分享我的二维码 +共享我的二维码 消息类型缺失 检查房间状态 查看已读回执 @@ -1862,8 +1862,8 @@此空间没有房间 请联系你的主服务器管理员以取得进一步资讯 看来你的主服务器尚未支持空间 -想要做点实验? -\n你可以将现有的空间添加到其他空间中。 +想要使用实验功能? +\n你可以将现有的空间添加到其它空间中。 管理房间和空间 标记为不建议 标记为建议 @@ -1894,7 +1894,7 @@刚到此房间 他们将可以探索 %s 邀请至 %s -分享链接 +共享链接 通过电子邮件进行邀请 此刻只有你。%s与他人一道会更好。 邀请至 %s @@ -1904,7 +1904,7 @@正在创建空间…… 随机 一般性 -让我们为每个主题创建一个房间。你也可以稍后再进行增加,包括现有房间。 +让我们为他们每个人创建一个房间。 您也可以稍后添加更多内容,包括已经存在的内容。 你在做些什么? 我们将会为此创建房间。你也可以在稍后增加更多。 你希望在 %s 中进行哪些讨论? @@ -1933,9 +1933,9 @@创建空间 公开房间 未检查 -开启挂件 +打开小部件 屏幕截图 -${app_name} 要求你输入凭据才能执行此操作。 +${app_name} 需要您输入凭据才能执行此操作。 呼叫转移时发生错误 先询问 查找电话号码时发生了错误 @@ -1944,9 +1944,9 @@有未保存的更改。要放弃更改吗? 房间尚未创建。取消创建房间? 未扫描二维码! -无效的二维码(无效的 URI)! +无效的二维码(无效的标识)! 无法向你自己发送私聊消息! -通过文字分享 +通过文字共享 更改你当前的 PIN 更改 PIN 🔐️ 在 ${app_name} 上加入我的行列 @@ -2049,7 +2049,7 @@房间版本 👓 通过比较表情符号来验证 使用此设备扫描 -使用您的其他设备扫描代码或切换并使用此设备扫描 +使用其它设备扫码或切换并使用本设备扫码 主服务器 API 网址 缺少权限 要执行此操作,请从系统设置中授予相机权限。 @@ -2078,7 +2078,7 @@录制语音消息 需要升级 语音 -您可能不知道的其他空间或房间 +您可能不知道的其它空间或房间 你知道的包含这个房间的空间 决定谁能找到并加入这个房间。 点按即可编辑空间 @@ -2099,22 +2099,22 @@我的用户名 我的显示名称 通知事项 -其他 +其它 提及和关键词 默认通知 -活跃视频通话 -活跃语音通话 +可用视频通话 +可用语音通话 在 ${app_name} 中直接接收邀请的设置 %s。 -将此邮箱与您的账户相链接 -加入这个空间的邀请被发送至 %s,此邮箱未与您的账户相关联 -加入这个房间的邀请被发送至 %s,此邮箱未与您的账户相关联 +将此电子邮件地址与您的帐户链接 +此空间的邀请已发送至与您的帐户无关的 %s +此房间的邀请已发送至与您的帐户无关的 %s 你所在的全部房间将显示在主页上。 在主页上显示所有房间 滑动结束通话 %1$s 轻按返回 -活跃通话 (%1$s) · +可用通话 (%1$s) · - - %1$d 个活跃通话·
+- %1$d 个可用通话·
无应答 未接视频通话 @@ -2170,8 +2170,8 @@任何人均可找到此空间并加入 空间访问 谁可以访问? -启用 %s 的电邮通知 -要接收通知邮件,请将一个电子邮箱关联到你的 Matrix 账户 +为 %s 启用电子邮件通知 +要接收带有通知的电子邮件,请将电子邮件地址链接到您的 Matrix 帐户 电子邮件通知 升级空间 更改空间名称 @@ -2213,16 +2213,16 @@添加选项 选项 %1$d 创建选项 -问题或主题 -投票问题或主题 +问题或话题 +投票问题或话题 创建投票 投票 -向 %s 发送电子邮件和电话号码 +向%s发送电子邮件地址和电话号码 您的联系人是私密的。 要从您的联系人中发现用户,我们需要您的许可才能将联系信息发送到您的身份服务器。 已登出此会话! 已离开此房间! 你同意发送此信息吗? -要发现现有的联系人,您需要将联系人信息(电子邮件和电话号码)发送到您的身份服务器。出乎隐私考量,我们会在发送前对您的数据进行散列处理。 +要发现现有的联系人,你需要将联系人信息(电子邮件地址和电话号码)发送到你的身份服务器。出乎隐私考量,我们会在发送前对您的数据进行散列处理。 不是现在 您确定要删除此投票吗?一旦移除,就无法恢复。 删除投票 @@ -2265,33 +2265,33 @@ \n你可以阅读我们所有的条款 %s。帮助改进 ${app_name} 启用 -不允许加入此房间 +你不能加入这个房间 - 修改服务器 %d 的 ACLs
消息列帮助你的对话不离题且易于跟踪。 -显示当前房间的所有子区 -所有子区 -筛选器 -子区 -在房间中筛选子区 -复制子区的链接 +显示当前房间的所有消息列 +所有消息列 +过滤器 +消息列 +过滤房间中的消息列 +复制消息列的链接 在房间中查看 -群组通知 -我们越来越接近将消息列发布为公开Beta版。 + 房间通知 +我们越来越接近发布消息列的公共 Beta 版。 \n -\n在我们为此准备时,我们需要做一些变动:在此之前创建的消息列将会被显示为普通回复。 +\n在我们为此做准备时,我们需要进行一些更改:在此之前创建的消息列将显示为常规回复。 \n -\n这会是一次性的过渡,因为消息列现在是Matrix规范的一部分了。 +\n这将是一次性的过渡,因为消息列现在是 Matrix 规范的一部分。- -- 还有%1$d个
+- %1$d 更多
请注意:这是一个使用临时实现的实验室功能。这意味着你将无法删除你的位置历史,并且在你停止与这个房间分享你的实时位置后,高级用户仍能看到你的位置历史。 +请注意:这是使用临时实现的实验室功能。这意味着您将无法删除您的位置历史记录,即使您停止与此房间共享您的实时位置,高级用户也将能够看到您的位置历史记录。 当前网关:%s 网关 提供反馈 -为你的团队传递消息。 -消息列beta +为您的团队发送消息。 +消息列 beta 消息列 为所有消息显示最新资料信息(头像和显示名称)。 显示名称 @@ -2299,74 +2299,74 @@覆盖显示名称颜色 检查你的电子邮件。 电子邮件 -你已掌控你的资料。 +一切由您掌控。 BETA -分享你的实时位置 +共享你的实时位置 缩放到当前位置 -地图上选定位置的图钉 +地图上选定位置的固定标记 无投票 验证你的电子邮件 - -- %d条消息已移除
+- %d 条消息已移除
启用位置分享 +启用位置共享 实时位置共享 找不到端点。 -目前端点:%s +当前端点:%s 端点 -目前正在使用%s。 +目前正在使用 %s。 方式 - -- 找到%d种方式。
+- 找到 %d 个方式。
除了后台同步,没有发现其他方法。 -找不到除了Google Play服务以外的方式。 +除了后台同步,没有发现其它方法。 +找不到除了 Google Play 服务以外的方式。 可用方式 通知方式 后台同步 -Google服务 +Google 服务 选择如何接收通知 屏幕共享进行中 -${app_name}屏幕共享 +${app_name} 屏幕共享 用户 通知整个房间 显示更少 -分享位置 +共享位置 显示消息气泡 -分享位置 -你需要有正确的权限,才能在这个房间里分享实时位置。 -你没有权限分享实时位置 -%1$s前已更新 +共享位置 +您需要拥有正确的权限才能在此房间中共享实时位置。 +你没有权限共享实时位置 +%1$s 前已更新 临时执行:地点在房间历史中持续存在 启用实时位置共享 位置共享正在进行中 -${app_name}实时位置 -剩余%1$s +${app_name} 实时位置 +剩余 %1$s 停止 -实时分享直到%1$s +实时共享直到 %1$s 查看实时位置 实时位置已结束 正在加载实时位置…… -实时位置已启用 +启用实时位置 加载地图失败 打开,用 -${app_name}无法访问你的位置。请稍后再试。 -${app_name}无法访问你的位置 +${app_name} 无法访问你的位置。请稍后再试。 +${app_name} 无法访问你的位置 在房间中查看 -MSC3061:为过去的消息分享房间密钥 -当在分享历史的加密的房间中邀请时,加密的历史将总是可见。 -8小时 -1小时 -15分钟 -分享此位置 -分享此位置 -分享实时位置 -分享实时位置 -分享我目前的位置 -分享我目前的位置 +MSC3061:为过去的消息共享房间密钥 +在共享历史的加密房间中邀请时,加密历史将可见。 +8 小时 +1 小时 +15 分钟 +共享此位置 +共享此位置 +共享实时位置 +共享实时位置 +共享我当前的位置 +共享我当前的位置 地图 位置 -分享位置 +共享位置 结果仅在你结束投票后展示 封闭式投票 投票者一投票就能看到结果 @@ -2409,7 +2409,7 @@发送图片和视频 打开相机 服务器政策 -Element Matrix Services(EMS)是一个健壮且可靠的主机托管服务,可实现快速、安全和实时的通信。在<a href=\"${ftue_ems_url}\">element.io/ems</a>上了解如何使用 +Element Matrix Services (EMS) 是一种强大且可靠的托管服务,可实现快速、安全和实时的通信。 了解如何在 <a href=\"${ftue_ems_url}\">element.io/ems</a> 想架设自己的服务器? 服务器URL 选择你的服务器 @@ -2419,14 +2419,14 @@我们会帮你建立连接 你会与谁聊最多? ${app_name}也非常适合工作场所。受到世界上最安全的组织信任。 -选择保存你的对话的位置,给你控制权和独立性。通过Matrix连接。 +选择保存你的对话的位置,给予你控制权和独立性。通过 Matrix 连接。 安全且独立的通信,为你提供和在家中面对面对话同样等级的隐私。 安全传送消息。 向主服务器注册端点token失败: \n%1$s -Threads帮助保持你的对话不离题且易于跟踪。%s启用消息列会刷新应用。这对一些账户可能需要更长时间。 +消息列有助于使您的对话保持话题并易于跟踪。%s 创建消息列将刷新应用程序。对于某些帐户,这可能需要更长的时间。 重启应用以使更改生效。 -启用LaTeX数学 +启用 LaTeX 数学 (%1$s) %1$s(%2$s) 无法播放%1$s @@ -2445,8 +2445,8 @@离开全部 此空间里的东西 忙 -无法启用生物验证。 -生物验证被停用了,因为最近新增了新的生物验证方式。你可以在设置里重新启用。 +无法启用生物特征识别。 +生物特征识别被禁用,因为最近添加了新的生物特征识别方法。 您可以在“设置”中再次启用它。 主服务器不接收仅有数字的用户名。 发送你的第一条消息邀请%s聊天 加密配置错误 @@ -2454,8 +2454,8 @@还原加密 请联系管理员将加密还原到有效状态。 加密被错误地配置了。 -分享了他们的实时位置 -分享了他们的位置 +共享了他们的实时位置 +共享了他们的位置 无法打开此链接:社群已被空间取代 我已经有账户了 创建账户 @@ -2473,7 +2473,7 @@或 你的对话将进行的地方 必须有8个及以上的字符 -其他人可以通过%s发现你 +其他人可以通过 %s 发现你 创建你的账户 恭喜! 你的账户%s已创建 @@ -2481,7 +2481,7 @@连接服务器 想要加入已有的服务器? 跳过此问题 -朋友和家人 +家人和朋友 拥有你的对话。 位置 Threads Beta反馈 @@ -2509,16 +2509,16 @@自动播放动画图片 端点成功注册到主服务器。 端点注册 -你的主服务器当前不支持消息列,所以此功能可能不可靠。Some threaded messages may not be reliably available. %s你仍要启用消息列吗? +您的主服务器当前不支持消息列,因此此功能可能不可靠。某些消息列的消息可能无法可靠地使用。 %s 您仍然要启用消息列吗? Threads接近Beta了 🎉 来自消息列 实用提示:长按消息并使用“%s”。 使用消息列来保持讨论的条理性 显示你参与的所有消息列 -我的消息列s +我的消息列 加密被错误地配置了,所以你无法发送消息。点击以打开设置。 加密被错误地配置了,所以你无法发送消息。请联系管理员将加密还原到有效的状态。 -%1$s、%2$s与其他人 +%1$s、%2$s 与其他人 %1$s与%2$s 共享屏幕 停止共享屏幕 @@ -2526,33 +2526,32 @@进一步了解 试试看 停用 -查看Threads +查看消息列 秒 分钟 小时 - 一些用户已被取消忽略 初始同步请求 - - %1$s与其他%2$d人
+- %1$s 与其他 %2$d人
正在更新你的数据…… -自动批准Element通话组件,授予相机/麦克风权限 -启用Element通话权限捷径 +自动允许 Element 通话小部件并授予相机/麦克风访问权限 +启用 Element 通话权限快捷方式 实时位置 -这个QR码看起来不正常。请尝试用另一个方法验证。 +此二维码看起来格式不正确。请尝试使用其它方法进行验证。 你无法访问加密消息历史。重置你的安全消息备份和验证密钥以重新开始。 无法验证此设备 你的服务器地址是什么? 你的对话发生的地方 -%1$s和%2$s -电子邮件未确认,检查你的收件箱 +%1$s 和 %2$s +电子邮件未验证,请检查您的收件箱 无法加载地图 \n此主服务器可能没有设置好显示地图。 打开设置 全部聊天 -显示全部会话(V2, WIP) 为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。 -其他会话 +其它会话 会话 打开空间列表 创建新对话或房间 @@ -2563,7 +2562,7 @@A—Z 活动 排序方式 -显示最近的 +显示最近 显示过滤条件 布局偏好 探索房间 @@ -2571,8 +2570,8 @@开始聊天 抱歉,未发现此房间。 \n请晚些重试。%s -未验证 · 上次活跃 %1$s -已验证 · 上次活跃 %1$s +未验证 · 上次活动 %1$s +已验证 · 上次活动 %1$s 查看全部(%1$d) 查看详情 验证会话 @@ -2588,32 +2587,32 @@没有新的东西。 你的新请求和邀请会在这里。 - - %1$d+天不活跃(%2$s)
+- 闲置 %1$d+ 天 (%2$s)
安全建议 按照这些建议改善你的账户安全。 未验证的会话 验证未验证的会话或从之登出。 -不活跃的会话 +闲置会话 - - 请考虑从不再使用的旧会话(%1$d天或更久)登出。
欢迎来到${app_name}, + 欢迎来到 ${app_name}, \n%s。 -未读消息会在这里显示。 +当您有一些未读消息时,这里会显示您的未读消息。 提供反馈 点击右上角查看反馈选项。 试用 空间是对房间和人进行分组的新方式。创建一个空间来开始吧。 启用新布局 IP地址 -验证你的会话以增强消息传输的安全性,或从那些你不认识或不再使用的会话登出。 +验证您的会话以增强安全消息传递或从您不再识别或不再使用的会话中登出。 尚未准备好安全收发消息 准备好安全收发消息 已验证 全部会话 -筛选 -上次活跃%1$s +过滤器 +上次活动 %1$s 设备 会话 当前会话 @@ -2623,5 +2622,171 @@你当前的会话已准备好安全地收发消息。 仅在首条消息创建私聊消息 启用延迟的私聊消息 -简化的Element,带有可选的标签 +简化的 Element,带有可选的标签 +无痕键盘 +请求键盘不要根据您在对话中输入的内容更新任何个性化数据,例如输入历史记录和字典。 请注意,某些键盘可能不遵守此设置。 +${app_name}需要权限来显示通知。通知可以显示消息、邀请等。 +\n +\n请在下个弹窗允许访问以便查看通知。 +试用富文本编辑器(纯文本模式即将到来) +启用富文本编辑器 +折叠 %s 子空间 +展开 %s 子空间 +启用新的会话管理器 +访问空间 +欢迎使用新视图! +⚠ 此房间里有未经验证的设备,它们将无法解密你发送的消息。 +永远不要向这个房间里未经验证的会话发送加密的消息。 +%s +\n看起来有点空荡荡的。 +能够在房间时间线中录制和发送语音广播。 +启用语音广播(正在积极开发中) +记录客户端名称、版本和网址,以便在会话管理器中更轻松地识别会话。 +启用客户端信息记录 +对所有会话有更好的可见性和控制。 +您加入的直接消息和聊天室中的其他用户可以查看您的会话的完整列表。 +\n +\n这让他们确信他们真的在与您交谈,但这也意味着他们可以看到您在此处输入的会话名称。 +重命名会话 +已验证会话已使用您的凭据登录,然后使用您的安全密码或通过交叉验证进行验证。 +\n +\n这意味着他们持有您之前消息的加密密钥,并向您正在与之通信的其他用户确认这些会话确实是您。 +闲置会话是您一段时间未使用的会话,但它们会继续接收加密密钥。 +\n +\n删除闲置会话可以提高安全性和性能,并使您更容易识别新会话是否可疑。 +闲置会话 +您可以使用此设备通过二维码登录移动设备或网络设备。 有两种方法可以做到这一点: +使用二维码登录 +请注意,与您交流的人也可以看到会话名称。 +自定义会话名称可以帮助您更轻松地识别您的设备。 +重命名会话 +操作系统 +型号 +浏览器 +网址 +版本 +名称 +应用 +上次活动 +会话名称 +接收有关此会话的推送通知。 +推送通知 +应用程序、设备和活动信息。 +会话详情 +登出此会话 +清除过滤器 +未找到闲置会话。 +未找到未验证的会话。 +未找到已验证的会话。 ++ +- 考虑登出您不再使用的旧会话(%1$d 天或更长时间)。
+闲置 +未验证 +为获得最佳安全性,请从您不认识或不再使用的任何会话中登出。 +已验证 +过滤器 ++ +- 闲置 %1$d 天或更长时间
+未验证 +未验证 · 您当前的会话 +验证您当前的会话以显示此会话的验证状态。 +未知的验证状态 +开始语音广播 +缓冲 +暂停语音广播 +实时 +知道了 +应用下划线格式 +应用删除线格式 +应用斜体格式 +应用粗体格式 +请确保您知道此代码的来源。 通过链接设备,您将为某人提供对您帐户的完全访问权限。 +确认 +再试一次 +不匹配? +登录 +连接到设备 +扫描二维码 +登录移动设备? +在此设备中显示二维码 +选择“扫描二维码” +从登录屏幕开始 +选择“使用二维码登录” +从登录屏幕开始 +选择“显示二维码” +转到设置 -> 安全和隐私 +在您的其它设备上打开应用程序 +主服务器不支持二维码登录。 +登录已在另一台设备上取消。 +该二维码无效。 +另一台设备必须登录。 +另一台设备已登录。 +链接未在规定时间内完成。 +设置安全消息传递时遇到安全问题。 以下其中一项可能会受到损害:您的家庭服务器; 您的互联网连接; 您的设备; +请求失败。 +该请求在另一台设备上被拒绝。 +不支持与此设备链接。 +连接不成功 +检查您已登录的设备,应显示以下代码。 确认以下代码与该设备匹配: +已建立安全连接 +使用已退出登录的设备扫描下方二维码。 +使用您已登录的设备扫描下方二维码: +使用二维码登录 +使用此设备上的相机扫描其它设备上显示的二维码: +扫描二维码 +3 +2 +1 +为了简化您的 ${app_name},选项卡现在是可选的。 使用右上角的菜单管理它们。 +无需报告。 +适用于团队、朋友和组织的一体化安全聊天应用程序。 创建一个聊天室,或加入一个现有的房间,开始。 +空间是一种对房间和人员进行分组的新方式。 使用右下角的按钮添加现有房间或创建新房间。 +已验证会话 +未验证会话是使用您的凭据登录但未经交叉验证的会话。 +\n +\n您应特别确定您识别这些会话,因为它们可能代表未经授权使用您的帐户。 +未验证会话 +闲置 +会话名称 +验证或登出此会话以获得最佳安全性和可靠性。 +播放或恢复语音广播 +此设备无法保证此加密消息的真实性。 +将 (╯°□°)╯︵ ┻━┻ 添加到纯文本消息中 +${app_name} 需要显示通知的权限。 +\n请授予权限。 +打开开发者工具屏幕 +🔒 你已在安全设置中为所有房间启用了仅对已验证会话加密。 +授予权限 +停止语音广播录制 +暂停语音广播录制 +继续语音广播录制 +扫描二维码 +语音广播 +已启用: +会话ID: +出了点差错。请检查您的网络连接并重试。 +联系人 +切换全屏模式 +选择会话 +文本格式 +相机 +位置 +投票 +语音广播 +附件 +贴纸 +照片库 +您没有在此房间内开始语音广播所需的权限。联系房间管理员升级您的权限。 +其他人已经在录制语音广播。等待他们的语音广播结束以开始新的广播。 +您已经在录制语音广播。请结束您当前的语音广播以开始新的语音广播。 +无法开始新的语音广播 +快进 30 秒 +快退 30 秒 +取消全选 +全选 ++ \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 876084d566..dc5f6d85e3 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -873,10 +873,10 @@- 已选择 %1$d
+推送規則 未定義通送規則 沒有已註冊的推送閘道 -app_id: -push_key: -app_display_name: -session_name: +App ID: +推送金鑰: +應用程式顯示名稱: +工作階段顯示名稱: Url: 格式: 音訊與視訊 @@ -938,11 +938,11 @@您正在使用 %1$s 來探索與被您認識的既有聯絡人探索。 您目前並未使用身份識別伺服器。要探索與被您認識的聯絡人探索,請在下方設定一個。 可探索的電子郵件地址 -在您新增電子郵件後,探索選項將會出現。 +在您新增電子郵件地址後,探索選項將會出現。 在您新增電話號碼後,探索選項將會出現。 與您的身份識別伺服器斷線代表您無法被其他使用者探索,且您將無法透過電子郵件或電話邀請其他人。 可探索的電話號碼 -我們將會傳送確認電子郵件到 %s 給您,請檢查您的電子郵件並在確認連結上點選 +我們已傳送電子郵件到 %s,請檢查您的電子郵件並在確認連結上點選 輸入身份識別伺服器 URL 無法連線到身份識別伺服器 請輸入身份識別伺服器 URL @@ -1069,7 +1069,7 @@應用程式無法在此家伺服器上建立帳號。 \n \n您想要使用網路客戶端註冊嗎? -此電子郵件未關聯到任何帳號。 +此電子郵件地址未關聯到任何帳號。 在 %1$s 上重設密碼 驗證郵件已傳送到您的收件匣以確認您要設定新密碼。 下一步 @@ -1078,7 +1078,7 @@警告! 變更您的密碼將會重設在您所有工作階段中任何的端到端加密金鑰,讓已加密的聊天歷史無法讀取。請在重設您的密碼前從其他工作階段設定金鑰備份或匯出您的聊天室金鑰。 繼續 -此電子郵件未被連結到任何帳號 +此電子郵件地址未被連結到任何帳號 檢查您的收件匣 驗證電子郵件已傳送至 %1$s。 輕點連結以確認您的新密碼。在您使用了其中包含的連結後,請點擊下方。 @@ -1092,7 +1092,7 @@ \n \n停止密碼變更流程?設定電子郵件地址 -設定電子郵件以復原您的帳號。之後您也可以選擇性地讓您認識的人透過您的電子郵件找到您。 +設定電子郵件地址以復原您的帳號。之後您也可以選擇性地讓您認識的人透過此地址找到您。 電子郵件 電子郵件(選擇性) 下一個 @@ -1430,7 +1430,7 @@訊息已移除 顯示已移除的訊息 為已移除的訊息顯示佔位符 -我們已傳送確認電子郵件給 %s,請先檢查您的電子郵件並點擊確認連結 +我們已傳送電子郵件到 %s,請先檢查您的電子郵件並點擊確認連結 驗證代碼不正確。 媒體 此聊天室中沒有媒體 @@ -1453,7 +1453,7 @@此動作是不可能的。家伺服器太舊了。 請先設定身份識別伺服器。 請先在設定中同意身份識別伺服器的條款。 -為了保護您的隱私,${app_name} 僅支援傳送雜湊過的使用者電子郵件與電話號碼。 +為了保護您的隱私,${app_name} 僅支援傳送雜湊過的使用者電子郵件地址與電話號碼。 關聯失敗。 目前沒有此識別符的關聯。 您的家伺服器 (%1$s) 建議將 %2$s 用於您的身份識別伺服器 @@ -1623,12 +1623,12 @@此電話號碼已被定義。 未新增電話號碼到您的帳號 電子郵件地址 -未傳送電子郵件到您的帳號 +未新增電子郵件地址至您的帳號 電話號碼 移除 %s? 確保您已經點擊我們傳送給您的電子郵件中的連結。 電子郵件與電話號碼 -管理連結到您 Matrix 帳號的電子郵件與電話號碼 +管理連結到您 Matrix 帳號的電子郵件地址與電話號碼 代碼 請使用國際格式(電話號碼必須以 \'+\' 開頭) 透過確認此登入來驗證您的身份,以及授予存取加密訊息的權限。 @@ -1744,7 +1744,7 @@%2$d 中的 %1$d 給予同意 撤銷我的同意 -您已同意傳送電子郵件與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。 +您已同意傳送電子郵件地址與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。 傳送電子郵件或電話號碼 建議 已知的使用者 @@ -2103,7 +2103,7 @@提及與關鍵字 預設通知 設定中的 %s 可直接在 ${app_name} 中接收邀請。 -將此電子郵件與您的帳號連結 +將此電子郵件地址與您的帳號連結 此空間的邀請已傳送給與您的帳號無關的 %s 此聊天室的邀請已傳送給與您的帳號無關的 %s 您所在的所有聊天室都會顯示在 Home 中。 @@ -2171,7 +2171,7 @@空間存取 誰可以存取? 啟用 %s 的電子郵件通知 -要收到通知用的電子郵件,請將電子郵件關聯至您的 Matrix 帳號 +要收到通知用的電子郵件,請將電子郵件地址關聯至您的 Matrix 帳號 電子郵件通知 升級空間 變更空間名稱 @@ -2217,12 +2217,12 @@投票問題或主題 建立投票 投票 -向 %s 傳送電子郵件與電話號碼 +向 %s 傳送電子郵件地址與電話號碼 您的通訊錄是私人的。要從您的通訊錄中探索使用者,我們需要您的權限來傳送聯絡人資訊到您的身份識別伺服器。 已登出工作階段! 已離開聊天室! 您同意傳送此資訊嗎? -要探索現有聯絡人,您必須傳送聯絡人資訊(電子郵件與電話號碼)到您的身份識別伺服器。我們會在傳送前對您的資料進行雜湊處理以保護隱私。 +要探索現有聯絡人,您必須傳送聯絡人資訊(電子郵件地址與電話號碼)到您的身份識別伺服器。我們會在傳送前對您的資料進行雜湊處理以保護隱私。 現在不要 您確定要移除此投票?移除後將無法復原。 移除投票 @@ -2550,7 +2550,6 @@ \n此家伺服器可能未設定好顯示地圖。開啟設定 所有聊天 -顯示所有工作階段 (V2, WIP) 為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。 其他工作階段 工作階段 @@ -2656,4 +2655,159 @@啟用延期直接訊息 包含選擇性分頁的簡潔 Element 啟用新佈局 +您加入的直接消息與聊天室中的其他使用者可以檢視您的工作階段的完整清單。 +\n +\n這讓他們確信他們真的在與您交談,但這也意味著他們可以看到您在此處輸入的工作階段名稱。 +正在重新命名工作階段 +已驗證的工作階段代表使用您的憑證登入,然後使用您的安全通關密語或透過交叉驗證進行驗證。 +\n +\n這代表了它們持有您先前訊息的加密金鑰,並向您正在與之通訊的其他使用者確認這些工作階段確實是您。 +已驗證的工作階段 +未驗證的工作階段是使用您的憑證登入但未交叉驗證的工作階段。 +\n +\n您應特別確定您可以識別這些工作階段,因為它們可能代表未經授權使用您的帳號。 +未驗證的工作階段 +不活躍的工作階段是您有一段時間未使用的工作階段,但它們會繼續接收加密金鑰。 +\n +\n移除不活躍的工作階段可以改善安全性與效能,並讓您可以更容易地識別新的工作階段是否可疑。 +不活躍的工作階段 +請注意,與您交流的人也可以看到工作階段名稱。 +自訂工作階段名稱可以協助您更輕鬆地識別您的裝置。 +工作階段名稱 +重新命名工作階段 +登出此工作階段 +未驗證 · 您目前的工作階段 +開始語音廣播 +此裝置無法保證此加密訊息的真實性。 +要求鍵盤不要根據您在對話中輸入的內容更新任何個人化資料(如輸入歷史紀錄與字典等)。請注意,某些鍵盤可能不會遵守此設定。 +無痕式鍵盤 +將 (╯°□°)╯︵ ┻━┻ 放到純文字訊息之前 +語音廣播 +開啟開發者工具畫面 +🔒 您已在「安全」設定中為所有聊天室啟用加密驗證工作階段。 +⚠ 此聊天室中有未驗證的裝置,它們將無法解密您傳送的訊息。 +切莫向此聊天室中未經驗證的工作階段傳送加密訊息。 +知道了 +套用底線格式 +套用刪除線格式 +套用義式斜體格式 +套用粗體格式 +記錄客戶端名稱、版本與 URL,以便在工作階段管理程式中可以更簡單地辨認工作階段。 +啟用客戶端資訊記錄 +對所有工作階段有更大的能見度與控制。 +啟用新的工作階段管理程式 +作業系統 +模型 +瀏覽器 +URL +版本 +名稱 +應用程式 +接收關於此工作階段的推播通知。 +推播通知 +驗證您目前的工作階段以顯示此工作階段的驗證狀態。 +未知的驗證狀態 +已啟用: +工作階段 ID: +發生了一些問題。請檢查您的網路連線並再試一次。 +授予權限 +${app_name} 需要權限以顯示通知。 +\n請授予權限。 +${app_name} 需要權限才能顯示通知。通知可以顯示您的訊息、您的邀請等等。 +\n +\n請在下一個彈出式視窗允許存取以檢視通知。 +試用格式化文字編輯器(純文字模式即將推出) +啟用格式化文字編輯器 +請確保您知道此驗證碼的來源。透過連結裝置,您將為某人提供對您帳號的完整存取權限。 +確認 +再試一次 +不相符? +登入 +連線至裝置 +掃描 QR code +正在使用行動裝置登入? +在此裝置顯示 QR code +選取「掃描 QR code」 +從登入畫面開始 +選取「使用 QR code 登入」 +從登入畫面開始 +選取「顯示 QR code」 +到「設定」→「安全與隱私」 +在您的其他裝置上開啟應用程式 +請求在另一台裝置上被拒絕。 +連結未在規定時間內完成。 +不支援與其裝置連結。 +連線不成功 +請檢查您已登入的裝置,應該會顯示以下驗證碼。請確認以下驗證碼與該裝置相符: +已建立安全連線 +使用您已登出的裝置掃描以下 QR code。 +使用您已登入的裝置來掃描下方的 QR code: +使用 QR code 登入 +使用此裝置的相機掃描您其他裝置上顯示的 QR code: +掃描 QR code +3 +2 +1 +您可以使用此裝置透過 QR code 登入移動裝置或網路裝置。有兩種方法可以作到: +使用 QR code 登入 +掃描 QR code +家伺服器不支援使用 QR code 登入。 +登入已在其他裝置上取消。 +該 QR code 無效。 +其他裝置必須登入。 +其他裝置已登入。 +設定安全訊息傳遞時遇到安全問題。以下其中一項可能已被駭入:您的家伺服器、您的網際網路連線、您的裝置; +請求失敗。 +可以在聊天室時間軸中錄製並傳送語音廣播。 +啟用語音廣播(正在積極開發中) +正在緩衝 +暫停語音廣播 +播放或繼續語音廣播 +停止語音廣播錄製 +暫停語音廣播錄製 +繼續語音廣播錄製 +直播 +選取工作階段 +聯絡人 +相機 +位置 +投票 +音訊廣播 +附件 +貼圖 +照片媒體庫 +取消選取全部 +選取全部 ++ +- 已選取 %1$d
+切換全螢幕模式 +文字格式化 +您已在錄製語音廣播。請結束您目前的語音廣播以開始新的。 +其他人已在錄製語音廣播。等待他們的語音廣播結束以開始新的。 +您沒有在此聊天室中開始語音廣播的必要權限。請聯絡聊天室管理員以升級您的權限。 +無法開始新的語音廣播 +快轉30秒 +快退30秒 +已驗證的工作階段是您輸入通關密語或透過另一個已驗證工作階段確認您的身份後使用此帳號的任何地方。 +\n +\n這代表了您擁有解鎖加密訊息並向其他使用者確認您信任此工作階段所需的所有金鑰。 ++ +- 登出 %1$d 個工作階段
+登出 +剩餘 %1$s +已建立投票。 +已傳送貼圖。 +已傳送影片。 +已傳送圖片。 +已傳送語音訊息。 +已傳送音訊檔。 +已傳送檔案。 +回覆給 +隱藏 IP 位置 +顯示 IP 位置 +引用 +回覆給 %s +正在編輯 \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values/donottranslate.xml b/library/ui-strings/src/main/res/values/donottranslate.xml index 741d23dbc6..bfe751ef5a 100755 --- a/library/ui-strings/src/main/res/values/donottranslate.xml +++ b/library/ui-strings/src/main/res/values/donottranslate.xml @@ -2,6 +2,7 @@diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 0fb03f0ea3..4c911c9e97 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -47,7 +47,9 @@ … +– Not implemented yet in ${app_name} diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 4104f9abce..62928caa75 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1,6 +1,13 @@+ + diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle index ee5771d995..c805153e1d 100644 --- a/library/ui-styles/build.gradle +++ b/library/ui-styles/build.gradle @@ -21,6 +21,8 @@ plugins { android { + namespace "im.vector.lib.ui.styles" + compileSdk versions.compileSdk defaultConfig { minSdk versions.minSdk diff --git a/library/ui-styles/src/debug/AndroidManifest.xml b/library/ui-styles/src/debug/AndroidManifest.xml index e32676136d..be7aeafb07 100644 --- a/library/ui-styles/src/debug/AndroidManifest.xml +++ b/library/ui-styles/src/debug/AndroidManifest.xml @@ -1,6 +1,5 @@ -+ + +- %1$d selected
+- %1$d selected
+%s\'s invitation Your invitation %1$s created the room @@ -407,6 +414,8 @@Learn more Next Got it +Select all +Deselect all Copied to clipboard @@ -446,6 +455,9 @@Enable deferred DMs Create DM only on first message +Enable rich text editor +Try out the rich text editor (plain text mode coming soon) +Invites Low priority @@ -635,6 +647,8 @@${app_name} needs permission to access your microphone to perform audio calls. ${app_name} needs permission to access your camera and your microphone to perform video calls.\n\nPlease allow access on the next pop-ups to be able to make the call. + +${app_name} needs permission to display notifications. Notifications can display your messages, your invitations, etc.\n\nPlease allow access on the next pop-ups to be able to view notification. To scan a QR code, you need to allow camera access. Allow permission to access your contacts. @@ -854,7 +868,9 @@System Settings. Notifications are enabled in the system settings. Notifications are disabled in the system settings.\nPlease check system settings. +${app_name} needs the permission to show notifications.\nPlease grant the permission. Open Settings +Grant Permission Account Settings. Notifications are enabled for your account. @@ -1016,6 +1032,8 @@Use /confetti command or send a message containing ❄️ or 🎉 Autoplay animated images Play animated images in the timeline as soon as they are visible +Enable direct share +Show recent chats in the system share menu Show join and leave events Invites, removes, and bans are unaffected. Show account events @@ -1626,7 +1644,10 @@It looks like you’re trying to connect to another homeserver. Do you want to sign out? Edit +Editing Reply +Replying to %s +Quoting Reply in thread View In Room @@ -1663,6 +1684,8 @@Create New Room Create New Space No network. Please check your Internet connection. + +Something went wrong. Please check your network connection and try again. "Change network" "Please wait…" Updating your data… @@ -1702,13 +1725,15 @@No push rules defined No registered push gateways -app_id: -push_key: -app_display_name: -session_name: +App ID: +Push Key: +App Display Name: +Session Display Name: +Session ID: Url: Format: Profile tag: +Enabled: Voice & Video Help & About @@ -2115,7 +2140,7 @@Your password is not yet changed.\n\nStop the password change process? Set email address -Set an email address to recover your account. Later, you can optionally allow people you know to discover you by your this address. +Set an email address to recover your account. Later, you can optionally allow people you know to discover you by this address. Email (optional) Next @@ -2173,6 +2198,7 @@If you don’t know your password, go back to reset it. This is not a valid user identifier. Expected format: \'@user:homeserver.org\' Unable to find a valid homeserver. Please check your identifier +Scan QR code Seen by @@ -3071,6 +3097,23 @@%1$s (%2$s) (%1$s) +Live + +Buffering… +Resume voice broadcast record +Pause voice broadcast record +Stop voice broadcast record +Play or resume voice broadcast +Pause voice broadcast +Fast backward 30 seconds +Fast forward 30 seconds +Can’t start a new voice broadcast +You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions. +Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one. +You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. + +%1$s left +Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. @@ -3190,6 +3233,16 @@Share location Start a voice broadcast +Photo library +Stickers +Attachments +Voice broadcast +Polls +Location +Camera +Contact +Text formatting +Show less - "%1$d more"
@@ -3238,7 +3291,6 @@Auto-approve Element Call widgets and grant camera / mic access -Show All Sessions (V2, WIP) Other sessions For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Mobile @@ -3247,10 +3299,12 @@Unknown device type Verified session Unverified session +Unknown verification status 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 your current session to reveal this session\'s verification status. Verify Session View Details View All (%1$d) @@ -3303,25 +3357,53 @@No unverified sessions found. No inactive sessions found. Clear Filter +Select sessions +Sign out ++ +- Sign out of %1$d session
+- Sign out of %1$d sessions
+Show IP address +Hide IP address Sign out of this session Session details Application, device, and activity information. +Push notifications +Receive push notifications on this session. Session name Session ID Last activity +Application +Name +Version +URL +Browser +Model +Operating system IP address Rename session Session name Custom session names can help you recognize your devices more easily. Please be aware that session names are also visible to people you communicate with. +Sign in with QR Code +You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this: +Inactive sessions Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious. Unverified sessions Unverified sessions are sessions that have logged in with your credentials but not been cross-verified.\n\nYou should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account. Verified sessions -Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you. + +Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you. +Verified sessions are anywhere you are using this account after entering your passphrase or confirming your identity with another verified session.\n\nThis means that you have all the keys needed to unlock your encrypted messages and confirm to other users that you trust this session. Renaming sessions Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here. +Enable new session manager +Have greater visibility and control over all your sessions. +Enable client info recording +Record the client name, version, and url to recognise sessions more easily in session manager. +Enable voice broadcast (under active development) +Be able to record and send voice broadcast in room timeline. %s\nis looking a little empty. @@ -3344,4 +3426,60 @@Tap top right to see the option to feedback. Try it out +1 +2 +3 + + +Scan QR code +Use the camera on this device to scan the QR code shown on your other device: +Sign in with QR code +Use your signed in device to scan the QR code below: +Scan the QR code below with your device that’s signed out. +Secure connection established +Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device: +Unsuccessful connection +Linking with this device is not supported. +The linking wasn’t completed in the required time. +The request was denied on the other device. +The request failed. +A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s); +The other device is already signed in. +The other device must be signed in. +That QR code is invalid. +The sign in was cancelled on the other device. +The homeserver doesn\'t support sign in with QR code. +Open the app on your other device +Go to Settings -> Security & Privacy +Select \'Show QR code\' +Start at the sign in screen +Select \'Sign in with QR code\' +Start at the sign in screen +Select \'Scan QR code\' +Show QR code in this device +Signing in a mobile device? +Scan QR code +Connecting to device +Signing you in +No match? +Try again +Confirm +Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account. + + +Apply bold format +Apply italic format +Apply strikethrough format +Apply underline format +Toggle full screen mode + + +In reply to +sent a file. +sent an audio file. +sent a voice message. +sent an image. +sent a video. +sent a sticker. +created a poll. + - + diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index f4384adb40..9d8645a707 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -146,10 +146,16 @@\ No newline at end of file + - #91A1C0 #FF4B55 #0FFF4B55 +@color/palette_gray_200 @color/palette_white @color/palette_black_950 + ++ #EEF8F4 +#1D292A +56dp 52dp 1dp - +28dp +14dp +44dp 28dp 6dp @@ -72,6 +74,10 @@12dp 22dp + +48dp +36dp +112dp diff --git a/library/ui-styles/src/main/res/values/stylable_qr_code_instructions_view.xml b/library/ui-styles/src/main/res/values/stylable_qr_code_instructions_view.xml new file mode 100644 index 0000000000..c9a4bb9d05 --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_qr_code_instructions_view.xml @@ -0,0 +1,11 @@ + ++ + diff --git a/library/ui-styles/src/main/res/values/stylable_qr_code_login_header_view.xml b/library/ui-styles/src/main/res/values/stylable_qr_code_login_header_view.xml new file mode 100644 index 0000000000..99f56084d9 --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_qr_code_login_header_view.xml @@ -0,0 +1,11 @@ + ++ + ++ + + + + + diff --git a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml index d3884f247d..6428cd6eac 100644 --- a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml @@ -6,4 +6,10 @@+ + ++ + + + + + + diff --git a/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml index 098ec263fc..c1a51000b7 100644 --- a/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml @@ -5,6 +5,7 @@+ + + + diff --git a/library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml b/library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml new file mode 100644 index 0000000000..1f72eeb396 --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_voice_broadcast_metadata_view.xml @@ -0,0 +1,9 @@ + + + + diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml index 8de548dd03..94f4d86160 100644 --- a/library/ui-styles/src/main/res/values/styles_edit_text.xml +++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml @@ -4,11 +4,24 @@ - \ No newline at end of file + + + diff --git a/library/ui-styles/src/main/res/values/styles_voice_broadcast.xml b/library/ui-styles/src/main/res/values/styles_voice_broadcast.xml new file mode 100644 index 0000000000..eb85378141 --- /dev/null +++ b/library/ui-styles/src/main/res/values/styles_voice_broadcast.xml @@ -0,0 +1,19 @@ + ++ + ++ + + + + + diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index 9f4e5c1e28..d5aaa88ab8 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -152,6 +152,9 @@- @dimen/collapsing_toolbar_layout_medium_size
+ + +- @color/vctr_rich_text_editor_menu_button_background_dark
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index c8182abecc..1978db9139 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -153,6 +153,9 @@- @dimen/collapsing_toolbar_layout_medium_size
+ + +- @color/vctr_rich_text_editor_menu_button_background_light
diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle index ab97b19a9d..bcc070f23a 100644 --- a/matrix-sdk-android-flow/build.gradle +++ b/matrix-sdk-android-flow/build.gradle @@ -6,6 +6,8 @@ plugins { apply from: '../flavor.gradle' android { + namespace "org.matrix.android.sdk.flow" + compileSdk versions.compileSdk defaultConfig { diff --git a/matrix-sdk-android-flow/src/main/AndroidManifest.xml b/matrix-sdk-android-flow/src/main/AndroidManifest.xml index 2392c0bfcb..b2d3ea1235 100644 --- a/matrix-sdk-android-flow/src/main/AndroidManifest.xml +++ b/matrix-sdk-android-flow/src/main/AndroidManifest.xml @@ -1,5 +1,2 @@ -- - \ No newline at end of file +diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index a6b4cc98a6..7ad342b22f 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -100,8 +100,8 @@ class FlowRoom(private val room: Room) { return room.readService().getReadMarkerLive().asFlow() } - fun liveReadReceipt(): Flow > { - return room.readService().getMyReadReceiptLive().asFlow() + fun liveReadReceipt(threadId: String?): Flow > { + return room.readService().getMyReadReceiptLive(threadId).asFlow() } fun liveEventReadReceipts(eventId: String): Flow > { diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 718bd75070..a06d6e59ed 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -44,6 +44,8 @@ dokkaHtml { apply from: '../flavor.gradle' android { + namespace "org.matrix.android.sdk" + testOptions.unitTests.includeAndroidResources = true compileSdk versions.compileSdk @@ -61,7 +63,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.5.4\"" + buildConfigField "String", "SDK_VERSION", "\"1.5.10\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/matrix-sdk-android/src/androidTest/assets/session_42.realm b/matrix-sdk-android/src/androidTest/assets/session_42.realm new file mode 100644 index 0000000000..b92d13dab2 Binary files /dev/null and b/matrix-sdk-android/src/androidTest/assets/session_42.realm differ diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index fa7511d43e..7345c98446 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder import timber.log.Timber import java.util.UUID import java.util.concurrent.CountDownLatch @@ -348,6 +349,10 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: assertTrue(registrationResult is RegistrationResult.Success) val session = (registrationResult as RegistrationResult.Success).session session.open() + session.filterService().setSyncFilter( + SyncFilterBuilder() + .lazyLoadMembersForStateEvents(true) + ) if (sessionTestParams.withInitialSync) { syncSession(session, 120_000) } @@ -435,7 +440,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: } private val backoff = listOf(500L, 1_000L, 1_000L, 3_000L, 3_000L, 5_000L) - suspend fun betterRetryPeriodically( + suspend fun retryWithBackoff( timeout: Long = TestConstants.timeOutMillis, // we use on fail to let caller report a proper error that will show nicely in junit test result with correct line // just call fail with your message diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index 170df3c1f4..05ddee6210 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -147,7 +147,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { } private suspend fun ensureMembersHaveJoined(session: Session, invitedUserSessions: List
, roomId: String) { - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("Members ${invitedUserSessions.map { it.myUserId.take(10) }} should have join from the pov of ${session.myUserId.take(10)}") } @@ -163,7 +163,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { } private suspend fun waitForAndAcceptInviteInRoom(session: Session, roomId: String) { - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("${session.myUserId} cannot see the invite from ${session.myUserId.take(10)}") } @@ -185,7 +185,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { } Log.v("#E2E TEST", "${session.myUserId} waiting for join echo ...") - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("${session.myUserId.take(10)} cannot see the join echo for ${roomId}") } @@ -373,7 +373,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { roomId = roomId ).transactionId - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("Bob should see an incoming request from alice") } @@ -397,7 +397,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { ) // wait for it to be readied - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("Alice should see the verification in ready state") } @@ -417,25 +417,25 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { var alicePovTx: SasVerificationTransaction? = null var bobPovTx: SasVerificationTransaction? = null - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("Alice should should see a verification code") } ) { alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? SasVerificationTransaction - Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state}") + Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state()}") alicePovTx?.getDecimalCodeRepresentation() != null } // wait for alice to get the ready - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("Bob should should see a verification code") } ) { bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? SasVerificationTransaction - Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}") + Log.v("TEST", "== bobPovTx is ${bobPovTx?.state()}") // bobPovTx?.state == VerificationTxState.ShortCodeReady bobPovTx?.getDecimalCodeRepresentation() != null } @@ -445,11 +445,11 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { bobPovTx!!.userHasVerifiedShortCode() alicePovTx!!.userHasVerifiedShortCode() - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) } - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId) } } 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 3870ea93ec..f3a795822e 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 @@ -96,7 +96,7 @@ class E2eeSanityTests : InstrumentedTest { // All should be able to decrypt otherAccounts.forEach { otherSession -> - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("${otherSession.myUserId.take(10)} should be able to decrypt") }) { @@ -122,7 +122,7 @@ class E2eeSanityTests : InstrumentedTest { // check that messages are encrypted (uisi) newAccount.forEach { otherSession -> - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("New Users shouldn't be able to decrypt history") } @@ -145,7 +145,7 @@ class E2eeSanityTests : InstrumentedTest { // new members should be able to decrypt it newAccount.forEach { otherSession -> // ("${otherSession.myUserId} should be able to decrypt") - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("New user ${otherSession.myUserId.take(10)} should be able to decrypt the second message") } @@ -207,7 +207,7 @@ class E2eeSanityTests : InstrumentedTest { sentEventIds.add(it) } - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("Bob should be able to decrypt all messages") } @@ -225,7 +225,7 @@ class E2eeSanityTests : InstrumentedTest { // Let's wait a bit to be sure that bob has backed up the session Log.v("#E2E TEST", "Force key backup for Bob...") - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("All keys should be backedup") } @@ -251,7 +251,7 @@ class E2eeSanityTests : InstrumentedTest { // check that bob can't currently decrypt Log.v("#E2E TEST", "check that bob can't currently decrypt") sentEventIds.forEach { sentEventId -> - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also { Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}") } @@ -318,7 +318,7 @@ class E2eeSanityTests : InstrumentedTest { sentEventIds.add(it) } - testHelper.betterRetryPeriodically( + testHelper.retryWithBackoff( onFail = { fail("${bobSession.myUserId.take(10)} should be able to decrypt message sent by alice}") } @@ -377,8 +377,8 @@ class E2eeSanityTests : InstrumentedTest { // Now mark new bob session as verified - bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!) - newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId!!) + bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId) + newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId) // now let new session re-request sentEventIds.forEach { sentEventId -> @@ -413,7 +413,7 @@ class E2eeSanityTests : InstrumentedTest { firstMessage.let { text -> firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId) timeLineEvent != null && timeLineEvent.isEncrypted() && @@ -439,7 +439,7 @@ class E2eeSanityTests : InstrumentedTest { secondMessage.let { text -> secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId) timeLineEvent != null && timeLineEvent.isEncrypted() && @@ -483,7 +483,7 @@ class E2eeSanityTests : InstrumentedTest { // old session should have shared the key at earliest known index now // we should be able to decrypt both - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { val canDecryptFirst = try { newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") true @@ -506,7 +506,7 @@ class E2eeSanityTests : InstrumentedTest { val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) timeline.start() - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { val decryptedMsg = timeline.getSnapshot() .filter { it.root.getClearType() == EventType.MESSAGE } .also { list -> @@ -639,11 +639,11 @@ class E2eeSanityTests : InstrumentedTest { // wait for it to be synced back the other side Timber.v("#TEST: Wait for message to be synced back") - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null } - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null } @@ -660,11 +660,11 @@ class E2eeSanityTests : InstrumentedTest { val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!! Timber.v("#TEST: Wait for message to be synced back") - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null } - testHelper.betterRetryPeriodically { + testHelper.retryWithBackoff { secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null } 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 73833116f8..bf65b7857f 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 @@ -35,11 +35,11 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest +import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction -import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest @@ -149,8 +149,9 @@ class SASTest : InstrumentedTest { val bobListener = object : VerificationService.Listener { override fun transactionUpdated(tx: VerificationTransaction) { - if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) { - cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode + tx as SasVerificationTransaction + if (tx.transactionId == tid && tx.state() is SasTransactionState.Cancelled) { + cancelReason = (tx.state() as SasTransactionState.Cancelled).cancelCode cancelLatch.countDown() } } @@ -174,7 +175,8 @@ class SASTest : InstrumentedTest { val aliceListener = object : VerificationService.Listener { override fun transactionUpdated(tx: VerificationTransaction) { - if (tx.state is VerificationTxState.SasStarted && tx is SasVerificationTransaction) { + tx as SasVerificationTransaction + if (tx.state() is SasTransactionState.SasStarted) { runBlocking { tx.acceptVerification() } @@ -316,7 +318,8 @@ class SASTest : InstrumentedTest { } override fun transactionUpdated(tx: VerificationTransaction) { - if (tx.state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) { + tx as SasVerificationTransaction + if (tx.state() is SasTransactionState.Cancelled && !(tx.state() as SasTransactionState.Cancelled).byMe) { aliceCancelledLatch.countDown() } } @@ -353,7 +356,7 @@ class SASTest : InstrumentedTest { // val aliceAcceptedLatch = CountDownLatch(1) // val aliceListener = object : VerificationService.Listener { // override fun transactionUpdated(tx: VerificationTransaction) { -// if (tx.state is VerificationTxState.OnAccepted) { +// if (tx.state() is VerificationTxState.OnAccepted) { // aliceAcceptedLatch.countDown() // } // } @@ -362,7 +365,7 @@ class SASTest : InstrumentedTest { // // val bobListener = object : VerificationService.Listener { // override fun transactionUpdated(tx: VerificationTransaction) { -// if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) { +// if (tx.state() is VerificationTxState.OnStarted && tx is SasVerificationTransaction) { // bobVerificationService.removeListener(this) // runBlocking { // tx.acceptVerification() @@ -407,14 +410,14 @@ class SASTest : InstrumentedTest { // val latch = CountDownLatch(2) // val aliceListener = object : VerificationService.Listener { // override fun transactionUpdated(tx: VerificationTransaction) { -// Timber.v("Alice transactionUpdated: ${tx.state}") +// Timber.v("Alice transactionUpdated: ${tx.state()}") // latch.countDown() // } // } // aliceSession.cryptoService().verificationService().addListener(aliceListener) // val bobListener = object : VerificationService.Listener { // override fun transactionUpdated(tx: VerificationTransaction) { -// Timber.v("Bob transactionUpdated: ${tx.state}") +// Timber.v("Bob transactionUpdated: ${tx.state()}") // latch.countDown() // } // } @@ -459,10 +462,10 @@ class SASTest : InstrumentedTest { var matched = false var verified = false override fun transactionUpdated(tx: VerificationTransaction) { - Timber.v("Alice transactionUpdated: ${tx.state} on thread:${Thread.currentThread()}") if (tx !is SasVerificationTransaction) return - when (tx.state) { - VerificationTxState.SasShortCodeReady -> { + Timber.v("Alice transactionUpdated: ${tx.state()} on thread:${Thread.currentThread()}") + when (tx.state()) { + SasTransactionState.SasShortCodeReady -> { if (!matched) { matched = true runBlocking { @@ -471,7 +474,7 @@ class SASTest : InstrumentedTest { } } } - VerificationTxState.Verified -> { + is SasTransactionState.Done -> { if (!verified) { verified = true verifiedLatch.countDown() @@ -493,9 +496,9 @@ class SASTest : InstrumentedTest { } override fun transactionUpdated(tx: VerificationTransaction) { - Timber.v("Bob transactionUpdated: ${tx.state} on thread: ${Thread.currentThread()}") if (tx !is SasVerificationTransaction) return - when (tx.state) { + Timber.v("Bob transactionUpdated: ${tx.state()} on thread: ${Thread.currentThread()}") + when (tx.state()) { // VerificationTxState.SasStarted -> { // if (!accepted) { // accepted = true @@ -504,7 +507,7 @@ class SASTest : InstrumentedTest { // } // } // } - VerificationTxState.SasShortCodeReady -> { + SasTransactionState.SasShortCodeReady -> { if (!matched) { matched = true runBlocking { @@ -513,7 +516,7 @@ class SASTest : InstrumentedTest { } } } - VerificationTxState.Verified -> { + is SasTransactionState.Done -> { if (!verified) { verified = true verifiedLatch.countDown() @@ -602,13 +605,13 @@ class SASTest : InstrumentedTest { testHelper.retryPeriodically { alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction Log.v("TEST", "== alicePovTx is $alicePovTx") - alicePovTx?.state == VerificationTxState.SasShortCodeReady + alicePovTx?.state() == SasTransactionState.SasShortCodeReady } // wait for alice to get the ready testHelper.retryPeriodically { bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction Log.v("TEST", "== bobPovTx is $bobPovTx") - bobPovTx?.state == VerificationTxState.SasShortCodeReady + bobPovTx?.state() == SasTransactionState.SasShortCodeReady } } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration43Test.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration43Test.kt new file mode 100644 index 0000000000..e74aa52495 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration43Test.kt @@ -0,0 +1,130 @@ +/* + * 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 + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import org.amshove.kluent.fail +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotBe +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.internal.database.mapper.EventMapper +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.SessionRealmModule +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.util.Normalizer + +@RunWith(AndroidJUnit4::class) +class RealmSessionStoreMigration43Test { + + @get:Rule val configurationFactory = TestRealmConfigurationFactory() + + lateinit var context: Context + var realm: Realm? = null + + @Before + fun setUp() { + context = InstrumentationRegistry.getInstrumentation().context + } + + @After + fun tearDown() { + realm?.close() + } + + @Test + fun migrationShouldBeNeeed() { + val realmName = "session_42.realm" + val realmConfiguration = configurationFactory.createConfiguration( + realmName, + "efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0", + SessionRealmModule(), + 43, + null + ) + configurationFactory.copyRealmFromAssets(context, realmName, realmName) + + try { + realm = Realm.getInstance(realmConfiguration) + fail("Should need a migration") + } catch (failure: Throwable) { + // nop + } + } + + // Database key for alias `session_db_e00482619b2597069b1f192b86de7da9`: efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0 + // $WEJ8U6Zsx3TDZx3qmHIOKh-mXe5kqL_MnPcIkStEwwI + // $11EtAQ8RYcudJVtw7e6B5Vm4ufCqKTOWKblY2U_wrpo + @Test + fun testMigration43() { + val realmName = "session_42.realm" + val migration = RealmSessionStoreMigration(Normalizer()) + val realmConfiguration = configurationFactory.createConfiguration( + realmName, + "efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0", + SessionRealmModule(), + 43, + migration + ) + configurationFactory.copyRealmFromAssets(context, realmName, realmName) + + realm = Realm.getInstance(realmConfiguration) + + // assert that the edit from 42 are migrated + val editions = EventAnnotationsSummaryEntity + .where(realm!!, "\$WEJ8U6Zsx3TDZx3qmHIOKh-mXe5kqL_MnPcIkStEwwI") + .findFirst() + ?.editSummary + ?.editions + + editions shouldNotBe null + editions!!.size shouldBe 1 + val firstEdition = editions.first() + firstEdition?.eventId shouldBeEqualTo "\$DvOyA8vJxwGfTaJG3OEJVcL4isShyaVDnprihy38W28" + firstEdition?.isLocalEcho shouldBeEqualTo false + + val editEvent = EventMapper.map(firstEdition!!.event!!) + val body = editEvent.content.toModel ()?.body + body shouldBeEqualTo "* Message 2 with edit" + + // assert that the edit from 42 are migrated + val editionsOfE2E = EventAnnotationsSummaryEntity + .where(realm!!, "\$11EtAQ8RYcudJVtw7e6B5Vm4ufCqKTOWKblY2U_wrpo") + .findFirst() + ?.editSummary + ?.editions + + editionsOfE2E shouldNotBe null + editionsOfE2E!!.size shouldBe 1 + val firstEditionE2E = editionsOfE2E.first() + firstEditionE2E?.eventId shouldBeEqualTo "\$HUwJOQRCJwfPv7XSKvBPcvncjM0oR3q2tGIIIdv9Zts" + firstEditionE2E?.isLocalEcho shouldBeEqualTo false + + val editEventE2E = EventMapper.map(firstEditionE2E!!.event!!) + val body2 = editEventE2E.getClearContent().toModel ()?.body + body2 shouldBeEqualTo "* Message 2, e2e edit" + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/SessionSanityMigrationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/SessionSanityMigrationTest.kt new file mode 100644 index 0000000000..fc1a78835b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/SessionSanityMigrationTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.matrix.android.sdk.internal.database.model.SessionRealmModule +import org.matrix.android.sdk.internal.util.Normalizer + +@RunWith(AndroidJUnit4::class) +class SessionSanityMigrationTest { + + @get:Rule val configurationFactory = TestRealmConfigurationFactory() + + lateinit var context: Context + var realm: Realm? = null + + @Before + fun setUp() { + context = InstrumentationRegistry.getInstrumentation().context + } + + @After + fun tearDown() { + realm?.close() + } + + @Test + fun sessionDatabaseShouldMigrateGracefully() { + val realmName = "session_42.realm" + val migration = RealmSessionStoreMigration(Normalizer()) + val realmConfiguration = configurationFactory.createConfiguration( + realmName, + "efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0", + SessionRealmModule(), + migration.schemaVersion, + migration + ) + configurationFactory.copyRealmFromAssets(context, realmName, realmName) + + realm = Realm.getInstance(realmConfiguration) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/TestRealmConfigurationFactory.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/TestRealmConfigurationFactory.kt new file mode 100644 index 0000000000..fc5a017287 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/database/TestRealmConfigurationFactory.kt @@ -0,0 +1,196 @@ +/* + * 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 + +import android.content.Context +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmMigration +import org.junit.rules.TemporaryFolder +import org.junit.runner.Description +import org.junit.runners.model.Statement +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.lang.IllegalStateException +import java.util.Collections +import java.util.Locale +import java.util.concurrent.ConcurrentHashMap +import kotlin.Throws + +/** + * Based on https://github.com/realm/realm-java/blob/master/realm/realm-library/src/testUtils/java/io/realm/TestRealmConfigurationFactory.java + */ +class TestRealmConfigurationFactory : TemporaryFolder() { + private val map: Map = ConcurrentHashMap() + private val configurations = Collections.newSetFromMap(map) + @get:Synchronized private var isUnitTestFailed = false + private var testName = "" + private var tempFolder: File? = null + + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + setTestName(description) + before() + try { + base.evaluate() + } catch (throwable: Throwable) { + setUnitTestFailed() + throw throwable + } finally { + after() + } + } + } + } + + @Throws(Throwable::class) + override fun before() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + super.before() + } + + override fun after() { + try { + for (configuration in configurations) { + Realm.deleteRealm(configuration) + } + } catch (e: IllegalStateException) { + // Only throws the exception caused by deleting the opened Realm if the test case itself doesn't throw. + if (!isUnitTestFailed) { + throw e + } + } finally { + // This will delete the temp directory. + super.after() + } + } + + @Throws(IOException::class) + override fun create() { + super.create() + tempFolder = File(super.getRoot(), testName) + check(!(tempFolder!!.exists() && !tempFolder!!.delete())) { "Could not delete folder: " + tempFolder!!.absolutePath } + check(tempFolder!!.mkdir()) { "Could not create folder: " + tempFolder!!.absolutePath } + } + + override fun getRoot(): File { + checkNotNull(tempFolder) { "the temporary folder has not yet been created" } + return tempFolder!! + } + + /** + * To be called in the [.apply]. + */ + protected fun setTestName(description: Description) { + testName = description.displayName + } + + @Synchronized + fun setUnitTestFailed() { + isUnitTestFailed = true + } + + // This builder creates a configuration that is *NOT* managed. + // You have to delete it yourself. + private fun createConfigurationBuilder(): RealmConfiguration.Builder { + return RealmConfiguration.Builder().directory(root) + } + + fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } + + fun createConfiguration( + name: String, + key: String?, + module: Any, + schemaVersion: Long, + migration: RealmMigration? + ): RealmConfiguration { + val builder = createConfigurationBuilder() + builder + .directory(root) + .name(name) + .apply { + if (key != null) { + encryptionKey(key.decodeHex()) + } + } + .modules(module) + // Allow writes on UI + .allowWritesOnUiThread(true) + .schemaVersion(schemaVersion) + .apply { + migration?.let { migration(it) } + } + val configuration = builder.build() + configurations.add(configuration) + return configuration + } + + // Copies a Realm file from assets to temp dir + @Throws(IOException::class) + fun copyRealmFromAssets(context: Context, realmPath: String, newName: String) { + val config = RealmConfiguration.Builder() + .directory(root) + .name(newName) + .build() + copyRealmFromAssets(context, realmPath, config) + } + + @Throws(IOException::class) + fun copyRealmFromAssets(context: Context, realmPath: String, config: RealmConfiguration) { + check(!File(config.path).exists()) { String.format(Locale.ENGLISH, "%s exists!", config.path) } + val outFile = File(config.realmDirectory, config.realmFileName) + copyFileFromAssets(context, realmPath, outFile) + } + + @Throws(IOException::class) + fun copyFileFromAssets(context: Context, assetPath: String?, outFile: File?) { + var stream: InputStream? = null + var os: FileOutputStream? = null + try { + stream = context.assets.open(assetPath!!) + os = FileOutputStream(outFile) + val buf = ByteArray(1024) + var bytesRead: Int + while (stream.read(buf).also { bytesRead = it } > -1) { + os.write(buf, 0, bytesRead) + } + } finally { + if (stream != null) { + try { + stream.close() + } catch (ignore: IOException) { + } + } + if (os != null) { + try { + os.close() + } catch (ignore: IOException) { + } + } + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt index a37d2ce015..a52e3cd7c7 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt @@ -66,7 +66,7 @@ class PollAggregationTest : InstrumentedTest { val aliceEventsListener = object : Timeline.Listener { override fun onTimelineUpdated(snapshot: List ) { - snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START }?.let { pollEvent -> + snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START.values }?.let { pollEvent -> val pollEventId = pollEvent.eventId val pollContent = pollEvent.root.content?.toModel () val pollSummary = pollEvent.annotations?.pollResponseSummary diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index 6ef90193d8..81351523e9 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.session.search +import org.amshove.kluent.shouldBeEqualTo import org.junit.Assert.assertTrue import org.junit.FixMethodOrder import org.junit.Test @@ -43,7 +44,7 @@ class SearchMessagesTest : InstrumentedTest { cryptoTestData.firstSession .searchService() .search( - searchTerm = "lore", + searchTerm = "lorem", limit = 10, includeProfile = true, afterLimit = 0, @@ -61,7 +62,7 @@ class SearchMessagesTest : InstrumentedTest { cryptoTestData.firstSession .searchService() .search( - searchTerm = "lore", + searchTerm = "lorem", roomId = cryptoTestData.roomId, limit = 10, includeProfile = true, @@ -73,7 +74,28 @@ class SearchMessagesTest : InstrumentedTest { } } - private fun doTest(block: suspend (CryptoTestData) -> SearchResult) = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> + @Test + fun sendTextMessageAndSearchPartOfItIncompleteWord() { + doTest(expectedNumberOfResult = 0) { cryptoTestData -> + cryptoTestData.firstSession + .searchService() + .search( + searchTerm = "lore", /* incomplete word */ + roomId = cryptoTestData.roomId, + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null + ) + } + } + + private fun doTest( + expectedNumberOfResult: Int = 2, + block: suspend (CryptoTestData) -> SearchResult, + ) = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper -> val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId @@ -87,7 +109,7 @@ class SearchMessagesTest : InstrumentedTest { val data = block.invoke(cryptoTestData) - assertTrue(data.results?.size == 2) + data.results?.size shouldBeEqualTo expectedNumberOfResult assertTrue( data.results ?.all { diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index af9439dc9b..aa6d54ad3c 100755 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -232,9 +232,13 @@ internal class DefaultCryptoService @Inject constructor( } override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { + deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor) + } + + override suspend fun deleteDevices(deviceIds: List , userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { withContext(coroutineDispatchers.crypto) { deleteDeviceTask - .execute(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) + .execute(DeleteDeviceTask.Params(deviceIds, userInteractiveAuthInterceptor, null)) } } @@ -242,7 +246,7 @@ internal class DefaultCryptoService @Inject constructor( return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version } - override suspend fun getMyCryptoDevice(): CryptoDeviceInfo { + override fun getMyCryptoDevice(): CryptoDeviceInfo { return myDeviceInfoHolder.get().myDevice } @@ -343,8 +347,8 @@ internal class DefaultCryptoService @Inject constructor( } } - fun onSyncWillProcess(isInitialSync: Boolean) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + override suspend fun onSyncWillProcess(isInitialSync: Boolean) { + withContext(coroutineDispatchers.crypto) { if (isInitialSync) { try { // On initial sync, we start all our tracking from diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 4f3900adb9..7e9e156003 100755 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -18,13 +18,17 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.extensions.measureMetric +import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper +import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope @@ -47,8 +51,11 @@ internal class DeviceListManager @Inject constructor( coroutineDispatchers: MatrixCoroutineDispatchers, private val taskExecutor: TaskExecutor, private val clock: Clock, + matrixConfiguration: MatrixConfiguration ) { + private val metricPlugins = matrixConfiguration.metricPlugins + interface UserDevicesUpdateListener { fun onUsersDeviceUpdate(userIds: List ) } @@ -345,19 +352,25 @@ internal class DeviceListManager @Inject constructor( return MXUsersDevicesMap() } val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken()) - val response = try { - downloadKeysForUsersTask.execute(params) - } catch (throwable: Throwable) { - Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") - if (throwable is CancellationException) { - // the crypto module is getting closed, so we cannot access the DB anymore - Timber.w("The crypto module is closed, ignoring this error") - } else { - onKeysDownloadFailed(filteredUsers) + val relevantPlugins = metricPlugins.filterIsInstance () + + val response: KeysQueryResponse + relevantPlugins.measureMetric { + response = try { + downloadKeysForUsersTask.execute(params) + } catch (throwable: Throwable) { + Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") + if (throwable is CancellationException) { + // the crypto module is getting closed, so we cannot access the DB anymore + Timber.w("The crypto module is closed, ignoring this error") + } else { + onKeysDownloadFailed(filteredUsers) + } + throw throwable } - throw throwable + Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } - Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") + for (userId in filteredUsers) { // al devices = val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt index 3218b99948..0f29404d4f 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/CrossSigningOlm.kt @@ -83,9 +83,7 @@ internal class CrossSigningOlm @Inject constructor( val signaturesMadeByMyKey = signatures[myUserID] // Signatures made by me ?.get("ed25519:$pubKey") - if (signaturesMadeByMyKey.isNullOrBlank()) { - throw IllegalArgumentException("Not signed with my key $type") - } + require(signaturesMadeByMyKey.orEmpty().isNotBlank()) { "Not signed with my key $type" } // Check that Alice USK signature of Bob MSK is valid olmUtility.verifyEd25519Signature(signaturesMadeByMyKey, pubKey, JsonCanonicalizer.getCanonicalJson(Map::class.java, signable)) diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index 9275da2535..0128b11a64 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -22,6 +22,7 @@ import com.squareup.moshi.JsonClass import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.where +import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo @@ -175,7 +176,9 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses ?.devices val trustMap = devicesEntities?.associateWith { device -> - crossSigningService.checkDeviceTrust(userId, device.deviceId ?: "", CryptoMapper.mapToModel(device).trustLevel?.locallyVerified) + runBlocking { + crossSigningService.checkDeviceTrust(userId, device.deviceId ?: "", CryptoMapper.mapToModel(device).trustLevel?.locallyVerified) + } } // Update trust if needed diff --git a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 665cc50627..a931a45ee7 100644 --- a/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -255,23 +255,23 @@ internal class DefaultVerificationService @Inject constructor( // } } - override suspend fun sasCodeMatch(theyMatch: Boolean, transactionId: String) { - val deferred = CompletableDeferred () - stateMachine.send( - if (theyMatch) { - VerificationIntent.ActionSASCodeMatches( - transactionId, - deferred, - ) - } else { - VerificationIntent.ActionSASCodeDoesNotMatch( - transactionId, - deferred, - ) - } - ) - deferred.await() - } +// override suspend fun sasCodeMatch(theyMatch: Boolean, transactionId: String) { +// val deferred = CompletableDeferred () +// stateMachine.send( +// if (theyMatch) { +// VerificationIntent.ActionSASCodeMatches( +// transactionId, +// deferred, +// ) +// } else { +// VerificationIntent.ActionSASCodeDoesNotMatch( +// transactionId, +// deferred, +// ) +// } +// ) +// deferred.await() +// } suspend fun onRoomReadyFromOneOfMyOtherDevice(event: Event) { val requestInfo = event.content.toModel () @@ -1212,8 +1212,7 @@ internal class DefaultVerificationService @Inject constructor( // } override suspend fun startKeyVerification(method: VerificationMethod, otherUserId: String, requestId: String): String? { - if (method != VerificationMethod.SAS) throw IllegalArgumentException("Unknown verification method") - + require(method == VerificationMethod.SAS) { "Unknown verification method" } val deferred = CompletableDeferred () stateMachine.send( VerificationIntent.ActionStartSasVerification( diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml index de0731422c..7f940d4e1c 100644 --- a/matrix-sdk-android/src/main/AndroidManifest.xml +++ b/matrix-sdk-android/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index e9456cdd9b..0e3559f01b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api import okhttp3.ConnectionSpec import okhttp3.Interceptor import org.matrix.android.sdk.api.crypto.MXCryptoConfig +import org.matrix.android.sdk.api.metrics.MetricPlugin import java.net.Proxy data class MatrixConfiguration( @@ -75,4 +76,9 @@ data class MatrixConfiguration( * Sync configuration. */ val syncConfig: SyncConfig = SyncConfig(), + + /** + * Metrics plugin that can be used to capture metrics from matrix-sdk-android. + */ + val metricPlugins: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/account/LocalNotificationSettingsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/account/LocalNotificationSettingsContent.kt new file mode 100644 index 0000000000..2a95ccce7a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/account/LocalNotificationSettingsContent.kt @@ -0,0 +1,25 @@ +/* + * 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.api.account + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class LocalNotificationSettingsContent( + @Json(name = "is_silenced") val isSilenced: Boolean = false +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt index 5ae70e1978..252c33a8c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt @@ -124,4 +124,24 @@ interface AuthenticationService { initialDeviceName: String, deviceId: String? = null ): Session + + /** + * @param homeServerConnectionConfig the information about the homeserver and other configuration + * Return true if qr code login is supported by the server, false otherwise. + */ + suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean + + /** + * Authenticate using m.login.token method during sign in with QR code. + * @param homeServerConnectionConfig the information about the homeserver and other configuration + * @param loginToken the m.login.token + * @param initialDeviceName the initial device name + * @param deviceId the device id, optional. If not provided or null, the server will generate one. + */ + suspend fun loginUsingQrLoginToken( + homeServerConnectionConfig: HomeServerConnectionConfig, + loginToken: String, + initialDeviceName: String? = null, + deviceId: String? = null + ): Session } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt index 627a825679..991b7b654d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt @@ -22,7 +22,8 @@ enum class LoginType { UNSUPPORTED, CUSTOM, DIRECT, - UNKNOWN; + UNKNOWN, + QR; companion object { @@ -32,6 +33,7 @@ enum class LoginType { UNSUPPORTED.name -> UNSUPPORTED CUSTOM.name -> CUSTOM DIRECT.name -> DIRECT + QR.name -> QR else -> UNKNOWN } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt new file mode 100644 index 0000000000..7f0e828f62 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MetricsExtensions.kt @@ -0,0 +1,67 @@ +/* + * 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.api.extensions + +import org.matrix.android.sdk.api.metrics.MetricPlugin +import org.matrix.android.sdk.api.metrics.SpannableMetricPlugin +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Executes the given [block] while measuring the transaction. + * + * @param block Action/Task to be executed within this span. + */ +@OptIn(ExperimentalContracts::class) +inline fun List .measureMetric(block: () -> Unit) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + this.forEach { plugin -> plugin.startTransaction() } // Start the transaction. + block() + } catch (throwable: Throwable) { + this.forEach { plugin -> plugin.onError(throwable) } // Capture if there is any exception thrown. + throw throwable + } finally { + this.forEach { plugin -> plugin.finishTransaction() } // Finally, finish this transaction. + } +} + +/** + * Executes the given [block] while measuring a span. + * + * @param operation Name of the new span. + * @param description Description of the new span. + * @param block Action/Task to be executed within this span. + */ +@OptIn(ExperimentalContracts::class) +inline fun List .measureSpan(operation: String, description: String, block: () -> Unit) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + this.forEach { plugin -> plugin.startSpan(operation, description) } // Start the transaction. + block() + } catch (throwable: Throwable) { + this.forEach { plugin -> plugin.onError(throwable) } // Capture if there is any exception thrown. + throw throwable + } finally { + this.forEach { plugin -> plugin.finishSpan() } // Finally, finish this transaction. + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt index ae65963f37..22af8cebbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt @@ -27,6 +27,7 @@ open class LoggerTag(name: String, parentTag: LoggerTag? = null) { object SYNC : LoggerTag("SYNC") object VOIP : LoggerTag("VOIP") object CRYPTO : LoggerTag("CRYPTO") + object RENDEZVOUS : LoggerTag("RZ") val value: String = if (parentTag == null) { name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/DownloadDeviceKeysMetricsPlugin.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/DownloadDeviceKeysMetricsPlugin.kt new file mode 100644 index 0000000000..66ec0abf51 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/DownloadDeviceKeysMetricsPlugin.kt @@ -0,0 +1,32 @@ +/* + * 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.api.metrics + +import org.matrix.android.sdk.api.logger.LoggerTag +import timber.log.Timber + +private val loggerTag = LoggerTag("DownloadKeysMetricsPlugin", LoggerTag.CRYPTO) + +/** + * Extension of MetricPlugin for download_device_keys task. + */ +interface DownloadDeviceKeysMetricsPlugin : MetricPlugin { + + override fun logTransaction(message: String?) { + Timber.tag(loggerTag.value).v("## downloadDeviceKeysMetricPlugin() : $message") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/MetricPlugin.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/MetricPlugin.kt new file mode 100644 index 0000000000..3a4b13c494 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/MetricPlugin.kt @@ -0,0 +1,46 @@ +/* + * 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.api.metrics + +/** + * A plugin that can be used to capture metrics in Client. + */ +interface MetricPlugin { + /** + * Start the measurement of the metrics as soon as task is started. + */ + fun startTransaction() + + /** + * Mark the measuring transaction finished once the task is completed. + */ + fun finishTransaction() + + /** + * Invoked when there is any error in the ongoing task. The metrics tool can use this information to attach to the ongoing transaction. + * + * @param throwable Exception thrown in the running task. + */ + fun onError(throwable: Throwable) + + /** + * Can be used to log this transaction. + */ + fun logTransaction(message: String? = "") { + // no-op + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SpannableMetricPlugin.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SpannableMetricPlugin.kt new file mode 100644 index 0000000000..54aa21877e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SpannableMetricPlugin.kt @@ -0,0 +1,36 @@ +/* + * 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.api.metrics + +/** + * A plugin that tracks span along with transactions. + */ +interface SpannableMetricPlugin : MetricPlugin { + + /** + * Starts the span for a sub-task. + * + * @param operation Name of the new span. + * @param description Description of the new span. + */ + fun startSpan(operation: String, description: String) + + /** + * Finish the span when sub-task is completed. + */ + fun finishSpan() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt new file mode 100644 index 0000000000..79ece002e9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/metrics/SyncDurationMetricPlugin.kt @@ -0,0 +1,32 @@ +/* + * 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.api.metrics + +import org.matrix.android.sdk.api.logger.LoggerTag +import timber.log.Timber + +private val loggerTag = LoggerTag("SyncDurationMetricPlugin", LoggerTag.CRYPTO) + +/** + * An spannable metric plugin for sync response handling task. + */ +interface SyncDurationMetricPlugin : SpannableMetricPlugin { + + override fun logTransaction(message: String?) { + Timber.tag(loggerTag.value).v("## syncResponseHandler() : $message") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt new file mode 100644 index 0000000000..914513ec34 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt @@ -0,0 +1,229 @@ +/* + * 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.api.rendezvous + +import android.net.Uri +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel +import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode +import org.matrix.android.sdk.api.rendezvous.model.Outcome +import org.matrix.android.sdk.api.rendezvous.model.Payload +import org.matrix.android.sdk.api.rendezvous.model.PayloadType +import org.matrix.android.sdk.api.rendezvous.model.Protocol +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent +import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.util.MatrixJsonParser +import timber.log.Timber + +/** + * Implementation of MSC3906 to sign in + E2EE set up using a QR code. + */ +class Rendezvous( + val channel: RendezvousChannel, + val theirIntent: RendezvousIntent, +) { + companion object { + private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value + + @Throws(RendezvousError::class) + fun buildChannelFromCode(code: String): Rendezvous { + val parsed = try { + // we rely on moshi validating the code and throwing exception if invalid JSON or doesn't + MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code) + } catch (a: Throwable) { + throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode) + } ?: throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode) + + val transport = SimpleHttpRendezvousTransport(parsed.rendezvous.transport.uri) + + return Rendezvous( + ECDHRendezvousChannel(transport, parsed.rendezvous.key), + parsed.intent + ) + } + } + + private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java) + + // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE + val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE + + @Throws(RendezvousError::class) + private suspend fun checkCompatibility() { + val incompatible = theirIntent == ourIntent + + Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible") + + if (incompatible) { + // inform the other side + send(Payload(PayloadType.FINISH, intent = ourIntent)) + if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) { + throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn) + } else { + throw RendezvousError("The other device is already signed in", RendezvousFailureReason.OtherDeviceAlreadySignedIn) + } + } + } + + @Throws(RendezvousError::class) + suspend fun startAfterScanningCode(): String { + val checksum = channel.connect() + + Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum") + + checkCompatibility() + + // get protocols + Timber.tag(TAG).i("Waiting for protocols") + val protocolsResponse = receive() + + if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) { + send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED)) + throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver) + } + + send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN)) + + return checksum + } + + @Throws(RendezvousError::class) + suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session { + Timber.tag(TAG).i("Waiting for login_token") + + val loginToken = receive() + + if (loginToken?.type == PayloadType.FINISH) { + when (loginToken.outcome) { + Outcome.DECLINED -> { + throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined) + } + Outcome.UNSUPPORTED -> { + throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver) + } + else -> { + throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown) + } + } + } + + val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError) + val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError) + + Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver") + + val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver)) + return authenticationService.loginUsingQrLoginToken(hsConfig, token) + } + + @Throws(RendezvousError::class) + suspend fun completeVerificationOnNewDevice(session: Session) { + val userId = session.myUserId + val crypto = session.cryptoService() + val deviceId = crypto.getMyCryptoDevice().deviceId + val deviceKey = crypto.getMyCryptoDevice().fingerprint() + send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey)) + + // await confirmation of verification + val verificationResponse = receive() + if (verificationResponse?.outcome == Outcome.VERIFIED) { + val verifyingDeviceId = verificationResponse.verifyingDeviceId + ?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError) + val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId) + if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) { + Timber.tag(TAG).w( + "Verifying device $verifyingDeviceId key doesn't match: ${ + verifyingDeviceFromServer?.fingerprint() + } vs ${verificationResponse.verifyingDeviceKey})" + ) + // inform the other side + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + } + + verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice -> + // verifying device provided us with a master key, so use it to check integrity + + // see what the homeserver told us + val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey() + + // n.b. if no local master key this is a problem, as well as it not matching + if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) { + Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey") + // inform the other side + send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR)) + throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue) + } + + // set other device as verified + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + + Timber.tag(TAG).i("Setting master key as trusted") + crypto.crossSigningService().markMyMasterKeyAsTrusted() + } ?: run { + // set other device as verified anyway + Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified") + crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId) + + Timber.tag(TAG).i("No master key given by verifying device") + } + + // request secrets from the verifying device + Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId") + + session.sharedSecretStorageService().let { + it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId) + it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId) + } + } else { + Timber.tag(TAG).i("Not doing verification") + } + } + + @Throws(RendezvousError::class) + private suspend fun receive(): Payload? { + val data = channel.receive() ?: return null + val payload = try { + adapter.fromJson(data.toString(Charsets.UTF_8)) + } catch (e: Exception) { + Timber.tag(TAG).w(e, "Failed to parse payload") + throw RendezvousError("Invalid payload received", RendezvousFailureReason.Unknown) + } + + return payload + } + + private suspend fun send(payload: Payload) { + channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8)) + } + + suspend fun close() { + channel.close() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt new file mode 100644 index 0000000000..0956a5b0a0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt @@ -0,0 +1,51 @@ +/* + * 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.api.rendezvous + +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError + +/** + * Representation of a rendezvous channel such as that described by MSC3903. + */ +interface RendezvousChannel { + val transport: RendezvousTransport + + /** + * @returns the checksum/confirmation digits to be shown to the user + */ + @Throws(RendezvousError::class) + suspend fun connect(): String + + /** + * Send a payload via the channel. + * @param data payload to send + */ + @Throws(RendezvousError::class) + suspend fun send(data: ByteArray) + + /** + * Receive a payload from the channel. + * @returns the received payload + */ + @Throws(RendezvousError::class) + suspend fun receive(): ByteArray? + + /** + * Closes the channel and cleans up. + */ + suspend fun close() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt new file mode 100644 index 0000000000..18e625d825 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt @@ -0,0 +1,32 @@ +/* + * 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.api.rendezvous + +enum class RendezvousFailureReason(val canRetry: Boolean = true) { + UserDeclined, + OtherDeviceNotSignedIn, + OtherDeviceAlreadySignedIn, + Unknown, + Expired, + UserCancelled, + InvalidCode, + UnsupportedAlgorithm(false), + UnsupportedTransport(false), + UnsupportedHomeserver(false), + ProtocolError, + E2EESecurityIssue(false) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt new file mode 100644 index 0000000000..81632e951a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt @@ -0,0 +1,36 @@ +/* + * 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.api.rendezvous + +import okhttp3.MediaType +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails + +interface RendezvousTransport { + var ready: Boolean + + @Throws(RendezvousError::class) + suspend fun details(): RendezvousTransportDetails + + @Throws(RendezvousError::class) + suspend fun send(contentType: MediaType, data: ByteArray) + + @Throws(RendezvousError::class) + suspend fun receive(): ByteArray? + + suspend fun close() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt new file mode 100644 index 0000000000..4612e5463b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt @@ -0,0 +1,183 @@ +/* + * 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.api.rendezvous.channels + +import android.util.Base64 +import com.squareup.moshi.JsonClass +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import okhttp3.MediaType.Companion.toMediaType +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.rendezvous.RendezvousChannel +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousTransport +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm +import org.matrix.android.sdk.api.util.MatrixJsonParser +import org.matrix.android.sdk.internal.crypto.verification.getDecimalCodeRepresentation +import org.matrix.olm.OlmSAS +import timber.log.Timber +import java.security.SecureRandom +import java.util.LinkedList +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +/** + * Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903: + * https://github.com/matrix-org/matrix-spec-proposals/pull/3903 + */ +class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?) : RendezvousChannel { + companion object { + private const val ALGORITHM_SPEC = "AES/GCM/NoPadding" + private const val KEY_SPEC = "AES" + private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value + } + + @JsonClass(generateAdapter = true) + internal data class ECDHPayload( + val algorithm: SecureRendezvousChannelAlgorithm? = null, + val key: String? = null, + val ciphertext: String? = null, + val iv: String? = null + ) + + private val olmSASMutex = Mutex() + private var olmSAS: OlmSAS? + private val ourPublicKey: ByteArray + private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java) + private var theirPublicKey: ByteArray? = null + private var aesKey: ByteArray? = null + + init { + theirPublicKeyBase64?.let { + theirPublicKey = Base64.decode(it, Base64.NO_WRAP) + } + olmSAS = OlmSAS() + ourPublicKey = Base64.decode(olmSAS!!.publicKey, Base64.NO_WRAP) + } + + @Throws(RendezvousError::class) + override suspend fun connect(): String { + val sas = olmSAS ?: throw RendezvousError("Channel closed", RendezvousFailureReason.Unknown) + val isInitiator = theirPublicKey == null + + if (isInitiator) { + Timber.tag(TAG).i("Waiting for other device to send their public key") + val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError) + + if (res.key == null) { + throw RendezvousError( + "Unsupported algorithm: ${res.algorithm}", + RendezvousFailureReason.UnsupportedAlgorithm, + ) + } + theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP) + } else { + // send our public key unencrypted + Timber.tag(TAG).i("Sending public key") + send( + ECDHPayload( + algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1, + key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP) + ) + ) + } + + olmSASMutex.withLock { + sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP)) + + val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP) + val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP) + val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey" + + aesKey = sas.generateShortCode(aesInfo, 32) + + val rawChecksum = sas.generateShortCode(aesInfo, 5) + return rawChecksum.getDecimalCodeRepresentation(separator = "-") + } + } + + private suspend fun send(payload: ECDHPayload) { + transport.send("application/json".toMediaType(), ecdhAdapter.toJson(payload).toByteArray(Charsets.UTF_8)) + } + + override suspend fun send(data: ByteArray) { + if (aesKey == null) { + throw IllegalStateException("Shared secret not established") + } + send(encrypt(data)) + } + + private suspend fun receiveAsPayload(): ECDHPayload? { + transport.receive()?.toString(Charsets.UTF_8)?.let { + return ecdhAdapter.fromJson(it) + } ?: return null + } + + override suspend fun receive(): ByteArray? { + if (aesKey == null) { + throw IllegalStateException("Shared secret not established") + } + val payload = receiveAsPayload() ?: return null + return decrypt(payload) + } + + override suspend fun close() { + val sas = olmSAS ?: throw IllegalStateException("Channel already closed") + olmSASMutex.withLock { + // this does a double release check already so we don't re-check ourselves + sas.releaseSas() + olmSAS = null + } + transport.close() + } + + private fun encrypt(plainText: ByteArray): ECDHPayload { + val iv = ByteArray(16) + SecureRandom().nextBytes(iv) + + val cipherText = LinkedList () + + val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC) + val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC) + val ivParameterSpec = IvParameterSpec(iv) + encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec) + cipherText.addAll(encryptCipher.update(plainText).toList()) + cipherText.addAll(encryptCipher.doFinal().toList()) + + return ECDHPayload( + ciphertext = Base64.encodeToString(cipherText.toByteArray(), Base64.NO_WRAP), + iv = Base64.encodeToString(iv, Base64.NO_WRAP) + ) + } + + private fun decrypt(payload: ECDHPayload): ByteArray { + val iv = Base64.decode(payload.iv, Base64.NO_WRAP) + val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC) + val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC) + val ivParameterSpec = IvParameterSpec(iv) + encryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + + val plainText = LinkedList () + plainText.addAll(encryptCipher.update(Base64.decode(payload.ciphertext, Base64.NO_WRAP)).toList()) + plainText.addAll(encryptCipher.doFinal().toList()) + + return plainText.toByteArray() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt new file mode 100644 index 0000000000..55bac6397e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt @@ -0,0 +1,26 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ECDHRendezvous( + val transport: SimpleHttpRendezvousTransportDetails, + val algorithm: SecureRendezvousChannelAlgorithm, + val key: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt new file mode 100644 index 0000000000..575b5d4bfd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt @@ -0,0 +1,25 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ECDHRendezvousCode( + val intent: RendezvousIntent, + val rendezvous: ECDHRendezvous +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt new file mode 100644 index 0000000000..0ebd1f88b3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt @@ -0,0 +1,38 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class Outcome(val value: String) { + @Json(name = "success") + SUCCESS("success"), + + @Json(name = "declined") + DECLINED("declined"), + + @Json(name = "unsupported") + UNSUPPORTED("unsupported"), + + @Json(name = "verified") + VERIFIED("verified"), + + @Json(name = "e2ee_security_error") + E2EE_SECURITY_ERROR("e2ee_security_error") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt new file mode 100644 index 0000000000..04631ce959 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt @@ -0,0 +1,36 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class Payload( + val type: PayloadType, + val intent: RendezvousIntent? = null, + val outcome: Outcome? = null, + val protocols: List ? = null, + val protocol: Protocol? = null, + val homeserver: String? = null, + @Json(name = "login_token") val loginToken: String? = null, + @Json(name = "device_id") val deviceId: String? = null, + @Json(name = "device_key") val deviceKey: String? = null, + @Json(name = "verifying_device_id") val verifyingDeviceId: String? = null, + @Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null, + @Json(name = "master_key") val masterKey: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt new file mode 100644 index 0000000000..33beb1f525 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt @@ -0,0 +1,32 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +internal enum class PayloadType(val value: String) { + @Json(name = "m.login.start") + START("m.login.start"), + + @Json(name = "m.login.finish") + FINISH("m.login.finish"), + + @Json(name = "m.login.progress") + PROGRESS("m.login.progress") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt new file mode 100644 index 0000000000..6fce2fa11c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt @@ -0,0 +1,26 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class Protocol(val value: String) { + @Json(name = "org.matrix.msc3906.login_token") + LOGIN_TOKEN("org.matrix.msc3906.login_token") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt new file mode 100644 index 0000000000..c52b11a322 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt @@ -0,0 +1,21 @@ +/* + * 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.api.rendezvous.model + +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason + +class RendezvousError(val description: String, val reason: RendezvousFailureReason) : Exception(description) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt new file mode 100644 index 0000000000..65037e1252 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt @@ -0,0 +1,26 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class RendezvousIntent { + @Json(name = "login.start") LOGIN_ON_NEW_DEVICE, + @Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt new file mode 100644 index 0000000000..1bde43ab7e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt @@ -0,0 +1,24 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +open class RendezvousTransportDetails( + val type: RendezvousTransportType +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt new file mode 100644 index 0000000000..6fca7efa71 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt @@ -0,0 +1,26 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class RendezvousTransportType(val value: String) { + @Json(name = "org.matrix.msc3886.http.v1") + MSC3886_SIMPLE_HTTP_V1("org.matrix.msc3886.http.v1") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt new file mode 100644 index 0000000000..75f0024fda --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt @@ -0,0 +1,26 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class SecureRendezvousChannelAlgorithm(val value: String) { + @Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256") + ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt new file mode 100644 index 0000000000..049aa8b756 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt @@ -0,0 +1,24 @@ +/* + * 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.api.rendezvous.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SimpleHttpRendezvousTransportDetails( + val uri: String +) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt new file mode 100644 index 0000000000..620b599e3d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt @@ -0,0 +1,173 @@ +/* + * 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.api.rendezvous.transports + +import kotlinx.coroutines.delay +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason +import org.matrix.android.sdk.api.rendezvous.RendezvousTransport +import org.matrix.android.sdk.api.rendezvous.model.RendezvousError +import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails +import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886 + */ +class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport { + companion object { + private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value + } + + override var ready = false + private var cancelled = false + private var uri: String? + private var etag: String? = null + private var expiresAt: Date? = null + + init { + uri = rendezvousUri + } + + override suspend fun details(): RendezvousTransportDetails { + val uri = uri ?: throw IllegalStateException("Rendezvous not set up") + + return SimpleHttpRendezvousTransportDetails(uri) + } + + @Throws(RendezvousError::class) + override suspend fun send(contentType: MediaType, data: ByteArray) { + if (cancelled) { + throw IllegalStateException("Rendezvous cancelled") + } + + val method = if (uri != null) "PUT" else "POST" + val uri = this.uri ?: throw RuntimeException("No rendezvous URI") + + val httpClient = okhttp3.OkHttpClient.Builder().build() + + val request = Request.Builder() + .url(uri) + .method(method, data.toRequestBody()) + .header("content-type", contentType.toString()) + + etag?.let { + request.header("if-match", it) + } + + val response = httpClient.newCall(request.build()).execute() + + if (response.code == 404) { + throw get404Error() + } + etag = response.header("etag") + + Timber.tag(TAG).i("Sent data to $uri new etag $etag") + + if (method == "POST") { + val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response") + + response.header("expires")?.let { + val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) + expiresAt = format.parse(it) + } + + // resolve location header which could be relative or absolute + this.uri = response.request.url.toUri().resolve(location).toString() + ready = true + } + } + + @Throws(RendezvousError::class) + override suspend fun receive(): ByteArray? { + if (cancelled) { + throw IllegalStateException("Rendezvous cancelled") + } + val uri = uri ?: throw IllegalStateException("Rendezvous not set up") + val httpClient = okhttp3.OkHttpClient.Builder().build() + while (true) { + Timber.tag(TAG).i("Polling: $uri after etag $etag") + val request = Request.Builder() + .url(uri) + .get() + + etag?.let { + request.header("if-none-match", it) + } + + val response = httpClient.newCall(request.build()).execute() + + try { + // expired + if (response.code == 404) { + throw get404Error() + } + + // rely on server expiring the channel rather than checking ourselves + + if (response.header("content-type") != "application/json") { + response.header("etag")?.let { + etag = it + } + } else if (response.code == 200) { + response.header("etag")?.let { + etag = it + } + return response.body?.bytes() + } + + // sleep for a second before polling again + // we rely on the server expiring the channel rather than checking it ourselves + delay(1000) + } finally { + response.close() + } + } + } + + private fun get404Error(): RendezvousError { + if (expiresAt != null && Date() > expiresAt) { + return RendezvousError("Expired", RendezvousFailureReason.Expired) + } + + return RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown) + } + + override suspend fun close() { + cancelled = true + ready = false + + uri?.let { + try { + val httpClient = okhttp3.OkHttpClient.Builder().build() + val request = Request.Builder() + .url(it) + .delete() + .build() + httpClient.newCall(request).execute() + } catch (e: Throwable) { + Timber.tag(TAG).w(e, "Failed to delete channel") + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt index 91167d896f..0edd824c26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataTypes.kt @@ -28,4 +28,5 @@ object UserAccountDataTypes { const val TYPE_IDENTITY_SERVER = "m.identity_server" const val TYPE_ACCEPTED_TERMS = "m.accepted_terms" const val TYPE_OVERRIDE_COLORS = "im.vector.setting.override_colors" + const val TYPE_LOCAL_NOTIFICATION_SETTINGS = "org.matrix.msc3890.local_notification_settings." } 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 610e73fa99..c6ee3aad30 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.crypto import android.content.Context +import androidx.annotation.Size import androidx.lifecycle.LiveData import androidx.paging.PagedList import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -56,6 +57,8 @@ interface CryptoService { suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) + suspend fun deleteDevices(@Size(min = 1) deviceIds: List , userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) + fun getCryptoVersion(context: Context, longFormat: Boolean): String fun isCryptoEnabled(): Boolean @@ -68,7 +71,7 @@ interface CryptoService { suspend fun getUserDevices(userId: String): List - suspend fun getMyCryptoDevice(): CryptoDeviceInfo + fun getMyCryptoDevice(): CryptoDeviceInfo fun getGlobalBlacklistUnverifiedDevices(): Boolean @@ -216,6 +219,7 @@ interface CryptoService { fun close() fun start() + suspend fun onSyncWillProcess(isInitialSync: Boolean) fun isStarted(): Boolean suspend fun receiveSyncChanges(toDevice: ToDeviceSyncResponse?, deviceChanges: DeviceListResponse?, keyCounts: DeviceOneTimeKeysCountSyncResponse?) fun onLiveEvent(roomId: String, event: Event, initialSync: Boolean) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt index 7bd3c0667d..263affb08f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt @@ -16,21 +16,20 @@ package org.matrix.android.sdk.api.session.crypto.model -import org.matrix.rustcomponents.sdk.crypto.KeysImportResult - data class ImportRoomKeysResult( val totalNumberOfKeys: Int, val successfullyNumberOfImportedKeys: Int, /**It's a map from room id to a map of the sender key to a list of session*/ val importedSessionInfo: Map >> -) { - companion object { - fun fromOlm(result: KeysImportResult): ImportRoomKeysResult { - return ImportRoomKeysResult( - result.total.toInt(), - result.imported.toInt(), - result.keys - ) - } - } -} +) +// { +// companion object { +// fun fromOlm(result: KeysImportResult): ImportRoomKeysResult { +// return ImportRoomKeysResult( +// result.total.toInt(), +// result.imported.toInt(), +// result.keys +// ) +// } +// } +// } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedAnnotation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedAnnotation.kt index 239f749993..5b2ab77467 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedAnnotation.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedAnnotation.kt @@ -38,5 +38,4 @@ data class AggregatedAnnotation( override val limited: Boolean? = false, override val count: Int? = 0, val chunk: List ? = null - ) : UnsignedRelationInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt index ae8ed3941f..6577a9b41e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedRelations.kt @@ -19,7 +19,8 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * + * Server side relation aggregation. + * ``` * { * "m.annotation": { * "chunk": [ @@ -43,12 +44,13 @@ import com.squareup.moshi.JsonClass * "count": 1 * } * } - *
+ * ``` */ @JsonClass(generateAdapter = true) data class AggregatedRelations( @Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null, @Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null, + @Json(name = "m.replace") val replaces: AggregatedReplace? = null, @Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedReplace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedReplace.kt new file mode 100644 index 0000000000..2ae091a1a4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/AggregatedReplace.kt @@ -0,0 +1,33 @@ +/* + * 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.api.session.events.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Note that there can be multiple events with an m.replace relationship to a given event (for example, if an event is edited multiple times). + * These should be aggregated by the homeserver. + * https://spec.matrix.org/v1.4/client-server-api/#server-side-aggregation-of-mreplace-relationships + * + */ +@JsonClass(generateAdapter = true) +data class AggregatedReplace( + @Json(name = "event_id") val eventId: String? = null, + @Json(name = "origin_server_ts") val originServerTs: Long? = null, + @Json(name = "sender") val senderId: String? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index f5d2c0d9a0..40ce6ecb5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -26,12 +26,12 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent -import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.session.room.model.relation.isReply import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.threads.ThreadDetails @@ -52,7 +52,7 @@ inline funContent?.toModel(catchError: Boolean = true): T? { val moshiAdapter = moshi.adapter(T::class.java) return try { moshiAdapter.fromJsonValue(this) - } catch (e: Exception) { + } catch (e: Throwable) { if (catchError) { Timber.e(e, "To model failed : $e") null @@ -227,11 +227,14 @@ data class Event( return when { isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) isFileMessage() -> "sent a file." + isVoiceMessage() -> "sent a voice message." isAudioMessage() -> "sent an audio file." isImageMessage() -> "sent an image." isVideoMessage() -> "sent a video." - isSticker() -> "sent a sticker" + isSticker() -> "sent a sticker." isPoll() -> getPollQuestion() ?: "created a poll." + isLiveLocation() -> "Live location." + isLocationMessage() -> "has shared their location." else -> text } } @@ -357,6 +360,10 @@ fun Event.isAudioMessage(): Boolean { } } +fun Event.isVoiceMessage(): Boolean { + return this.asMessageAudioEvent()?.content?.voiceMessageIndicator != null +} + fun Event.isFileMessage(): Boolean { return when (getMsgType()) { MessageType.MSGTYPE_FILE -> true @@ -381,24 +388,18 @@ fun Event.isLocationMessage(): Boolean { } } -fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END +fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START.values || getClearType() in EventType.POLL_END.values fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER -fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO +fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO.values fun Event.getRelationContent(): RelationDefaultContent? { return if (isEncrypted()) { content.toModel ()?.relatesTo } else { - content.toModel ()?.relatesTo ?: run { - // Special cases when there is only a local msgtype for some event types - when (getClearType()) { - EventType.STICKER -> getClearContent().toModel ()?.relatesTo - in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel ()?.relatesTo - else -> null - } - } + content.toModel ()?.relatesTo + ?: getClearContent()?.get("m.relates_to")?.toContent().toModel() // Special cases when there is only a local msgtype for some event types } } @@ -415,7 +416,7 @@ fun Event.getRelationContentForType(type: String): RelationDefaultContent? = getRelationContent()?.takeIf { it.type == type } fun Event.isReply(): Boolean { - return getRelationContent()?.inReplyTo?.eventId != null + return getRelationContent().isReply() } fun Event.isReplyRenderedInThread(): Boolean { @@ -438,11 +439,11 @@ fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER && content?.toModel ()?.membership == Membership.INVITE fun Event.getPollContent(): MessagePollContent? { - return content.toModel () + return getClearContent().toModel () } fun Event.supportsNotification() = - this.getClearType() in EventType.MESSAGE + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + this.getClearType() in EventType.MESSAGE + EventType.POLL_START.values + EventType.STATE_ROOM_BEACON_INFO.values fun Event.isContentReportable() = - this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO + this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventExt.kt new file mode 100644 index 0000000000..32d5ebed8c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventExt.kt @@ -0,0 +1,46 @@ +/* + * 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.api.session.events.model + +fun Event.toValidDecryptedEvent(): ValidDecryptedEvent? { + if (!this.isEncrypted()) return null + val decryptedContent = this.getDecryptedContent() ?: return null + val eventId = this.eventId ?: return null + val roomId = this.roomId ?: return null + val type = this.getDecryptedType() ?: return null + val senderKey = this.getSenderKey() ?: return null + val algorithm = this.content?.get("algorithm") as? String ?: return null + + // copy the relation as it's in clear in the encrypted content + val updatedContent = this.content.get("m.relates_to")?.let { + decryptedContent.toMutableMap().apply { + put("m.relates_to", it) + } + } ?: decryptedContent + return ValidDecryptedEvent( + type = type, + eventId = eventId, + clearContent = updatedContent, + prevContent = this.prevContent, + originServerTs = this.originServerTs ?: 0, + cryptoSenderKey = senderKey, + roomId = roomId, + unsignedData = this.unsignedData, + redacts = this.redacts, + algorithm = algorithm + ) +} 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 b001a30342..021e634b24 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 @@ -49,11 +49,10 @@ object EventType { const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" - val STATE_ROOM_BEACON_INFO = listOf("org.matrix.msc3672.beacon_info", "m.beacon_info") - val BEACON_LOCATION_DATA = listOf("org.matrix.msc3672.beacon", "m.beacon") + val STATE_ROOM_BEACON_INFO = StableUnstableId(stable = "m.beacon_info", unstable = "org.matrix.msc3672.beacon_info") + val BEACON_LOCATION_DATA = StableUnstableId(stable = "m.beacon", unstable = "org.matrix.msc3672.beacon") const val STATE_SPACE_CHILD = "m.space.child" - const val STATE_SPACE_PARENT = "m.space.parent" /** @@ -81,8 +80,7 @@ object EventType { const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_REJECT = "m.call.reject" const val CALL_HANGUP = "m.call.hangup" - const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity" - const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity" + val CALL_ASSERTED_IDENTITY = StableUnstableId(stable = "m.call.asserted_identity", unstable = "org.matrix.call.asserted_identity") // This type is not processed by the client, just sent to the server const val CALL_REPLACES = "m.call.replaces" @@ -90,10 +88,7 @@ object EventType { // Key share events const val ROOM_KEY_REQUEST = "m.room_key_request" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" - val ROOM_KEY_WITHHELD = StableUnstableId( - stable = "m.room_key.withheld", - unstable = "org.matrix.room_key.withheld" - ) + val ROOM_KEY_WITHHELD = StableUnstableId(stable = "m.room_key.withheld", unstable = "org.matrix.room_key.withheld") const val REQUEST_SECRET = "m.secret.request" const val SEND_SECRET = "m.secret.send" @@ -112,9 +107,9 @@ object EventType { const val REACTION = "m.reaction" // Poll - val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start") - val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response") - val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end") + val POLL_START = StableUnstableId(stable = "m.poll.start", unstable = "org.matrix.msc3381.poll.start") + val POLL_RESPONSE = StableUnstableId(stable = "m.poll.response", unstable = "org.matrix.msc3381.poll.response") + val POLL_END = StableUnstableId(stable = "m.poll.end", unstable = "org.matrix.msc3381.poll.end") // Unwedging internal const val DUMMY = "m.dummy" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt new file mode 100644 index 0000000000..b305bf19b0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/ValidDecryptedEvent.kt @@ -0,0 +1,37 @@ +/* + * 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.api.session.events.model + +import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +data class ValidDecryptedEvent( + val type: String, + val eventId: String, + val clearContent: Content, + val prevContent: Content? = null, + val originServerTs: Long, + val cryptoSenderKey: String, + val roomId: String, + val unsignedData: UnsignedData? = null, + val redacts: String? = null, + val algorithm: String, +) + +fun ValidDecryptedEvent.getRelationContent(): RelationDefaultContent? { + return clearContent.toModel ()?.relatesTo +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index b5d6d891e4..11638837cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -59,7 +59,22 @@ data class HomeServerCapabilities( /** * True if the home server supports controlling the logout of all devices when changing password. */ - val canControlLogoutDevices: Boolean = false + val canControlLogoutDevices: Boolean = false, + + /** + * True if the home server supports login via qr code, false otherwise. + */ + val canLoginWithQrCode: Boolean = false, + + /** + * True if the home server supports threaded read receipts and unread notifications. + */ + val canUseThreadReadReceiptsAndNotifications: Boolean = false, + + /** + * True if the home server supports remote toggle of Pusher for a given device. + */ + val canRemotelyTogglePushNotificationsOfDevices: Boolean = false, ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt index 9d2c48e194..c65a5382fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilitiesService.kt @@ -16,6 +16,9 @@ package org.matrix.android.sdk.api.session.homeserver +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.util.Optional + /** * This interface defines a method to retrieve the homeserver capabilities. */ @@ -30,4 +33,9 @@ interface HomeServerCapabilitiesService { * Get the HomeServer capabilities. */ fun getHomeServerCapabilities(): HomeServerCapabilities + + /** + * Get a LiveData on the HomeServer capabilities. + */ + fun getHomeServerCapabilitiesLive(): LiveData > } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt index 1ae23e2b70..1258c5c02f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt @@ -58,6 +58,16 @@ data class HttpPusher( */ val url: String, + /** + * Whether the pusher should actively create push notifications. + */ + val enabled: Boolean, + + /** + * The device ID of the session that registered the pusher. + */ + val deviceId: String, + /** * If true, the homeserver should add another pusher with the given pushkey and App ID in addition * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt index b85ab32b21..92ac6c483b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt @@ -24,8 +24,9 @@ data class Pusher( val profileTag: String? = null, val lang: String?, val data: PusherData, - - val state: PusherState + val enabled: Boolean, + val deviceId: String?, + val state: PusherState, ) { companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index d7958ea3cd..6a27f7af61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -67,6 +67,14 @@ interface PushersService { append: Boolean = true ) + /** + * Enables or disables a registered pusher. + * + * @param pusher The pusher being toggled + * @param enable Whether the pusher should be enabled or disabled + */ + suspend fun togglePusher(pusher: Pusher, enable: Boolean) + /** * Directly ask the push gateway to send a push to this device. * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index cd8acbcccc..93208be27b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -47,10 +47,9 @@ interface LocationSharingService { /** * Starts sharing live location in the room. * @param timeoutMillis timeout of the live in milliseconds - * @param description description of the live for text fallback * @return the result of the update of the live */ - suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult + suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult /** * Stops sharing live location in the room. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt index 67bab626cb..7d445a5cc6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt @@ -15,10 +15,10 @@ */ package org.matrix.android.sdk.api.session.room.model -import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event data class EditAggregatedSummary( - val latestContent: Content? = null, + val latestEdit: Event? = null, // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) val sourceEvents: List , val localEchos: List , diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt index 5639730219..da7e4ea928 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt @@ -18,5 +18,6 @@ package org.matrix.android.sdk.api.session.room.model data class ReadReceipt( val roomMember: RoomMemberSummary, - val originServerTs: Long + val originServerTs: Long, + val threadId: String? ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index ff4977491f..b63656dc50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -97,6 +97,14 @@ data class RoomSummary( * Number of unread and highlighted message in this room. */ val highlightCount: Int = 0, + /** + * Number of threads with unread messages in this room. + */ + val threadNotificationCount: Int = 0, + /** + * Number of threads with highlighted messages in this room. + */ + val threadHighlightCount: Int = 0, /** * True if this room has unread messages. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt new file mode 100644 index 0000000000..38ced8f385 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt @@ -0,0 +1,47 @@ +/* + * 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.api.session.room.model.message + +import org.matrix.android.sdk.api.extensions.tryOrNull +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.toModel + +/** + * [Event] wrapper for [EventType.MESSAGE] event type. + * Provides additional fields and functions related to this event type. + */ +@JvmInline +value class MessageAudioEvent(val root: Event) { + + /** + * The mapped [MessageAudioContent] model of the event content. + */ + val content: MessageAudioContent + get() = root.getClearContent().toModel () as MessageAudioContent + + init { + require(tryOrNull { content } != null) + } +} + +/** + * Map a [EventType.MESSAGE] event to a [MessageAudioEvent]. + */ +fun Event.asMessageAudioEvent() = if (getClearType() == EventType.MESSAGE) { + tryOrNull { MessageAudioEvent(this) } +} else null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt index f8c1c0d798..627ce53df6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt @@ -34,7 +34,7 @@ data class MessageStickerContent( * Required. A textual representation of the image. This could be the alt text of the image, the filename of the image, * or some kind of content description for accessibility e.g. 'image attachment'. */ - @Json(name = "body") override val body: String, + @Json(name = "body") override val body: String = "", /** * Metadata about the image referred to in url. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index a0873482c2..647deef95e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -45,4 +45,7 @@ object MessageType { // Fake message types for live location events to be able to inherit them from MessageContent const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info" const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data" + + // Fake message types for voice broadcast events to be able to inherit them from MessageContent + const val MSGTYPE_VOICE_BROADCAST_INFO = "io.element.voicebroadcast.info" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationDefaultContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationDefaultContent.kt index 5dcb1b4323..b9f9335dbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationDefaultContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationDefaultContent.kt @@ -28,3 +28,5 @@ data class RelationDefaultContent( ) : RelationContent fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false + +fun RelationDefaultContent?.isReply(): Boolean = this?.inReplyTo?.eventId != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index d34ea3c7d3..e7fcabf386 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -91,7 +91,8 @@ interface RelationService { * Edit a text message body. Limited to "m.text" contentType. * @param targetEvent The event to edit * @param msgType the message type - * @param newBodyText The edited body + * @param newBodyText The edited body in plain text + * @param newFormattedBodyText The edited body with format * @param newBodyAutoMarkdown true to parse markdown on the new body * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ @@ -99,6 +100,7 @@ interface RelationService { targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, + newFormattedBodyText: CharSequence? = null, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText" ): Cancelable @@ -108,13 +110,15 @@ interface RelationService { * This method will take the new body (stripped from fallbacks) and re-add them before sending. * @param replyToEdit The event to edit * @param originalTimelineEvent the message that this reply (being edited) is relating to - * @param newBodyText The edited body (stripped from in reply to content) + * @param newBodyText The plain text edited body (stripped from in reply to content) + * @param newFormattedBodyText The formatted edited body (stripped from in reply to content) * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, + newFormattedBodyText: String? = null, compatibilityBodyText: String = "* $newBodyText" ): Cancelable @@ -133,6 +137,7 @@ interface RelationService { * by the sdk into pills. * @param eventReplied the event referenced by the reply * @param replyText the reply text + * @param replyFormattedText the reply text, formatted * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @param showInThread If true, relation will be added to the reply in order to be visible from within threads * @param rootThreadEventId If show in thread is true then we need the rootThreadEventId to generate the relation @@ -140,6 +145,7 @@ interface RelationService { fun replyToMessage( eventReplied: TimelineEvent, replyText: CharSequence, + replyFormattedText: CharSequence? = null, autoMarkdown: Boolean = false, showInThread: Boolean = false, rootThreadEventId: String? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt index dac1a1a773..83680ec2d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt @@ -34,12 +34,14 @@ interface ReadService { /** * Force the read marker to be set on the latest event. */ - suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH) + suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, mainTimeLineOnly: Boolean = true) /** * Set the read receipt on the event with provided eventId. + * @param eventId the id of the event where read receipt will be set + * @param threadId the id of the thread in which read receipt will be set. For main thread use [ReadService.THREAD_ID_MAIN] constant */ - suspend fun setReadReceipt(eventId: String) + suspend fun setReadReceipt(eventId: String, threadId: String) /** * Set the read marker on the event with provided eventId. @@ -59,10 +61,10 @@ interface ReadService { /** * Returns a live read receipt id for the room. */ - fun getMyReadReceiptLive(): LiveData > + fun getMyReadReceiptLive(threadId: String?): LiveData > /** - * Get the eventId where the read receipt for the provided user is. + * Get the eventId from the main timeline where the read receipt for the provided user is. * @param userId the id of the user to look for * * @return the eventId where the read receipt for the provided user is attached, or null if not found @@ -74,4 +76,8 @@ interface ReadService { * @param eventId the event */ fun getEventReadReceiptsLive(eventId: String): LiveData > + + companion object { + const val THREAD_ID_MAIN = "main" + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 9cf062356f..6a6fadc95a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -21,6 +21,7 @@ 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.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.PollType +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -44,28 +45,49 @@ interface SendService { * @param text the text message to send * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendTextMessage(text: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable + fun sendTextMessage( + text: CharSequence, + msgType: String = MessageType.MSGTYPE_TEXT, + autoMarkdown: Boolean = false, + additionalContent: Content? = null, + ): Cancelable /** * Method to send a text message with a formatted body. * @param text the text message to send * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable + fun sendFormattedTextMessage( + text: String, + formattedText: String, + msgType: String = MessageType.MSGTYPE_TEXT, + additionalContent: Content? = null, + ): Cancelable /** * Method to quote an events content. * @param quotedEvent The event to which we will quote it's content. - * @param text the text message to send + * @param text the plain text message to send + * @param formattedText the formatted text message to send * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @param rootThreadEventId when this param is not null, the message will be sent in this specific thread + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String? = null): Cancelable + fun sendQuotedTextMessage( + quotedEvent: TimelineEvent, + text: String, + formattedText: String? = null, + autoMarkdown: Boolean, + rootThreadEventId: String? = null, + additionalContent: Content? = null, + ): Cancelable /** * Method to send a media asynchronously. @@ -74,13 +96,17 @@ interface SendService { * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread + * @param relatesTo add a relation content to the media event + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ fun sendMedia( attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set
, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, + relatesTo: RelationDefaultContent? = null, + additionalContent: Content? = null, ): Cancelable /** @@ -90,13 +116,15 @@ interface SendService { * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @param rootThreadEventId when this param is not null, all the Media will be sent in this specific thread + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ fun sendMedias( attachments: List , compressBeforeSending: Boolean, roomIds: Set , - rootThreadEventId: String? = null + rootThreadEventId: String? = null, + additionalContent: Content? = null, ): Cancelable /** @@ -104,31 +132,35 @@ interface SendService { * @param pollType indicates open or closed polls * @param question the question * @param options list of options + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun sendPoll(pollType: PollType, question: String, options: List ): Cancelable + fun sendPoll(pollType: PollType, question: String, options: List , additionalContent: Content? = null): Cancelable /** * Method to send a poll response. * @param pollEventId the poll currently replied to * @param answerId The id of the answer + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun voteToPoll(pollEventId: String, answerId: String): Cancelable + fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content? = null): Cancelable /** * End a poll in the room. * @param pollEventId event id of the poll + * @param additionalContent additional content to put in the event content * @return a [Cancelable] */ - fun endPoll(pollEventId: String): Cancelable + fun endPoll(pollEventId: String, additionalContent: Content? = null): Cancelable /** * Redact (delete) the given event. * @param event The event to redact * @param reason Optional reason string + * @param additionalContent additional content to put in the event content */ - fun redactEvent(event: Event, reason: String?): Cancelable + fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable /** * Schedule this message to be resent. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt index 8f214e0f89..634e71c43b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt @@ -33,5 +33,7 @@ object RoomSummaryConstants { EventType.ENCRYPTED, EventType.STICKER, EventType.REACTION - ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + ) + + EventType.POLL_START.values + + EventType.STATE_ROOM_BEACON_INFO.values } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 1824d5dc6c..9ac33c0545 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -106,6 +106,8 @@ interface Timeline { /** * Called when new events come through the sync. + * Note that the corresponding events may not be available yet in the database. + * [onTimelineUpdated] will be called with the event content. */ fun onNewTimelineEvents(eventIds: List ) = Unit diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index d391abf1e6..9053425a39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.timeline import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.extensions.orFalse +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.RelationType @@ -33,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -141,13 +143,21 @@ fun TimelineEvent.getEditedEventId(): String? { fun TimelineEvent.getLastMessageContent(): MessageContent? { return when (root.getClearType()) { EventType.STICKER -> root.getClearContent().toModel () - in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel () - in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel () - in EventType.BEACON_LOCATION_DATA -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel () - else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + // XXX + // Polls/Beacon are not message contents like others as there is no msgtype subtype to discriminate moshi parsing + // so toModel won't parse them correctly + // It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion? + in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel () + in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel () + in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel () + else -> (getLastEditNewContent() ?: root.getClearContent()).toModel() } } +fun TimelineEvent.getLastEditNewContent(): Content? { + return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel ()?.newContent +} + /** * Returns true if it's a reply. */ @@ -179,9 +189,16 @@ fun TimelineEvent.isRootThread(): Boolean { /** * Get the latest message body, after a possible edition, stripping the reply prefix if necessary. + * @param formatted Indicates whether the formatted HTML body of the message should be retrieved of the plain text one. + * @return If [formatted] is `true`, the HTML body of the message will be retrieved if available. Otherwise, the plain text/markdown version will be returned. */ -fun TimelineEvent.getTextEditableContent(): String { - val lastContentBody = getLastMessageContent()?.body ?: return "" +fun TimelineEvent.getTextEditableContent(formatted: Boolean): String { + val lastMessageContent = getLastMessageContent() + val lastContentBody = if (formatted && lastMessageContent is MessageContentWithFormattedBody) { + lastMessageContent.formattedBody ?: lastMessageContent.body + } else { + lastMessageContent?.body + } ?: return "" return if (isReply()) { extractUsefulTextFromReply(lastContentBody) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index 46433f387d..aa9afd5c8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -55,4 +55,9 @@ interface TimelineService { * Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO. */ fun getAttachmentMessages(): List + + /** + * Returns a snapshot list of TimelineEvent with a content relation of the given type to the given eventId. + */ + fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt index bc592df474..7347bee165 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt @@ -16,19 +16,12 @@ package org.matrix.android.sdk.api.session.sync +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder + interface FilterService { - enum class FilterPreset { - NoFilter, - - /** - * Filter for Element, will include only known event type. - */ - ElementFilter - } - /** * Configure the filter for the sync. */ - fun setFilter(filterPreset: FilterPreset) + suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt new file mode 100644 index 0000000000..ad55b26dfd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt @@ -0,0 +1,129 @@ +/* + * 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.api.session.sync.filter + +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.internal.session.filter.Filter +import org.matrix.android.sdk.internal.session.filter.RoomEventFilter +import org.matrix.android.sdk.internal.session.filter.RoomFilter +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams + +class SyncFilterBuilder { + private var lazyLoadMembersForStateEvents: Boolean? = null + private var lazyLoadMembersForMessageEvents: Boolean? = null + private var useThreadNotifications: Boolean? = null + private var listOfSupportedEventTypes: List ? = null + private var listOfSupportedStateEventTypes: List ? = null + + fun lazyLoadMembersForStateEvents(lazyLoadMembersForStateEvents: Boolean) = apply { this.lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents } + + fun lazyLoadMembersForMessageEvents(lazyLoadMembersForMessageEvents: Boolean) = + apply { this.lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents } + + fun useThreadNotifications(useThreadNotifications: Boolean) = + apply { this.useThreadNotifications = useThreadNotifications } + + fun listOfSupportedStateEventTypes(listOfSupportedStateEventTypes: List ) = + apply { this.listOfSupportedStateEventTypes = listOfSupportedStateEventTypes } + + fun listOfSupportedTimelineEventTypes(listOfSupportedEventTypes: List ) = + apply { this.listOfSupportedEventTypes = listOfSupportedEventTypes } + + internal fun with(currentFilterParams: SyncFilterParams?) = + apply { + currentFilterParams?.let { + useThreadNotifications = currentFilterParams.useThreadNotifications + lazyLoadMembersForMessageEvents = currentFilterParams.lazyLoadMembersForMessageEvents + lazyLoadMembersForStateEvents = currentFilterParams.lazyLoadMembersForStateEvents + listOfSupportedEventTypes = currentFilterParams.listOfSupportedEventTypes?.toList() + listOfSupportedStateEventTypes = currentFilterParams.listOfSupportedStateEventTypes?.toList() + } + } + + internal fun extractParams(): SyncFilterParams { + return SyncFilterParams( + useThreadNotifications = useThreadNotifications, + lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents, + lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents, + listOfSupportedEventTypes = listOfSupportedEventTypes, + listOfSupportedStateEventTypes = listOfSupportedStateEventTypes, + ) + } + + internal fun build(homeServerCapabilities: HomeServerCapabilities): Filter { + return Filter( + room = buildRoomFilter(homeServerCapabilities) + ) + } + + private fun buildRoomFilter(homeServerCapabilities: HomeServerCapabilities): RoomFilter { + return RoomFilter( + timeline = buildTimelineFilter(homeServerCapabilities), + state = buildStateFilter() + ) + } + + private fun buildTimelineFilter(homeServerCapabilities: HomeServerCapabilities): RoomEventFilter? { + val resolvedUseThreadNotifications = if (homeServerCapabilities.canUseThreadReadReceiptsAndNotifications) { + useThreadNotifications + } else { + null + } + return RoomEventFilter( + enableUnreadThreadNotifications = resolvedUseThreadNotifications, + lazyLoadMembers = lazyLoadMembersForMessageEvents + ).orNullIfEmpty() + } + + private fun buildStateFilter(): RoomEventFilter? = + RoomEventFilter( + lazyLoadMembers = lazyLoadMembersForStateEvents, + types = listOfSupportedStateEventTypes + ).orNullIfEmpty() + + private fun RoomEventFilter.orNullIfEmpty(): RoomEventFilter? { + return if (hasData()) { + this + } else { + null + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SyncFilterBuilder + + if (lazyLoadMembersForStateEvents != other.lazyLoadMembersForStateEvents) return false + if (lazyLoadMembersForMessageEvents != other.lazyLoadMembersForMessageEvents) return false + if (useThreadNotifications != other.useThreadNotifications) return false + if (listOfSupportedEventTypes != other.listOfSupportedEventTypes) return false + if (listOfSupportedStateEventTypes != other.listOfSupportedStateEventTypes) return false + + return true + } + + override fun hashCode(): Int { + var result = lazyLoadMembersForStateEvents?.hashCode() ?: 0 + result = 31 * result + (lazyLoadMembersForMessageEvents?.hashCode() ?: 0) + result = 31 * result + (useThreadNotifications?.hashCode() ?: 0) + result = 31 * result + (listOfSupportedEventTypes?.hashCode() ?: 0) + result = 31 * result + (listOfSupportedStateEventTypes?.hashCode() ?: 0) + return result + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt index e5ac0a39b2..7eb44c1350 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt @@ -47,6 +47,11 @@ data class RoomSync( */ @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null, + /** + * The count of threads with unread notifications (not the total # of notifications in all threads). + */ + @Json(name = "unread_thread_notifications") val unreadThreadNotifications: Map ? = null, + /** * The room summary. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt new file mode 100644 index 0000000000..70524d299a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt @@ -0,0 +1,33 @@ +/* + * 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.api.session.sync.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RoomSyncUnreadThreadNotifications( + /** + * The number of threads with unread messages that match the push notification rules. + */ + @Json(name = "notification_count") val notificationCount: Int? = null, + + /** + * The number of threads with highlighted unread messages (subset of notifications). + */ + @Json(name = "highlight_count") val highlightCount: Int? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Compat.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Compat.kt new file mode 100644 index 0000000000..b685c49713 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Compat.kt @@ -0,0 +1,42 @@ +/* + * 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.api.util + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build + +fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(flags.toLong()) + ) + else -> @Suppress("DEPRECATION") getApplicationInfo(packageName, flags) + } +} + +fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int): PackageInfo { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(flags.toLong()) + ) + else -> @Suppress("DEPRECATION") getPackageInfo(packageName, flags) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Optional.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Optional.kt index 42724746c0..2da6d4cbf8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Optional.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Optional.kt @@ -15,15 +15,13 @@ */ package org.matrix.android.sdk.api.util -data class Optional constructor(private val value: T?) { +data class Optional (private val value: T?) { - fun get(): T { - return value!! - } + fun get(): T = value!! - fun getOrNull(): T? { - return value - } + fun orNull(): T? = value + + fun getOrNull(): T? = value fun map(fn: (T) -> U?): Optional { return if (value == null) { @@ -33,23 +31,19 @@ data class Optional constructor(private val value: T?) { } } - fun getOrElse(fn: () -> T): T { + fun orElse(fn: () -> T): T { return value ?: fn() } - fun hasValue(): Boolean { - return value != null - } + fun hasValue(): Boolean = value != null companion object { - fun from(value: T?): Optional { - return Optional(value) - } + fun from(value: T?): Optional = Optional(value) - fun empty(): Optional { - return Optional(null) - } + fun empty(): Optional = Optional(null) } } +fun T?.toOption() = Optional(this) + fun T?.toOptional() = Optional(this) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt index 463692e574..b1f65194f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt @@ -29,7 +29,9 @@ import org.matrix.android.sdk.internal.auth.db.AuthRealmModule import org.matrix.android.sdk.internal.auth.db.RealmPendingSessionStore import org.matrix.android.sdk.internal.auth.db.RealmSessionParamsStore import org.matrix.android.sdk.internal.auth.login.DefaultDirectLoginTask +import org.matrix.android.sdk.internal.auth.login.DefaultQrLoginTokenTask import org.matrix.android.sdk.internal.auth.login.DirectLoginTask +import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.AuthDatabase import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter @@ -94,4 +96,7 @@ internal abstract class AuthModule { @Binds abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService + + @Binds + abstract fun bindQrLoginTokenTask(task: DefaultQrLoginTokenTask): QrLoginTokenTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 446f931847..5449c0a735 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session @@ -39,9 +40,11 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask +import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices +import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk import org.matrix.android.sdk.internal.di.Unauthenticated @@ -62,7 +65,8 @@ internal class DefaultAuthenticationService @Inject constructor( private val sessionCreator: SessionCreator, private val pendingSessionStore: PendingSessionStore, private val getWellknownTask: GetWellknownTask, - private val directLoginTask: DirectLoginTask + private val directLoginTask: DirectLoginTask, + private val qrLoginTokenTask: QrLoginTokenTask ) : AuthenticationService { private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData() @@ -404,6 +408,36 @@ internal class DefaultAuthenticationService @Inject constructor( ) } + override suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean { + val authAPI = buildAuthAPI(homeServerConnectionConfig) + val versions = runCatching { + executeRequest(null) { + authAPI.versions() + } + } + return if (versions.isSuccess) { + versions.getOrNull()?.doesServerSupportQrCodeLogin().orFalse() + } else { + false + } + } + + override suspend fun loginUsingQrLoginToken( + homeServerConnectionConfig: HomeServerConnectionConfig, + loginToken: String, + initialDeviceName: String?, + deviceId: String?, + ): Session { + return qrLoginTokenTask.execute( + QrLoginTokenTask.Params( + homeServerConnectionConfig = homeServerConnectionConfig, + loginToken = loginToken, + deviceName = initialDeviceName, + deviceId = deviceId + ) + ) + } + private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString()) return retrofit.create(AuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt index ea8578ed8c..8646752083 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt @@ -18,4 +18,6 @@ package org.matrix.android.sdk.internal.auth.data internal interface LoginParams { val type: String + val deviceDisplayName: String? + val deviceId: String? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt index 5f0a2298cb..062b2466e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt @@ -30,8 +30,8 @@ internal data class PasswordLoginParams( @Json(name = "identifier") val identifier: Map , @Json(name = "password") val password: String, @Json(name = "type") override val type: String, - @Json(name = "initial_device_display_name") val deviceDisplayName: String?, - @Json(name = "device_id") val deviceId: String? + @Json(name = "initial_device_display_name") override val deviceDisplayName: String?, + @Json(name = "device_id") override val deviceId: String? ) : LoginParams { companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt index 0c6fb45e78..52045a1d7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt @@ -23,5 +23,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes @JsonClass(generateAdapter = true) internal data class TokenLoginParams( @Json(name = "type") override val type: String = LoginFlowTypes.TOKEN, - @Json(name = "token") val token: String + @Json(name = "token") val token: String, + @Json(name = "initial_device_display_name") override val deviceDisplayName: String? = null, + @Json(name = "device_id") override val deviceId: String? = null ) : LoginParams diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt new file mode 100644 index 0000000000..477719f607 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt @@ -0,0 +1,88 @@ +/* + * 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.auth.login + +import dagger.Lazy +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.auth.LoginType +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.auth.SessionCreator +import org.matrix.android.sdk.internal.auth.data.TokenLoginParams +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory +import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface QrLoginTokenTask : Task { + data class Params( + val homeServerConnectionConfig: HomeServerConnectionConfig, + val loginToken: String, + val deviceName: String?, + val deviceId: String? + ) +} + +internal class DefaultQrLoginTokenTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy , + private val retrofitFactory: RetrofitFactory, + private val sessionCreator: SessionCreator, +) : QrLoginTokenTask { + + override suspend fun execute(params: QrLoginTokenTask.Params): Session { + val client = buildClient(params.homeServerConnectionConfig) + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() + + val authAPI = retrofitFactory.create(client, homeServerUrl) + .create(AuthAPI::class.java) + + val loginParams = TokenLoginParams( + token = params.loginToken, + deviceDisplayName = params.deviceName, + deviceId = params.deviceId + ) + + val credentials = try { + executeRequest(null) { + authAPI.login(loginParams) + } + } catch (throwable: Throwable) { + throw when (throwable) { + is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure( + homeServerUrl, + throwable.fingerprint + ) + else -> throwable + } + } + + return sessionCreator.createSession(credentials, params.homeServerConnectionConfig, LoginType.QR) + } + + private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { + return okHttpClient.get() + .newBuilder() + .addSocketFactory(homeServerConnectionConfig) + .build() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt index 75639c6a21..d443d6e3c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt @@ -60,5 +60,6 @@ internal data class HomeServerVersion( val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0) val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1) val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0) + val v1_4_0 = HomeServerVersion(major = 1, minor = 4, patch = 0) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index 915b25134b..f4de6a9ae9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.version import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.extensions.orFalse /** * Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions. @@ -53,6 +54,10 @@ private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token" private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind" private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440" private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable" +private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882" +private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" +private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" +private const val FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881 = "org.matrix.msc3881" /** * Return true if the SDK supports this homeserver version. @@ -78,6 +83,19 @@ internal fun Versions.doesServerSupportThreads(): Boolean { return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false } +/** + * Indicate if the homeserver support MSC3771 and MSC3773 for threaded read receipts and unread notifications. + */ +internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean { + val msc3771 = unstableFeatures?.get(FEATURE_THREADS_MSC3771) ?: false + val msc3773 = unstableFeatures?.get(FEATURE_THREADS_MSC3773) ?: false + return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773) +} + +internal fun Versions.doesServerSupportQrCodeLogin(): Boolean { + return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false +} + /** * Return true if the server support the lazy loading of room members. * @@ -126,3 +144,12 @@ private fun Versions.getMaxVersion(): HomeServerVersion { ?.maxOrNull() ?: HomeServerVersion.r0_0_0 } + +/** + * Indicate if the server supports MSC3881: https://github.com/matrix-org/matrix-spec-proposals/pull/3881. + * + * @return true if remote toggle of push notifications is supported + */ +internal fun Versions.doesServerSupportRemoteToggleOfPushNotifications(): Boolean { + return unstableFeatures?.get(FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881).orFalse() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index 2b44af3c5f..b7a46fb48f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt @@ -19,6 +19,7 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams +import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDevicesParams import org.matrix.android.sdk.internal.crypto.model.rest.KeyChangesResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse @@ -134,6 +135,17 @@ internal interface CryptoApi { @Body params: DeleteDeviceParams ) + /** + * Deletes the given devices, and invalidates any access token associated with them. + * Doc: https://spec.matrix.org/v1.4/client-server-api/#post_matrixclientv3delete_devices + * + * @param params the deletion parameters + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_V3 + "delete_devices") + suspend fun deleteDevices( + @Body params: DeleteDevicesParams + ) + /** * Update the device information. * Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#put-matrix-client-r0-devices-deviceid diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt index c26c6107c4..24dccc4d90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt @@ -23,6 +23,9 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) internal data class DeleteDeviceParams( + /** + * Additional authentication information for the user-interactive authentication API. + */ @Json(name = "auth") - val auth: Map ? = null + val auth: Map ? = null, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDevicesParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDevicesParams.kt new file mode 100644 index 0000000000..19b33b2a69 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDevicesParams.kt @@ -0,0 +1,37 @@ +/* + * 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.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This class provides the parameter to delete several devices. + */ +@JsonClass(generateAdapter = true) +internal data class DeleteDevicesParams( + /** + * Additional authentication information for the user-interactive authentication API. + */ + @Json(name = "auth") + val auth: Map ? = null, + + /** + * Required: The list of device IDs to delete. + */ + @Json(name = "devices") + val deviceIds: List , +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt index 0a77d33acc..549122447e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt @@ -16,12 +16,14 @@ package org.matrix.android.sdk.internal.crypto.tasks +import androidx.annotation.Size import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.uia.UiaResult import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams +import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDevicesParams import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -30,7 +32,7 @@ import javax.inject.Inject internal interface DeleteDeviceTask : Task { data class Params( - val deviceId: String, + @Size(min = 1) val deviceIds: List , val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor?, val userAuthParam: UIABaseAuth? ) @@ -42,9 +44,24 @@ internal class DefaultDeleteDeviceTask @Inject constructor( ) : DeleteDeviceTask { override suspend fun execute(params: DeleteDeviceTask.Params) { + require(params.deviceIds.isNotEmpty()) + try { executeRequest(globalErrorReceiver) { - cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap())) + val userAuthParam = params.userAuthParam?.asMap() + if (params.deviceIds.size == 1) { + cryptoApi.deleteDevice( + deviceId = params.deviceIds.first(), + DeleteDeviceParams(auth = userAuthParam) + ) + } else { + cryptoApi.deleteDevices( + DeleteDevicesParams( + auth = userAuthParam, + deviceIds = params.deviceIds + ) + ) + } } } catch (throwable: Throwable) { if (params.userInteractiveAuthInterceptor == null || diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt index e437996d6f..b34a6056b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt @@ -45,9 +45,8 @@ internal class DefaultEncryptEventTask @Inject constructor( // don't want to wait for any query // if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event val localEvent = params.event - if (localEvent.eventId == null || localEvent.type == null) { - throw IllegalArgumentException() - } + require(localEvent.eventId != null) + require(localEvent.type != null) localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt index 85b920dc55..cf4932efdc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationEmoji.kt @@ -100,7 +100,7 @@ internal fun getEmojiForCode(code: Int): EmojiRepresentation { * The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers, * or with the three numbers on separate lines. */ -fun ByteArray.getDecimalCodeRepresentation(): String { +fun ByteArray.getDecimalCodeRepresentation(separator: String = " "): String { val b0 = this[0].toUnsignedInt() // need unsigned byte val b1 = this[1].toUnsignedInt() // need unsigned byte val b2 = this[2].toUnsignedInt() // need unsigned byte @@ -112,7 +112,7 @@ fun ByteArray.getDecimalCodeRepresentation(): String { val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000 // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000 val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000 - return "$first $second $third" + return listOf(first, second, third).joinToString(separator) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt index 7d263f1937..a1ea88a70c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt @@ -26,17 +26,17 @@ import kotlinx.coroutines.withContext import timber.log.Timber import kotlin.system.measureTimeMillis -internal fun CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) { +internal fun CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: (realm: Realm) -> T) { asyncTransaction(monarchy.realmConfiguration, transaction) } -internal fun CoroutineScope.asyncTransaction(realmConfiguration: RealmConfiguration, transaction: suspend (realm: Realm) -> T) { +internal fun CoroutineScope.asyncTransaction(realmConfiguration: RealmConfiguration, transaction: (realm: Realm) -> T) { launch { awaitTransaction(realmConfiguration, transaction) } } -internal suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T { +internal suspend fun awaitTransaction(config: RealmConfiguration, transaction: (realm: Realm) -> T): T { return withContext(Realm.WRITE_EXECUTOR.asCoroutineDispatcher()) { Realm.getInstance(config).use { bgRealm -> bgRealm.beginTransaction() 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 2693ca474c..2fb87ca874 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 @@ -54,6 +54,14 @@ 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.database.migration.MigrateSessionTo037 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -62,7 +70,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 37L, + schemaVersion = 45L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -109,5 +117,13 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 35) MigrateSessionTo035(realm).perform() if (oldVersion < 36) MigrateSessionTo036(realm).perform() if (oldVersion < 37) MigrateSessionTo037(realm).perform() + if (oldVersion < 38) MigrateSessionTo038(realm).perform() + if (oldVersion < 39) MigrateSessionTo039(realm).perform() + if (oldVersion < 40) MigrateSessionTo040(realm).perform() + if (oldVersion < 41) MigrateSessionTo041(realm).perform() + if (oldVersion < 42) MigrateSessionTo042(realm).perform() + if (oldVersion < 43) MigrateSessionTo043(realm).perform() + if (oldVersion < 44) MigrateSessionTo044(realm).perform() + if (oldVersion < 45) MigrateSessionTo045(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index 221abe0df5..43f84e771a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -83,7 +83,6 @@ internal fun ChunkEntity.addTimelineEvent( this.eventId = eventId this.roomId = roomId this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() - ?.also { it.cleanUp(eventEntity.sender) } this.readReceipts = readReceiptsSummaryEntity this.displayIndex = displayIndex this.ownedByThreadChunk = ownedByThreadChunk @@ -133,7 +132,7 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE val originServerTs = eventEntity.originServerTs if (originServerTs != null) { val timestampOfEvent = originServerTs.toDouble() - val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId) + val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId, threadId = eventEntity.rootThreadEventId) // If the synced RR is older, update if (timestampOfEvent > readReceiptOfSender.originServerTs) { val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index dfac7f6708..7999a2ea14 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -65,11 +65,11 @@ internal fun Map .updateThreadSummaryIfNeeded( inThreadMessages = inThreadMessages, latestMessageTimelineEventEntity = latestEventInThread ) - } - } - if (shouldUpdateNotifications) { - updateNotificationsNew(roomId, realm, currentUserId) + if (shouldUpdateNotifications) { + updateThreadNotifications(roomId, realm, currentUserId, rootThreadEventId) + } + } } } @@ -273,8 +273,8 @@ internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm, /** * Find the read receipt for the current user. */ -internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? = - ReadReceiptEntity.where(realm, roomId = roomId, userId = userId) +internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): String? = + ReadReceiptEntity.where(realm, roomId = roomId, userId = userId, threadId = threadId) .findFirst() ?.eventId @@ -293,28 +293,29 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin * Important: It will work only with the latest chunk, while read marker will be changed * immediately so we should not display wrong notifications */ -internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) { - val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return +internal fun updateThreadNotifications(roomId: String, realm: Realm, currentUserId: String, rootThreadEventId: String) { + val readReceipt = findMyReadReceipt(realm, roomId, currentUserId, threadId = rootThreadEventId) ?: return val readReceiptChunk = ChunkEntity .findIncludingEvent(realm, readReceipt) ?: return - val readReceiptChunkTimelineEvents = readReceiptChunk + val readReceiptChunkThreadEvents = readReceiptChunk .timelineEvents .where() .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) .findAll() ?: return - val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt } + val readReceiptChunkPosition = readReceiptChunkThreadEvents.indexOfFirst { it.eventId == readReceipt } if (readReceiptChunkPosition == -1) return - if (readReceiptChunkPosition < readReceiptChunkTimelineEvents.lastIndex) { + if (readReceiptChunkPosition < readReceiptChunkThreadEvents.lastIndex) { // If the read receipt is found inside the chunk - val threadEventsAfterReadReceipt = readReceiptChunkTimelineEvents - .slice(readReceiptChunkPosition..readReceiptChunkTimelineEvents.lastIndex) + val threadEventsAfterReadReceipt = readReceiptChunkThreadEvents + .slice(readReceiptChunkPosition..readReceiptChunkThreadEvents.lastIndex) .filter { it.root?.isThread() == true } // In order for the below code to work for old events, we should save the previous read receipt @@ -343,26 +344,21 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: it.root?.rootThreadEventId } - // Find the root events in the new thread events - val rootThreads = threadEventsAfterReadReceipt.distinctBy { it.root?.rootThreadEventId }.mapNotNull { it.root?.rootThreadEventId } + // Update root thread event only if the user have participated in + val isUserParticipating = TimelineEventEntity.isUserParticipatingInThread( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId, + senderId = currentUserId + ) + val rootThreadEventEntity = EventEntity.where(realm, rootThreadEventId).findFirst() - // Update root thread events only if the user have participated in - rootThreads.forEach { eventId -> - val isUserParticipating = TimelineEventEntity.isUserParticipatingInThread( - realm = realm, - roomId = roomId, - rootThreadEventId = eventId, - senderId = currentUserId - ) - val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst() + if (isUserParticipating) { + rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE + } - if (isUserParticipating) { - rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE - } - - if (userMentionsList.contains(eventId)) { - rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE - } + if (userMentionsList.contains(rootThreadEventId)) { + rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt index 193710f962..0ac8dc7902 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort -import io.realm.kotlin.createObject import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError @@ -103,32 +102,6 @@ internal fun ThreadSummaryEntity.updateThreadSummaryLatestEvent( } } -private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap ): TimelineEventEntity { - val roomId = roomId - val eventId = eventId - val localId = TimelineEventEntity.nextId(realm) - val senderId = sender ?: "" - - val timelineEventEntity = realm.createObject ().apply { - this.localId = localId - this.root = this@toTimelineEventEntity - this.eventId = eventId - this.roomId = roomId - this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() - ?.also { it.cleanUp(sender) } - this.ownedByThreadChunk = true // To skip it from the original event flow - val roomMemberContent = roomMemberContentsByUser[senderId] - this.senderAvatar = roomMemberContent?.avatarUrl - this.senderName = roomMemberContent?.displayName - isUniqueDisplayName = if (roomMemberContent?.displayName != null) { - computeIsUnique(realm, roomId, false, roomMemberContent, roomMemberContentsByUser) - } else { - true - } - } - return timelineEventEntity -} - internal fun ThreadSummaryEntity.Companion.createOrUpdate( threadSummaryType: ThreadSummaryUpdateType, realm: Realm, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapper.kt new file mode 100644 index 0000000000..8c209f2f2a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapper.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.mapper + +import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary +import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.EditionOfEvent + +internal object EditAggregatedSummaryEntityMapper { + + fun map(summary: EditAggregatedSummaryEntity?): EditAggregatedSummary? { + summary ?: return null + /** + * The most recent event is determined by comparing origin_server_ts; + * if two or more replacement events have identical origin_server_ts, + * the event with the lexicographically largest event_id is treated as more recent. + */ + val latestEdition = summary.editions.sortedWith(compareBy { it.timestamp }.thenBy { it.eventId }) + .lastOrNull() ?: return null + val editEvent = latestEdition.event + + return EditAggregatedSummary( + latestEdit = editEvent?.asDomain(), + sourceEvents = summary.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho } + .map { editionOfEvent -> editionOfEvent.eventId }, + localEchos = summary.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho } + .map { editionOfEvent -> editionOfEvent.eventId }, + lastEditTs = latestEdition.timestamp + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt index 6bbeb17fdd..d4bb5791a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.database.mapper -import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary @@ -35,18 +34,7 @@ internal object EventAnnotationsSummaryMapper { it.sourceLocalEcho.toList() ) }, - editSummary = annotationsSummary.editSummary - ?.let { - val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp } ?: return@let null - EditAggregatedSummary( - latestContent = ContentMapper.map(latestEdition.content), - sourceEvents = it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho } - .map { editionOfEvent -> editionOfEvent.eventId }, - localEchos = it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho } - .map { editionOfEvent -> editionOfEvent.eventId }, - lastEditTs = latestEdition.timestamp - ) - }, + editSummary = EditAggregatedSummaryEntityMapper.map(annotationsSummary.editSummary), referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let { ReferencesAggregatedSummary( ContentMapper.map(it.content), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt new file mode 100644 index 0000000000..645cb41af5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt @@ -0,0 +1,61 @@ +/* + * 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.mapper + +import io.realm.RealmList +import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams +import javax.inject.Inject + +internal class FilterParamsMapper @Inject constructor() { + + fun map(entity: SyncFilterParamsEntity): SyncFilterParams { + val eventTypes = if (entity.listOfSupportedEventTypesHasBeenSet) { + entity.listOfSupportedEventTypes?.toList() + } else { + null + } + val stateEventTypes = if (entity.listOfSupportedStateEventTypesHasBeenSet) { + entity.listOfSupportedStateEventTypes?.toList() + } else { + null + } + return SyncFilterParams( + useThreadNotifications = entity.useThreadNotifications, + lazyLoadMembersForMessageEvents = entity.lazyLoadMembersForMessageEvents, + lazyLoadMembersForStateEvents = entity.lazyLoadMembersForStateEvents, + listOfSupportedEventTypes = eventTypes, + listOfSupportedStateEventTypes = stateEventTypes, + ) + } + + fun map(params: SyncFilterParams): SyncFilterParamsEntity { + return SyncFilterParamsEntity( + useThreadNotifications = params.useThreadNotifications, + lazyLoadMembersForMessageEvents = params.lazyLoadMembersForMessageEvents, + lazyLoadMembersForStateEvents = params.lazyLoadMembersForStateEvents, + listOfSupportedEventTypes = params.listOfSupportedEventTypes.toRealmList(), + listOfSupportedEventTypesHasBeenSet = params.listOfSupportedEventTypes != null, + listOfSupportedStateEventTypes = params.listOfSupportedStateEventTypes.toRealmList(), + listOfSupportedStateEventTypesHasBeenSet = params.listOfSupportedStateEventTypes != null, + ) + } + + private fun List ?.toRealmList(): RealmList ? { + return this?.toTypedArray()?.let { RealmList(*it) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 184a0108b9..89657ad882 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -43,7 +43,10 @@ internal object HomeServerCapabilitiesMapper { defaultIdentityServerUrl = entity.defaultIdentityServerUrl, roomVersions = mapRoomVersion(entity.roomVersionsJson), canUseThreading = entity.canUseThreading, - canControlLogoutDevices = entity.canControlLogoutDevices + canControlLogoutDevices = entity.canControlLogoutDevices, + canLoginWithQrCode = entity.canLoginWithQrCode, + canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications, + canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt index 2dba2c228b..c3a37f5b95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt @@ -33,7 +33,9 @@ internal object PushersMapper { profileTag = pushEntity.profileTag, lang = pushEntity.lang, data = PusherData(pushEntity.data?.url, pushEntity.data?.format), - state = pushEntity.state + enabled = pushEntity.enabled, + deviceId = pushEntity.deviceId, + state = pushEntity.state, ) } @@ -46,7 +48,9 @@ internal object PushersMapper { deviceDisplayName = pusher.deviceDisplayName, profileTag = pusher.profileTag, lang = pusher.lang, - data = PusherDataEntity(pusher.data?.url, pusher.data?.format) + data = PusherDataEntity(pusher.data?.url, pusher.data?.format), + enabled = pusher.enabled, + deviceId = pusher.deviceId, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt index 2be4510b6f..3b71ae3dea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt @@ -50,7 +50,7 @@ internal class ReadReceiptsSummaryMapper @Inject constructor( .mapNotNull { val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst() ?: return@mapNotNull null - ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong()) + ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong(), it.threadId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 72b0f7a043..6e9fff78e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -61,6 +61,8 @@ internal class RoomSummaryMapper @Inject constructor( otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, + threadHighlightCount = roomSummaryEntity.threadHighlightCount, + threadNotificationCount = roomSummaryEntity.threadNotificationCount, hasUnreadMessages = roomSummaryEntity.hasUnreadMessages, tags = tags, typingUsers = typingUsers, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt index b61bf7e6fa..f85a0661c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt @@ -25,11 +25,11 @@ internal class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8 override fun doMigrate(realm: DynamicRealm) { val editionOfEventSchema = realm.schema.create("EditionOfEvent") - .addField(EditionOfEventFields.CONTENT, String::class.java) + .addField("content", String::class.java) .addField(EditionOfEventFields.EVENT_ID, String::class.java) .setRequired(EditionOfEventFields.EVENT_ID, true) - .addField(EditionOfEventFields.SENDER_ID, String::class.java) - .setRequired(EditionOfEventFields.SENDER_ID, true) + .addField("senderId", String::class.java) + .setRequired("senderId", true) .addField(EditionOfEventFields.TIMESTAMP, Long::class.java) .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo038.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo038.kt new file mode 100644 index 0000000000..b5848f04cd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo038.kt @@ -0,0 +1,31 @@ +/* + * 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.PusherEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo038(realm: DynamicRealm) : RealmMigrator(realm, 38) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("PusherEntity") + ?.addField(PusherEntityFields.ENABLED, Boolean::class.java) + ?.addField(PusherEntityFields.DEVICE_ID, String::class.java) + ?.transform { obj -> obj.set(PusherEntityFields.ENABLED, true) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo039.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo039.kt new file mode 100644 index 0000000000..190a71c9be --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo039.kt @@ -0,0 +1,34 @@ +/* + * 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.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo039(realm: DynamicRealm) : RealmMigrator(realm, 39) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, false) + } + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt new file mode 100644 index 0000000000..b3e02342dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt @@ -0,0 +1,34 @@ +/* + * 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.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo040(realm: DynamicRealm) : RealmMigrator(realm, 40) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, false) + } + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt new file mode 100644 index 0000000000..b58d80e50a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt @@ -0,0 +1,30 @@ +/* + * 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.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo041(realm: DynamicRealm) : RealmMigrator(realm, 41) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.THREAD_HIGHLIGHT_COUNT, Int::class.java) + ?.addField(RoomSummaryEntityFields.THREAD_NOTIFICATION_COUNT, Int::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo042.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo042.kt new file mode 100644 index 0000000000..8826d894c1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo042.kt @@ -0,0 +1,31 @@ +/* + * 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.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo042(realm: DynamicRealm) : RealmMigrator(realm, 42) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_REMOTELY_TOGGLE_PUSH_NOTIFICATIONS_OF_DEVICES, Boolean::class.java) + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt new file mode 100644 index 0000000000..49e9bac18c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo043.kt @@ -0,0 +1,42 @@ +/* + * 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.EditionOfEventFields +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo043(realm: DynamicRealm) : RealmMigrator(realm, 43) { + + override fun doMigrate(realm: DynamicRealm) { + // content(string) & senderId(string) have been removed and replaced by a link to the actual event + realm.schema.get("EditionOfEvent") + ?.addRealmObjectField(EditionOfEventFields.EVENT.`$`, realm.schema.get("EventEntity")!!) + ?.transform { dynamicObject -> + realm.where("EventEntity") + .equalTo(EventEntityFields.EVENT_ID, dynamicObject.getString(EditionOfEventFields.EVENT_ID)) + .equalTo(EventEntityFields.SENDER, dynamicObject.getString("senderId")) + .findFirst() + .let { + dynamicObject.setObject(EditionOfEventFields.EVENT.`$`, it) + } + } + ?.removeField("senderId") + ?.removeField("content") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo044.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo044.kt new file mode 100644 index 0000000000..2d3efc8338 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo044.kt @@ -0,0 +1,29 @@ +/* + * 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.ReadReceiptEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo044(realm: DynamicRealm) : RealmMigrator(realm, 44) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("ReadReceiptEntity") + ?.addField(ReadReceiptEntityFields.THREAD_ID, String::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt new file mode 100644 index 0000000000..d2b43ded28 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo045.kt @@ -0,0 +1,38 @@ +/* + * 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.SyncFilterParamsEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo045(realm: DynamicRealm) : RealmMigrator(realm, 45) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.create("SyncFilterParamsEntity") + .addField(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_STATE_EVENTS, Boolean::class.java) + .setNullable(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_STATE_EVENTS, true) + .addField(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_MESSAGE_EVENTS, Boolean::class.java) + .setNullable(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_MESSAGE_EVENTS, true) + .addField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_EVENT_TYPES_HAS_BEEN_SET, Boolean::class.java) + .addField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_STATE_EVENT_TYPES_HAS_BEEN_SET, Boolean::class.java) + .addField(SyncFilterParamsEntityFields.USE_THREAD_NOTIFICATIONS, Boolean::class.java) + .setNullable(SyncFilterParamsEntityFields.USE_THREAD_NOTIFICATIONS, true) + .addRealmListField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_EVENT_TYPES.`$`, String::class.java) + .addRealmListField(SyncFilterParamsEntityFields.LIST_OF_SUPPORTED_STATE_EVENT_TYPES.`$`, String::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt index 61acd51dd4..7b7b90f82d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt @@ -32,9 +32,8 @@ internal open class EditAggregatedSummaryEntity( @RealmClass(embedded = true) internal open class EditionOfEvent( - var senderId: String = "", var eventId: String = "", - var content: String? = null, var timestamp: Long = 0, - var isLocalEcho: Boolean = false + var isLocalEcho: Boolean = false, + var event: EventEntity? = null, ) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt index 645998d0c0..9a201ab4e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt @@ -19,7 +19,6 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity -import timber.log.Timber internal open class EventAnnotationsSummaryEntity( @PrimaryKey @@ -32,21 +31,6 @@ internal open class EventAnnotationsSummaryEntity( var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null, ) : RealmObject() { - /** - * Cleanup undesired editions, done by users different from the originalEventSender. - */ - fun cleanUp(originalEventSenderId: String?) { - originalEventSenderId ?: return - - editSummary?.editions?.filter { - it.senderId != originalEventSenderId - } - ?.forEach { - Timber.w("Deleting an edition from ${it.senderId} of event sent by $originalEventSenderId") - it.deleteFromRealm() - } - } - companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 9d90973f8a..2b60f7723c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -30,7 +30,10 @@ internal open class HomeServerCapabilitiesEntity( var defaultIdentityServerUrl: String? = null, var lastUpdatedTimestamp: Long = 0L, var canUseThreading: Boolean = false, - var canControlLogoutDevices: Boolean = false + var canControlLogoutDevices: Boolean = false, + var canLoginWithQrCode: Boolean = false, + var canUseThreadReadReceiptsAndNotifications: Boolean = false, + var canRemotelyTogglePushNotificationsOfDevices: Boolean = false, ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt index af8e4f2d37..c08f695168 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt @@ -18,15 +18,6 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import org.matrix.android.sdk.api.session.pushers.PusherState -// TODO -// at java.lang.Thread.run(Thread.java:764) -// Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object. -// at io.realm.ProxyState.checkValidObject(ProxyState.java:213) -// at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy -// .realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413) -// at org.matrix.android.sdk.internal.database.model.PusherEntity.setData(PusherEntity.kt:16) -// at org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70) -// at io.realm.Realm.executeTransaction(Realm.java:1493) internal open class PusherEntity( var pushKey: String = "", var kind: String? = null, @@ -35,7 +26,9 @@ internal open class PusherEntity( var deviceDisplayName: String? = null, var profileTag: String? = null, var lang: String? = null, - var data: PusherDataEntity? = null + var data: PusherDataEntity? = null, + var enabled: Boolean = true, + var deviceId: String? = null, ) : RealmObject() { private var stateStr: String = PusherState.UNREGISTERED.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt index 9623c95359..cedd5e7424 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ReadReceiptEntity.kt @@ -26,6 +26,7 @@ internal open class ReadReceiptEntity( var eventId: String = "", var roomId: String = "", var userId: String = "", + var threadId: String? = null, var originServerTs: Double = 0.0 ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 471bec59af..650dd3c5cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -115,6 +115,16 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var threadNotificationCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var threadHighlightCount: Int = 0 + set(value) { + if (value != field) field = value + } + var readMarkerId: String? = null set(value) { if (value != field) field = value 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 b222bcb710..93ff67a911 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 @@ -70,7 +70,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit SpaceChildSummaryEntity::class, SpaceParentSummaryEntity::class, UserPresenceEntity::class, - ThreadSummaryEntity::class + ThreadSummaryEntity::class, + SyncFilterParamsEntity::class, ] ) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt new file mode 100644 index 0000000000..e4b62f28e8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SyncFilterParamsEntity.kt @@ -0,0 +1,36 @@ +/* + * 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.RealmList +import io.realm.RealmObject + +/** + * This entity stores Sync Filter configuration data, provided by the client. + */ +internal open class SyncFilterParamsEntity( + var lazyLoadMembersForStateEvents: Boolean? = null, + var lazyLoadMembersForMessageEvents: Boolean? = null, + var useThreadNotifications: Boolean? = null, + var listOfSupportedEventTypes: RealmList ? = null, + var listOfSupportedEventTypesHasBeenSet: Boolean = false, + var listOfSupportedStateEventTypes: RealmList ? = null, + var listOfSupportedStateEventTypesHasBeenSet: Boolean = false, +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt index c8f22dc2cc..1deca47b70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt @@ -20,6 +20,7 @@ import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index import io.realm.annotations.LinkingObjects +import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.internal.extensions.assertIsManaged internal open class TimelineEventEntity( @@ -52,3 +53,7 @@ internal fun TimelineEventEntity.deleteOnCascade(canDeleteRoot: Boolean) { } deleteFromRealm() } + +internal fun TimelineEventEntity.getThreadId(): String { + return root?.rootThreadEventId ?: ReadService.THREAD_ID_MAIN +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index 0b0f01a67d..ebfe23105e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -18,17 +18,20 @@ package org.matrix.android.sdk.internal.database.query import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.internal.database.helper.isMoreRecentThan import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.getThreadId internal fun isEventRead( realmConfiguration: RealmConfiguration, userId: String?, roomId: String?, - eventId: String? + eventId: String?, + shouldCheckIfReadInEventsThread: Boolean ): Boolean { if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) { return false @@ -45,7 +48,8 @@ internal fun isEventRead( eventToCheck.root?.sender == userId -> true // If new event exists and the latest event is from ourselves we can infer the event is read latestEventIsFromSelf(realm, roomId, userId) -> true - eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId) -> true + eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId, null) -> true + (shouldCheckIfReadInEventsThread && eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId, eventToCheck.getThreadId())) -> true else -> false } } @@ -54,27 +58,33 @@ internal fun isEventRead( private fun latestEventIsFromSelf(realm: Realm, roomId: String, userId: String) = TimelineEventEntity.latestEvent(realm, roomId, true) ?.root?.sender == userId -private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String): Boolean { - return ReadReceiptEntity.where(realm, roomId, userId).findFirst()?.let { readReceipt -> +private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): Boolean { + val isMoreRecent = ReadReceiptEntity.where(realm, roomId, userId, threadId).findFirst()?.let { readReceipt -> val readReceiptEvent = TimelineEventEntity.where(realm, roomId, readReceipt.eventId).findFirst() readReceiptEvent?.isMoreRecentThan(this) } ?: false + return isMoreRecent } /** * Missing events can be caused by the latest timeline chunk no longer contain an older event or * by fast lane eagerly displaying events before the database has finished updating. */ -private fun hasReadMissingEvent(realm: Realm, latestChunkEntity: ChunkEntity, roomId: String, userId: String, eventId: String): Boolean { - return realm.doesEventExistInChunkHistory(eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId) +private fun hasReadMissingEvent(realm: Realm, + latestChunkEntity: ChunkEntity, + roomId: String, + userId: String, + eventId: String, + threadId: String? = ReadService.THREAD_ID_MAIN): Boolean { + return realm.doesEventExistInChunkHistory(eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId, threadId) } private fun Realm.doesEventExistInChunkHistory(eventId: String): Boolean { return ChunkEntity.findIncludingEvent(this, eventId) != null } -private fun Realm.hasReadReceiptInLatestChunk(latestChunkEntity: ChunkEntity, roomId: String, userId: String): Boolean { - return ReadReceiptEntity.where(this, roomId = roomId, userId = userId).findFirst()?.let { +private fun Realm.hasReadReceiptInLatestChunk(latestChunkEntity: ChunkEntity, roomId: String, userId: String, threadId: String?): Boolean { + return ReadReceiptEntity.where(this, roomId = roomId, userId = userId, threadId = threadId).findFirst()?.let { latestChunkEntity.timelineEvents.find(it.eventId) } != null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt index 170814d3f2..0f9f56b938 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt @@ -20,12 +20,20 @@ import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.createObject import io.realm.kotlin.where +import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntityFields -internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery { +internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String, threadId: String?): RealmQuery { return realm.where () - .equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId)) + .equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, threadId)) +} + +internal fun ReadReceiptEntity.Companion.forMainTimelineWhere(realm: Realm, roomId: String, userId: String): RealmQuery { + return realm.where () + .equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, ReadService.THREAD_ID_MAIN)) + .or() + .equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, null)) } internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: String): RealmQuery { @@ -38,23 +46,37 @@ internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: Strin .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId) } -internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity { +internal fun ReadReceiptEntity.Companion.createUnmanaged( + roomId: String, + eventId: String, + userId: String, + threadId: String?, + originServerTs: Double +): ReadReceiptEntity { return ReadReceiptEntity().apply { - this.primaryKey = "${roomId}_$userId" + this.primaryKey = buildPrimaryKey(roomId, userId, threadId) this.eventId = eventId this.roomId = roomId this.userId = userId + this.threadId = threadId this.originServerTs = originServerTs } } -internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity { - return ReadReceiptEntity.where(realm, roomId, userId).findFirst() - ?: realm.createObject (buildPrimaryKey(roomId, userId)) +internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String, threadId: String?): ReadReceiptEntity { + return ReadReceiptEntity.where(realm, roomId, userId, threadId).findFirst() + ?: realm.createObject (buildPrimaryKey(roomId, userId, threadId)) .apply { this.roomId = roomId this.userId = userId + this.threadId = threadId } } -private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId" +private fun buildPrimaryKey(roomId: String, userId: String, threadId: String?): String { + return if (threadId == null) { + "${roomId}_${userId}" + } else { + "${roomId}_${userId}_${threadId}" + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 30010f90fd..1b4b359916 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -110,8 +110,7 @@ internal fun RealmQuery .filterEvents(filters: TimelineEvent endGroup() } if (filters.filterUseless) { - not() - .equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) + not().equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) } if (filters.filterEdits) { not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt index 6eb4d5b104..04e8cf8a29 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt @@ -20,6 +20,8 @@ import android.content.Context import android.os.Build import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.util.getApplicationInfoCompat +import org.matrix.android.sdk.api.util.getPackageInfoCompat import javax.inject.Inject class ComputeUserAgentUseCase @Inject constructor( @@ -36,7 +38,7 @@ class ComputeUserAgentUseCase @Inject constructor( val appPackageName = context.applicationContext.packageName val pm = context.packageManager - val appName = tryOrNull { pm.getApplicationLabel(pm.getApplicationInfo(appPackageName, 0)).toString() } + val appName = tryOrNull { pm.getApplicationLabel(pm.getApplicationInfoCompat(appPackageName, 0)).toString() } ?.takeIf { it.matches("\\A\\p{ASCII}*\\z".toRegex()) } @@ -44,7 +46,7 @@ class ComputeUserAgentUseCase @Inject constructor( // Use appPackageName instead of appName if appName is null or contains any non-ASCII character appPackageName } - val appVersion = tryOrNull { pm.getPackageInfo(context.applicationContext.packageName, 0).versionName } ?: FALLBACK_APP_VERSION + val appVersion = tryOrNull { pm.getPackageInfoCompat(context.applicationContext.packageName, 0).versionName } ?: FALLBACK_APP_VERSION val deviceManufacturer = Build.MANUFACTURER val deviceModel = Build.MODEL diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt index 5aec7db66c..4bfda0bf3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt @@ -22,6 +22,7 @@ internal object NetworkConstants { const val URI_API_PREFIX_PATH_ = "$URI_API_PREFIX_PATH/" const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/" const val URI_API_PREFIX_PATH_V1 = "$URI_API_PREFIX_PATH/v1/" + const val URI_API_PREFIX_PATH_V3 = "$URI_API_PREFIX_PATH/v3/" const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/" // Media diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/EventInsertLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/EventInsertLiveProcessor.kt index a650fa2d64..9741a7bd15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/EventInsertLiveProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/EventInsertLiveProcessor.kt @@ -24,7 +24,7 @@ internal interface EventInsertLiveProcessor { fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean - suspend fun process(realm: Realm, event: Event) + fun process(realm: Realm, event: Event) /** * Called after transaction. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index b15a647421..b6ad7581fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -41,9 +41,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.ENCRYPTED, - EventType.CALL_ASSERTED_IDENTITY, - EventType.CALL_ASSERTED_IDENTITY_PREFIX - ) + ) + + EventType.CALL_ASSERTED_IDENTITY.values private val eventsToPostProcess = mutableListOf () @@ -54,7 +53,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH return allowedTypes.contains(eventType) } - override suspend fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event) { eventsToPostProcess.add(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 48a9dfd3da..d824aaa51a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -84,8 +84,7 @@ internal class CallSignalingHandler @Inject constructor( EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } - EventType.CALL_ASSERTED_IDENTITY, - EventType.CALL_ASSERTED_IDENTITY_PREFIX -> { + in EventType.CALL_ASSERTED_IDENTITY.values -> { handleCallAssertedIdentityEvent(event) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt index c023646c7f..eee55735e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt @@ -130,6 +130,7 @@ internal class FileUploader @Inject constructor( workingFile.outputStream().use { inputStream.copyTo(it) } + inputStream.close() workingFile } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt index 3fa9ffb0e1..1531d70083 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.internal.session.content import kotlinx.coroutines.withContext -import org.apache.sanselan.Sanselan -import org.apache.sanselan.formats.jpeg.JpegImageMetadata -import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter -import org.apache.sanselan.formats.tiff.constants.ExifTagConstants -import org.apache.sanselan.formats.tiff.constants.GPSTagConstants +import org.apache.commons.imaging.Imaging +import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata +import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter +import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants +import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.util.TemporaryFileCreator @@ -46,24 +46,13 @@ internal class ImageExifTagRemover @Inject constructor( */ suspend fun removeSensitiveJpegExifTags(jpegImageFile: File): File = withContext(coroutineDispatchers.io) { val outputSet = tryOrNull("Unable to read JpegImageMetadata") { - (Sanselan.getMetadata(jpegImageFile) as? JpegImageMetadata)?.exif?.outputSet + (Imaging.getMetadata(jpegImageFile) as? JpegImageMetadata)?.exif?.outputSet } ?: return@withContext jpegImageFile tryOrNull("Unable to remove ExifData") { - outputSet.removeField(ExifTagConstants.EXIF_TAG_GPSINFO) - outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_1) - outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_2) - outputSet.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE_REF) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE) - outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE_REF) + tagsToRemove.forEach { tagInfo -> + outputSet.removeField(tagInfo) + } } ?: return@withContext jpegImageFile val scrubbedFile = temporaryFileCreator.create() @@ -82,4 +71,12 @@ internal class ImageExifTagRemover @Inject constructor( } ) } + + private val tagsToRemove + get() = GpsTagConstants.ALL_GPS_TAGS + + listOf( + ExifTagConstants.EXIF_TAG_GPSINFO, + ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION, + ExifTagConstants.EXIF_TAG_USER_COMMENT, + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 1e62b5d7f5..3dd440737a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo +import org.matrix.android.sdk.api.session.events.model.Content 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.message.MessageAudioContent @@ -407,7 +408,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter newAttachmentAttributes: NewAttachmentAttributes ) { localEchoRepository.updateEcho(eventId) { _, event -> - val messageContent: MessageContent? = event.asDomain().content.toModel() + val content: Content? = event.asDomain(castJsonNumbers = true).content + val messageContent: MessageContent? = content.toModel() + // Retrieve potential additional content from the original event + val additionalContent = content.orEmpty() - messageContent?.toContent().orEmpty().keys val updatedContent = when (messageContent) { is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes) is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes) @@ -415,7 +419,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter is MessageAudioContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize) else -> messageContent } - event.content = ContentMapper.map(updatedContent.toContent()) + event.content = ContentMapper.map(updatedContent.toContent().plus(additionalContent)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt index 1d1bb0e715..4e5b005584 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt @@ -17,74 +17,71 @@ package org.matrix.android.sdk.internal.session.filter import com.zhuinden.monarchy.Monarchy -import io.realm.Realm import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.mapper.FilterParamsMapper import org.matrix.android.sdk.internal.database.model.FilterEntity -import org.matrix.android.sdk.internal.database.model.FilterEntityFields +import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity import org.matrix.android.sdk.internal.database.query.get import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject -internal class DefaultFilterRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : FilterRepository { +internal class DefaultFilterRepository @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val filterParamsMapper: FilterParamsMapper +) : FilterRepository { - override suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - val filterEntity = FilterEntity.get(realm) - // Filter has changed, or no filter Id yet - filterEntity == null || - filterEntity.filterBodyJson != filter.toJSONString() || - filterEntity.filterId.isBlank() - }.also { hasChanged -> - if (hasChanged) { - // Filter is new or has changed, store it and reset the filter Id. - // This has to be done outside of the Realm.use(), because awaitTransaction change the current thread - monarchy.awaitTransaction { realm -> - // We manage only one filter for now - val filterJson = filter.toJSONString() - val roomEventFilterJson = roomEventFilter.toJSONString() - - val filterEntity = FilterEntity.getOrCreate(realm) - - filterEntity.filterBodyJson = filterJson - filterEntity.roomEventFilterJson = roomEventFilterJson - // Reset filterId - filterEntity.filterId = "" - } - } - } - } - - override suspend fun storeFilterId(filter: Filter, filterId: String) { - monarchy.awaitTransaction { + override suspend fun storeSyncFilter(filter: Filter, filterId: String, roomEventFilter: RoomEventFilter) { + monarchy.awaitTransaction { realm -> // We manage only one filter for now val filterJson = filter.toJSONString() + val roomEventFilterJson = roomEventFilter.toJSONString() - // Update the filter id, only if the filter body matches - it.where () - .equalTo(FilterEntityFields.FILTER_BODY_JSON, filterJson) - ?.findFirst() - ?.filterId = filterId + val filterEntity = FilterEntity.getOrCreate(realm) + + filterEntity.filterBodyJson = filterJson + filterEntity.roomEventFilterJson = roomEventFilterJson + filterEntity.filterId = filterId } } - override suspend fun getFilter(): String { + override suspend fun getStoredSyncFilterBody(): String { return monarchy.awaitTransaction { - val filter = FilterEntity.getOrCreate(it) - if (filter.filterId.isBlank()) { - // Use the Json format - filter.filterBodyJson + FilterEntity.getOrCreate(it).filterBodyJson + } + } + + override suspend fun getStoredSyncFilterId(): String? { + return monarchy.awaitTransaction { + val id = FilterEntity.get(it)?.filterId + if (id.isNullOrBlank()) { + null } else { - // Use FilterId - filter.filterId + id } } } - override suspend fun getRoomFilter(): String { + override suspend fun getRoomFilterBody(): String { return monarchy.awaitTransaction { FilterEntity.getOrCreate(it).roomEventFilterJson } } + + override suspend fun getStoredFilterParams(): SyncFilterParams? { + return monarchy.awaitTransaction { realm -> + realm.where ().findFirst()?.let { + filterParamsMapper.map(it) + } + } + } + + override suspend fun storeFilterParams(params: SyncFilterParams) { + return monarchy.awaitTransaction { realm -> + val entity = filterParamsMapper.map(params) + realm.insertOrUpdate(entity) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt index 2e68d02d8c..c54e7de07a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt @@ -17,19 +17,27 @@ package org.matrix.android.sdk.internal.session.filter import org.matrix.android.sdk.api.session.sync.FilterService -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource import javax.inject.Inject internal class DefaultFilterService @Inject constructor( private val saveFilterTask: SaveFilterTask, - private val taskExecutor: TaskExecutor + private val filterRepository: FilterRepository, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, ) : FilterService { // TODO Pass a list of support events instead - override fun setFilter(filterPreset: FilterService.FilterPreset) { - saveFilterTask - .configureWith(SaveFilterTask.Params(filterPreset)) - .executeBy(taskExecutor) + override suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder) { + filterRepository.storeFilterParams(filterBuilder.extractParams()) + + // don't upload/store filter until homeserver capabilities are fetched + homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.let { homeServerCapabilities -> + saveFilterTask.execute( + SaveFilterTask.Params( + filter = filterBuilder.build(homeServerCapabilities) + ) + ) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt index 676a4f6a38..1bd2e59e59 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt @@ -28,7 +28,7 @@ internal object FilterFactory { limit = numberOfEvents, // senders = listOf(userId), // relationSenders = userId?.let { listOf(it) }, - relationTypes = listOf(RelationType.THREAD) + relationTypes = listOf(RelationType.THREAD), ) } @@ -37,7 +37,7 @@ internal object FilterFactory { limit = numberOfEvents, containsUrl = true, types = listOf(EventType.MESSAGE), - lazyLoadMembers = true + lazyLoadMembers = true, ) } @@ -45,51 +45,7 @@ internal object FilterFactory { return FilterUtil.enableLazyLoading(Filter(), true) } - fun createElementFilter(): Filter { - return Filter( - room = RoomFilter( - timeline = createElementTimelineFilter(), - state = createElementStateFilter() - ) - ) - } - fun createDefaultRoomFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true - ) + return RoomEventFilter(lazyLoadMembers = true) } - - fun createElementRoomFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true - // TODO Enable this for optimization - // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() - ) - } - - private fun createElementTimelineFilter(): RoomEventFilter? { - return null // RoomEventFilter().apply { - // TODO Enable this for optimization - // types = listOfSupportedEventTypes.toMutableList() - // } - } - - private fun createElementStateFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true - ) - } - - // Get only managed types by Element - private val listOfSupportedEventTypes = listOf( - // TODO Complete the list - EventType.MESSAGE - ) - - // Get only managed types by Element - private val listOfSupportedStateEventTypes = listOf( - // TODO Complete the list - EventType.STATE_ROOM_MEMBER - ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt index 8531bed1ff..ca9f798fd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt @@ -44,4 +44,7 @@ internal abstract class FilterModule { @Binds abstract fun bindSaveFilterTask(task: DefaultSaveFilterTask): SaveFilterTask + + @Binds + abstract fun bindGetCurrentFilterTask(task: DefaultGetCurrentFilterTask): GetCurrentFilterTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt index f40231c8cf..71d7391e87 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt @@ -16,25 +16,42 @@ package org.matrix.android.sdk.internal.session.filter +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams + +/** + * Repository for request filters. + */ internal interface FilterRepository { /** - * Return true if the filterBody has changed, or need to be sent to the server. + * Stores sync filter and room filter. + * Note: It looks like we could use [Filter.room.timeline] instead of a separate [RoomEventFilter], but it's not clear if it's safe, so research is needed + * @return true if the filterBody has changed, or need to be sent to the server. */ - suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean + suspend fun storeSyncFilter(filter: Filter, filterId: String, roomEventFilter: RoomEventFilter) /** - * Set the filterId of this filter. + * Returns stored sync filter's JSON body if it exists. */ - suspend fun storeFilterId(filter: Filter, filterId: String) + suspend fun getStoredSyncFilterBody(): String? /** - * Return filter json or filter id. + * Returns stored sync filter's ID if it exists. */ - suspend fun getFilter(): String + suspend fun getStoredSyncFilterId(): String? /** * Return the room filter. */ - suspend fun getRoomFilter(): String + suspend fun getRoomFilterBody(): String + + /** + * Returns filter params stored in local storage if it exists. + */ + suspend fun getStoredFilterParams(): SyncFilterParams? + + /** + * Stores filter params to local storage. + */ + suspend fun storeFilterParams(params: SyncFilterParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt new file mode 100644 index 0000000000..e88f286e27 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt @@ -0,0 +1,55 @@ +/* + * 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.filter + +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetCurrentFilterTask : Task + +internal class DefaultGetCurrentFilterTask @Inject constructor( + private val filterRepository: FilterRepository, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, + private val saveFilterTask: SaveFilterTask +) : GetCurrentFilterTask { + + override suspend fun execute(params: Unit): String { + val storedFilterId = filterRepository.getStoredSyncFilterId() + val storedFilterBody = filterRepository.getStoredSyncFilterBody() + val homeServerCapabilities = homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities() + val currentFilter = SyncFilterBuilder() + .with(filterRepository.getStoredFilterParams()) + .build(homeServerCapabilities) + + val currentFilterBody = currentFilter.toJSONString() + + return when (storedFilterBody) { + currentFilterBody -> storedFilterId ?: storedFilterBody + else -> saveFilter(currentFilter) + } + } + + private suspend fun saveFilter(filter: Filter) = saveFilterTask + .execute( + SaveFilterTask.Params( + filter = filter + ) + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt index 220c401137..4bb66a6159 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.filter import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.sync.model.RoomSync import org.matrix.android.sdk.internal.di.MoshiProvider /** @@ -74,9 +75,15 @@ internal data class RoomEventFilter( */ @Json(name = "contains_url") val containsUrl: Boolean? = null, /** - * If true, enables lazy-loading of membership events. See Lazy-loading room members for more information. Defaults to false. + * If true, enables lazy-loading of membership events. + * See Lazy-loading room members for more information. + * Defaults to false. */ - @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null + @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null, + /** + * If true, this will opt-in for the server to return unread threads notifications in [RoomSync]. + */ + @Json(name = "unread_thread_notifications") val enableUnreadThreadNotifications: Boolean? = null, ) { fun toJSONString(): String { @@ -92,6 +99,7 @@ internal data class RoomEventFilter( rooms != null || notRooms != null || containsUrl != null || - lazyLoadMembers != null) + lazyLoadMembers != null || + enableUnreadThreadNotifications != null) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index 63afa1bbbc..82d5ff4d2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.filter -import org.matrix.android.sdk.api.session.sync.FilterService import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest @@ -26,10 +25,10 @@ import javax.inject.Inject /** * Save a filter, in db and if any changes, upload to the server. */ -internal interface SaveFilterTask : Task { +internal interface SaveFilterTask : Task { data class Params( - val filterPreset: FilterService.FilterPreset + val filter: Filter ) } @@ -37,33 +36,21 @@ internal class DefaultSaveFilterTask @Inject constructor( @UserId private val userId: String, private val filterAPI: FilterApi, private val filterRepository: FilterRepository, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, ) : SaveFilterTask { - override suspend fun execute(params: SaveFilterTask.Params) { - val filterBody = when (params.filterPreset) { - FilterService.FilterPreset.ElementFilter -> { - FilterFactory.createElementFilter() - } - FilterService.FilterPreset.NoFilter -> { - FilterFactory.createDefaultFilter() - } - } - val roomFilter = when (params.filterPreset) { - FilterService.FilterPreset.ElementFilter -> { - FilterFactory.createElementRoomFilter() - } - FilterService.FilterPreset.NoFilter -> { - FilterFactory.createDefaultRoomFilter() - } - } - val updated = filterRepository.storeFilter(filterBody, roomFilter) - if (updated) { - val filterResponse = executeRequest(globalErrorReceiver) { - // TODO auto retry - filterAPI.uploadFilter(userId, filterBody) - } - filterRepository.storeFilterId(filterBody, filterResponse.filterId) + override suspend fun execute(params: SaveFilterTask.Params): String { + val filter = params.filter + val filterResponse = executeRequest(globalErrorReceiver) { + // TODO auto retry + filterAPI.uploadFilter(userId, filter) } + + filterRepository.storeSyncFilter( + filter = filter, + filterId = filterResponse.filterId, + roomEventFilter = FilterFactory.createDefaultRoomFilter() + ) + return filterResponse.filterId } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt index 4c755b54b5..eb9e862de2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.internal.session.homeserver +import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService +import org.matrix.android.sdk.api.util.Optional import javax.inject.Inject internal class DefaultHomeServerCapabilitiesService @Inject constructor( @@ -33,4 +35,8 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor( return homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities() } + + override fun getHomeServerCapabilitiesLive(): LiveData > { + return homeServerCapabilitiesDataSource.getHomeServerCapabilitiesLive() + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index add69dd8c7..11e86a5c51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -20,11 +20,13 @@ import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices +import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin +import org.matrix.android.sdk.internal.auth.version.doesServerSupportRemoteToggleOfPushNotifications +import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity @@ -132,8 +134,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let { MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) } - homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ - getVersionResult?.doesServerSupportThreads().orFalse() } if (getMediaConfigResult != null) { @@ -142,8 +142,18 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } if (getVersionResult != null) { - homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk() - homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices() + homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = + getVersionResult.isLoginAndRegistrationSupportedBySdk() + homeServerCapabilitiesEntity.canControlLogoutDevices = + getVersionResult.doesServerSupportLogoutDevices() + homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */ + getVersionResult.doesServerSupportThreads() + homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications = + getVersionResult.doesServerSupportThreadUnreadNotifications() + homeServerCapabilitiesEntity.canLoginWithQrCode = + getVersionResult.doesServerSupportQrCodeLogin() + homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices = + getVersionResult.doesServerSupportRemoteToggleOfPushNotifications() } if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt index 6c913fa41e..beb1e67e40 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt @@ -16,9 +16,14 @@ package org.matrix.android.sdk.internal.session.homeserver +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import io.realm.kotlin.where import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.query.get @@ -26,7 +31,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject internal class HomeServerCapabilitiesDataSource @Inject constructor( - @SessionDatabase private val monarchy: Monarchy + @SessionDatabase private val monarchy: Monarchy, ) { fun getHomeServerCapabilities(): HomeServerCapabilities? { return Realm.getInstance(monarchy.realmConfiguration).use { realm -> @@ -35,4 +40,14 @@ internal class HomeServerCapabilitiesDataSource @Inject constructor( } } } + + fun getHomeServerCapabilitiesLive(): LiveData > { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> realm.where () }, + { HomeServerCapabilitiesMapper.map(it) } + ) + return Transformations.map(liveData) { + it.firstOrNull().toOptional() + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt index 40444edcab..22bb3d37b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt @@ -17,26 +17,40 @@ package org.matrix.android.sdk.internal.session.profile +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.user.UserEntityFactory import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject internal abstract class GetProfileInfoTask : Task { data class Params( - val userId: String + val userId: String, + val storeInDatabase: Boolean = true, ) } internal class DefaultGetProfileInfoTask @Inject constructor( private val profileAPI: ProfileAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + @SessionDatabase private val monarchy: Monarchy, ) : GetProfileInfoTask() { override suspend fun execute(params: Params): JsonDict { return executeRequest(globalErrorReceiver) { profileAPI.getProfile(params.userId) + }.also { user -> + if (params.storeInDatabase) { + // Insert into DB + monarchy.awaitTransaction { + it.insertOrUpdate(UserEntityFactory.create(User.fromJson(params.userId, user))) + } + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index 7d81e19265..3e145dc668 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -38,6 +38,7 @@ internal class DefaultAddPusherTask @Inject constructor( private val requestExecutor: RequestExecutor, private val globalErrorReceiver: GlobalErrorReceiver ) : AddPusherTask { + override suspend fun execute(params: AddPusherTask.Params) { val pusher = params.pusher try { @@ -71,6 +72,8 @@ internal class DefaultAddPusherTask @Inject constructor( echo.profileTag = pusher.profileTag echo.data?.format = pusher.data?.format echo.data?.url = pusher.data?.url + echo.enabled = pusher.enabled + echo.deviceId = pusher.deviceId echo.state = PusherState.REGISTERED } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index e912d9ccf8..e89cfa508c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -42,6 +42,7 @@ internal class DefaultPushersService @Inject constructor( private val getPusherTask: GetPushersTask, private val pushGatewayNotifyTask: PushGatewayNotifyTask, private val addPusherTask: AddPusherTask, + private val togglePusherTask: TogglePusherTask, private val removePusherTask: RemovePusherTask, private val taskExecutor: TaskExecutor ) : PushersService { @@ -78,7 +79,9 @@ internal class DefaultPushersService @Inject constructor( appDisplayName = appDisplayName, deviceDisplayName = deviceDisplayName, data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), - append = append + append = append, + enabled = enabled, + deviceId = deviceId, ) override suspend fun addEmailPusher( @@ -106,6 +109,24 @@ internal class DefaultPushersService @Inject constructor( ) } + override suspend fun togglePusher(pusher: Pusher, enable: Boolean) { + togglePusherTask.execute(TogglePusherTask.Params(pusher.toJsonPusher(), enable)) + } + + private fun Pusher.toJsonPusher() = JsonPusher( + pushKey = pushKey, + kind = kind, + appId = appId, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + profileTag = profileTag, + lang = lang, + data = JsonPusherData(data.url, data.format), + append = false, + enabled = enabled, + deviceId = deviceId, + ) + private fun enqueueAddPusher(pusher: JsonPusher): UUID { val params = AddPusherWorker.Params(sessionId, pusher) val request = workManagerProvider.matrixOneTimeWorkRequestBuilder () diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt index 71a1ea8c66..c1cf3eb276 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt @@ -33,6 +33,8 @@ import java.security.InvalidParameterException * "device_display_name": "Alice's Phone", * "profile_tag": "xyz", * "lang": "en-US", + * "enabled": true, + * "device_id": "abc123", * "data": { * "url": "https://example.com/_matrix/push/v1/notify" * } @@ -112,7 +114,19 @@ internal data class JsonPusher( * The default is false. */ @Json(name = "append") - val append: Boolean? = false + val append: Boolean? = false, + + /** + * Whether the pusher should actively create push notifications. + */ + @Json(name = "org.matrix.msc3881.enabled") + val enabled: Boolean = true, + + /** + * The device_id of the session that registered the pusher. + */ + @Json(name = "org.matrix.msc3881.device_id") + val deviceId: String? = null, ) { init { // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index 4528c95e69..37c1c0c3ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -68,6 +68,9 @@ internal abstract class PushersModule { @Binds abstract fun bindAddPusherTask(task: DefaultAddPusherTask): AddPusherTask + @Binds + abstract fun bindTogglePusherTask(task: DefaultTogglePusherTask): TogglePusherTask + @Binds abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt new file mode 100644 index 0000000000..87836e1c76 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.pushers + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.RequestExecutor +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface TogglePusherTask : Task { + data class Params(val pusher: JsonPusher, val enable: Boolean) +} + +internal class DefaultTogglePusherTask @Inject constructor( + private val pushersAPI: PushersAPI, + @SessionDatabase private val monarchy: Monarchy, + private val requestExecutor: RequestExecutor, + private val globalErrorReceiver: GlobalErrorReceiver +) : TogglePusherTask { + + override suspend fun execute(params: TogglePusherTask.Params) { + val pusher = params.pusher.copy(enabled = params.enable) + + requestExecutor.executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(pusher) + } + + monarchy.awaitTransaction { realm -> + val entity = PusherEntity.where(realm, params.pusher.pushKey).findFirst() + entity?.apply { enabled = params.enable }?.let { realm.insertOrUpdate(it) } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt index 09d7d50ecb..9fe93d8262 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt @@ -56,8 +56,8 @@ internal class DefaultProcessEventForPushTask @Inject constructor( val allEvents = (newJoinEvents + inviteEvents).filter { event -> when (event.type) { - in EventType.POLL_START, - in EventType.STATE_ROOM_BEACON_INFO, + in EventType.POLL_START.values, + in EventType.STATE_ROOM_BEACON_INFO.values, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt new file mode 100644 index 0000000000..41d0c3f6ab --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventEditValidator.kt @@ -0,0 +1,126 @@ +/* + * 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 + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import timber.log.Timber +import javax.inject.Inject + +internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCryptoStore) { + + sealed class EditValidity { + object Valid : EditValidity() + data class Invalid(val reason: String) : EditValidity() + object Unknown : EditValidity() + } + + /** + * There are a number of requirements on replacement events, which must be satisfied for the replacement + * to be considered valid: + * As with all event relationships, the original event and replacement event must have the same room_id + * (i.e. you cannot send an event in one room and then an edited version in a different room). + * The original event and replacement event must have the same sender (i.e. you cannot edit someone else’s messages). + * The replacement and original events must have the same type (i.e. you cannot change the original event’s type). + * The replacement and original events must not have a state_key property (i.e. you cannot edit state events at all). + * The original event must not, itself, have a rel_type of m.replace + * (i.e. you cannot edit an edit — though you can send multiple edits for a single original event). + * The replacement event (once decrypted, if appropriate) must have an m.new_content property. + * + * If the original event was encrypted, the replacement should be too. + */ + fun validateEdit(originalEvent: Event?, replaceEvent: Event): EditValidity { + Timber.v("###REPLACE valide event $originalEvent replaced $replaceEvent") + // we might not know the original event at that time. In this case we can't perform the validation + // Edits should be revalidated when the original event is received + if (originalEvent == null) { + return EditValidity.Unknown + } + + if (LocalEcho.isLocalEchoId(replaceEvent.eventId.orEmpty())) { + // Don't validate local echo + return EditValidity.Unknown + } + + if (originalEvent.roomId != replaceEvent.roomId) { + return EditValidity.Invalid("original event and replacement event must have the same room_id") + } + if (originalEvent.isStateEvent() || replaceEvent.isStateEvent()) { + return EditValidity.Invalid("replacement and original events must not have a state_key property") + } + // check it's from same sender + + if (originalEvent.isEncrypted()) { + if (!replaceEvent.isEncrypted()) return EditValidity.Invalid("If the original event was encrypted, the replacement should be too") + val originalDecrypted = originalEvent.toValidDecryptedEvent() + ?: return EditValidity.Unknown // UTD can't decide + val replaceDecrypted = replaceEvent.toValidDecryptedEvent() + ?: return EditValidity.Unknown // UTD can't decide + + val originalCryptoSenderId = cryptoStore.deviceWithIdentityKey(originalDecrypted.cryptoSenderKey)?.userId + val editCryptoSenderId = cryptoStore.deviceWithIdentityKey(replaceDecrypted.cryptoSenderKey)?.userId + + if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) { + return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ") + } + + if (originalCryptoSenderId == null || editCryptoSenderId == null) { + // mm what can we do? we don't know if it's cryptographically from same user? + // let valid and UI should display send by deleted device warning? + val bestEffortOriginal = originalCryptoSenderId ?: originalEvent.senderId + val bestEffortEdit = editCryptoSenderId ?: replaceEvent.senderId + if (bestEffortOriginal != bestEffortEdit) { + return EditValidity.Invalid("original event and replacement event must have the same sender") + } + } else { + if (originalCryptoSenderId != editCryptoSenderId) { + return EditValidity.Invalid("Crypto: original event and replacement event must have the same sender") + } + } + + if (originalDecrypted.type != replaceDecrypted.type) { + return EditValidity.Invalid("replacement and original events must have the same type") + } + if (replaceDecrypted.clearContent.toModel ()?.newContent == null) { + return EditValidity.Invalid("replacement event must have an m.new_content property") + } + } else { + if (originalEvent.getRelationContent()?.type == RelationType.REPLACE) { + return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ") + } + + // check the sender + if (originalEvent.senderId != replaceEvent.senderId) { + return EditValidity.Invalid("original event and replacement event must have the same sender") + } + if (originalEvent.type != replaceEvent.type) { + return EditValidity.Invalid("replacement and original events must have the same type") + } + if (replaceEvent.content.toModel ()?.newContent == null) { + return EditValidity.Invalid("replacement event must have an m.new_content property") + } + } + + return EditValidity.Valid + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index d24e240130..be73309837 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper +import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.EditionOfEvent import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity @@ -72,6 +73,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, + private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -79,6 +81,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.MESSAGE, EventType.REDACTION, EventType.REACTION, + // The aggregator handles verification events but just to render tiles in the timeline + // It's not participating in verification itself, just timeline display EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_ACCEPT, @@ -87,13 +91,18 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, EventType.ENCRYPTED - ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA + ) + + EventType.POLL_START.values + + EventType.POLL_RESPONSE.values + + EventType.POLL_END.values + + EventType.STATE_ROOM_BEACON_INFO.values + + EventType.BEACON_LOCATION_DATA.values override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return allowedTypes.contains(eventType) } - override suspend fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event) { try { // Temporary catch, should be removed val roomId = event.roomId if (roomId == null) { @@ -101,7 +110,31 @@ internal class EventRelationsAggregationProcessor @Inject constructor( return } val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - when (event.type) { + + // It might be a late decryption of the original event or a event received when back paginating? + // let's check if there is already a summary for it and do some cleaning + if (!isLocalEcho) { + EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId.orEmpty()) + .findFirst() + ?.editSummary + ?.editions + ?.forEach { editionOfEvent -> + EventEntity.where(realm, editionOfEvent.eventId).findFirst()?.asDomain()?.let { editEvent -> + when (editValidator.validateEdit(event, editEvent)) { + is EventEditValidator.EditValidity.Invalid -> { + // delete it, it was invalid + Timber.v("## Replace: Removing a previously accepted edit for event ${event.eventId}") + editionOfEvent.deleteFromRealm() + } + else -> { + // nop + } + } + } + } + } + + when (event.getClearType()) { EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") @@ -112,21 +145,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) - EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() - ?.let { - TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() - ?.forEach { tet -> tet.annotations = it } - } + // XXX do something for aggregated edits? + // it's a bit strange as it would require to do a server query to get the edition? } - val content: MessageContent? = event.content.toModel() - if (content?.relatesTo?.type == RelationType.REPLACE) { + val relationContent = event.getRelationContent() + if (relationContent?.type == RelationType.REPLACE) { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") // A replace! - handleReplace(realm, event, content, roomId, isLocalEcho) + handleReplace(realm, event, roomId, isLocalEcho, relationContent.eventId) } } - EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_ACCEPT, @@ -141,74 +170,31 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - + // As for now Live event processors are not receiving UTD events. + // They will get an update if the event is decrypted later EventType.ENCRYPTED -> { - // Relation type is in clear + // Relation type is in clear, it might be possible to do some things? + // Notice that if the event is decrypted later, process be called again val encryptedEventContent = event.content.toModel () - if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE || - encryptedEventContent?.relatesTo?.type == RelationType.RESPONSE - ) { - event.getClearContent().toModel ()?.let { - if (encryptedEventContent.relatesTo.type == RelationType.REPLACE) { - Timber.v("###REPLACE in room $roomId for event ${event.eventId}") - // A replace! - handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } else if (event.getClearType() in EventType.POLL_RESPONSE) { - sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> - pollAggregationProcessor.handlePollResponseEvent(session, realm, event) - } - } + when (encryptedEventContent?.relatesTo?.type) { + RelationType.REPLACE -> { + Timber.v("###REPLACE in room $roomId for event ${event.eventId}") + // A replace! + handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } - } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { - when (event.getClearType()) { - EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_CANCEL, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_MAC, - EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY -> { - Timber.v("## SAS REF in room $roomId for event ${event.eventId}") - encryptedEventContent.relatesTo.eventId?.let { - handleVerification(realm, event, roomId, isLocalEcho, it) - } - } - in EventType.POLL_RESPONSE -> { - event.getClearContent().toModel (catchError = true)?.let { - sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> - pollAggregationProcessor.handlePollResponseEvent(session, realm, event) - } - } - } - in EventType.POLL_END -> { - sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> - getPowerLevelsHelper(event.roomId)?.let { - pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) - } - } - } - in EventType.BEACON_LOCATION_DATA -> { - handleBeaconLocationData(event, realm, roomId, isLocalEcho) - } + RelationType.RESPONSE -> { + // can we / should we do we something for UTD response?? + Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") } - } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { - // Reaction - if (event.getClearType() == EventType.REACTION) { - // we got a reaction!! - Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}") - handleReaction(realm, event, roomId, isLocalEcho) + RelationType.REFERENCE -> { + // can we / should we do we something for UTD reference?? + Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + RelationType.ANNOTATION -> { + // can we / should we do we something for UTD annotation?? + Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") } } - // HandleInitialAggregatedRelations should also be applied in encrypted messages with annotations -// else if (event.unsignedData?.relations?.annotations != null) { -// Timber.v("###REACTION e2e Aggregation in room $roomId for event ${event.eventId}") -// handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) -// EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() -// ?.let { -// TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll() -// ?.forEach { tet -> tet.annotations = it } -// } -// } } EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } @@ -216,9 +202,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( when (eventToPrune.type) { EventType.MESSAGE -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") -// val unsignedData = EventMapper.map(eventToPrune).unsignedData -// ?: UnsignedData(null, null) - // was this event a m.replace val contentModel = ContentMapper.map(eventToPrune.content)?.toModel () if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { @@ -230,34 +213,34 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - in EventType.POLL_START -> { + in EventType.POLL_START.values -> { val content: MessagePollContent? = event.content.toModel() if (content?.relatesTo?.type == RelationType.REPLACE) { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") // A replace! - handleReplace(realm, event, content, roomId, isLocalEcho) + handleReplace(realm, event, roomId, isLocalEcho, content.relatesTo.eventId) } } - in EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE.values -> { event.content.toModel (catchError = true)?.let { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> pollAggregationProcessor.handlePollResponseEvent(session, realm, event) } } } - in EventType.POLL_END -> { + in EventType.POLL_END.values -> { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> getPowerLevelsHelper(event.roomId)?.let { pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) } } } - in EventType.STATE_ROOM_BEACON_INFO -> { + in EventType.STATE_ROOM_BEACON_INFO.values -> { event.content.toModel (catchError = true)?.let { liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho) } } - in EventType.BEACON_LOCATION_DATA -> { + in EventType.BEACON_LOCATION_DATA.values -> { handleBeaconLocationData(event, realm, roomId, isLocalEcho) } else -> Timber.v("UnHandled event ${event.eventId}") @@ -273,23 +256,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private fun handleReplace( realm: Realm, event: Event, - content: MessageContent, roomId: String, isLocalEcho: Boolean, - relatedEventId: String? = null + relatedEventId: String? ) { val eventId = event.eventId ?: return - val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return - val newContent = content.newContent ?: return - - // Check that the sender is the same + val targetEventId = relatedEventId ?: return val editedEvent = EventEntity.where(realm, targetEventId).findFirst() - if (editedEvent == null) { - // We do not know yet about the edited event - } else if (editedEvent.sender != event.senderId) { - // Edited by someone else, ignore - Timber.w("Ignore edition by someone else") - return + + when (val validity = editValidator.validateEdit(editedEvent?.asDomain(), event)) { + is EventEditValidator.EditValidity.Invalid -> return Unit.also { + Timber.w("Dropping invalid edit ${event.eventId}, reason:${validity.reason}") + } + EventEditValidator.EditValidity.Unknown, // we can't drop the source event might be unknown, will be validated later + EventEditValidator.EditValidity.Valid -> { + // continue + } } // ok, this is a replace @@ -304,11 +286,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( .also { editSummary -> editSummary.editions.add( EditionOfEvent( - senderId = event.senderId ?: "", eventId = event.eventId, - content = ContentMapper.map(newContent), - timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0, - isLocalEcho = isLocalEcho + event = EventEntity.where(realm, eventId).findFirst(), + timestamp = if (isLocalEcho) clock.epochMillis() else event.originServerTs ?: clock.epochMillis(), + isLocalEcho = isLocalEcho, ) ) } @@ -325,17 +306,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // ok it has already been managed Timber.v("###REPLACE Receiving remote echo of edit (edit already done)") existingSummary.editions.firstOrNull { it.eventId == txId }?.let { - it.eventId = event.eventId + it.eventId = eventId it.timestamp = event.originServerTs ?: clock.epochMillis() it.isLocalEcho = false + it.event = EventEntity.where(realm, eventId).findFirst() } } else { Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)") existingSummary.editions.add( EditionOfEvent( - senderId = event.senderId ?: "", - eventId = event.eventId, - content = ContentMapper.map(newContent), + eventId = eventId, + event = EventEntity.where(realm, eventId).findFirst(), timestamp = if (isLocalEcho) { clock.epochMillis() } else { @@ -348,7 +329,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - if (event.getClearType() in EventType.POLL_START) { + if (event.getClearType() in EventType.POLL_START.values) { pollAggregationProcessor.handlePollStartEvent(realm, event) } @@ -500,7 +481,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull { it.eventId == redacted.eventId } if (sourceToDiscard == null) { - Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard") + Timber.w("Redaction of a replace that was not known in aggregation") return } // Need to remove this event from the edition list @@ -598,12 +579,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private fun handleBeaconLocationData(event: Event, realm: Realm, roomId: String, isLocalEcho: Boolean) { event.getClearContent().toModel (catchError = true)?.let { liveLocationAggregationProcessor.handleBeaconLocationData( - realm, - event, - it, - roomId, - event.getRelationContent()?.eventId, - isLocalEcho + realm = realm, + event = event, + content = it, + roomId = roomId, + relatedEventId = event.getRelationContent()?.eventId, + isLocalEcho = isLocalEcho ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 9bcb7b8e4c..31bed90b62 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMembersRespon import org.matrix.android.sdk.internal.session.room.membership.admin.UserIdAndReason import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBody import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody +import org.matrix.android.sdk.internal.session.room.read.ReadBody import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody import org.matrix.android.sdk.internal.session.room.send.SendResponse @@ -173,7 +174,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("receiptType") receiptType: String, @Path("eventId") eventId: String, - @Body body: JsonDict = emptyMap() + @Body body: ReadBody ) /** 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 509cf6b7f8..d6defc6d9d 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,6 +21,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure @@ -95,7 +96,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( * Create a local room entity from the given room creation params. * This will also generate and store in database the chunk and the events related to the room params in order to retrieve and display the local room. */ - private suspend fun createLocalRoomEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { + private fun createLocalRoomEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { RoomEntity.getOrCreate(realm, roomId).apply { membership = Membership.JOIN chunks.add(createLocalRoomChunk(realm, roomId, createRoomBody)) @@ -148,13 +149,16 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( * * @return a chunk entity */ - private suspend fun createLocalRoomChunk(realm: Realm, roomId: String, createRoomBody: CreateRoomBody): ChunkEntity { + private fun createLocalRoomChunk(realm: Realm, roomId: String, createRoomBody: CreateRoomBody): ChunkEntity { val chunkEntity = realm.createObject ().apply { isLastBackward = true isLastForward = true } - val eventList = createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(createRoomBody)) + // Can't suspend when using realm as it could jump thread + val eventList = runBlocking { + createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(createRoomBody)) + } val roomMemberContentsByUser = HashMap () for (event in eventList) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt index eb966b684c..8b5fde6ab7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/RoomCreateEventProcessor.kt @@ -30,7 +30,7 @@ import javax.inject.Inject internal class RoomCreateEventProcessor @Inject constructor() : EventInsertLiveProcessor { - override suspend fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event) { val createRoomContent = event.getClearContent().toModel () val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: return diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt index 60312071d7..c36efa064f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt @@ -73,7 +73,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return sendLiveLocationTask.execute(params) } - override suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult { + override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult { // Ensure to stop any active live before starting a new one if (checkIfExistingActiveLive()) { val result = stopLiveLocationShare() @@ -84,7 +84,6 @@ internal class DefaultLocationSharingService @AssistedInject constructor( val params = StartLiveLocationShareTask.Params( roomId = roomId, timeoutMillis = timeoutMillis, - description = description ) return startLiveLocationShareTask.execute(params) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt index a8d955af1d..ae7022a204 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt @@ -39,7 +39,7 @@ internal class DefaultGetActiveBeaconInfoForUserTask @Inject constructor( ) : GetActiveBeaconInfoForUserTask { override suspend fun execute(params: GetActiveBeaconInfoForUserTask.Params): Event? { - return EventType.STATE_ROOM_BEACON_INFO + return EventType.STATE_ROOM_BEACON_INFO.values .mapNotNull { stateEventDataSource.getStateEvent( roomId = params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt index fa3479ed3c..dbdc5dc228 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt @@ -40,7 +40,7 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() : return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO } - override suspend fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event) { if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) { return } @@ -48,7 +48,7 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() : val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst() ?: return - if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) { + if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO.values) { val liveSummary = LiveLocationShareAggregatedSummaryEntity.get(realm, eventId = redactedEvent.eventId) if (liveSummary != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt index 79019e4765..13753115ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt @@ -30,7 +30,6 @@ internal interface StartLiveLocationShareTask : Task allowedKeys.contains(key) } - eventToPrune.content = ContentMapper.map(prunedContent) - } else { - when (typeToPrune) { - EventType.ENCRYPTED, - EventType.MESSAGE, - in EventType.STATE_ROOM_BEACON_INFO, - in EventType.BEACON_LOCATION_DATA, - in EventType.POLL_START -> { - Timber.d("REDACTION for message ${eventToPrune.eventId}") - val unsignedData = EventMapper.map(eventToPrune).unsignedData - ?: UnsignedData(null, null) + when { + allowedKeys.isNotEmpty() -> { + val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) } + eventToPrune.content = ContentMapper.map(prunedContent) + } + canPruneEventType(typeToPrune) -> { + Timber.d("REDACTION for message ${eventToPrune.eventId}") + val unsignedData = EventMapper.map(eventToPrune).unsignedData ?: UnsignedData(null, null) - // was this event a m.replace + // was this event a m.replace // val contentModel = ContentMapper.map(eventToPrune.content)?.toModel () // if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { // eventRelationsAggregationUpdater.handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) // } - val modified = unsignedData.copy(redactedEvent = redactionEvent) - // Deleting the content of a thread message will result to delete the thread relation, however threads are now dynamic - // so there is not much of a problem - eventToPrune.content = ContentMapper.map(emptyMap()) - eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) - eventToPrune.decryptionResultJson = null - eventToPrune.decryptionErrorCode = null + val modified = unsignedData.copy(redactedEvent = redactionEvent) + eventToPrune.content = ContentMapper.map(emptyMap()) + eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) + eventToPrune.decryptionResultJson = null + eventToPrune.decryptionErrorCode = null - handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho) - } -// EventType.REACTION -> { -// eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId) -// } + handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho) + } + else -> { + Timber.w("Not pruning event (type $typeToPrune)") } } if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) { @@ -167,4 +158,28 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr else -> emptyList() } } + + private fun canPruneEventType(eventType: String): Boolean { + return when { + EventType.isCallEvent(eventType) -> false + EventType.isVerificationEvent(eventType) -> false + eventType == EventType.STATE_ROOM_WIDGET_LEGACY || + eventType == EventType.STATE_ROOM_WIDGET || + eventType == EventType.STATE_ROOM_NAME || + eventType == EventType.STATE_ROOM_TOPIC || + eventType == EventType.STATE_ROOM_AVATAR || + eventType == EventType.STATE_ROOM_THIRD_PARTY_INVITE || + eventType == EventType.STATE_ROOM_GUEST_ACCESS || + eventType == EventType.STATE_SPACE_CHILD || + eventType == EventType.STATE_SPACE_PARENT || + eventType == EventType.STATE_ROOM_TOMBSTONE || + eventType == EventType.STATE_ROOM_HISTORY_VISIBILITY || + eventType == EventType.STATE_ROOM_RELATED_GROUPS || + eventType == EventType.STATE_ROOM_PINNED_EVENT || + eventType == EventType.STATE_ROOM_ENCRYPTION || + eventType == EventType.STATE_ROOM_SERVER_ACL || + eventType == EventType.REACTION -> false + else -> true + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index b30c66c82e..36ec5e8dac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -30,17 +30,20 @@ import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity +import org.matrix.android.sdk.internal.database.query.forMainTimelineWhere import org.matrix.android.sdk.internal.database.query.isEventRead import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource internal class DefaultReadService @AssistedInject constructor( @Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, private val setReadMarkersTask: SetReadMarkersTask, private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - @UserId private val userId: String + @UserId private val userId: String, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource ) : ReadService { @AssistedFactory @@ -48,17 +51,28 @@ internal class DefaultReadService @AssistedInject constructor( fun create(roomId: String): DefaultReadService } - override suspend fun markAsRead(params: ReadService.MarkAsReadParams) { + override suspend fun markAsRead(params: ReadService.MarkAsReadParams, mainTimeLineOnly: Boolean) { + val readReceiptThreadId = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canUseThreadReadReceiptsAndNotifications == true) { + if (mainTimeLineOnly) ReadService.THREAD_ID_MAIN else null + } else { + null + } val taskParams = SetReadMarkersTask.Params( roomId = roomId, forceReadMarker = params.forceReadMarker(), - forceReadReceipt = params.forceReadReceipt() + forceReadReceipt = params.forceReadReceipt(), + readReceiptThreadId = readReceiptThreadId ) setReadMarkersTask.execute(taskParams) } - override suspend fun setReadReceipt(eventId: String) { - val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId) + override suspend fun setReadReceipt(eventId: String, threadId: String) { + val readReceiptThreadId = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canUseThreadReadReceiptsAndNotifications == true) { + threadId + } else { + null + } + val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId, readReceiptThreadId = readReceiptThreadId) setReadMarkersTask.execute(params) } @@ -68,7 +82,8 @@ internal class DefaultReadService @AssistedInject constructor( } override fun isEventRead(eventId: String): Boolean { - return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId) + val shouldCheckIfReadInEventsThread = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canUseThreadReadReceiptsAndNotifications == true + return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId, shouldCheckIfReadInEventsThread) } override fun getReadMarkerLive(): LiveData > { @@ -81,9 +96,9 @@ internal class DefaultReadService @AssistedInject constructor( } } - override fun getMyReadReceiptLive(): LiveData > { + override fun getMyReadReceiptLive(threadId: String?): LiveData > { val liveRealmData = monarchy.findAllMappedWithChanges( - { ReadReceiptEntity.where(it, roomId = roomId, userId = userId) }, + { ReadReceiptEntity.where(it, roomId = roomId, userId = userId, threadId = threadId) }, { it.eventId } ) return Transformations.map(liveRealmData) { @@ -94,10 +109,11 @@ internal class DefaultReadService @AssistedInject constructor( override fun getUserReadReceipt(userId: String): String? { var eventId: String? = null monarchy.doWithRealm { - eventId = ReadReceiptEntity.where(it, roomId = roomId, userId = userId) + eventId = ReadReceiptEntity.forMainTimelineWhere(it, roomId = roomId, userId = userId) .findFirst() ?.eventId } + return eventId } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/ReadBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/ReadBody.kt new file mode 100644 index 0000000000..9374de5d5f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/ReadBody.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.read + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class ReadBody( + @Json(name = "thread_id") val threadId: String?, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index a124a8a4c2..8e7592a8b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.read import com.zhuinden.monarchy.Monarchy import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.isEventRead @@ -45,8 +46,9 @@ internal interface SetReadMarkersTask : Task { val roomId: String, val fullyReadEventId: String? = null, val readReceiptEventId: String? = null, + val readReceiptThreadId: String? = null, val forceReadReceipt: Boolean = false, - val forceReadMarker: Boolean = false + val forceReadMarker: Boolean = false, ) } @@ -61,12 +63,14 @@ internal class DefaultSetReadMarkersTask @Inject constructor( @UserId private val userId: String, private val globalErrorReceiver: GlobalErrorReceiver, private val clock: Clock, + private val homeServerCapabilitiesService: HomeServerCapabilitiesService, ) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { val markers = mutableMapOf () Timber.v("Execute set read marker with params: $params") val latestSyncedEventId = latestSyncedEventId(params.roomId) + val readReceiptThreadId = params.readReceiptThreadId val fullyReadEventId = if (params.forceReadMarker) { latestSyncedEventId } else { @@ -77,6 +81,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( } else { params.readReceiptEventId } + if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy.realmConfiguration, params.roomId, fullyReadEventId)) { if (LocalEcho.isLocalEchoId(fullyReadEventId)) { Timber.w("Can't set read marker for local event $fullyReadEventId") @@ -84,8 +89,12 @@ internal class DefaultSetReadMarkersTask @Inject constructor( markers[READ_MARKER] = fullyReadEventId } } + + val shouldCheckIfReadInEventsThread = readReceiptThreadId != null && + homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications + if (readReceiptEventId != null && - !isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId)) { + !isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId, shouldCheckIfReadInEventsThread)) { if (LocalEcho.isLocalEchoId(readReceiptEventId)) { Timber.w("Can't set read receipt for local event $readReceiptEventId") } else { @@ -95,7 +104,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( val shouldUpdateRoomSummary = readReceiptEventId != null && readReceiptEventId == latestSyncedEventId if (markers.isNotEmpty() || shouldUpdateRoomSummary) { - updateDatabase(params.roomId, markers, shouldUpdateRoomSummary) + updateDatabase(params.roomId, readReceiptThreadId, markers, shouldUpdateRoomSummary) } if (markers.isNotEmpty()) { executeRequest( @@ -104,7 +113,8 @@ internal class DefaultSetReadMarkersTask @Inject constructor( ) { if (markers[READ_MARKER] == null) { if (readReceiptEventId != null) { - roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId) + val readBody = ReadBody(threadId = params.readReceiptThreadId) + roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId, readBody) } } else { // "m.fully_read" value is mandatory to make this call @@ -119,7 +129,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId } - private suspend fun updateDatabase(roomId: String, markers: Map , shouldUpdateRoomSummary: Boolean) { + private suspend fun updateDatabase(roomId: String, readReceiptThreadId: String?, markers: Map , shouldUpdateRoomSummary: Boolean) { monarchy.awaitTransaction { realm -> val readMarkerId = markers[READ_MARKER] val readReceiptId = markers[READ_RECEIPT] @@ -127,7 +137,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId)) } if (readReceiptId != null) { - val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis()) + val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, readReceiptThreadId, clock.epochMillis()) readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null) } if (shouldUpdateRoomSummary) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 9839a44427..ddf3e41dff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -105,19 +105,21 @@ internal class DefaultRelationService @AssistedInject constructor( targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, + newFormattedBodyText: CharSequence?, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String ): Cancelable { - return eventEditor.editTextMessage(targetEvent, msgType, newBodyText, newBodyAutoMarkdown, compatibilityBodyText) + return eventEditor.editTextMessage(targetEvent, msgType, newBodyText, newFormattedBodyText, newBodyAutoMarkdown, compatibilityBodyText) } override fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, + newFormattedBodyText: String?, compatibilityBodyText: String ): Cancelable { - return eventEditor.editReply(replyToEdit, originalTimelineEvent, newBodyText, compatibilityBodyText) + return eventEditor.editReply(replyToEdit, originalTimelineEvent, newBodyText, newFormattedBodyText, compatibilityBodyText) } override suspend fun fetchEditHistory(eventId: String): List { @@ -127,6 +129,7 @@ internal class DefaultRelationService @AssistedInject constructor( override fun replyToMessage( eventReplied: TimelineEvent, replyText: CharSequence, + replyFormattedText: CharSequence?, autoMarkdown: Boolean, showInThread: Boolean, rootThreadEventId: String? @@ -135,6 +138,7 @@ internal class DefaultRelationService @AssistedInject constructor( roomId = roomId, eventReplied = eventReplied, replyText = replyText, + replyTextFormatted = replyFormattedText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId, showInThread = showInThread @@ -178,6 +182,7 @@ internal class DefaultRelationService @AssistedInject constructor( roomId = roomId, eventReplied = eventReplied, replyText = replyInThreadText, + replyTextFormatted = formattedText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId, showInThread = false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index 795e9003ce..c83539c8fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable +import org.matrix.android.sdk.api.util.TextContent import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository @@ -42,19 +43,25 @@ internal class EventEditor @Inject constructor( targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, + newBodyFormattedText: CharSequence?, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String ): Cancelable { val roomId = targetEvent.roomId if (targetEvent.root.sendState.hasFailed()) { // We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event. - val editedEvent = eventFactory.createTextEvent(roomId, msgType, newBodyText, newBodyAutoMarkdown).copy( + val editedEvent = if (newBodyFormattedText != null) { + val content = TextContent(newBodyText.toString(), newBodyFormattedText.toString()) + eventFactory.createFormattedTextEvent(roomId, content, msgType) + } else { + eventFactory.createTextEvent(roomId, msgType, newBodyText, newBodyAutoMarkdown) + }.copy( eventId = targetEvent.eventId ) return sendFailedEvent(targetEvent, editedEvent) } else if (targetEvent.root.sendState.isSent()) { val event = eventFactory - .createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText) + .createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyFormattedText, newBodyAutoMarkdown, msgType, compatibilityBodyText) return sendReplaceEvent(event) } else { // Should we throw? @@ -100,6 +107,7 @@ internal class EventEditor @Inject constructor( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, + newBodyFormattedText: String?, compatibilityBodyText: String ): Cancelable { val roomId = replyToEdit.roomId @@ -109,6 +117,7 @@ internal class EventEditor @Inject constructor( roomId = roomId, eventReplied = originalTimelineEvent, replyText = newBodyText, + replyTextFormatted = newBodyFormattedText, autoMarkdown = false, showInThread = false )?.copy( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt index 5f5c000171..93c7f143fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider 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.timeline.TimelineEventDataSource import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -35,7 +36,8 @@ internal interface FetchEditHistoryTask : Task { @@ -50,10 +52,14 @@ internal class DefaultFetchEditHistoryTask @Inject constructor( } // Filter out edition form other users, and redacted editions - val originalSenderId = response.originalEvent?.senderId + val originalEvent = eventDataSource.getTimelineEvent( + roomId = params.roomId, + eventId = params.eventId, + ) + val originalSenderId = originalEvent?.senderInfo?.userId val events = response.chunks .filter { it.senderId == originalSenderId } .filter { !it.isRedacted() } - return events + listOfNotNull(response.originalEvent) + return events + listOfNotNull(originalEvent?.root) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt index a65165d457..f2b0651e01 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.events.model.Event @JsonClass(generateAdapter = true) internal data class RelationsResponse( @Json(name = "chunk") val chunks: List , - @Json(name = "original_event") val originalEvent: Event?, @Json(name = "next_batch") val nextBatch: String?, @Json(name = "prev_batch") val prevBatch: String? ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index 53a81fda26..48d340b6e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.session.room.relation.threads import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import kotlinx.coroutines.runBlocking +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult @@ -24,6 +26,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity @@ -46,6 +49,7 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction @@ -87,6 +91,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val cryptoService: CryptoService, private val clock: Clock, + private val realmSessionProvider: RealmSessionProvider, + private val getEventTask: GetEventTask, ) : FetchThreadTimelineTask { enum class Result { @@ -114,11 +120,28 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( params: FetchThreadTimelineTask.Params ): Result { val threadList = response.chunks - val threadRootEvent = response.originalEvent val hasReachEnd = response.nextBatch == null - monarchy.awaitTransaction { realm -> + val isRootThreadTimelineEventEntityKnown: Boolean + var threadRootEvent: Event? = null + if (hasReachEnd) { + isRootThreadTimelineEventEntityKnown = realmSessionProvider.withRealm { realm -> + TimelineEventEntity + .where(realm, roomId = params.roomId, eventId = params.rootThreadEventId) + .findFirst() + } != null + if (!isRootThreadTimelineEventEntityKnown) { + // Fetch the root event from the server + threadRootEvent = tryOrNull { + runBlocking { + getEventTask.execute(GetEventTask.Params(roomId = params.roomId, eventId = params.rootThreadEventId)) + } + } + } + } + + monarchy.awaitTransaction { realm -> val threadChunk = ChunkEntity.findLastForwardChunkOfThread(realm, params.roomId, params.rootThreadEventId) ?: run { return@awaitTransaction @@ -173,7 +196,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( // Case when thread event is not in the device Timber.i("###THREADS FetchThreadTimelineTask root thread event: ${params.rootThreadEventId} NOT FOUND! Lets create a temp one") val eventEntity = createEventEntity(params.roomId, threadRootEvent, realm) - roomMemberContentsByUser.addSenderState(realm, params.roomId, threadRootEvent.senderId) + roomMemberContentsByUser.addSenderState(realm, params.roomId, threadRootEvent.senderId!!) threadChunk.addTimelineEvent( roomId = params.roomId, eventEntity = eventEntity, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 418000abed..9cdbc7ff46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl import org.matrix.android.sdk.api.session.content.ContentAttachmentData +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.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -87,51 +89,60 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable { - return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown) + override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable { - return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType) + override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String?): Cancelable { + override fun sendQuotedTextMessage( + quotedEvent: TimelineEvent, + text: String, + formattedText: String?, + autoMarkdown: Boolean, + rootThreadEventId: String?, + additionalContent: Content?, + ): Cancelable { return localEchoEventFactory.createQuotedTextEvent( roomId = roomId, quotedEvent = quotedEvent, text = text, + formattedText = formattedText, autoMarkdown = autoMarkdown, - rootThreadEventId = rootThreadEventId + rootThreadEventId = rootThreadEventId, + additionalContent = additionalContent, ) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun sendPoll(pollType: PollType, question: String, options: List ): Cancelable { - return localEchoEventFactory.createPollEvent(roomId, pollType, question, options) + override fun sendPoll(pollType: PollType, question: String, options: List , additionalContent: Content?): Cancelable { + return localEchoEventFactory.createPollEvent(roomId, pollType, question, options, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun voteToPoll(pollEventId: String, answerId: String): Cancelable { - return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId) + override fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun endPoll(pollEventId: String): Cancelable { - return localEchoEventFactory.createEndPollEvent(roomId, pollEventId) + override fun endPoll(pollEventId: String, additionalContent: Content?): Cancelable { + return localEchoEventFactory.createEndPollEvent(roomId, pollEventId, additionalContent) .also { createLocalEcho(it) } .let { sendEvent(it) } } - override fun redactEvent(event: Event, reason: String?): Cancelable { + override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable { // TODO manage media/attachements? - val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) + val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent) .also { createLocalEcho(it) } return eventSenderProcessor.postRedaction(redactionEcho, reason) } @@ -257,7 +268,8 @@ internal class DefaultSendService @AssistedInject constructor( attachments: List , compressBeforeSending: Boolean, roomIds: Set , - rootThreadEventId: String? + rootThreadEventId: String?, + additionalContent: Content?, ): Cancelable { return attachments.mapTo(CancelableBag()) { sendMedia( @@ -273,7 +285,9 @@ internal class DefaultSendService @AssistedInject constructor( attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set , - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + additionalContent: Content?, ): Cancelable { // Ensure that the event will not be send in a thread if we are a different flow. // Like sending files to multiple rooms @@ -288,7 +302,9 @@ internal class DefaultSendService @AssistedInject constructor( localEchoEventFactory.createMediaEvent( roomId = it, attachment = attachment, - rootThreadEventId = rootThreadId + rootThreadEventId = rootThreadId, + relatesTo, + additionalContent, ).also { event -> createLocalEcho(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 4fbc91e9ec..2f8be69473 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -95,12 +95,12 @@ internal class LocalEchoEventFactory @Inject constructor( private val permalinkFactory: PermalinkFactory, private val clock: Clock, ) { - fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event { + fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean, additionalContent: Content? = null): Event { if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) { - return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType) + return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType, additionalContent) } val content = MessageTextContent(msgType = msgType, body = text.toString()) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent { @@ -116,35 +116,41 @@ internal class LocalEchoEventFactory @Inject constructor( return TextContent(text.toString()) } - fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event { - return createMessageEvent(roomId, textContent.toMessageTextContent(msgType)) + fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String, additionalContent: Content? = null): Event { + return createMessageEvent(roomId, textContent.toMessageTextContent(msgType), additionalContent) } fun createReplaceTextEvent( roomId: String, targetEventId: String, newBodyText: CharSequence, + newBodyFormattedText: CharSequence?, newBodyAutoMarkdown: Boolean, msgType: String, - compatibilityText: String + compatibilityText: String, + additionalContent: Content? = null, ): Event { + val content = if (newBodyFormattedText != null) { + TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType) + } else { + createTextContent(newBodyText, newBodyAutoMarkdown).toMessageTextContent(msgType) + }.toContent() return createMessageEvent( roomId, MessageTextContent( msgType = msgType, body = compatibilityText, relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), - newContent = createTextContent(newBodyText, newBodyAutoMarkdown) - .toMessageTextContent(msgType) - .toContent() - ) + newContent = content, + ), + additionalContent, ) } private fun createPollContent( question: String, options: List , - pollType: PollType + pollType: PollType, ): MessagePollContent { return MessagePollContent( unstablePollCreationInfo = PollCreationInfo( @@ -162,7 +168,8 @@ internal class LocalEchoEventFactory @Inject constructor( pollType: PollType, targetEventId: String, question: String, - options: List + options: List , + additionalContent: Content? = null, ): Event { val newContent = MessagePollContent( relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), @@ -174,15 +181,16 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_START.first(), - content = newContent.toContent() + type = EventType.POLL_START.stable, + content = newContent.toContent().plus(additionalContent.orEmpty()) ) } fun createPollReplyEvent( roomId: String, pollEventId: String, - answerId: String + answerId: String, + additionalContent: Content? = null, ): Event { val content = MessagePollResponseContent( body = answerId, @@ -198,8 +206,8 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_RESPONSE.first(), - content = content.toContent(), + type = EventType.POLL_RESPONSE.stable, + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -208,7 +216,8 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, pollType: PollType, question: String, - options: List + options: List , + additionalContent: Content? = null, ): Event { val content = createPollContent(question, options, pollType) val localId = LocalEcho.createLocalEchoId() @@ -217,15 +226,16 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_START.first(), - content = content.toContent(), + type = EventType.POLL_START.stable, + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } fun createEndPollEvent( roomId: String, - eventId: String + eventId: String, + additionalContent: Content? = null, ): Event { val content = MessageEndPollContent( relatesTo = RelationDefaultContent( @@ -239,8 +249,8 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.POLL_END.first(), - content = content.toContent(), + type = EventType.POLL_END.stable, + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -250,7 +260,8 @@ internal class LocalEchoEventFactory @Inject constructor( latitude: Double, longitude: Double, uncertainty: Double?, - isUserLocation: Boolean + isUserLocation: Boolean, + additionalContent: Content? = null, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN @@ -262,7 +273,7 @@ internal class LocalEchoEventFactory @Inject constructor( unstableTimestampMillis = clock.epochMillis(), unstableText = geoUri ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } fun createLiveLocationEvent( @@ -270,7 +281,8 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, latitude: Double, longitude: Double, - uncertainty: Double? + uncertainty: Double?, + additionalContent: Content? = null, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val content = MessageBeaconLocationDataContent( @@ -288,8 +300,8 @@ internal class LocalEchoEventFactory @Inject constructor( originServerTs = dummyOriginServerTs(), senderId = userId, eventId = localId, - type = EventType.BEACON_LOCATION_DATA.first(), - content = content.toContent(), + type = EventType.BEACON_LOCATION_DATA.stable, + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -301,7 +313,8 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyText: String, autoMarkdown: Boolean, msgType: String, - compatibilityText: String + compatibilityText: String, + additionalContent: Content? = null, ): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" @@ -336,25 +349,42 @@ internal class LocalEchoEventFactory @Inject constructor( formattedBody = replyFormatted ) .toContent() - ) + ), + additionalContent, ) } fun createMediaEvent( roomId: String, attachment: ContentAttachmentData, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + additionalContent: Content? = null, ): Event { return when (attachment.type) { - ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent) + ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent) + ContentAttachmentData.Type.AUDIO -> createAudioEvent( + roomId, + attachment, + isVoiceMessage = false, + rootThreadEventId = rootThreadEventId, + relatesTo, + additionalContent + ) + ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent( + roomId, + attachment, + isVoiceMessage = true, + rootThreadEventId = rootThreadEventId, + relatesTo, + additionalContent, + ) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent) } } - fun createReactionEvent(roomId: String, targetEventId: String, reaction: String): Event { + fun createReactionEvent(roomId: String, targetEventId: String, reaction: String, additionalContent: Content? = null): Event { val content = ReactionContent( ReactionInfo( RelationType.ANNOTATION, @@ -369,12 +399,18 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = EventType.REACTION, - content = content.toContent(), + content = content.toContent().plus(additionalContent.orEmpty()), unsignedData = UnsignedData(age = null, transactionId = localId) ) } - private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createImageEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + additionalContent: Content?, + ): Event { var width = attachment.width var height = attachment.height @@ -399,19 +435,18 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } - private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createVideoEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + additionalContent: Content?, + ): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -443,23 +478,18 @@ internal class LocalEchoEventFactory @Inject constructor( thumbnailInfo = thumbnailInfo ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } private fun createAudioEvent( roomId: String, attachment: ContentAttachmentData, isVoiceMessage: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + additionalContent: Content? ): Event { val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, @@ -475,19 +505,18 @@ internal class LocalEchoEventFactory @Inject constructor( waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } - private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createFileEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + additionalContent: Content? + ): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", @@ -496,24 +525,18 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } - private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event { - return createEvent(roomId, EventType.MESSAGE, content.toContent()) + private fun createMessageEvent(roomId: String, content: MessageContent, additionalContent: Content?): Event { + return createEvent(roomId, EventType.MESSAGE, content.toContent(), additionalContent) } - fun createEvent(roomId: String, type: String, content: Content?): Event { + fun createEvent(roomId: String, type: String, content: Content?, additionalContent: Content? = null): Event { val newContent = enhanceStickerIfNeeded(type, content) ?: content + val updatedNewContent = newContent?.plus(additionalContent.orEmpty()) ?: additionalContent val localId = LocalEcho.createLocalEchoId() return Event( roomId = roomId, @@ -521,7 +544,7 @@ internal class LocalEchoEventFactory @Inject constructor( senderId = userId, eventId = localId, type = type, - content = newContent, + content = updatedNewContent, unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -555,7 +578,8 @@ internal class LocalEchoEventFactory @Inject constructor( text: CharSequence, msgType: String, autoMarkdown: Boolean, - formattedText: String? + formattedText: String?, + additionalContent: Content? = null, ): Event { val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown) return createEvent( @@ -565,8 +589,7 @@ internal class LocalEchoEventFactory @Inject constructor( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), msgType = msgType - ) - .toContent() + ).toContent().plus(additionalContent.orEmpty()) ) } @@ -581,9 +604,11 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, eventReplied: TimelineEvent, replyText: CharSequence, + replyTextFormatted: CharSequence?, autoMarkdown: Boolean, rootThreadEventId: String? = null, - showInThread: Boolean + showInThread: Boolean, + additionalContent: Content? = null ): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null @@ -594,7 +619,7 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val replyTextFormatted = markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() + val finalReplyTextFormatted = replyTextFormatted?.toString() ?: markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. val bodyFormatted = body.formattedText ?: markdownParser.parse(body.text, force = true, advanced = autoMarkdown).takeFormatted() val replyFormatted = buildFormattedReply( @@ -602,7 +627,7 @@ internal class LocalEchoEventFactory @Inject constructor( userLink, userId, bodyFormatted, - replyTextFormatted + finalReplyTextFormatted ) // // > <@alice:example.org> This is the original body @@ -621,9 +646,17 @@ internal class LocalEchoEventFactory @Inject constructor( showInThread = showInThread ) ) - return createMessageEvent(roomId, content) + return createMessageEvent(roomId, content, additionalContent) } + private fun generateThreadRelationContent(rootThreadEventId: String) = + RelationDefaultContent( + type = RelationType.THREAD, + eventId = rootThreadEventId, + isFallingBack = true, + inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)), + ) + /** * Generates the appropriate relatesTo object for a reply event. * It can either be a regular reply or a reply within a thread @@ -742,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor( } } */ - fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event { + fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event { val localId = LocalEcho.createLocalEchoId() return Event( roomId = roomId, @@ -751,7 +784,7 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.REDACTION, redacts = eventId, - content = reason?.let { mapOf("reason" to it).toContent() }, + content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent, unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -765,51 +798,75 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, quotedEvent: TimelineEvent, text: String, + formattedText: String?, autoMarkdown: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, + additionalContent: Content? = null, ): Event { val messageContent = quotedEvent.getLastMessageContent() - val textMsg = messageContent?.body - val quoteText = legacyRiotQuoteText(textMsg, text) - + val formattedQuotedText = (messageContent as? MessageContentWithFormattedBody)?.formattedBody + val textContent = createQuoteTextContent(messageContent?.body, formattedQuotedText, text, formattedText, autoMarkdown) return if (rootThreadEventId != null) { createMessageEvent( roomId, - markdownParser - .parse(quoteText, force = true, advanced = autoMarkdown) - .toThreadTextContent( + textContent.toThreadTextContent( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), msgType = MessageType.MSGTYPE_TEXT - ) + ), + additionalContent, ) } else { createFormattedTextEvent( roomId, - markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), - MessageType.MSGTYPE_TEXT + textContent, + MessageType.MSGTYPE_TEXT, + additionalContent, ) } } - private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { - val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray() - return buildString { - if (messageParagraphs != null) { - for (i in messageParagraphs.indices) { - if (messageParagraphs[i].isNotBlank()) { - append("> ") - append(messageParagraphs[i]) - } + private fun createQuoteTextContent( + quotedText: String?, + formattedQuotedText: String?, + text: String, + formattedText: String?, + autoMarkdown: Boolean + ): TextContent { + val currentFormattedText = formattedText ?: if (autoMarkdown) { + val parsed = markdownParser.parse(text, force = true, advanced = true) + // If formattedText == text, formattedText is returned as null + parsed.formattedText ?: parsed.text + } else { + text + } + val processedFormattedQuotedText = formattedQuotedText ?: quotedText - if (i != messageParagraphs.lastIndex) { - append("\n\n") - } + val plainTextBody = buildString { + val plainMessageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray().orEmpty() + plainMessageParagraphs.forEachIndexed { index, paragraph -> + if (paragraph.isNotBlank()) { + append("> ") + append(paragraph) + } + + if (index != plainMessageParagraphs.lastIndex) { + append("\n\n") } } append("\n\n") - append(myText) + append(text) } + val formattedTextBody = buildString { + if (!processedFormattedQuotedText.isNullOrBlank()) { + append(" ") + append(processedFormattedQuotedText) + append("") + } + append("
") + append(currentFormattedText) + } + return TextContent(plainTextBody, formattedTextBody) } companion object { 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 97d967ea6c..83c1f8a52e 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 @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull 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.toModel +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent @@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper 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.sync.model.RoomSyncUnreadNotifications +import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadThreadNotifications import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -72,7 +74,9 @@ internal class RoomSummaryUpdater @Inject constructor( private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver, private val eventDecryptor: EventDecryptor, - private val roomAccountDataDataSource: RoomAccountDataDataSource +// private val crossSigningService: DefaultCrossSigningService, + private val roomAccountDataDataSource: RoomAccountDataDataSource, + private val homeServerCapabilitiesService: HomeServerCapabilitiesService, ) { fun refreshLatestPreviewContent(realm: Realm, roomId: String) { @@ -89,6 +93,7 @@ internal class RoomSummaryUpdater @Inject constructor( membership: Membership? = null, roomSummary: RoomSyncSummary? = null, unreadNotifications: RoomSyncUnreadNotifications? = null, + unreadThreadNotifications: Map? = null, updateMembers: Boolean = false, inviterId: String? = null, aggregator: SyncResponsePostTreatmentAggregator? = null @@ -109,6 +114,14 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0 roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 + roomSummaryEntity.threadHighlightCount = unreadThreadNotifications + ?.count { (it.value.highlightCount ?: 0) > 0 } + ?: 0 + + roomSummaryEntity.threadNotificationCount = unreadThreadNotifications + ?.count { (it.value.notificationCount ?: 0) > 0 } + ?: 0 + if (membership != null) { roomSummaryEntity.membership = membership } @@ -139,9 +152,11 @@ internal class RoomSummaryUpdater @Inject constructor( latestPreviewableEvent.attemptToDecrypt() } + val shouldCheckIfReadInEventsThread = homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications + roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 || // avoid this call if we are sure there are unread events - latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false + latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId, shouldCheckIfReadInEventsThread) } ?: false roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index c380ccf14f..0854cc5cf4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -411,7 +411,7 @@ internal class DefaultTimeline( private fun ensureReadReceiptAreLoaded(realm: Realm) { readReceiptHandler.getContentFromInitSync(roomId) ?.also { - Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId") + Timber.d("INIT_SYNC Insert when opening timeline RR for room $roomId") } ?.let { readReceiptContent -> realm.executeTransactionAsync { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 53c0253876..b1a3d51b36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -96,4 +96,8 @@ internal class DefaultTimelineService @AssistedInject constructor( override fun getAttachmentMessages(): List { return timelineEventDataSource.getAttachmentMessages(roomId) } + + override fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List { + return timelineEventDataSource.getTimelineEventsRelatedTo(roomId, relationType, eventId) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt index 96646b42ed..9d8d8ecbf1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt @@ -47,7 +47,7 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor( ) : FetchTokenAndPaginateTask { override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { - val filter = filterRepository.getRoomFilter() + val filter = filterRepository.getRoomFilterBody() val response = executeRequest(globalErrorReceiver) { roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt index 015e55f070..c3911dfa2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt @@ -39,7 +39,7 @@ internal class DefaultGetContextOfEventTask @Inject constructor( ) : GetContextOfEventTask { override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result { - val filter = filterRepository.getRoomFilter() + val filter = filterRepository.getRoomFilterBody() val response = executeRequest(globalErrorReceiver) { // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process. roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt index 8aeccb66c8..1a7b1cdac4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt @@ -41,7 +41,7 @@ internal class DefaultPaginationTask @Inject constructor( ) : PaginationTask { override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { - val filter = filterRepository.getRoomFilter() + val filter = filterRepository.getRoomFilterBody() val chunk = executeRequest( globalErrorReceiver, canRetry = true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt index b1b9e4bb22..2d6082f9b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt @@ -19,8 +19,11 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.Sort +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider @@ -63,4 +66,24 @@ internal class TimelineEventDataSource @Inject constructor( .orEmpty() } } + + fun getTimelineEventsRelatedTo(roomId: String, eventType: String, eventId: String): List { + // TODO Remove this trick and call relations API + // see https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1roomsroomidrelationseventidreltypeeventtype + return realmSessionProvider.withRealm { realm -> + TimelineEventEntity.whereRoomId(realm, roomId) + .sort(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, Sort.ASCENDING) + .distinct(TimelineEventEntityFields.EVENT_ID) + .findAll() + .mapNotNull { + timelineEventMapper.map(it) + .takeIf { + val isEventRelatedTo = it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null + val isContentRelatedTo = it.root.getClearContent()?.toModel () + ?.relatesTo?.takeIf { it.type == eventType && it.eventId == eventId } != null + isEventRelatedTo || isContentRelatedTo + } + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt index 2b404775f0..3684bec167 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tombstone/RoomTombstoneEventProcessor.kt @@ -30,7 +30,7 @@ import javax.inject.Inject internal class RoomTombstoneEventProcessor @Inject constructor() : EventInsertLiveProcessor { - override suspend fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event) { if (event.roomId == null) return val createRoomContent = event.getClearContent().toModel () if (createRoomContent?.replacementRoomId == null) return 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 05b824e69d..b2bdd65896 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 @@ -17,6 +17,11 @@ package org.matrix.android.sdk.internal.session.sync import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.extensions.measureMetric +import org.matrix.android.sdk.api.extensions.measureSpan +import org.matrix.android.sdk.api.metrics.SyncDurationMetricPlugin import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.pushrules.RuleScope @@ -50,9 +55,12 @@ internal class SyncResponseHandler @Inject constructor( private val tokenStore: SyncTokenStore, private val processEventForPushTask: ProcessEventForPushTask, private val pushRuleService: PushRuleService, - private val presenceSyncHandler: PresenceSyncHandler + private val presenceSyncHandler: PresenceSyncHandler, + matrixConfiguration: MatrixConfiguration, ) { + private val relevantPlugins = matrixConfiguration.metricPlugins.filterIsInstance () + suspend fun handleResponse( syncResponse: SyncResponse, fromToken: String?, @@ -61,40 +69,92 @@ internal class SyncResponseHandler @Inject constructor( val isInitialSync = fromToken == null Timber.v("Start handling sync, is InitialSync: $isInitialSync") -// measureTimeMillis { -// if (!cryptoService.isStarted()) { -// Timber.v("Should start cryptoService") -// // TODO surely there's a better place for this than the sync -// cryptoService.start() -// } -// }.also { -// Timber.v("Finish handling start cryptoService in $it ms") -// } + relevantPlugins.measureMetric { + startCryptoService(isInitialSync) - // Handle the to device events before the room ones - // to ensure to decrypt them properly - measureTimeMillis { - Timber.v("Handle toDevice") + // Handle the to device events before the room ones + // to ensure to decrypt them properly + handleToDevice(syncResponse, reporter) - cryptoService.receiveSyncChanges( - syncResponse.toDevice, - syncResponse.deviceLists, - syncResponse.deviceOneTimeKeysCount - ) - }.also { - Timber.v("Finish handling toDevice in $it ms") + val aggregator = SyncResponsePostTreatmentAggregator() + + // Prerequisite for thread events handling in RoomSyncHandler + // Disabled due to the new fallback + // if (!lightweightSettingsStorage.areThreadMessagesEnabled()) { + // threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse) + // } + + startMonarchyTransaction(syncResponse, isInitialSync, reporter, aggregator) + + aggregateSyncResponse(aggregator) + + postTreatmentSyncResponse(syncResponse, isInitialSync) + + markCryptoSyncCompleted(syncResponse) + + handlePostSync() + + Timber.v("On sync completed") } - val aggregator = SyncResponsePostTreatmentAggregator() + } - // Prerequisite for thread events handling in RoomSyncHandler -// Disabled due to the new fallback -// if (!lightweightSettingsStorage.areThreadMessagesEnabled()) { -// threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse) -// } + private suspend fun startCryptoService(isInitialSync: Boolean) { + relevantPlugins.measureSpan("task", "start_crypto_service") { + measureTimeMillis { + if (!cryptoService.isStarted()) { + Timber.v("Should start cryptoService") + cryptoService.start() + } + cryptoService.onSyncWillProcess(isInitialSync) + }.also { + Timber.v("Finish handling start cryptoService in $it ms") + } + } + } + private suspend fun handleToDevice(syncResponse: SyncResponse, reporter: ProgressReporter?) { + relevantPlugins.measureSpan("task", "handle_to_device") { + measureTimeMillis { + Timber.v("Handle toDevice") + + cryptoService.receiveSyncChanges( + syncResponse.toDevice, + syncResponse.deviceLists, + syncResponse.deviceOneTimeKeysCount + ) + }.also { + Timber.v("Finish handling toDevice in $it ms") + } + } + } + + private suspend fun startMonarchyTransaction( + syncResponse: SyncResponse, + isInitialSync: Boolean, + reporter: ProgressReporter?, + aggregator: SyncResponsePostTreatmentAggregator + ) { // Start one big transaction - monarchy.awaitTransaction { realm -> - // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local) + relevantPlugins.measureSpan("task", "monarchy_transaction") { + monarchy.awaitTransaction { realm -> + // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local) + handleRooms(reporter, syncResponse, realm, isInitialSync, aggregator) + handleAccountData(reporter, realm, syncResponse) + handlePresence(realm, syncResponse) + + tokenStore.saveToken(realm, syncResponse.nextBatch) + } + } + } + + private fun handleRooms( + reporter: ProgressReporter?, + syncResponse: SyncResponse, + realm: Realm, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator + ) { + relevantPlugins.measureSpan("task", "handle_rooms") { measureTimeMillis { Timber.v("Handle rooms") reportSubtask(reporter, InitialSyncStep.ImportingAccountRoom, 1, 0.8f) { @@ -105,7 +165,11 @@ internal class SyncResponseHandler @Inject constructor( }.also { Timber.v("Finish handling rooms in $it ms") } + } + } + private fun handleAccountData(reporter: ProgressReporter?, realm: Realm, syncResponse: SyncResponse) { + relevantPlugins.measureSpan("task", "handle_account_data") { measureTimeMillis { reportSubtask(reporter, InitialSyncStep.ImportingAccountData, 1, 0.1f) { Timber.v("Handle accountData") @@ -114,44 +178,59 @@ internal class SyncResponseHandler @Inject constructor( }.also { Timber.v("Finish handling accountData in $it ms") } + } + } + private fun handlePresence(realm: Realm, syncResponse: SyncResponse) { + relevantPlugins.measureSpan("task", "handle_presence") { measureTimeMillis { Timber.v("Handle Presence") presenceSyncHandler.handle(realm, syncResponse.presence) }.also { Timber.v("Finish handling Presence in $it ms") } - tokenStore.saveToken(realm, syncResponse.nextBatch) } + } - // Everything else we need to do outside the transaction - measureTimeMillis { - aggregatorHandler.handle(aggregator) - }.also { - Timber.v("Aggregator management took $it ms") - } - - measureTimeMillis { - syncResponse.rooms?.let { - checkPushRules(it, isInitialSync) - userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) - dispatchInvitedRoom(it) + private suspend fun aggregateSyncResponse(aggregator: SyncResponsePostTreatmentAggregator) { + relevantPlugins.measureSpan("task", "aggregator_management") { + // Everything else we need to do outside the transaction + measureTimeMillis { + aggregatorHandler.handle(aggregator) + }.also { + Timber.v("Aggregator management took $it ms") } - }.also { - Timber.v("SyncResponse.rooms post treatment took $it ms") } + } - measureTimeMillis { - cryptoService.onSyncCompleted(syncResponse) - }.also { - Timber.v("cryptoService.onSyncCompleted took $it ms") + private suspend fun postTreatmentSyncResponse(syncResponse: SyncResponse, isInitialSync: Boolean) { + relevantPlugins.measureSpan("task", "sync_response_post_treatment") { + measureTimeMillis { + syncResponse.rooms?.let { + checkPushRules(it, isInitialSync) + userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) + dispatchInvitedRoom(it) + } + }.also { + Timber.v("SyncResponse.rooms post treatment took $it ms") + } } + } - // post sync stuffs + private suspend fun markCryptoSyncCompleted(syncResponse: SyncResponse) { + relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { + measureTimeMillis { + cryptoService.onSyncCompleted(syncResponse) + }.also { + Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") + } + } + } + + private fun handlePostSync() { 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/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index ea296d379d..8a287fb0b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -36,7 +36,7 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.toFailure import org.matrix.android.sdk.internal.session.SessionListeners import org.matrix.android.sdk.internal.session.dispatchTo -import org.matrix.android.sdk.internal.session.filter.FilterRepository +import org.matrix.android.sdk.internal.session.filter.GetCurrentFilterTask import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.user.UserStore @@ -64,11 +64,9 @@ internal interface SyncTask : Task { internal class DefaultSyncTask @Inject constructor( private val syncAPI: SyncAPI, @UserId private val userId: String, - private val filterRepository: FilterRepository, private val syncResponseHandler: SyncResponseHandler, private val syncRequestStateTracker: SyncRequestStateTracker, private val syncTokenStore: SyncTokenStore, - private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val userStore: UserStore, private val session: Session, private val sessionListeners: SessionListeners, @@ -79,6 +77,8 @@ internal class DefaultSyncTask @Inject constructor( private val syncResponseParser: InitialSyncResponseParser, private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val clock: Clock, + private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, + private val getCurrentFilterTask: GetCurrentFilterTask ) : SyncTask { private val workingDir = File(fileDirectory, "is") @@ -100,8 +100,13 @@ internal class DefaultSyncTask @Inject constructor( requestParams["since"] = token timeout = params.timeout } + + // Maybe refresh the homeserver capabilities data we know + getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) + val filter = getCurrentFilterTask.execute(Unit) + requestParams["timeout"] = timeout.toString() - requestParams["filter"] = filterRepository.getFilter() + requestParams["filter"] = filter params.presence?.let { requestParams["set_presence"] = it.value } val isInitialSync = token == null @@ -115,8 +120,6 @@ internal class DefaultSyncTask @Inject constructor( ) syncRequestStateTracker.startRoot(InitialSyncStep.ImportingAccount, 100) } - // Maybe refresh the homeserver capabilities data we know - getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) @@ -140,7 +143,7 @@ internal class DefaultSyncTask @Inject constructor( executeRequest(globalErrorReceiver) { syncAPI.sync( params = requestParams, - readTimeOut = readTimeOut + readTimeOut = readTimeOut, ) } } @@ -178,7 +181,7 @@ internal class DefaultSyncTask @Inject constructor( syncRequestStateTracker.setSyncRequestState( SyncRequestState.IncrementalSyncParsing( rooms = nbRooms, - toDevice = nbToDevice + toDevice = nbToDevice, ) ) syncResponseHandler.handleResponse(syncResponse, token, null) 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 index 1f840a82d5..1ee2fc4802 100644 --- 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 @@ -71,10 +71,16 @@ internal class UpdateUserWorker(context: Context, params: WorkerParameters, sess ?.saveLocally() } - private suspend fun fetchUsers(userIdsToFetch: Collection ) = userIdsToFetch.mapNotNull { - tryOrNull { - val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it)) - User.fromJson(it, profileJson) + private suspend fun fetchUsers(userIdsToFetch: Collection ): List { + return userIdsToFetch.mapNotNull { userId -> + tryOrNull { + val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params( + userId = userId, + // Bulk insert later, so tell the task not to store the User. + storeInDatabase = false, + )) + User.fromJson(userId, profileJson) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt index 7329611a01..7f12ce653c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt @@ -33,10 +33,11 @@ import javax.inject.Inject // value : dict key $UserId // value dict key ts // dict value ts value -internal typealias ReadReceiptContent = Map >>> +internal typealias ReadReceiptContent = Map >>> private const val READ_KEY = "m.read" private const val TIMESTAMP_KEY = "ts" +private const val THREAD_ID_KEY = "thread_id" internal class ReadReceiptHandler @Inject constructor( private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore @@ -47,14 +48,19 @@ internal class ReadReceiptHandler @Inject constructor( fun createContent( userId: String, eventId: String, + threadId: String?, currentTimeMillis: Long ): ReadReceiptContent { + val userReadReceipt = mutableMapOf ( + TIMESTAMP_KEY to currentTimeMillis.toDouble(), + ) + threadId?.let { + userReadReceipt.put(THREAD_ID_KEY, threadId) + } return mapOf( eventId to mapOf( READ_KEY to mapOf( - userId to mapOf( - TIMESTAMP_KEY to currentTimeMillis.toDouble() - ) + userId to userReadReceipt ) ) ) @@ -98,8 +104,9 @@ internal class ReadReceiptHandler @Inject constructor( val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId) for ((userId, paramsDict) in userIdsDict) { - val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0 - val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, ts) + val ts = paramsDict[TIMESTAMP_KEY] as? Double ?: 0.0 + val threadId = paramsDict[THREAD_ID_KEY] as String? + val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, threadId, ts) readReceiptsSummary.readReceipts.add(receiptEntity) } readReceiptSummaries.add(readReceiptsSummary) @@ -115,7 +122,7 @@ internal class ReadReceiptHandler @Inject constructor( ) { // First check if we have data from init sync to handle getContentFromInitSync(roomId)?.let { - Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId") + Timber.d("INIT_SYNC Insert during incremental sync RR for room $roomId") doIncrementalSyncStrategy(realm, roomId, it) aggregator?.ephemeralFilesToDelete?.add(roomId) } @@ -132,8 +139,9 @@ internal class ReadReceiptHandler @Inject constructor( } for ((userId, paramsDict) in userIdsDict) { - val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0 - val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId) + val ts = paramsDict[TIMESTAMP_KEY] as? Double ?: 0.0 + val threadId = paramsDict[THREAD_ID_KEY] as String? + val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId, threadId) // ensure new ts is superior to the previous one if (ts > receiptEntity.originServerTs) { ReadReceiptsSummaryEntity.where(realm, receiptEntity.eventId).findFirst()?.also { 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 eac1d450ad..0c51f85730 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 @@ -26,6 +26,8 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult 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.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.room.model.Membership @@ -49,6 +51,7 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain 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.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity @@ -287,6 +290,7 @@ internal class RoomSyncHandler @Inject constructor( Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, + roomSync.unreadThreadNotifications, updateMembers = hasRoomMember, aggregator = aggregator ) @@ -372,7 +376,8 @@ 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, aggregator = aggregator) + roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, + roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) return roomEntity } @@ -484,23 +489,41 @@ internal class RoomSyncHandler @Inject constructor( cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) // Try to remove local echo - event.unsignedData?.transactionId?.also { - val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it) + event.unsignedData?.transactionId?.also { txId -> + val sendingEventEntity = roomEntity.sendingTimelineEvents.find(txId) if (sendingEventEntity != null) { - Timber.v("Remove local echo for tx:$it") + Timber.v("Remove local echo for tx:$txId") roomEntity.sendingTimelineEvents.remove(sendingEventEntity) if (event.isEncrypted() && event.content?.get("algorithm") as? String == MXCRYPTO_ALGORITHM_MEGOLM) { - // updated with echo decryption, to avoid seeing it decrypt again + // updated with echo decryption, to avoid seeing txId decrypt again val adapter = MoshiProvider.providesMoshi().adapter (OlmDecryptionResult::class.java) sendingEventEntity.root?.decryptionResultJson?.let { json -> eventEntity.decryptionResultJson = json event.mxDecryptionResult = adapter.fromJson(json) } } + // also update potential edit that could refer to that event? + // If not display will flicker :/ + val relationContent = event.getRelationContent() + if (relationContent?.type == RelationType.REPLACE) { + relationContent.eventId?.let { targetId -> + EventAnnotationsSummaryEntity.where(realm, roomId, targetId) + .findFirst() + ?.editSummary + ?.editions + ?.forEach { + if (it.eventId == txId) { + // just do that, the aggregation processor will to the rest + it.event = eventEntity + } + } + } + } + // Finally delete the local echo sendingEventEntity.deleteOnCascade(true) } else { - Timber.v("Can't find corresponding local echo for tx:$it") + Timber.v("Can't find corresponding local echo for tx:$txId") } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt index f9feb04e97..98108008fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt @@ -66,6 +66,8 @@ internal class UserDataSource @Inject constructor( } } + fun getUserOrDefault(userId: String): User = getUser(userId) ?: User(userId) + fun getUserLive(userId: String): LiveData > { val liveData = monarchy.findAllMappedWithChanges( { UserEntity.where(it, userId) }, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index df9dcfb903..c73446cf25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -66,7 +66,7 @@ internal class DefaultSessionAccountDataService @Inject constructor( override suspend fun updateUserAccountData(type: String, content: Content) { val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) - awaitCallback { callback -> + awaitCallback { callback -> updateUserAccountDataTask.configureWith(params) { this.retryCount = 5 // TODO Need to refactor retrying out into a helper method. this.callback = callback diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt index 8bd61a7bdf..a43c59a83b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.sender.SenderInfo -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetType @@ -74,7 +73,7 @@ internal class WidgetFactory @Inject constructor( // Ref: https://github.com/matrix-org/matrix-widget-api/blob/master/src/templating/url-template.ts#L29-L33 fun computeURL(widget: Widget, isLightTheme: Boolean): String? { var computedUrl = widget.widgetContent.url ?: return null - val myUser = userDataSource.getUser(userId) ?: User(userId) + val myUser = userDataSource.getUserOrDefault(userId) val keyValue = widget.widgetContent.data.mapKeys { "\$${it.key}" }.toMutableMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt new file mode 100644 index 0000000000..a7de7f5579 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt @@ -0,0 +1,25 @@ +/* + * 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.sync.filter + +internal data class SyncFilterParams( + val lazyLoadMembersForStateEvents: Boolean? = null, + val lazyLoadMembersForMessageEvents: Boolean? = null, + val useThreadNotifications: Boolean? = null, + val listOfSupportedEventTypes: List ? = null, + val listOfSupportedStateEventTypes: List ? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt index 6152eacae5..af3ba80fe4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt @@ -22,7 +22,7 @@ import io.realm.RealmModel import org.matrix.android.sdk.internal.database.awaitTransaction import java.util.concurrent.atomic.AtomicReference -internal suspend fun Monarchy.awaitTransaction(transaction: suspend (realm: Realm) -> T): T { +internal suspend fun Monarchy.awaitTransaction(transaction: (realm: Realm) -> T): T { return awaitTransaction(realmConfiguration, transaction) } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapperTest.kt new file mode 100644 index 0000000000..7ad5bb40e3 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/EditAggregatedSummaryEntityMapperTest.kt @@ -0,0 +1,114 @@ +/* + * 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.mapper + +import io.mockk.every +import io.mockk.mockk +import io.realm.RealmList +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotBe +import org.junit.Test +import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.EditionOfEvent +import org.matrix.android.sdk.internal.database.model.EventEntity + +class EditAggregatedSummaryEntityMapperTest { + + @Test + fun `test mapping summary entity to model`() { + val edits = RealmList ( + EditionOfEvent( + timestamp = 0L, + eventId = "e0", + isLocalEcho = false, + event = mockEvent("e0") + ), + EditionOfEvent( + timestamp = 1L, + eventId = "e1", + isLocalEcho = false, + event = mockEvent("e1") + ), + EditionOfEvent( + timestamp = 30L, + eventId = "e2", + isLocalEcho = true, + event = mockEvent("e2") + ) + ) + val fakeSummaryEntity = mockk { + every { editions } returns edits + } + + val mapped = EditAggregatedSummaryEntityMapper.map(fakeSummaryEntity) + mapped shouldNotBe null + mapped!!.sourceEvents.size shouldBeEqualTo 2 + mapped.localEchos.size shouldBeEqualTo 1 + mapped.localEchos.first() shouldBeEqualTo "e2" + + mapped.lastEditTs shouldBeEqualTo 30L + mapped.latestEdit?.eventId shouldBeEqualTo "e2" + } + + @Test + fun `event with lexicographically largest event_id is treated as more recent`() { + val lowerId = "\$Albatross" + val higherId = "\$Zebra" + + (higherId > lowerId) shouldBeEqualTo true + val timestamp = 1669288766745L + val edits = RealmList ( + EditionOfEvent( + timestamp = timestamp, + eventId = lowerId, + isLocalEcho = false, + event = mockEvent(lowerId) + ), + EditionOfEvent( + timestamp = timestamp, + eventId = higherId, + isLocalEcho = false, + event = mockEvent(higherId) + ), + EditionOfEvent( + timestamp = 1L, + eventId = "e2", + isLocalEcho = true, + event = mockEvent("e2") + ) + ) + + val fakeSummaryEntity = mockk { + every { editions } returns edits + } + val mapped = EditAggregatedSummaryEntityMapper.map(fakeSummaryEntity) + mapped!!.lastEditTs shouldBeEqualTo timestamp + mapped.latestEdit?.eventId shouldBeEqualTo higherId + } + + private fun mockEvent(eventId: String): EventEntity { + return EventEntity().apply { + this.eventId = eventId + this.content = """ + { + "body" : "Hello", + "msgtype": "text" + } + """.trimIndent() + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt new file mode 100644 index 0000000000..08ed20a766 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt @@ -0,0 +1,64 @@ +/* + * 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.mapper + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher +import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity + +class PushersMapperTest { + + @Test + fun `when mapping PusherEntity, then it is mapped into Pusher successfully`() { + val pusherEntity = aPusherEntity() + + val mappedPusher = PushersMapper.map(pusherEntity) + + mappedPusher.pushKey shouldBeEqualTo pusherEntity.pushKey + mappedPusher.kind shouldBeEqualTo pusherEntity.kind.orEmpty() + mappedPusher.appId shouldBeEqualTo pusherEntity.appId + mappedPusher.appDisplayName shouldBeEqualTo pusherEntity.appDisplayName + mappedPusher.deviceDisplayName shouldBeEqualTo pusherEntity.deviceDisplayName + mappedPusher.profileTag shouldBeEqualTo pusherEntity.profileTag + mappedPusher.lang shouldBeEqualTo pusherEntity.lang + mappedPusher.data.url shouldBeEqualTo pusherEntity.data?.url + mappedPusher.data.format shouldBeEqualTo pusherEntity.data?.format + mappedPusher.enabled shouldBeEqualTo pusherEntity.enabled + mappedPusher.deviceId shouldBeEqualTo pusherEntity.deviceId + mappedPusher.state shouldBeEqualTo pusherEntity.state + } + + @Test + fun `when mapping JsonPusher, then it is mapped into Pusher successfully`() { + val jsonPusher = aJsonPusher() + + val mappedPusherEntity = PushersMapper.map(jsonPusher) + + mappedPusherEntity.pushKey shouldBeEqualTo jsonPusher.pushKey + mappedPusherEntity.kind shouldBeEqualTo jsonPusher.kind + mappedPusherEntity.appId shouldBeEqualTo jsonPusher.appId + mappedPusherEntity.appDisplayName shouldBeEqualTo jsonPusher.appDisplayName + mappedPusherEntity.deviceDisplayName shouldBeEqualTo jsonPusher.deviceDisplayName + mappedPusherEntity.profileTag shouldBeEqualTo jsonPusher.profileTag + mappedPusherEntity.lang shouldBeEqualTo jsonPusher.lang + mappedPusherEntity.data?.url shouldBeEqualTo jsonPusher.data?.url + mappedPusherEntity.data?.format shouldBeEqualTo jsonPusher.data?.format + mappedPusherEntity.enabled shouldBeEqualTo jsonPusher.enabled + mappedPusherEntity.deviceId shouldBeEqualTo jsonPusher.deviceId + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt index 9ed6f28d7e..2170371cde 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt @@ -27,6 +27,8 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.Before import org.junit.Test import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.api.util.getApplicationInfoCompat +import org.matrix.android.sdk.api.util.getPackageInfoCompat import java.lang.Exception private const val A_PACKAGE_NAME = "org.matrix.sdk" @@ -49,8 +51,8 @@ class ComputeUserAgentUseCaseTest { every { context.applicationContext } returns context every { context.packageName } returns A_PACKAGE_NAME every { context.packageManager } returns packageManager - every { packageManager.getApplicationInfo(any(), any()) } returns applicationInfo - every { packageManager.getPackageInfo(any (), any()) } returns packageInfo + every { packageManager.getApplicationInfoCompat(any(), any()) } returns applicationInfo + every { packageManager.getPackageInfoCompat(any(), any()) } returns packageInfo } @Test diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/event/ValidDecryptedEventTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/event/ValidDecryptedEventTest.kt new file mode 100644 index 0000000000..5fda242b90 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/event/ValidDecryptedEventTest.kt @@ -0,0 +1,134 @@ +/* + * 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.event + +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldNotBe +import org.junit.Test +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult +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.EncryptedEventContent +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.events.model.toValidDecryptedEvent +import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent + +class ValidDecryptedEventTest { + + private val fakeEvent = Event( + type = EventType.ENCRYPTED, + eventId = "\$eventId", + roomId = "!fakeRoom", + content = EncryptedEventContent( + algorithm = MXCRYPTO_ALGORITHM_MEGOLM, + ciphertext = "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...", + sessionId = "TO2G4u2HlnhtbIJk", + senderKey = "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0", + deviceId = "FAKEE" + ).toContent() + ) + + @Test + fun `A failed to decrypt message should give a null validated decrypted event`() { + fakeEvent.toValidDecryptedEvent() shouldBe null + } + + @Test + fun `Mismatch sender key detection`() { + val decryptedEvent = fakeEvent + .apply { + mxDecryptionResult = OlmDecryptionResult( + payload = mapOf( + "type" to EventType.MESSAGE, + "content" to mapOf( + "body" to "some message", + "msgtype" to "m.text" + ), + ), + senderKey = "the_real_sender_key", + ) + } + + val validDecryptedEvent = decryptedEvent.toValidDecryptedEvent() + validDecryptedEvent shouldNotBe null + + fakeEvent.content!!["senderKey"] shouldNotBe "the_real_sender_key" + validDecryptedEvent!!.cryptoSenderKey shouldBe "the_real_sender_key" + } + + @Test + fun `Mixed content event should be detected`() { + val mixedEvent = Event( + type = EventType.ENCRYPTED, + eventId = "\$eventd ", + roomId = "!fakeRoo", + content = mapOf( + "algorithm" to "m.megolm.v1.aes-sha2", + "ciphertext" to "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...", + "sessionId" to "TO2G4u2HlnhtbIJk", + "senderKey" to "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0", + "deviceId" to "FAKEE", + "body" to "some message", + "msgtype" to "m.text" + ).toContent() + ) + + val unValidatedContent = mixedEvent.getClearContent().toModel () + unValidatedContent?.body shouldBe "some message" + + mixedEvent.toValidDecryptedEvent()?.clearContent?.toModel () shouldBe null + } + + @Test + fun `Basic field validation`() { + val decryptedEvent = fakeEvent + .apply { + mxDecryptionResult = OlmDecryptionResult( + payload = mapOf( + "type" to EventType.MESSAGE, + "content" to mapOf( + "body" to "some message", + "msgtype" to "m.text" + ), + ), + senderKey = "the_real_sender_key", + ) + } + + decryptedEvent.toValidDecryptedEvent() shouldNotBe null + decryptedEvent.copy(roomId = null).toValidDecryptedEvent() shouldBe null + decryptedEvent.copy(eventId = null).toValidDecryptedEvent() shouldBe null + } + + @Test + fun `A clear event is not a valid decrypted event`() { + val mockTextEvent = Event( + type = EventType.MESSAGE, + eventId = "eventId", + roomId = "!fooe:example.com", + content = mapOf( + "body" to "some message", + "msgtype" to "m.text" + ), + originServerTs = 1000, + senderId = "@anne:example.com", + ) + mockTextEvent.toValidDecryptedEvent() shouldBe null + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index dac33069f3..a971973f56 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -71,7 +71,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { + fun `given a persisted pusher, when adding Pusher, then updates api and mutates persisted result with Registered state`() { val realmResult = PusherEntity(appDisplayName = null) monarchy.givenWhereReturns(result = realmResult) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) @@ -85,7 +85,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows`() { + fun `given a persisted push entity and SetPush API fails, when adding Pusher, then mutates persisted result with Failed registration state and rethrows`() { val realmResult = PusherEntity() monarchy.givenWhereReturns(result = realmResult) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) @@ -99,7 +99,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { + fun `given no persisted push entity and SetPush API fails, when adding Pusher, then rethrows error`() { monarchy.givenWhereReturns (result = null) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) pushersAPI.givenSetPusherErrors(SocketException()) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt new file mode 100644 index 0000000000..a00ac3a17d --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt @@ -0,0 +1,66 @@ +/* + * 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.pushers + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.android.sdk.test.fakes.FakeAddPusherTask +import org.matrix.android.sdk.test.fakes.FakeGetPushersTask +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeRemovePusherTask +import org.matrix.android.sdk.test.fakes.FakeTaskExecutor +import org.matrix.android.sdk.test.fakes.FakeTogglePusherTask +import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider +import org.matrix.android.sdk.test.fakes.internal.FakePushGatewayNotifyTask +import org.matrix.android.sdk.test.fixtures.PusherFixture + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultPushersServiceTest { + + private val workManagerProvider = FakeWorkManagerProvider() + private val monarchy = FakeMonarchy() + private val sessionId = "" + private val getPushersTask = FakeGetPushersTask() + private val pushGatewayNotifyTask = FakePushGatewayNotifyTask() + private val addPusherTask = FakeAddPusherTask() + private val togglePusherTask = FakeTogglePusherTask() + private val removePusherTask = FakeRemovePusherTask() + private val taskExecutor = FakeTaskExecutor() + + private val pushersService = DefaultPushersService( + workManagerProvider.instance, + monarchy.instance, + sessionId, + getPushersTask, + pushGatewayNotifyTask, + addPusherTask, + togglePusherTask, + removePusherTask, + taskExecutor.instance, + ) + + @Test + fun `when togglePusher, then execute task`() = runTest { + val pusher = PusherFixture.aPusher() + val enable = true + + pushersService.togglePusher(pusher, enable) + + togglePusherTask.verifyExecution(pusher, enable) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt new file mode 100644 index 0000000000..3c54f6f1e1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt @@ -0,0 +1,64 @@ +/* + * 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.pushers + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.model.PusherEntityFields +import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakePushersAPI +import org.matrix.android.sdk.test.fakes.FakeRequestExecutor +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher +import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultTogglePusherTaskTest { + + private val pushersAPI = FakePushersAPI() + private val monarchy = FakeMonarchy() + private val requestExecutor = FakeRequestExecutor() + private val globalErrorReceiver = FakeGlobalErrorReceiver() + + private val togglePusherTask = DefaultTogglePusherTask(pushersAPI, monarchy.instance, requestExecutor, globalErrorReceiver) + + @Test + fun `execution toggles enable on both local and remote`() = runTest { + val jsonPusher = aJsonPusher(enabled = false) + val params = TogglePusherTask.Params(aJsonPusher(), true) + + val pusherEntity = aPusherEntity(enabled = false) + monarchy.givenWhere () + .givenEqualTo(PusherEntityFields.PUSH_KEY, jsonPusher.pushKey) + .givenFindFirst(pusherEntity) + + togglePusherTask.execute(params) + + val expectedPayload = jsonPusher.copy(enabled = true) + pushersAPI.verifySetPusher(expectedPayload) + monarchy.verifyInsertOrUpdate { + withArg { actual -> + actual.enabled shouldBeEqualTo true + } + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt new file mode 100644 index 0000000000..0ae712bff1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventEditValidatorTest.kt @@ -0,0 +1,372 @@ +/* + * 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 + +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBeInstanceOf +import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult +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.internal.crypto.store.IMXCryptoStore + +class EventEditValidatorTest { + + private val mockTextEvent = Event( + type = EventType.MESSAGE, + eventId = "\$WX8WlNC2reiXrwHIA_CQHmU_pSR-jhOA2xKPRcJN9wQ", + roomId = "!GXKhWsRwiWWvbQDBpe:example.com", + content = mapOf( + "body" to "some message", + "msgtype" to "m.text" + ), + originServerTs = 1000, + senderId = "@alice:example.com", + ) + + private val mockEdit = Event( + type = EventType.MESSAGE, + eventId = "\$-SF7RWLPzRzCbHqK3ZAhIrX5Auh3B2lS5AqJiypt1p0", + roomId = "!GXKhWsRwiWWvbQDBpe:example.com", + content = mapOf( + "body" to "* some message edited", + "msgtype" to "m.text", + "m.new_content" to mapOf( + "body" to "some message edited", + "msgtype" to "m.text" + ), + "m.relates_to" to mapOf( + "rel_type" to "m.replace", + "event_id" to mockTextEvent.eventId + ) + ), + originServerTs = 2000, + senderId = "@alice:example.com", + ) + + @Test + fun `edit should be valid`() { + val mockCryptoStore = mockk () + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit(mockTextEvent, mockEdit) shouldBeInstanceOf EventEditValidator.EditValidity.Valid::class + } + + @Test + fun `original event and replacement event must have the same sender`() { + val mockCryptoStore = mockk () + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + mockTextEvent, + mockEdit.copy(senderId = "@bob:example.com") + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + @Test + fun `original event and replacement event must have the same room_id`() { + val mockCryptoStore = mockk () + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + mockTextEvent, + mockEdit.copy(roomId = "!someotherroom") + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + + validator + .validateEdit( + encryptedEvent, + encryptedEditEvent.copy(roomId = "!someotherroom") + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + @Test + fun `replacement and original events must not have a state_key property`() { + val mockCryptoStore = mockk () + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + mockTextEvent, + mockEdit.copy(stateKey = "") + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + + validator + .validateEdit( + mockTextEvent.copy(stateKey = ""), + mockEdit + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + @Test + fun `replacement event must have an new_content property`() { + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + mockk { + every { userId } returns "@alice:example.com" + } + } + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit(mockTextEvent, mockEdit.copy( + content = mockEdit.content!!.toMutableMap().apply { + this.remove("m.new_content") + } + )) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + + validator + .validateEdit( + encryptedEvent, + encryptedEditEvent.copy().apply { + mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( + payload = mapOf( + "type" to EventType.MESSAGE, + "content" to mapOf( + "body" to "* some message edited", + "msgtype" to "m.text", + "m.relates_to" to mapOf( + "rel_type" to "m.replace", + "event_id" to mockTextEvent.eventId + ) + ) + ) + ) + } + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + @Test + fun `The original event must not itself have a rel_type of m_replace`() { + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + mockk { + every { userId } returns "@alice:example.com" + } + } + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + mockTextEvent.copy( + content = mockTextEvent.content!!.toMutableMap().apply { + this["m.relates_to"] = mapOf( + "rel_type" to "m.replace", + "event_id" to mockTextEvent.eventId + ) + } + ), + mockEdit + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + + validator + .validateEdit( + encryptedEvent.copy( + content = encryptedEvent.content!!.toMutableMap().apply { + put( + "m.relates_to", + mapOf( + "rel_type" to "m.replace", + "event_id" to mockTextEvent.eventId + ) + ) + } + ).apply { + mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( + payload = mapOf( + "type" to EventType.MESSAGE, + "content" to mapOf( + "body" to "some message", + "msgtype" to "m.text", + ), + ) + ) + }, + encryptedEditEvent + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + @Test + fun `valid e2ee edit`() { + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + mockk { + every { userId } returns "@alice:example.com" + } + } + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + encryptedEvent, + encryptedEditEvent + ) shouldBeInstanceOf EventEditValidator.EditValidity.Valid::class + } + + @Test + fun `If the original event was encrypted, the replacement should be too`() { + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + mockk { + every { userId } returns "@alice:example.com" + } + } + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + encryptedEvent, + mockEdit + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + @Test + fun `encrypted, original event and replacement event must have the same sender`() { + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + mockk { + every { userId } returns "@alice:example.com" + } + every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns + mockk { + every { userId } returns "@bob:example.com" + } + } + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + encryptedEvent, + encryptedEditEvent.copy().apply { + mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( + senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI" + ) + } + + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + + // if sent fom a deleted device it should use the event claimed sender id + } + + @Test + fun `encrypted, sent fom a deleted device, original event and replacement event must have the same sender`() { + val mockCryptoStore = mockk { + every { deviceWithIdentityKey("R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo") } returns + mockk { + every { userId } returns "@alice:example.com" + } + every { deviceWithIdentityKey("7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI") } returns + null + } + val validator = EventEditValidator(mockCryptoStore) + + validator + .validateEdit( + encryptedEvent, + encryptedEditEvent.copy().apply { + mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( + senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI" + ) + } + + ) shouldBeInstanceOf EventEditValidator.EditValidity.Valid::class + + validator + .validateEdit( + encryptedEvent, + encryptedEditEvent.copy( + senderId = "bob@example.com" + ).apply { + mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy( + senderKey = "7V5e/2O93mf4GeW7Mtq4YWcRNpYS9NhQbdJMgdnIPUI" + ) + } + + ) shouldBeInstanceOf EventEditValidator.EditValidity.Invalid::class + } + + private val encryptedEditEvent = Event( + type = EventType.ENCRYPTED, + eventId = "\$-SF7RWLPzRzCbHqK3ZAhIrX5Auh3B2lS5AqJiypt1p0", + roomId = "!GXKhWsRwiWWvbQDBpe:example.com", + content = mapOf( + "algorithm" to "m.megolm.v1.aes-sha2", + "sender_key" to "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo", + "session_id" to "7tOd6xon/R2zJpy2LlSKcKWIek2jvkim0sNdnZZCWMQ", + "device_id" to "QDHBLWOTSN", + "ciphertext" to "AwgXErAC6TgQ4bV6NFldlffTWuUV1gsBYH6JLQMqG...deLfCQOSPunSSNDFdWuDkB8Cg", + "m.relates_to" to mapOf( + "rel_type" to "m.replace", + "event_id" to mockTextEvent.eventId + ) + ), + originServerTs = 2000, + senderId = "@alice:example.com", + ).apply { + mxDecryptionResult = OlmDecryptionResult( + payload = mapOf( + "type" to EventType.MESSAGE, + "content" to mapOf( + "body" to "* some message edited", + "msgtype" to "m.text", + "m.new_content" to mapOf( + "body" to "some message edited", + "msgtype" to "m.text" + ), + "m.relates_to" to mapOf( + "rel_type" to "m.replace", + "event_id" to mockTextEvent.eventId + ) + ) + ), + senderKey = "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo", + isSafe = true + ) + } + + private val encryptedEvent = Event( + type = EventType.ENCRYPTED, + eventId = "\$WX8WlNC2reiXrwHIA_CQHmU_pSR-jhOA2xKPRcJN9wQ", + roomId = "!GXKhWsRwiWWvbQDBpe:example.com", + content = mapOf( + "algorithm" to "m.megolm.v1.aes-sha2", + "sender_key" to "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo", + "session_id" to "7tOd6xon/R2zJpy2LlSKcKWIek2jvkim0sNdnZZCWMQ", + "device_id" to "QDHBLWOTSN", + "ciphertext" to "AwgXErAC6TgQ4bV6NFldlffTWuUV1gsBYH6JLQMqG+4Vr...Yf0gYyhVWZY4SedF3fTMwkjmTuel4fwrmq", + ), + originServerTs = 2000, + senderId = "@alice:example.com", + ).apply { + mxDecryptionResult = OlmDecryptionResult( + payload = mapOf( + "type" to EventType.MESSAGE, + "content" to mapOf( + "body" to "some message", + "msgtype" to "m.text" + ), + ), + senderKey = "R0s/7Aindgg/RNWqUGJyJOXtCz5H7Gx7fInFuroq1xo", + isSafe = true + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt index 129d49633e..bdd1fd9b0d 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt @@ -87,7 +87,7 @@ object PollEventsTestData { ) internal val A_POLL_START_EVENT = Event( - type = EventType.POLL_START.first(), + type = EventType.POLL_START.stable, eventId = AN_EVENT_ID, originServerTs = 1652435922563, senderId = A_USER_ID_1, @@ -96,7 +96,7 @@ object PollEventsTestData { ) internal val A_POLL_RESPONSE_EVENT = Event( - type = EventType.POLL_RESPONSE.first(), + type = EventType.POLL_RESPONSE.stable, eventId = AN_EVENT_ID, originServerTs = 1652435922563, senderId = A_USER_ID_1, @@ -105,7 +105,7 @@ object PollEventsTestData { ) internal val A_POLL_END_EVENT = Event( - type = EventType.POLL_END.first(), + type = EventType.POLL_END.stable, eventId = AN_EVENT_ID, originServerTs = 1652435922563, senderId = A_USER_ID_1, 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 d51ed77399..4a10795647 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 @@ -69,7 +69,7 @@ class DefaultGetActiveBeaconInfoForUserTaskTest { result shouldBeEqualTo currentStateEvent fakeStateEventDataSource.verifyGetStateEvent( roomId = params.roomId, - eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + eventType = EventType.STATE_ROOM_BEACON_INFO.stable, stateKey = QueryStringValue.Equals(A_USER_ID) ) } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt index a01f51604c..1f15a9bee8 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt @@ -53,7 +53,6 @@ private const val A_LATITUDE = 1.4 private const val A_LONGITUDE = 40.0 private const val AN_UNCERTAINTY = 5.0 private const val A_TIMEOUT = 15_000L -private const val A_DESCRIPTION = "description" private const val A_REASON = "reason" @ExperimentalCoroutinesApi @@ -143,7 +142,7 @@ internal class DefaultLocationSharingServiceTest { coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success("stopped-event-id") coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( @@ -157,7 +156,6 @@ internal class DefaultLocationSharingServiceTest { val expectedStartParams = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT, - description = A_DESCRIPTION ) coVerify { startLiveLocationShareTask.execute(expectedStartParams) } } @@ -168,7 +166,7 @@ internal class DefaultLocationSharingServiceTest { val error = Throwable() coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Failure(error) - val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( @@ -186,7 +184,7 @@ internal class DefaultLocationSharingServiceTest { coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns false coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( @@ -196,7 +194,6 @@ internal class DefaultLocationSharingServiceTest { val expectedStartParams = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT, - description = A_DESCRIPTION ) coVerify { startLiveLocationShareTask.execute(expectedStartParams) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt index aa8826243f..a5c126cf72 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt @@ -34,7 +34,6 @@ import org.matrix.android.sdk.test.fakes.FakeSendStateTask private const val A_USER_ID = "user-id" private const val A_ROOM_ID = "room-id" private const val AN_EVENT_ID = "event-id" -private const val A_DESCRIPTION = "description" private const val A_TIMEOUT = 15_000L private const val AN_EPOCH = 1655210176L @@ -60,7 +59,6 @@ internal class DefaultStartLiveLocationShareTaskTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT, - description = A_DESCRIPTION ) fakeClock.givenEpoch(AN_EPOCH) fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) @@ -69,7 +67,7 @@ internal class DefaultStartLiveLocationShareTaskTest { result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedBeaconContent = MessageBeaconInfoContent( - body = A_DESCRIPTION, + body = "Live location", timeout = params.timeoutMillis, isLive = true, unstableTimestampMillis = AN_EPOCH @@ -77,7 +75,7 @@ internal class DefaultStartLiveLocationShareTaskTest { val expectedParams = SendStateTask.Params( roomId = params.roomId, stateKey = A_USER_ID, - eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + eventType = EventType.STATE_ROOM_BEACON_INFO.stable, body = expectedBeaconContent ) fakeSendStateTask.verifyExecuteRetry( @@ -91,7 +89,6 @@ internal class DefaultStartLiveLocationShareTaskTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT, - description = A_DESCRIPTION ) fakeClock.givenEpoch(AN_EPOCH) fakeSendStateTask.givenExecuteRetryReturns("") @@ -106,7 +103,6 @@ internal class DefaultStartLiveLocationShareTaskTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT, - description = A_DESCRIPTION ) fakeClock.givenEpoch(AN_EPOCH) val error = Throwable() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt index 1abf179ccf..a7adadfc63 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt @@ -79,7 +79,7 @@ class DefaultStopLiveLocationShareTaskTest { val expectedSendParams = SendStateTask.Params( roomId = params.roomId, stateKey = A_USER_ID, - eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + eventType = EventType.STATE_ROOM_BEACON_INFO.stable, body = expectedBeaconContent ) fakeSendStateTask.verifyExecuteRetry( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt index 24d9c30039..d6edb69d93 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt @@ -79,7 +79,7 @@ class LiveLocationShareRedactionEventProcessorTest { @Test fun `given a redacted live location share event when processing it then related summaries are deleted from database`() = runTest { val event = Event(eventId = AN_EVENT_ID, redacts = A_REDACTED_EVENT_ID) - val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.first()) + val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.stable) fakeRealm.givenWhere () .givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) .givenFindFirst(redactedEventEntity) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactoryTests.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactoryTests.kt new file mode 100644 index 0000000000..19f58d690f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactoryTests.kt @@ -0,0 +1,237 @@ +/* + * 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.send + +import org.amshove.kluent.internal.assertEquals +import org.junit.Before +import org.junit.Test +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.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody +import org.matrix.android.sdk.api.session.room.sender.SenderInfo +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.util.TextContent +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeContext +import org.matrix.android.sdk.test.fakes.internal.session.content.FakeThumbnailExtractor +import org.matrix.android.sdk.test.fakes.internal.session.permalinks.FakePermalinkFactory +import org.matrix.android.sdk.test.fakes.internal.session.room.send.FakeLocalEchoRepository +import org.matrix.android.sdk.test.fakes.internal.session.room.send.FakeMarkdownParser +import org.matrix.android.sdk.test.fakes.internal.session.room.send.FakeWaveFormSanitizer +import org.matrix.android.sdk.test.fakes.internal.session.room.send.pills.FakeTextPillsUtils + +@Suppress("MaxLineLength") +class LocalEchoEventFactoryTests { + + companion object { + internal const val A_USER_ID_1 = "@user_1:matrix.org" + internal const val A_ROOM_ID = "!sUeOGZKsBValPTUMax:matrix.org" + internal const val AN_EVENT_ID = "\$vApgexcL8Vfh-WxYKsFKCDooo67ttbjm3TiVKXaWijU" + internal const val AN_EPOCH = 1655210176L + + val A_START_EVENT = Event( + type = EventType.STATE_ROOM_CREATE, + eventId = AN_EVENT_ID, + originServerTs = 1652435922563, + senderId = A_USER_ID_1, + roomId = A_ROOM_ID + ) + } + + private val fakeContext = FakeContext() + private val fakeMarkdownParser = FakeMarkdownParser() + private val fakeTextPillsUtils = FakeTextPillsUtils() + private val fakeThumbnailExtractor = FakeThumbnailExtractor() + private val fakeWaveFormSanitizer = FakeWaveFormSanitizer() + private val fakeLocalEchoRepository = FakeLocalEchoRepository() + private val fakePermalinkFactory = FakePermalinkFactory() + private val fakeClock = FakeClock() + + private val localEchoEventFactory = LocalEchoEventFactory( + context = fakeContext.instance, + userId = A_USER_ID_1, + markdownParser = fakeMarkdownParser.instance, + textPillsUtils = fakeTextPillsUtils.instance, + thumbnailExtractor = fakeThumbnailExtractor.instance, + waveformSanitizer = fakeWaveFormSanitizer.instance, + localEchoRepository = fakeLocalEchoRepository.instance, + permalinkFactory = fakePermalinkFactory.instance, + clock = fakeClock + ) + + @Before + fun setup() { + fakeClock.givenEpoch(AN_EPOCH) + fakeMarkdownParser.givenBoldMarkdown() + } + + @Test + fun `given a null quotedText, when a quote event is created, then the result message should only contain the new text after new lines`() { + val event = createTimelineEvent(null, null) + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "Text", + formattedText = null, + autoMarkdown = false, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel () + assertEquals("\n\nText", quotedContent?.body) + assertEquals("
Text", (quotedContent as? MessageContentWithFormattedBody)?.formattedBody) + } + + @Test + fun `given a plain text quoted message, when a quote event is created, then the result message should contain both the quoted and new text`() { + val event = createTimelineEvent("Quoted", null) + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "Text", + formattedText = null, + autoMarkdown = false, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel() + assertEquals("> Quoted\n\nText", quotedContent?.body) + assertEquals(" Quoted
Text", (quotedContent as? MessageContentWithFormattedBody)?.formattedBody) + } + + @Test + fun `given a formatted text quoted message, when a quote event is created, then the result message should contain both the formatted quote and new text`() { + val event = createTimelineEvent("Quoted", "Quoted") + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "Text", + formattedText = null, + autoMarkdown = false, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel() + // This still uses the plain text version + assertEquals("> Quoted\n\nText", quotedContent?.body) + // This one has the formatted one + assertEquals(" Quoted
Text", (quotedContent as? MessageContentWithFormattedBody)?.formattedBody) + } + + @Test + fun `given formatted text quoted message and new message, when a quote event is created, then the result message should contain both the formatted quote and new formatted text`() { + val event = createTimelineEvent("Quoted", "Quoted") + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "Text", + formattedText = "Formatted text", + autoMarkdown = false, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel() + // This still uses the plain text version + assertEquals("> Quoted\n\nText", quotedContent?.body) + // This one has the formatted one + assertEquals( + " Quoted
Formatted text", + (quotedContent as? MessageContentWithFormattedBody)?.formattedBody + ) + } + + @Test + fun `given formatted text quoted message and new message with autoMarkdown, when a quote event is created, then the result message should contain both the formatted quote and new formatted text, not the markdown processed text`() { + val event = createTimelineEvent("Quoted", "Quoted") + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "Text", + formattedText = "Formatted text", + autoMarkdown = true, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel() + // This still uses the plain text version + assertEquals("> Quoted\n\nText", quotedContent?.body) + // This one has the formatted one + assertEquals( + " Quoted
Formatted text", + (quotedContent as? MessageContentWithFormattedBody)?.formattedBody + ) + } + + @Test + fun `given a formatted text quoted message and a new message with autoMarkdown, when a quote event is created, then the result message should contain both the formatted quote and new processed formatted text`() { + val event = createTimelineEvent("Quoted", "Quoted") + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "**Text**", + formattedText = null, + autoMarkdown = true, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel() + // This still uses the markdown text version + assertEquals("> Quoted\n\n**Text**", quotedContent?.body) + // This one has the formatted one + assertEquals( + " Quoted
Text", + (quotedContent as? MessageContentWithFormattedBody)?.formattedBody + ) + } + + @Test + fun `given a plain text quoted message and a new message with autoMarkdown, when a quote event is created, then the result message should the plain text quote and new processed formatted text`() { + val event = createTimelineEvent("Quoted", null) + val quotedContent = localEchoEventFactory.createQuotedTextEvent( + roomId = A_ROOM_ID, + quotedEvent = event, + text = "**Text**", + formattedText = null, + autoMarkdown = true, + rootThreadEventId = null, + additionalContent = null, + ).content.toModel() + // This still uses the markdown text version + assertEquals("> Quoted\n\n**Text**", quotedContent?.body) + // This one has the formatted one + assertEquals( + " Quoted
Text", + (quotedContent as? MessageContentWithFormattedBody)?.formattedBody + ) + } + + private fun createTimelineEvent(quotedText: String?, formattedQuotedText: String?): TimelineEvent { + val textContent = quotedText?.let { + TextContent( + quotedText, + formattedQuotedText + ).toMessageTextContent().toContent() + } + return TimelineEvent( + root = A_START_EVENT.copy( + type = EventType.MESSAGE, + content = textContent + ), + localId = 1234, + eventId = AN_EVENT_ID, + displayIndex = 0, + senderInfo = SenderInfo(A_USER_ID_1, A_USER_ID_1, true, null), + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt new file mode 100644 index 0000000000..201423685c --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt @@ -0,0 +1,100 @@ +/* + * 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.sync + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder +import org.matrix.android.sdk.internal.session.filter.DefaultGetCurrentFilterTask +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams +import org.matrix.android.sdk.test.fakes.FakeFilterRepository +import org.matrix.android.sdk.test.fakes.FakeHomeServerCapabilitiesDataSource +import org.matrix.android.sdk.test.fakes.FakeSaveFilterTask + +private const val A_FILTER_ID = "filter-id" +private val A_HOMESERVER_CAPABILITIES = HomeServerCapabilities() +private val A_SYNC_FILTER_PARAMS = SyncFilterParams( + lazyLoadMembersForMessageEvents = true, + lazyLoadMembersForStateEvents = true, + useThreadNotifications = true +) + +@ExperimentalCoroutinesApi +class DefaultGetCurrentFilterTaskTest { + + private val filterRepository = FakeFilterRepository() + private val homeServerCapabilitiesDataSource = FakeHomeServerCapabilitiesDataSource() + private val saveFilterTask = FakeSaveFilterTask() + + private val getCurrentFilterTask = DefaultGetCurrentFilterTask( + filterRepository = filterRepository, + homeServerCapabilitiesDataSource = homeServerCapabilitiesDataSource.instance, + saveFilterTask = saveFilterTask + ) + + @Test + fun `given no filter is stored, when execute, then executes task to save new filter`() = runTest { + filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS) + + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES) + + filterRepository.givenFilterStored(null, null) + + getCurrentFilterTask.execute(Unit) + + val filter = SyncFilterBuilder() + .with(A_SYNC_FILTER_PARAMS) + .build(A_HOMESERVER_CAPABILITIES) + + saveFilterTask.verifyExecution(filter) + } + + @Test + fun `given filter is stored and didn't change, when execute, then returns stored filter id`() = runTest { + filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS) + + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES) + + val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES) + filterRepository.givenFilterStored(A_FILTER_ID, filter.toJSONString()) + + val result = getCurrentFilterTask.execute(Unit) + + result shouldBeEqualTo A_FILTER_ID + } + + @Test + fun `given filter is set and home server capabilities has changed, when execute, then executes task to save new filter`() = runTest { + filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS) + + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES) + + val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES) + filterRepository.givenFilterStored(A_FILTER_ID, filter.toJSONString()) + + val newHomeServerCapabilities = HomeServerCapabilities(canUseThreadReadReceiptsAndNotifications = true) + homeServerCapabilitiesDataSource.givenHomeServerCapabilities(newHomeServerCapabilities) + val newFilter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(newHomeServerCapabilities) + + getCurrentFilterTask.execute(Unit) + + saveFilterTask.verifyExecution(newFilter) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt new file mode 100644 index 0000000000..16cdd7a626 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt @@ -0,0 +1,22 @@ +/* + * 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.test.fakes + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.pushers.AddPusherTask + +class FakeAddPusherTask : AddPusherTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClipboardManager.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClipboardManager.kt new file mode 100644 index 0000000000..bce8b41aa9 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClipboardManager.kt @@ -0,0 +1,37 @@ +/* + * 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.test.fakes + +import android.content.ClipData +import android.content.ClipboardManager +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import io.mockk.verify + +class FakeClipboardManager { + val instance = mockk() + + fun givenSetPrimaryClip() { + every { instance.setPrimaryClip(any()) } just runs + } + + fun verifySetPrimaryClip(clipData: ClipData) { + verify { instance.setPrimaryClip(clipData) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeConnectivityManager.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeConnectivityManager.kt new file mode 100644 index 0000000000..5c3a245c51 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeConnectivityManager.kt @@ -0,0 +1,44 @@ +/* + * 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.test.fakes + +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import io.mockk.every +import io.mockk.mockk + +class FakeConnectivityManager { + val instance = mockk () + + fun givenNoActiveConnection() { + every { instance.activeNetwork } returns null + } + + fun givenHasActiveConnection() { + val network = mockk () + every { instance.activeNetwork } returns network + + val networkCapabilities = FakeNetworkCapabilities() + networkCapabilities.givenTransports( + NetworkCapabilities.TRANSPORT_CELLULAR, + NetworkCapabilities.TRANSPORT_WIFI, + NetworkCapabilities.TRANSPORT_VPN + ) + every { instance.getNetworkCapabilities(network) } returns networkCapabilities.instance + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeContext.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeContext.kt new file mode 100644 index 0000000000..966c6a1bb2 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeContext.kt @@ -0,0 +1,84 @@ +/* + * 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.test.fakes + +import android.content.ClipboardManager +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.net.Uri +import android.os.ParcelFileDescriptor +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import java.io.OutputStream + +class FakeContext( + private val contentResolver: ContentResolver = mockk() +) { + + val instance = mockk () + + init { + every { instance.contentResolver } returns contentResolver + every { instance.applicationContext } returns instance + } + + fun givenFileDescriptor(uri: Uri, mode: String, factory: () -> ParcelFileDescriptor?) { + val fileDescriptor = factory() + every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor + } + + fun givenSafeOutputStreamFor(uri: Uri): OutputStream { + val outputStream = mockk (relaxed = true) + every { contentResolver.openOutputStream(uri, "wt") } returns outputStream + return outputStream + } + + fun givenMissingSafeOutputStreamFor(uri: Uri) { + every { contentResolver.openOutputStream(uri, "wt") } returns null + } + + fun givenNoConnection() { + val connectivityManager = FakeConnectivityManager() + connectivityManager.givenNoActiveConnection() + givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance) + } + + fun givenService(name: String, klass: Class , service: T) { + every { instance.getSystemService(name) } returns service + every { instance.getSystemService(klass) } returns service + } + + fun givenHasConnection() { + val connectivityManager = FakeConnectivityManager() + connectivityManager.givenHasActiveConnection() + givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance) + } + + fun givenStartActivity(intent: Intent) { + every { instance.startActivity(intent) } just runs + } + + fun givenClipboardManager(): FakeClipboardManager { + val fakeClipboardManager = FakeClipboardManager() + givenService(Context.CLIPBOARD_SERVICE, ClipboardManager::class.java, fakeClipboardManager.instance) + return fakeClipboardManager + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt new file mode 100644 index 0000000000..b8225f21d6 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.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.test.fakes + +import io.mockk.coEvery +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.filter.FilterRepository +import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams + +internal class FakeFilterRepository : FilterRepository by mockk() { + + fun givenFilterStored(filterId: String?, filterBody: String?) { + coEvery { getStoredSyncFilterId() } returns filterId + coEvery { getStoredSyncFilterBody() } returns filterBody + } + + fun givenFilterParamsAreStored(syncFilterParams: SyncFilterParams?) { + coEvery { getStoredFilterParams() } returns syncFilterParams + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt new file mode 100644 index 0000000000..d5a41bb0e0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt @@ -0,0 +1,22 @@ +/* + * 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.test.fakes + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.pushers.GetPushersTask + +class FakeGetPushersTask : GetPushersTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt new file mode 100644 index 0000000000..9a56a599d1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeHomeServerCapabilitiesDataSource.kt @@ -0,0 +1,30 @@ +/* + * 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource + +internal class FakeHomeServerCapabilitiesDataSource { + val instance = mockk () + + fun givenHomeServerCapabilities(homeServerCapabilities: HomeServerCapabilities) { + every { instance.getHomeServerCapabilities() } returns homeServerCapabilities + } +} 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 93999458c6..76ede75910 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 @@ -38,9 +38,9 @@ internal class FakeMonarchy { init { mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") coEvery { - instance.awaitTransaction(any Any>()) - } coAnswers { - secondArg Any>().invoke(fakeRealm.instance) + instance.awaitTransaction(any<(Realm) -> Any>()) + } answers { + secondArg<(Realm) -> Any>().invoke(fakeRealm.instance) } coEvery { instance.doWithRealm(any()) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeNetworkCapabilities.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeNetworkCapabilities.kt new file mode 100644 index 0000000000..c630b94d47 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeNetworkCapabilities.kt @@ -0,0 +1,32 @@ +/* + * 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.test.fakes + +import android.net.NetworkCapabilities +import io.mockk.every +import io.mockk.mockk + +class FakeNetworkCapabilities { + val instance = mockk () + + fun givenTransports(vararg type: Int) { + every { instance.hasTransport(any()) } answers { + val input = it.invocation.args.first() as Int + type.contains(input) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt index 15a9823c79..9ad7032262 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.test.fakes import io.mockk.coEvery import io.mockk.mockk import io.mockk.mockkStatic -import io.mockk.slot import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.internal.database.awaitTransaction @@ -33,9 +32,8 @@ internal class FakeRealmConfiguration { val instance = mockk () fun givenAwaitTransaction(realm: Realm) { - val transaction = slot T>() - coEvery { awaitTransaction(instance, capture(transaction)) } coAnswers { - secondArg T>().invoke(realm) + coEvery { awaitTransaction(instance, any<(Realm) -> T>()) } answers { + secondArg<(Realm) -> T>().invoke(realm) } } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt new file mode 100644 index 0000000000..55a7607a03 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt @@ -0,0 +1,22 @@ +/* + * 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.test.fakes + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.pushers.RemovePusherTask + +class FakeRemovePusherTask : RemovePusherTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt new file mode 100644 index 0000000000..40bee227e0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSaveFilterTask.kt @@ -0,0 +1,40 @@ +/* + * 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.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import io.mockk.slot +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.internal.session.filter.Filter +import org.matrix.android.sdk.internal.session.filter.SaveFilterTask +import java.util.UUID + +internal class FakeSaveFilterTask : SaveFilterTask by mockk() { + + init { + coEvery { execute(any()) } returns UUID.randomUUID().toString() + } + + fun verifyExecution(filter: Filter) { + val slot = slot () + coVerify { execute(capture(slot)) } + val params = slot.captured + params.filter shouldBeEqualTo filter + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt new file mode 100644 index 0000000000..543dda8a4f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt @@ -0,0 +1,25 @@ +/* + * 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.test.fakes + +import io.mockk.mockk +import org.matrix.android.sdk.internal.task.TaskExecutor + +internal class FakeTaskExecutor { + + val instance: TaskExecutor = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt new file mode 100644 index 0000000000..b1e059a40e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt @@ -0,0 +1,35 @@ +/* + * 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.test.fakes + +import io.mockk.coVerify +import io.mockk.mockk +import io.mockk.slot +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.internal.session.pushers.TogglePusherTask + +class FakeTogglePusherTask : TogglePusherTask by mockk(relaxed = true) { + + fun verifyExecution(pusher: Pusher, enable: Boolean) { + val slot = slot () + coVerify { execute(capture(slot)) } + val params = slot.captured + params.pusher.pushKey shouldBeEqualTo pusher.pushKey + params.enable shouldBeEqualTo enable + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt new file mode 100644 index 0000000000..46a106dcb2 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt @@ -0,0 +1,22 @@ +/* + * 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.test.fakes.internal + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask + +class FakePushGatewayNotifyTask : PushGatewayNotifyTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/content/FakeThumbnailExtractor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/content/FakeThumbnailExtractor.kt new file mode 100644 index 0000000000..b541d24161 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/content/FakeThumbnailExtractor.kt @@ -0,0 +1,24 @@ +/* + * 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.test.fakes.internal.session.content + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor + +class FakeThumbnailExtractor { + internal val instance = mockk () +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/permalinks/FakePermalinkFactory.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/permalinks/FakePermalinkFactory.kt new file mode 100644 index 0000000000..3d7e85424e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/permalinks/FakePermalinkFactory.kt @@ -0,0 +1,24 @@ +/* + * 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.test.fakes.internal.session.permalinks + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory + +class FakePermalinkFactory { + internal val instance = mockk