Merge tag 'v1.5.10' into merge-v1.5.10

TODO: re-implement our composer constraint changes

Conflicts:
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
	vector/src/main/AndroidManifest.xml
	vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
	vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt
	vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
	vector/src/main/res/drawable/ic_voice_mic_recording.xml
	vector/src/main/res/layout/composer_layout.xml
	vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
	vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml
	vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml
	vector/src/main/res/layout/composer_rich_text_layout_constraint_set_expanded.xml
	vector/src/main/res/layout/composer_rich_text_layout_constraint_set_fullscreen.xml
	vector/src/main/res/layout/fragment_timeline.xml
	vector/src/main/res/layout/view_voice_message_recorder.xml
	vector/src/main/res/xml/vector_settings_preferences.xml
	vector/src/test/java/im/vector/app/core/session/ConfigureAndStartSessionUseCaseTest.kt

Change-Id: I55e95d86b4bb019544d75dcb653afe05194cd224
This commit is contained in:
SpiritCroc 2022-12-02 18:18:34 +01:00
commit 65be0039a5
315 changed files with 7420 additions and 3501 deletions

View file

@ -10,7 +10,6 @@ body:
id: checklist id: checklist
attributes: attributes:
label: Release checklist label: Release checklist
description: For the template example, we are releasing the version 1.2.3. Replace 1.2.3 with the version in the issue body.
placeholder: | placeholder: |
If you are reading this, you have deleted the content of the release template: undo the deletion or start again. If you are reading this, you have deleted the content of the release template: undo the deletion or start again.
value: | value: |
@ -24,27 +23,7 @@ body:
### Do the release ### Do the release
- [ ] Make sure `develop` and `main` are up to date and create a release with gitflow: `git checkout main; git pull; git checkout develop; git pull; git flow release start '1.2.3'` - [ ] Run the script ./tools/release/releaseScript.sh and follow the steps.
- [ ] 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 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): `git flow release finish '1.2.3'`
- [ ] Push `main` and the new tag `v1.2.3` to origin: `git push origin main; git push origin 'v1.2.3'`
- [ ] Checkout `develop`: `git 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`: `git commit -m 'version++'; git push origin 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
### Once tested and validated internally ### Once tested and validated internally
@ -81,29 +60,9 @@ body:
The SDK2 and the sample app are released only when Element has been pushed to production. 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 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:
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:
- [ ] Connect to https://s01.oss.sonatype.org - [ ] Connect to https://s01.oss.sonatype.org
- [ ] Click on Staging Repositories and check the the files have been uploaded - [ ] Click on Staging Repositories and check the the files have been uploaded
@ -111,15 +70,6 @@ body:
- [ ] Wait (check Activity tab until step "Repository closed" is displayed) - [ ] Wait (check Activity tab until step "Repository closed" is displayed)
- [ ] Click on release. The staging repository will disappear - [ ] 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 ### Android SDK2 sample
https://github.com/matrix-org/matrix-android-sdk2-sample https://github.com/matrix-org/matrix-android-sdk2-sample

View file

@ -16,7 +16,7 @@ env:
jobs: jobs:
# More info on should-i-run: # 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. # 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 # 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 # 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. if: github.event.pull_request.merged # Additionally require PR to have been completely merged.
steps: steps:
- run: echo "Run those tests!" # no-op success - run: echo "Run those tests!" # no-op success
ui-tests: ui-tests:
name: UI Tests (Synapse) name: UI Tests (Synapse)
needs: should-i-run needs: should-i-run
runs-on: buildjet-4vcpu-ubuntu-2204 runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 90 # We might need to increase it if the time for tests grows
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View file

@ -14,6 +14,7 @@ jobs:
tests: tests:
name: Runs all tests name: Runs all tests
runs-on: buildjet-4vcpu-ubuntu-2204 runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 90 # We might need to increase it if the time for tests grows
strategy: strategy:
matrix: matrix:
api-level: [28] api-level: [28]
@ -126,26 +127,26 @@ jobs:
# Unneeded as part of the test suite above, kept around in case we want to re-enable them. # Unneeded as part of the test suite above, kept around in case we want to re-enable them.
# #
# # Build Android Tests # # Build Android Tests
# build-android-tests: # build-android-tests:
# name: Build Android Tests # name: Build Android Tests
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# concurrency: # 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) }} # 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 # cancel-in-progress: true
# steps: # steps:
# - uses: actions/checkout@v3 # - uses: actions/checkout@v3
# - uses: actions/setup-java@v3 # - uses: actions/setup-java@v3
# with: # with:
# distribution: 'adopt' # distribution: 'adopt'
# java-version: 11 # java-version: 11
# - uses: actions/cache@v3 # - uses: actions/cache@v3
# with: # with:
# path: | # path: |
# ~/.gradle/caches # ~/.gradle/caches
# ~/.gradle/wrapper # ~/.gradle/wrapper
# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} # key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
# restore-keys: | # restore-keys: |
# ${{ runner.os }}-gradle- # ${{ runner.os }}-gradle-
# - name: Build Android Tests # - name: Build Android Tests
# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES # run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES

View file

@ -79,8 +79,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -103,8 +103,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -129,8 +129,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -154,8 +154,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -178,8 +178,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -203,8 +203,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -228,8 +228,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }
@ -258,8 +258,8 @@ jobs:
headers: '{"GraphQL-Features": "projects_next_graphql"}' headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: | query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) { mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
projectNextItem { item {
id id
} }
} }

1
7658.bugfix Normal file
View file

@ -0,0 +1 @@
[Rich text editor] Fix design and spacing of rich text editor

View file

@ -1,3 +1,39 @@
Changes in Element v1.5.10 (2022-11-30)
=======================================
Features ✨
----------
- Add setting to allow disabling direct share ([#2725](https://github.com/vector-im/element-android/issues/2725))
- [Device Manager] Toggle IP address visibility ([#7546](https://github.com/vector-im/element-android/issues/7546))
- New implementation of the full screen mode for the Rich Text Editor. ([#7577](https://github.com/vector-im/element-android/issues/7577))
Bugfixes 🐛
----------
- Fix italic text is truncated when bubble mode and markdown is enabled ([#5679](https://github.com/vector-im/element-android/issues/5679))
- Missing translations on "replyTo" messages ([#7555](https://github.com/vector-im/element-android/issues/7555))
- ANR on session start when sending client info is enabled ([#7604](https://github.com/vector-im/element-android/issues/7604))
- Make the plain text mode layout of the RTE more compact. ([#7620](https://github.com/vector-im/element-android/issues/7620))
- Push notification for thread message is now shown correctly when user observes rooms main timeline ([#7634](https://github.com/vector-im/element-android/issues/7634))
- Voice Broadcast - Fix playback stuck in buffering mode ([#7646](https://github.com/vector-im/element-android/issues/7646))
In development 🚧
----------------
- Voice Broadcast - Handle redaction of the state events on the listener and recorder sides ([#7629](https://github.com/vector-im/element-android/issues/7629))
- Voice Broadcast - Update the buffering display in the timeline ([#7655](https://github.com/vector-im/element-android/issues/7655))
- Voice Broadcast - Remove voice messages related to a VB from the room attachments ([#7656](https://github.com/vector-im/element-android/issues/7656))
SDK API changes ⚠️
------------------
- 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) ([#6996](https://github.com/vector-im/element-android/issues/6996))
- 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 ([#7626](https://github.com/vector-im/element-android/issues/7626))
Other changes
-------------
- Remove usage of Buildkite. ([#7583](https://github.com/vector-im/element-android/issues/7583))
- Better validation of edits ([#7594](https://github.com/vector-im/element-android/issues/7594))
Changes in Element v1.5.8 (2022-11-17) Changes in Element v1.5.8 (2022-11-17)
====================================== ======================================

View file

@ -13,6 +13,7 @@
* [Code quality](#code-quality) * [Code quality](#code-quality)
* [Internal tool](#internal-tool) * [Internal tool](#internal-tool)
* [ktlint](#ktlint) * [ktlint](#ktlint)
* [knit](#knit)
* [lint](#lint) * [lint](#lint)
* [Unit tests](#unit-tests) * [Unit tests](#unit-tests)
* [Tests](#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) 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
<pre>
./gradlew knit
</pre>
and commit the changes.
The CI will check that markdown files are up to date by running
<pre>
./gradlew knitCheck
</pre>
#### lint #### lint
<pre> <pre>

View file

@ -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) [![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) [![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) [![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
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app) [<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app)
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app) [<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](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 # 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. 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. 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 ## Contributing

View file

@ -24,7 +24,7 @@ buildscript {
classpath libs.gradle.gradlePlugin classpath libs.gradle.gradlePlugin
classpath libs.gradle.kotlinPlugin classpath libs.gradle.kotlinPlugin
classpath libs.gradle.hiltPlugin 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 'com.google.gms:google-services:4.3.14'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' 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.google.android.gms:oss-licenses-plugin:0.10.5'
@ -43,12 +43,12 @@ plugins {
// ktlint Plugin // ktlint Plugin
id "org.jlleitschuh.gradle.ktlint" version "11.0.0" id "org.jlleitschuh.gradle.ktlint" version "11.0.0"
// Detekt // Detekt
id "io.gitlab.arturbosch.detekt" version "1.21.0" id "io.gitlab.arturbosch.detekt" version "1.22.0"
// Ksp // Ksp
id "com.google.devtools.ksp" version "1.7.21-1.0.8" id "com.google.devtools.ksp" version "1.7.21-1.0.8"
// Dependency Analysis // Dependency Analysis
id 'com.autonomousapps.dependency-analysis' version "1.13.1" id 'com.autonomousapps.dependency-analysis' version "1.16.0"
// Gradle doctor // Gradle doctor
id "com.osacky.doctor" version "0.8.1" id "com.osacky.doctor" version "0.8.1"
} }

View file

@ -10,7 +10,7 @@ def gradle = "7.3.1"
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
def kotlin = "1.7.21" def kotlin = "1.7.21"
def kotlinCoroutines = "1.6.4" def kotlinCoroutines = "1.6.4"
def dagger = "2.44" def dagger = "2.44.2"
def appDistribution = "16.0.0-beta05" def appDistribution = "16.0.0-beta05"
def retrofit = "2.9.0" def retrofit = "2.9.0"
def markwon = "4.6.2" def markwon = "4.6.2"
@ -84,7 +84,7 @@ ext.libs = [
//'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", //'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
//'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", //'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.0" 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.1"
], ],
dagger : [ dagger : [
'dagger' : "com.google.dagger:dagger:$dagger", 'dagger' : "com.google.dagger:dagger:$dagger",
@ -99,7 +99,7 @@ ext.libs = [
], ],
element : [ element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0", 'opusencoder' : "io.element.android:opusencoder:1.1.0",
'wysiwyg' : "io.element.android:wysiwyg:0.4.0" 'wysiwyg' : "io.element.android:wysiwyg:0.7.0.1"
], ],
squareup : [ squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshi' : "com.squareup.moshi:moshi:$moshi",

View file

@ -0,0 +1,52 @@
## Installing from CI
<!--- TOC -->
* [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)
<!--- END -->
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.

View file

@ -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

View file

@ -0,0 +1,2 @@
Die wichtigsten Änderungen in dieser Version: Fehlerbehebungen und Verbesserungen.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Main changes in this version: New implementation of the full screen mode for the Rich Text Editor and bugfixes.
Full changelog: https://github.com/vector-im/element-android/releases

View file

@ -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

View file

@ -0,0 +1,2 @@
تغییرات عمده در این نگارش: رفع اشکال‌ها و بهبود.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases

View file

@ -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

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: perbaikan kutu dan fitur
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: correzione di errori e miglioramenti.
Cronologia completa: https://github.com/vector-im/element-android/releases

View file

@ -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

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Использование UnifiedPush и разрешение пользователям получать push-оповещения без FCM.
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Исправления различных багов и улучшения стабильности работы.
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Исправления различных багов и улучшения стабильности работы.
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Улучшены вход и регистрация
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Улучшены вход и регистрация
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Исправления различных багов и улучшения стабильности работы
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: отложённые личные сообщения включены по умолчанию
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: новый интерфейс для выбора прикреплённых файлов
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: новый интерфейс для выбора прикреплённых файлов
Полный список изменений: https://github.com/vector-im/element-android/releases

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,2 +1,2 @@
Основні зміни в цій версії: Нові можливості в налаштуваннях лабораторії: Текстовий редактор, нове керування пристроями, голосові повідомлення. Досі в активній розробці! Основні зміни в цій версії: Нові можливості в налаштуваннях лабораторії: Текстовий редактор, нове керування пристроями, голосові трансляції. Досі в активній розробці!
Список усіх змін: https://github.com/vector-im/element-android/releases Список усіх змін: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: усування вад і вдосконалення.
Перелік усіх змін: https://github.com/vector-im/element-android/releases

View file

@ -0,0 +1,2 @@
此版本中的主要變動:臭蟲修復與改善。
完整的變更紀錄https://github.com/vector-im/element-android/releases

View file

@ -2837,4 +2837,6 @@
<string name="attachment_type_selector_sticker">Adhesius</string> <string name="attachment_type_selector_sticker">Adhesius</string>
<string name="attachment_type_selector_gallery">Galeria</string> <string name="attachment_type_selector_gallery">Galeria</string>
<string name="attachment_type_selector_text_formatting">Format de text</string> <string name="attachment_type_selector_text_formatting">Format de text</string>
<string name="a11y_voice_broadcast_fast_backward">Enrere 30 segons</string>
<string name="a11y_voice_broadcast_fast_forward">Avança 30 segons</string>
</resources> </resources>

View file

@ -2899,4 +2899,27 @@
<string name="error_voice_broadcast_unauthorized_title">Nelze zahájit nové hlasové vysílání</string> <string name="error_voice_broadcast_unauthorized_title">Nelze zahájit nové hlasové vysílání</string>
<string name="a11y_voice_broadcast_fast_backward">Přetočení o 30 sekund zpět</string> <string name="a11y_voice_broadcast_fast_backward">Přetočení o 30 sekund zpět</string>
<string name="a11y_voice_broadcast_fast_forward">Přetočení o 30 sekund dopředu</string> <string name="a11y_voice_broadcast_fast_forward">Přetočení o 30 sekund dopředu</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Odhlásit se z %1$d relace</item>
<item quantity="few">Odhlásit se ze %1$d relací</item>
<item quantity="other">Odhlásit se z %1$d relací</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Odhlásit se</string>
<string name="voice_broadcast_recording_time_left">zbývá %1$s</string>
<string name="message_reply_to_sender_created_poll">vytvořil hlasování.</string>
<string name="message_reply_to_sender_sent_sticker">poslal nálepku.</string>
<string name="message_reply_to_sender_sent_video">poslal video.</string>
<string name="message_reply_to_sender_sent_image">poslal obrázek.</string>
<string name="message_reply_to_sender_sent_voice_message">poslal hlasovou zprávu.</string>
<string name="message_reply_to_sender_sent_audio_file">poslal zvukový soubor.</string>
<string name="message_reply_to_sender_sent_file">odeslal soubor.</string>
<string name="message_reply_to_prefix">V odpovědi na</string>
<string name="device_manager_other_sessions_hide_ip_address">Skrýt IP adresu</string>
<string name="device_manager_other_sessions_show_ip_address">Zobrazit IP adresu</string>
<string name="quoting">Citace</string>
<string name="replying_to">Odpovídám na %s</string>
<string name="editing">Úpravy</string>
</resources> </resources>

View file

@ -2232,13 +2232,13 @@
<item quantity="other">%1$s weitere Optionen benötigt</item> <item quantity="other">%1$s weitere Optionen benötigt</item>
</plurals> </plurals>
<string name="create_poll_empty_question_error">Frage darf nicht leer sein</string> <string name="create_poll_empty_question_error">Frage darf nicht leer sein</string>
<string name="create_poll_button">ABSTIMMUNG ERSTELLEN</string> <string name="create_poll_button">Umfrage erstellen</string>
<string name="create_poll_add_option">NEUE OPTION</string> <string name="create_poll_add_option">NEUE OPTION</string>
<string name="create_poll_options_hint">Option %1$d</string> <string name="create_poll_options_hint">Option %1$d</string>
<string name="create_poll_options_title">Optionen hinzufügen</string> <string name="create_poll_options_title">Optionen hinzufügen</string>
<string name="create_poll_question_hint">Frage oder Thema</string> <string name="create_poll_question_hint">Frage oder Thema</string>
<string name="create_poll_question_title">Abstimmungsthema oder Frage</string> <string name="create_poll_question_title">Abstimmungsthema oder Frage</string>
<string name="create_poll_title">Abstimmung erstellen</string> <string name="create_poll_title">Umfrage erstellen</string>
<string name="attachment_type_poll">Umfrage</string> <string name="attachment_type_poll">Umfrage</string>
<string name="open_discovery_settings">Auffindungseinstellungen öffnen</string> <string name="open_discovery_settings">Auffindungseinstellungen öffnen</string>
<string name="shortcut_disabled_reason_sign_out">Sitzung abgemeldet!</string> <string name="shortcut_disabled_reason_sign_out">Sitzung abgemeldet!</string>
@ -2306,21 +2306,21 @@
<string name="legals_identity_server_title">Richtlinie deines Identitäts-Servers</string> <string name="legals_identity_server_title">Richtlinie deines Identitäts-Servers</string>
<string name="legals_home_server_title">Richtlinie deines Heim-Servers</string> <string name="legals_home_server_title">Richtlinie deines Heim-Servers</string>
<string name="legals_application_title">Richtlinie von ${app_name}</string> <string name="legals_application_title">Richtlinie von ${app_name}</string>
<string name="tooltip_attachment_poll">Abstimmung erstellen</string> <string name="tooltip_attachment_poll">Umfrage erstellen</string>
<string name="tooltip_attachment_contact">Kontakte öffnen</string> <string name="tooltip_attachment_contact">Kontakte öffnen</string>
<string name="tooltip_attachment_sticker">Sticker verschicken</string> <string name="tooltip_attachment_sticker">Sticker verschicken</string>
<string name="tooltip_attachment_file">Datei hochladen</string> <string name="tooltip_attachment_file">Datei hochladen</string>
<string name="tooltip_attachment_gallery">Verschicke Fotos und Videos</string> <string name="tooltip_attachment_gallery">Verschicke Fotos und Videos</string>
<string name="tooltip_attachment_photo">Kamera öffnen</string> <string name="tooltip_attachment_photo">Kamera öffnen</string>
<string name="delete_poll_dialog_content">Willst du diese Abstimmung wirklich entfernen\? Du wirst sie nicht wiederherstellen können.</string> <string name="delete_poll_dialog_content">Willst du diese Umfrage wirklich entfernen\? Du wirst sie nicht wiederherstellen können.</string>
<string name="delete_poll_dialog_title">Abstimmung entfernen</string> <string name="delete_poll_dialog_title">Umfrage entfernen</string>
<string name="poll_end_room_list_preview">Abstimmung beendet</string> <string name="poll_end_room_list_preview">Umfrage beendet</string>
<string name="poll_response_room_list_preview">Stimme abgegeben</string> <string name="poll_response_room_list_preview">Stimme abgegeben</string>
<string name="end_poll_confirmation_approve_button">Abstimmung beenden</string> <string name="end_poll_confirmation_approve_button">Umfrage beenden</string>
<string name="end_poll_confirmation_description">Dies verhindert, dass andere Personen abstimmen können, und zeigt die Endergebnisse der Umfrage an.</string> <string name="end_poll_confirmation_description">Dies verhindert, dass andere Personen abstimmen können, und zeigt die Endergebnisse der Umfrage an.</string>
<string name="end_poll_confirmation_title">Diese Abstimmung beenden\?</string> <string name="end_poll_confirmation_title">Diese Umfrage beenden\?</string>
<string name="a11y_poll_winner_option">Gewinneroption</string> <string name="a11y_poll_winner_option">Gewinneroption</string>
<string name="poll_end_action">Abstimmung beenden</string> <string name="poll_end_action">Umfrage beenden</string>
<plurals name="poll_total_vote_count_after_ended"> <plurals name="poll_total_vote_count_after_ended">
<item quantity="one">Endgültiges Ergebnis basiert auf %1$d Stimme</item> <item quantity="one">Endgültiges Ergebnis basiert auf %1$d Stimme</item>
<item quantity="other">Endgültiges Ergebnis basiert auf %1$d Stimmen</item> <item quantity="other">Endgültiges Ergebnis basiert auf %1$d Stimmen</item>
@ -2333,11 +2333,11 @@
<string name="location_not_available_dialog_title">${app_name} konnte nicht auf deinen Standort zugreifen</string> <string name="location_not_available_dialog_title">${app_name} konnte nicht auf deinen Standort zugreifen</string>
<string name="location_activity_title_preview">Standort</string> <string name="location_activity_title_preview">Standort</string>
<string name="closed_poll_option_description">Die Ergebnisse werden erst sichtbar, sobald du die Umfrage beendest</string> <string name="closed_poll_option_description">Die Ergebnisse werden erst sichtbar, sobald du die Umfrage beendest</string>
<string name="closed_poll_option_title">Abgeschlossene Abstimmung</string> <string name="closed_poll_option_title">Versteckte Umfrage</string>
<string name="open_poll_option_description">Abstimmende können die Ergebnisse nach Stimmabgabe sehen</string> <string name="open_poll_option_description">Abstimmende können die Ergebnisse nach Stimmabgabe sehen</string>
<string name="open_poll_option_title">Laufende Abstimmung</string> <string name="open_poll_option_title">Offene Umfrage</string>
<string name="poll_type_title">Abstimmungsart</string> <string name="poll_type_title">Umfragetyp</string>
<string name="edit_poll_title">Abstimmung bearbeiten</string> <string name="edit_poll_title">Umfrage bearbeiten</string>
<string name="poll_no_votes_cast">Keine Stimmen abgegeben</string> <string name="poll_no_votes_cast">Keine Stimmen abgegeben</string>
<string name="login_splash_create_account">Konto erstellen</string> <string name="login_splash_create_account">Konto erstellen</string>
<string name="ftue_auth_carousel_workplace_title">Kommunikation für dein Team.</string> <string name="ftue_auth_carousel_workplace_title">Kommunikation für dein Team.</string>
@ -2527,7 +2527,7 @@
<string name="ftue_auth_terms_title">Server-Richtlinien</string> <string name="ftue_auth_terms_title">Server-Richtlinien</string>
<string name="ftue_auth_email_verification_subtitle">Folge den Anweisungen, die an %s gesendet wurden</string> <string name="ftue_auth_email_verification_subtitle">Folge den Anweisungen, die an %s gesendet wurden</string>
<string name="ftue_auth_email_verification_title">E-Mail bestätigen</string> <string name="ftue_auth_email_verification_title">E-Mail bestätigen</string>
<string name="poll_undisclosed_not_ended">Ergebnisse werden nach Abschluss der Abstimmung sichtbar sein</string> <string name="poll_undisclosed_not_ended">Ergebnisse werden nach Abschluss der Umfrage sichtbar sein</string>
<string name="ftue_auth_reset_password_breaker_title">Prüfe deine E-Mails.</string> <string name="ftue_auth_reset_password_breaker_title">Prüfe deine E-Mails.</string>
<string name="ftue_auth_reset_password">Passwort zurücksetzen</string> <string name="ftue_auth_reset_password">Passwort zurücksetzen</string>
<string name="ftue_auth_new_password_subtitle">Gib mindestens 8 Zeichen ein.</string> <string name="ftue_auth_new_password_subtitle">Gib mindestens 8 Zeichen ein.</string>
@ -2843,4 +2843,26 @@
<string name="error_voice_broadcast_blocked_by_someone_else_message">Jemand anderes nimmt bereits eine Sprachübertragung auf. Warte auf das Ende der Übertragung, bevor du eine neue startest.</string> <string name="error_voice_broadcast_blocked_by_someone_else_message">Jemand anderes nimmt bereits eine Sprachübertragung auf. Warte auf das Ende der Übertragung, bevor du eine neue startest.</string>
<string name="a11y_voice_broadcast_fast_forward">30 Sekunden vorspulen</string> <string name="a11y_voice_broadcast_fast_forward">30 Sekunden vorspulen</string>
<string name="a11y_voice_broadcast_fast_backward">30 Sekunden zurückspulen</string> <string name="a11y_voice_broadcast_fast_backward">30 Sekunden zurückspulen</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Von %1$d Sitzung abmelden</item>
<item quantity="other">Von %1$d Sitzungen abmelden</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Abmelden</string>
<string name="voice_broadcast_recording_time_left">%1$s übrig</string>
<string name="quoting">Zitieren</string>
<string name="editing">Bearbeiten</string>
<string name="message_reply_to_sender_created_poll">erstellte eine Umfrage.</string>
<string name="message_reply_to_sender_sent_sticker">sandte einen Sticker.</string>
<string name="message_reply_to_sender_sent_video">sandte ein Video.</string>
<string name="message_reply_to_sender_sent_image">sandte ein Bild.</string>
<string name="message_reply_to_sender_sent_voice_message">sandte eine Sprachnachricht.</string>
<string name="message_reply_to_sender_sent_audio_file">sandte eine Audiodatei.</string>
<string name="message_reply_to_sender_sent_file">sandte eine Datei.</string>
<string name="message_reply_to_prefix">Als Antwort auf</string>
<string name="replying_to">%s antworten</string>
<string name="device_manager_other_sessions_hide_ip_address">IP-Adresse ausblenden</string>
<string name="device_manager_other_sessions_show_ip_address">IP-Adresse anzeigen</string>
</resources> </resources>

View file

@ -2649,4 +2649,10 @@
<string name="create_room">Crear sala</string> <string name="create_room">Crear sala</string>
<string name="start_chat">Iniciar conversación</string> <string name="start_chat">Iniciar conversación</string>
<string name="all_chats">Todas las conversaciones</string> <string name="all_chats">Todas las conversaciones</string>
</resources> <string name="action_select_all">Seleccionar todo</string>
<string name="action_got_it">De acuerdo</string>
<plurals name="x_selected">
<item quantity="one">%1$d seleccionado</item>
<item quantity="other">%1$d seleccionados</item>
</plurals>
</resources>

View file

@ -2835,4 +2835,26 @@
<string name="error_voice_broadcast_unauthorized_title">Uue ringhäälingukõne alustamine pole võimalik</string> <string name="error_voice_broadcast_unauthorized_title">Uue ringhäälingukõne alustamine pole võimalik</string>
<string name="a11y_voice_broadcast_fast_backward">Keri tagasi 30 sekundi kaupa</string> <string name="a11y_voice_broadcast_fast_backward">Keri tagasi 30 sekundi kaupa</string>
<string name="a11y_voice_broadcast_fast_forward">Keri edasi 30 sekundi kaupa</string> <string name="a11y_voice_broadcast_fast_forward">Keri edasi 30 sekundi kaupa</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Logi välja %1$d\'st sessioonist</item>
<item quantity="other">Logi välja %1$d\'st sessioonist</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Logi välja</string>
<string name="voice_broadcast_recording_time_left">jäänud %1$s</string>
<string name="editing">Muudan sõnumit</string>
<string name="replying_to">Vastan sõnumile %s</string>
<string name="quoting">Tsiteerides</string>
<string name="device_manager_other_sessions_show_ip_address">Näita IP-aadressi</string>
<string name="device_manager_other_sessions_hide_ip_address">Peida IP-aadress</string>
<string name="message_reply_to_prefix">Vastuseks kasutajale</string>
<string name="message_reply_to_sender_sent_file">saatis faili.</string>
<string name="message_reply_to_sender_sent_audio_file">saatis helifaili.</string>
<string name="message_reply_to_sender_sent_voice_message">saatis häälsõnumi.</string>
<string name="message_reply_to_sender_sent_image">saatis pildi.</string>
<string name="message_reply_to_sender_sent_video">saatis video.</string>
<string name="message_reply_to_sender_sent_sticker">saatis kleepsu.</string>
<string name="message_reply_to_sender_created_poll">koostas küsitluse.</string>
</resources> </resources>

View file

@ -2825,4 +2825,23 @@
<string name="a11y_voice_broadcast_fast_forward">۳۰ ثانیه پیش‌روی</string> <string name="a11y_voice_broadcast_fast_forward">۳۰ ثانیه پیش‌روی</string>
<string name="a11y_voice_broadcast_fast_backward">۳۰ ثانیه پس‌روی</string> <string name="a11y_voice_broadcast_fast_backward">۳۰ ثانیه پس‌روی</string>
<string name="attachment_type_selector_text_formatting">قالب‌بندی متن</string> <string name="attachment_type_selector_text_formatting">قالب‌بندی متن</string>
<string name="device_manager_other_sessions_multi_signout_selection">خروج</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">خروج از ۱ نشست</item>
<item quantity="other">خروج از %1$d نشست</item>
</plurals>
<string name="voice_broadcast_recording_time_left">%1$s مانده</string>
<string name="message_reply_to_sender_sent_voice_message">پیام صوتی‌ای فرستاد.</string>
<string name="message_reply_to_sender_sent_audio_file">پروندهٔ صوتی‌ای فرستاد.</string>
<string name="message_reply_to_sender_created_poll">نظرسنجی‌ای ایجاد کرد.</string>
<string name="message_reply_to_sender_sent_sticker">عکس‌برگردانی فرستاد.</string>
<string name="message_reply_to_sender_sent_video">ویدیویی فرستاد.</string>
<string name="message_reply_to_sender_sent_image">تصویری فرستاد.</string>
<string name="message_reply_to_sender_sent_file">پرونده‌ای فرستاد.</string>
<string name="message_reply_to_prefix">در پاسخ به</string>
<string name="device_manager_other_sessions_hide_ip_address">نهفتن نشانی آی‌پی</string>
<string name="device_manager_other_sessions_show_ip_address">نمایش نشانی آی‌پی</string>
<string name="quoting">نقل کردن</string>
<string name="replying_to">پاسخ دادن به %s</string>
<string name="editing">ویرایش کردن</string>
</resources> </resources>

View file

@ -2844,4 +2844,26 @@
<string name="error_voice_broadcast_unauthorized_title">Impossible de commencer une nouvelle diffusion audio</string> <string name="error_voice_broadcast_unauthorized_title">Impossible de commencer une nouvelle diffusion audio</string>
<string name="a11y_voice_broadcast_fast_forward">Avance rapide de 30 secondes</string> <string name="a11y_voice_broadcast_fast_forward">Avance rapide de 30 secondes</string>
<string name="a11y_voice_broadcast_fast_backward">Retour rapide de 30 secondes</string> <string name="a11y_voice_broadcast_fast_backward">Retour rapide de 30 secondes</string>
<string name="device_manager_learn_more_sessions_verified_description">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é à laide dune autre session vérifiée.
\n
\nCela veut dire quelles 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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Déconnecter %1$d session</item>
<item quantity="other">Déconnecter %1$d sessions</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Déconnecter</string>
<string name="voice_broadcast_recording_time_left">%1$s restant</string>
<string name="message_reply_to_sender_created_poll">a créé un sondage.</string>
<string name="message_reply_to_sender_sent_sticker">a envoyé un autocollant.</string>
<string name="message_reply_to_sender_sent_video">a envoyé une vidéo.</string>
<string name="message_reply_to_sender_sent_image">a envoyé une image.</string>
<string name="message_reply_to_sender_sent_voice_message">envoyer un message vocal.</string>
<string name="message_reply_to_sender_sent_audio_file">a envoyé un fichier audio.</string>
<string name="message_reply_to_sender_sent_file">a envoyé un fichier.</string>
<string name="message_reply_to_prefix">En réponse à</string>
<string name="device_manager_other_sessions_hide_ip_address">Masquer ladresse IP</string>
<string name="device_manager_other_sessions_show_ip_address">Afficher ladresse IP</string>
<string name="quoting">Citation de</string>
<string name="replying_to">Réponse à %s</string>
<string name="editing">Modification</string>
</resources> </resources>

View file

@ -2836,4 +2836,34 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
<item quantity="one">%1$d kiválasztva</item> <item quantity="one">%1$d kiválasztva</item>
<item quantity="other">%1$d kiválasztva</item> <item quantity="other">%1$d kiválasztva</item>
</plurals> </plurals>
<string name="rich_text_editor_full_screen_toggle">Teljes képernyő váltás</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Kijelentkezés %1$d munkamenetből</item>
<item quantity="other">Kijelentkezés %1$d munkamenetből</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Kijelentkezés</string>
<string name="attachment_type_selector_text_formatting">Szöveg formázás</string>
<string name="error_voice_broadcast_already_in_progress_message">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.</string>
<string name="error_voice_broadcast_blocked_by_someone_else_message">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.</string>
<string name="error_voice_broadcast_permission_denied_message">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.</string>
<string name="error_voice_broadcast_unauthorized_title">Az új hang közvetítés nem indítható el</string>
<string name="a11y_voice_broadcast_fast_forward">30 másodperccel előre</string>
<string name="a11y_voice_broadcast_fast_backward">30 másodperccel vissza</string>
<string name="voice_broadcast_recording_time_left">visszavan: %1$s</string>
<string name="message_reply_to_sender_created_poll">szavazás elkészítve.</string>
<string name="message_reply_to_sender_sent_sticker">matrica elküldve.</string>
<string name="message_reply_to_sender_sent_video">videót küldött.</string>
<string name="message_reply_to_sender_sent_image">kép elküldve.</string>
<string name="message_reply_to_sender_sent_voice_message">hang üzenet elküldve.</string>
<string name="message_reply_to_sender_sent_audio_file">hangfájl elküldve.</string>
<string name="message_reply_to_sender_sent_file">fájl elküldve.</string>
<string name="message_reply_to_prefix">Válaszolva erre</string>
<string name="device_manager_other_sessions_hide_ip_address">IP címek elrejtése</string>
<string name="device_manager_other_sessions_show_ip_address">IP címek megjelenítése</string>
<string name="quoting">Idézet</string>
<string name="replying_to">Válasz erre: %s</string>
<string name="editing">Szerkesztés</string>
</resources> </resources>

View file

@ -2791,4 +2791,25 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
<string name="error_voice_broadcast_unauthorized_title">Tidak dapat memulai siaran suara baru</string> <string name="error_voice_broadcast_unauthorized_title">Tidak dapat memulai siaran suara baru</string>
<string name="a11y_voice_broadcast_fast_forward">Maju cepat 30 detik</string> <string name="a11y_voice_broadcast_fast_forward">Maju cepat 30 detik</string>
<string name="a11y_voice_broadcast_fast_backward">Mundur cepat 30 detik</string> <string name="a11y_voice_broadcast_fast_backward">Mundur cepat 30 detik</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="other">Keluarkan %1$d sesi</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Keluarkan</string>
<string name="voice_broadcast_recording_time_left">%1$s tersisa</string>
<string name="message_reply_to_sender_created_poll">membuat pemungutan suara.</string>
<string name="message_reply_to_sender_sent_sticker">mengirim stiker.</string>
<string name="message_reply_to_sender_sent_video">mengirim video.</string>
<string name="message_reply_to_sender_sent_image">mengirim gambar.</string>
<string name="message_reply_to_sender_sent_file">mengirim file.</string>
<string name="message_reply_to_sender_sent_audio_file">mengirim file audio.</string>
<string name="message_reply_to_sender_sent_voice_message">mengirim pesan suara.</string>
<string name="message_reply_to_prefix">Membalas ke</string>
<string name="device_manager_other_sessions_hide_ip_address">Sembunyikan alamat IP</string>
<string name="quoting">Mengutip</string>
<string name="editing">Mengedit</string>
<string name="device_manager_other_sessions_show_ip_address">Tampilkan alamat IP</string>
<string name="replying_to">Membalas ke %s</string>
</resources> </resources>

View file

@ -2827,4 +2827,34 @@
<item quantity="one">%1$d selezionato</item> <item quantity="one">%1$d selezionato</item>
<item quantity="other">%1$d selezionati</item> <item quantity="other">%1$d selezionati</item>
</plurals> </plurals>
<string name="rich_text_editor_full_screen_toggle">Attiva/disattiva schermo intero</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Disconnetti da %1$d sessione</item>
<item quantity="other">Disconnetti da %1$d sessioni</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Disconnetti</string>
<string name="attachment_type_selector_text_formatting">Formattazione testo</string>
<string name="error_voice_broadcast_already_in_progress_message">Stai già registrando una trasmissione vocale. Termina quella in corso per iniziarne una nuova.</string>
<string name="error_voice_broadcast_blocked_by_someone_else_message">Qualcun altro sta già registrando una trasmissione vocale. Aspetta che finisca prima di iniziarne una nuova.</string>
<string name="error_voice_broadcast_permission_denied_message">Non hai l\'autorizzazione necessaria per iniziare una trasmissione vocale in questa stanza. Contatta un amministratore della stanza per aggiornare le tue autorizzazioni.</string>
<string name="error_voice_broadcast_unauthorized_title">Impossibile iniziare una nuova trasmissione vocale</string>
<string name="a11y_voice_broadcast_fast_forward">Manda avanti di 30 secondi</string>
<string name="a11y_voice_broadcast_fast_backward">Manda indietro di 30 secondi</string>
<string name="voice_broadcast_recording_time_left">%1$s rimasti</string>
<string name="message_reply_to_sender_created_poll">creato un sondaggio.</string>
<string name="message_reply_to_sender_sent_sticker">inviato un adesivo.</string>
<string name="message_reply_to_sender_sent_video">inviato un video.</string>
<string name="message_reply_to_sender_sent_image">inviata un\'immagine.</string>
<string name="message_reply_to_sender_sent_voice_message">inviato un messaggio vocale.</string>
<string name="message_reply_to_sender_sent_audio_file">inviato un file audio.</string>
<string name="message_reply_to_sender_sent_file">inviato un file.</string>
<string name="message_reply_to_prefix">In risposta a</string>
<string name="device_manager_other_sessions_hide_ip_address">Nascondi indirizzo IP</string>
<string name="device_manager_other_sessions_show_ip_address">Mostra indirizzo IP</string>
<string name="quoting">Citazione</string>
<string name="replying_to">Risposta a %s</string>
<string name="editing">Modifica</string>
</resources> </resources>

View file

@ -2715,7 +2715,7 @@
<string name="home_layout_preferences">Preferencje interfejsu</string> <string name="home_layout_preferences">Preferencje interfejsu</string>
<string name="explore_rooms">Przeglądaj pokoje</string> <string name="explore_rooms">Przeglądaj pokoje</string>
<string name="create_room">Utwórz pokój</string> <string name="create_room">Utwórz pokój</string>
<string name="start_chat">Zacznij rozmawiać</string> <string name="start_chat">Rozpocznij czat</string>
<string name="all_chats">Wszystkie rozmowy</string> <string name="all_chats">Wszystkie rozmowy</string>
<string name="device_manager_other_sessions_description_unverified">Nie zweryfikowano · Ostatnia aktywność %1$s</string> <string name="device_manager_other_sessions_description_unverified">Nie zweryfikowano · Ostatnia aktywność %1$s</string>
<string name="device_manager_other_sessions_description_verified">Zweryfikowano · Ostatnia aktywność %1$s</string> <string name="device_manager_other_sessions_description_verified">Zweryfikowano · Ostatnia aktywność %1$s</string>
@ -2743,4 +2743,4 @@
<string name="home_empty_space_no_rooms_title">%s <string name="home_empty_space_no_rooms_title">%s
\nwygląda nieco pusto.</string> \nwygląda nieco pusto.</string>
<string name="space_list_empty_title">Brak przestrzeni.</string> <string name="space_list_empty_title">Brak przestrzeni.</string>
</resources> </resources>

View file

@ -2844,4 +2844,26 @@
<string name="error_voice_broadcast_unauthorized_title">Não dá pra começar um novo broadcast de voz</string> <string name="error_voice_broadcast_unauthorized_title">Não dá pra começar um novo broadcast de voz</string>
<string name="a11y_voice_broadcast_fast_forward">Avançar rápido 30 segundos</string> <string name="a11y_voice_broadcast_fast_forward">Avançar rápido 30 segundos</string>
<string name="a11y_voice_broadcast_fast_backward">Retroceder 30 segundos</string> <string name="a11y_voice_broadcast_fast_backward">Retroceder 30 segundos</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Fazer signout de %1$d sessão</item>
<item quantity="other">Fazer signout de %1$d sessões</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Fazer signout</string>
<string name="voice_broadcast_recording_time_left">%1$s restando</string>
<string name="message_reply_to_sender_created_poll">criou uma sondagem.</string>
<string name="message_reply_to_sender_sent_sticker">enviou um sticker.</string>
<string name="message_reply_to_sender_sent_video">enviou um vídeo.</string>
<string name="message_reply_to_sender_sent_image">enviou uma imagem.</string>
<string name="message_reply_to_sender_sent_voice_message">enviou uma mensagem de voz.</string>
<string name="message_reply_to_sender_sent_audio_file">enviou um arquivo de áudio.</string>
<string name="message_reply_to_sender_sent_file">enviou um arquivo.</string>
<string name="message_reply_to_prefix">Em resposta a</string>
<string name="device_manager_other_sessions_hide_ip_address">Esconder endereço de IP</string>
<string name="device_manager_other_sessions_show_ip_address">Mostrar endereço de IP</string>
<string name="quoting">Citando</string>
<string name="replying_to">Respondendo a %s</string>
<string name="editing">Editando</string>
</resources> </resources>

View file

@ -930,7 +930,7 @@
<string name="settings_security_and_privacy">Безопасность</string> <string name="settings_security_and_privacy">Безопасность</string>
<string name="settings_push_rules">Правила push-уведомлений</string> <string name="settings_push_rules">Правила push-уведомлений</string>
<string name="push_gateway_item_app_id">ID приложения:</string> <string name="push_gateway_item_app_id">ID приложения:</string>
<string name="push_gateway_item_push_key">push_key:</string> <string name="push_gateway_item_push_key">Ключ Push:</string>
<string name="push_gateway_item_app_display_name">Отображаемое название приложения:</string> <string name="push_gateway_item_app_display_name">Отображаемое название приложения:</string>
<string name="push_gateway_item_device_name">Отображаемое название сессии:</string> <string name="push_gateway_item_device_name">Отображаемое название сессии:</string>
<string name="push_gateway_item_url">Url:</string> <string name="push_gateway_item_url">Url:</string>
@ -1000,10 +1000,10 @@
<string name="settings_discovery_bad_identity_server">Не удалось подключиться к серверу обнаружения</string> <string name="settings_discovery_bad_identity_server">Не удалось подключиться к серверу обнаружения</string>
<string name="settings_discovery_please_enter_server">Пожалуйста, введите URL сервера обнаружения</string> <string name="settings_discovery_please_enter_server">Пожалуйста, введите URL сервера обнаружения</string>
<string name="settings_discovery_no_terms_title">Сервер обнаружения не имеет условий использования</string> <string name="settings_discovery_no_terms_title">Сервер обнаружения не имеет условий использования</string>
<string name="settings_discovery_no_mails">Параметры обнаружения появятся после добавления электронной почты.</string> <string name="settings_discovery_no_mails">Параметры обнаружения появятся после добавления адреса электронной почты.</string>
<string name="settings_discovery_no_msisdn">Параметры поиска появятся после добавления номера телефона.</string> <string name="settings_discovery_no_msisdn">Параметры поиска появятся после добавления номера телефона.</string>
<string name="settings_discovery_disconnect_identity_server_info">Отключение от сервера обнаружения будет означать, что другие пользователи не смогут обнаружить вас, и вы не сможете приглашать других по электронной почте или по телефону.</string> <string name="settings_discovery_disconnect_identity_server_info">Отключение от сервера обнаружения будет означать, что другие пользователи не смогут обнаружить вас, и вы не сможете приглашать других по электронной почте или по телефону.</string>
<string name="settings_discovery_confirm_mail">Мы отправили вам электронное письмо с подтверждением на %s, проверьте вашу электронную почту и нажмите на ссылку для подтверждения</string> <string name="settings_discovery_confirm_mail">Мы отправили вам электронное письмо на %s, проверьте вашу электронную почту и нажмите на ссылку для подтверждения</string>
<string name="settings_discovery_no_terms">Выбранный сервер обнаружения не имеет условий использования. Продолжайте, только если вы доверяете его владельцу</string> <string name="settings_discovery_no_terms">Выбранный сервер обнаружения не имеет условий использования. Продолжайте, только если вы доверяете его владельцу</string>
<string name="settings_text_message_sent">Текстовое сообщение отправлено %s. Введите код проверки, который он содержит.</string> <string name="settings_text_message_sent">Текстовое сообщение отправлено %s. Введите код проверки, который он содержит.</string>
<string name="settings_discovery_disconnect_with_bound_pid">В настоящее время вы делитесь адресами электронной почты или телефонными номерами на сервере обнаружения %1$s. Вам нужно повторно подключиться к %2$s, чтобы прекратить делиться ими.</string> <string name="settings_discovery_disconnect_with_bound_pid">В настоящее время вы делитесь адресами электронной почты или телефонными номерами на сервере обнаружения %1$s. Вам нужно повторно подключиться к %2$s, чтобы прекратить делиться ими.</string>
@ -1102,7 +1102,7 @@
<string name="login_reset_password_warning_title">Предупреждение!</string> <string name="login_reset_password_warning_title">Предупреждение!</string>
<string name="login_reset_password_warning_content">Смена пароля приведёт к сбросу всех сквозных ключей шифрования во всех ваших сессиях, что сделает зашифрованную историю разговоров нечитаемой. Настройте резервное копирование ключей или экспортируйте ключи от комнаты из другой сессии, прежде чем сбрасывать пароль.</string> <string name="login_reset_password_warning_content">Смена пароля приведёт к сбросу всех сквозных ключей шифрования во всех ваших сессиях, что сделает зашифрованную историю разговоров нечитаемой. Настройте резервное копирование ключей или экспортируйте ключи от комнаты из другой сессии, прежде чем сбрасывать пароль.</string>
<string name="login_reset_password_warning_submit">Продолжить</string> <string name="login_reset_password_warning_submit">Продолжить</string>
<string name="login_reset_password_error_not_found">Данный электронный ящик не связан ни с одним аккаунтом</string> <string name="login_reset_password_error_not_found">Данный адрес электронной почты не связан ни с одним аккаунтом</string>
<string name="login_reset_password_mail_confirmation_title">Проверьте свою почту</string> <string name="login_reset_password_mail_confirmation_title">Проверьте свою почту</string>
<string name="login_reset_password_mail_confirmation_notice">Письмо с подтверждением было отправлено на %1$s.</string> <string name="login_reset_password_mail_confirmation_notice">Письмо с подтверждением было отправлено на %1$s.</string>
<string name="login_reset_password_mail_confirmation_notice_2">Нажмите на ссылку, чтобы подтвердить свой новый пароль. Как только вы перейдете по ссылке нажмите ниже.</string> <string name="login_reset_password_mail_confirmation_notice_2">Нажмите на ссылку, чтобы подтвердить свой новый пароль. Как только вы перейдете по ссылке нажмите ниже.</string>
@ -1241,7 +1241,7 @@
<string name="notification_ticker_text_group">%1$s: %2$s %3$s</string> <string name="notification_ticker_text_group">%1$s: %2$s %3$s</string>
<string name="settings_show_redacted">Удалённые сообщения</string> <string name="settings_show_redacted">Удалённые сообщения</string>
<string name="settings_show_redacted_summary">Показывать заглушку на месте удалённых сообщений</string> <string name="settings_show_redacted_summary">Показывать заглушку на месте удалённых сообщений</string>
<string name="settings_discovery_confirm_mail_not_clicked">Мы отправили письмо для подтверждения на %s, проверьте почту и нажмите на ссылку для подтверждения</string> <string name="settings_discovery_confirm_mail_not_clicked">Мы отправили письмо на %s, пожалуйста проверьте почту и нажмите на ссылку для подтверждения</string>
<string name="settings_text_message_sent_wrong_code">Код подтверждения неверный.</string> <string name="settings_text_message_sent_wrong_code">Код подтверждения неверный.</string>
<string name="error_terms_not_accepted">Попробуйте снова после принятия условий обслуживания на вашем домашнем сервере.</string> <string name="error_terms_not_accepted">Попробуйте снова после принятия условий обслуживания на вашем домашнем сервере.</string>
<string name="error_network_timeout">Похоже, сервер долгое время не отвечает, что может быть вызвано плохим соединением или ошибкой на сервере. Попробуйте снова через некоторое время.</string> <string name="error_network_timeout">Похоже, сервер долгое время не отвечает, что может быть вызвано плохим соединением или ошибкой на сервере. Попробуйте снова через некоторое время.</string>
@ -1351,7 +1351,7 @@
<string name="login_server_url_form_common_notice">Введите адрес сервера, который вы хотите использовать</string> <string name="login_server_url_form_common_notice">Введите адрес сервера, который вы хотите использовать</string>
<string name="login_reset_password_notice">На ваш почтовый ящик будет отправлено письмо для подтверждения установки нового пароля.</string> <string name="login_reset_password_notice">На ваш почтовый ящик будет отправлено письмо для подтверждения установки нового пароля.</string>
<string name="login_reset_password_mail_confirmation_submit">Я подтвердил свою электронную почту</string> <string name="login_reset_password_mail_confirmation_submit">Я подтвердил свою электронную почту</string>
<string name="login_set_email_notice">Установите адрес электронной почты для восстановления вашей учетной записи. Позже вы можете дополнительно разрешить людям, которых вы знаете, обнаружить вас по электронной почте.</string> <string name="login_set_email_notice">Укажите адрес электронной почты для восстановления вашей учетной записи. Потом вы сможете, при желании, разрешить людям, которых вы знаете, обнаружить вас по адресу электронной почты.</string>
<string name="login_validation_code_is_not_correct">Введенный код неверен. Пожалуйста, проверьте.</string> <string name="login_validation_code_is_not_correct">Введенный код неверен. Пожалуйста, проверьте.</string>
<string name="login_connect_using_matrix_id_submit">Войти с Matrix ID</string> <string name="login_connect_using_matrix_id_submit">Войти с Matrix ID</string>
<string name="login_signin_matrix_id_title">Войти с Matrix ID</string> <string name="login_signin_matrix_id_title">Войти с Matrix ID</string>
@ -1482,7 +1482,7 @@
<string name="not_trusted">Незаверенная</string> <string name="not_trusted">Незаверенная</string>
<string name="verification_profile_device_verified_because">Эта сессия является доверенной для безопасного обмена сообщениями, так как %1$s (%2$s) проверил(а) его:</string> <string name="verification_profile_device_verified_because">Эта сессия является доверенной для безопасного обмена сообщениями, так как %1$s (%2$s) проверил(а) его:</string>
<string name="verification_profile_device_new_signing">%1$s (%2$s) вошел(ла), используя новую сессию:</string> <string name="verification_profile_device_new_signing">%1$s (%2$s) вошел(ла), используя новую сессию:</string>
<string name="verification_profile_device_untrust_info">Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Кроме того, вы можете подтвердить сессию вручную.</string> <string name="verification_profile_device_untrust_info">Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Вы также можете подтвердить эту сессию вручную.</string>
<string name="initialize_cross_signing">Начать перекрестную подпись</string> <string name="initialize_cross_signing">Начать перекрестную подпись</string>
<string name="reset_cross_signing">Сбросить ключи</string> <string name="reset_cross_signing">Сбросить ключи</string>
<string name="qr_code_scanned_by_other_notice">Почти готово! Показывает ли %s галочку\?</string> <string name="qr_code_scanned_by_other_notice">Почти готово! Показывает ли %s галочку\?</string>
@ -1606,7 +1606,7 @@
<string name="identity_server_error_outdated_home_server">Эта операция невозможна. Домашний сервер устарел.</string> <string name="identity_server_error_outdated_home_server">Эта операция невозможна. Домашний сервер устарел.</string>
<string name="identity_server_error_no_identity_server_configured">Пожалуйста, настройте сначала сервер идентификации.</string> <string name="identity_server_error_no_identity_server_configured">Пожалуйста, настройте сначала сервер идентификации.</string>
<string name="identity_server_error_terms_not_signed">Пожалуйста, примите сначала условия сервера идентификации в настройках.</string> <string name="identity_server_error_terms_not_signed">Пожалуйста, примите сначала условия сервера идентификации в настройках.</string>
<string name="identity_server_error_bulk_sha256_not_supported">Для вашей приватности, ${app_name} поддерживает отправку адреса электронной почты и номера телефона только в хэшированном виде.</string> <string name="identity_server_error_bulk_sha256_not_supported">Для вашей приватности, ${app_name} поддерживает отправку адреса электронной почты и номеров телефонов только в хэшированном виде.</string>
<string name="identity_server_error_binding_error">Привязка не удалась.</string> <string name="identity_server_error_binding_error">Привязка не удалась.</string>
<string name="identity_server_error_no_current_binding_error">Текущая взаимосвязь с этим идентификатором отсутствует.</string> <string name="identity_server_error_no_current_binding_error">Текущая взаимосвязь с этим идентификатором отсутствует.</string>
<string name="identity_server_set_default_notice">Ваш домашний сервер (%1$s) предлагает использовать %2$s для вашего сервера обнаружения</string> <string name="identity_server_set_default_notice">Ваш домашний сервер (%1$s) предлагает использовать %2$s для вашего сервера обнаружения</string>
@ -1793,7 +1793,7 @@
<string name="attachment_type_dialog_title">Добавить изображение из</string> <string name="attachment_type_dialog_title">Добавить изображение из</string>
<string name="create_room_topic_hint">Тема</string> <string name="create_room_topic_hint">Тема</string>
<string name="create_room_name_section">Название комнаты</string> <string name="create_room_name_section">Название комнаты</string>
<string name="settings_discovery_consent_notice_on">Вы дали свое согласие на отправку электронных писем и телефонных номеров на этот сервер обнаружения для обнаружения других пользователей из ваших контактов.</string> <string name="settings_discovery_consent_notice_on">Вы дали свое согласие на отправку адресов электронных почт и телефонных номеров на этот сервер идентификации для обнаружения других пользователей из ваших контактов.</string>
<string name="add_by_qr_code">Добавить по QR-коду</string> <string name="add_by_qr_code">Добавить по QR-коду</string>
<string name="permissions_denied_add_contact">Разрешить доступ к вашим контактам.</string> <string name="permissions_denied_add_contact">Разрешить доступ к вашим контактам.</string>
<string name="permissions_denied_qr_code">Чтобы отсканировать QR-код, вам нужно разрешить доступ к камере.</string> <string name="permissions_denied_qr_code">Чтобы отсканировать QR-код, вам нужно разрешить доступ к камере.</string>
@ -2396,7 +2396,7 @@
<string name="attachment_type_location">Местоположение</string> <string name="attachment_type_location">Местоположение</string>
<string name="identity_server_consent_dialog_content_question">Вы согласны отправить эту информацию\?</string> <string name="identity_server_consent_dialog_content_question">Вы согласны отправить эту информацию\?</string>
<string name="identity_server_consent_dialog_content_3">Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер обнаружения. Мы хешируем ваши данные перед отправкой для обеспечения конфиденциальности.</string> <string name="identity_server_consent_dialog_content_3">Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер обнаружения. Мы хешируем ваши данные перед отправкой для обеспечения конфиденциальности.</string>
<string name="identity_server_consent_dialog_title_2">Отправить электронные адреса и номера телефонов %s</string> <string name="identity_server_consent_dialog_title_2">Отправить адреса электронных почт и номера телефонов %s</string>
<string name="settings_discovery_consent_notice_off_2">Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер обнаружения.</string> <string name="settings_discovery_consent_notice_off_2">Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер обнаружения.</string>
<string name="preference_system_settings">Системные настройки</string> <string name="preference_system_settings">Системные настройки</string>
<string name="preference_versions">Версии</string> <string name="preference_versions">Версии</string>
@ -2805,4 +2805,162 @@
<string name="attachment_type_selector_location">Местоположение</string> <string name="attachment_type_selector_location">Местоположение</string>
<string name="attachment_type_selector_camera">Камера</string> <string name="attachment_type_selector_camera">Камера</string>
<string name="attachment_type_selector_contact">Контакт</string> <string name="attachment_type_selector_contact">Контакт</string>
<string name="settings_troubleshoot_test_system_settings_permission_failed">${app_name} нуждается в разрешении для отображения оповещений.
\nПожалуйста, дайте разрешение.</string>
<plurals name="search_space_multiple_parents">
<item quantity="one">%1$s и %2$d другой</item>
<item quantity="few">%1$s и %2$d другие</item>
<item quantity="many">%1$s и %2$d других</item>
<item quantity="other">%1$s и %2$d других</item>
</plurals>
<string name="permissions_rationale_msg_notification">${app_name} нуждается в резрешении для отображения оповещений. Оповещения могут показывать ваши сообщения, приглашения и тому подобное.
\n
\nПожалуйста разрешите доступ при следующем всплывающем сообщении, чтобы иметь возможность видеть оповещения.</string>
<string name="invites_empty_message">Здесь будут появляться новые запросы и приглашения.</string>
<string name="invites_title">Приглашения</string>
<string name="labs_enable_rich_text_editor_summary">Попробуйте расширенный текстовый редактор (режим набора обычного текста скоро появится)</string>
<string name="labs_enable_deferred_dm_summary">Создавать личные сообщения только при отправке первого сообщения</string>
<string name="labs_enable_deferred_dm_title">Включить отложенные личные сообщения</string>
<string name="action_deselect_all">Отменить выбор всего</string>
<string name="action_select_all">Выбрать всё</string>
<string name="a11y_collapse_space_children">Свернуть дочерние элементы %s</string>
<string name="a11y_expand_space_children">Развернуть дочерние элементы %s</string>
<plurals name="x_selected">
<item quantity="one">Выбрано %1$d</item>
<item quantity="few">Выбрано %1$d</item>
<item quantity="many">Выбрано %1$d</item>
<item quantity="other">Выбрано %1$d</item>
</plurals>
<string name="rich_text_editor_full_screen_toggle">Войти в полноэкранный режим</string>
<string name="rich_text_editor_format_underline">Применить форматирование подчёркиванием</string>
<string name="rich_text_editor_format_strikethrough">Применить форматирование перечёркиванием</string>
<string name="rich_text_editor_format_italic">Применить форматирование курсивом</string>
<string name="rich_text_editor_format_bold">Применить форматирование жирным</string>
<string name="qr_code_login_confirm_security_code_description">Пожалуйста удостоверьтесь в том, что вы знаете откуда этот код. При соединении устройств, вы даёте кому-то полный доступ к вашей учётной записи.</string>
<string name="qr_code_login_confirm_security_code">Подтвердить</string>
<string name="qr_code_login_try_again">Попробовать снова</string>
<string name="qr_code_login_status_no_match">Не сходится\?</string>
<string name="qr_code_login_signing_in">Вход</string>
<string name="qr_code_login_connecting_to_device">Соединение с устройством</string>
<string name="qr_code_login_scan_qr_code_button">Сканировать QR-код</string>
<string name="qr_code_login_signing_in_a_mobile_device">Входите с мобильного устройства\?</string>
<string name="qr_code_login_show_qr_code_button">Показать QR-код на этом устройстве</string>
<string name="qr_code_login_link_a_device_show_qr_code_instruction_2">Выберите «Сканировать QR-код»</string>
<string name="qr_code_login_link_a_device_show_qr_code_instruction_1">Начните с экрана входа</string>
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Выберите «Войти при помощи QR-кода»</string>
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Начните с экрана входа</string>
<string name="qr_code_login_new_device_instruction_3">Выберите «Показать QR-код»</string>
<string name="qr_code_login_new_device_instruction_2">Зайдите в Настройки -&gt; Безопасность и Приватность</string>
<string name="qr_code_login_new_device_instruction_1">Откройте приложение с другого устройства</string>
<string name="qr_code_login_header_failed_homeserver_is_not_supported_description">Домашний сервер не поддерживает вход при помощи QR-кода.</string>
<string name="qr_code_login_header_failed_user_cancelled_description">Вход был отменён с другого устройства.</string>
<string name="qr_code_login_header_failed_invalid_qr_code_description">Этот QR-код не работает.</string>
<string name="qr_code_login_header_failed_other_device_not_signed_in_description">Другое устройство должно войти в учётную запись.</string>
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">Другое устройство уже выполнило вход.</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Во время установки безопасной переписки возникла проблема с безопасностью. Одно из следующего является скомпроментированным: Ваш домашний сервер; Ваше интернет-соединение; Ваше устройство;</string>
<string name="qr_code_login_header_failed_other_description">Запрос не выполнен.</string>
<string name="qr_code_login_header_failed_denied_description">Запрос был отклонён на другом устройстве.</string>
<string name="qr_code_login_header_failed_timeout_description">Соединение не было выполнено за нужное время.</string>
<string name="qr_code_login_header_failed_device_is_not_supported_description">Соединение с этим устройством не поддерживается.</string>
<string name="qr_code_login_header_failed_title">Неудачное соединение</string>
<string name="qr_code_login_header_connected_description">Проверьте устройство, с которого вы вошли в учётную запись. На его экране должен появиться код снизу. Подтвердите, что код снизу такой же, как и на том устройстве:</string>
<string name="qr_code_login_header_connected_title">Безопасное соединение установлено</string>
<string name="qr_code_login_header_show_qr_code_link_a_device_description">Сканируйте QR-код снизу при помощи устройства, с которого вы вышли с учётной записи.</string>
<string name="qr_code_login_header_show_qr_code_new_device_description">Используйте устройство, с которого вы вошли в учётную запись, чтобы сканировать QR-код снизу:</string>
<string name="qr_code_login_header_show_qr_code_title">Войти при помощи QR-кода</string>
<string name="qr_code_login_header_scan_qr_code_description">Используйте камеру на этом устройстве, чтобы сканировать QR-код, отображённый на вашем другом устройстве:</string>
<string name="qr_code_login_header_scan_qr_code_title">Сканировать QR-код</string>
<string name="three">3</string>
<string name="two">2</string>
<string name="one">1</string>
<string name="onboarding_new_app_layout_feedback_message">Нажмите слева сверху, чтобы увидеть опцию отзыва.</string>
<string name="onboarding_new_app_layout_welcome_message">Чтобы упростить ${app_name}, вкладки теперь опциональные. Управляйте ими при помощи меню справа сверху.</string>
<string name="home_empty_no_rooms_message">Универсальное безопасное приложение для переписок с командами, друзьями и организациями. Создайте переписку или присоеденитесь к уже существующей, чтобы начать.</string>
<string name="home_empty_space_no_rooms_message">Пространства — новый способ групировать комнаты и людей. Добавьте существующую комнату или создайте новую, используя кнопку слева снизу.</string>
<string name="labs_enable_voice_broadcast_summary">Возможность записывать и отправлять голосовые трансляции в ленту комнаты.</string>
<string name="labs_enable_session_manager_summary">Получите лучший надзор и контроль над всеми вашими сессиями.</string>
<string name="device_manager_learn_more_sessions_verified_description">Подтверждённые сессии есть везде, где вы используете эту учётную запись, после введения вашего пароля или подтверждения вашей личности при помощи другой подтверждённой сессии.
\n
\nЭто значит, что у вас есть все нужные ключи, чтобы разблокировать зашифрованные сообщения и даёте другим пользователям знать, что вы доверяете этой сессии.</string>
<string name="device_manager_learn_more_sessions_verified" tools:ignore="UnusedResources">Подтверждённые сессии вошли при помощи ваших учётных данных и были подтверждены, либо при помощи вашего безопасного пароля, либо при помощи подтверждения с другого устройства.
\n
\nЭто значит, что на них находятся ключи шифрования для ваших предыдущих сообщений и дают другим пользователям знать, что эти сессии действительно принадлежат вам.</string>
<string name="device_manager_learn_more_sessions_unverified">Неподтверждённые сессии — это сессии, которые вошли при помощи ваших учётных данных, но не были подтверждены.
\n
\nВы должны удостовериться, что узнаёте эти сессии, так как они могут быть несанкционированным входом в вашу учётную запись.</string>
<string name="device_manager_sessions_sign_in_with_qr_code_description">Вы можете использовать это устройство для входа с телефона или веб-устройства при помощи QR-кода. Для этого есть два способа:</string>
<string name="device_manager_sessions_sign_in_with_qr_code_title">Войти при помощи QR-кода</string>
<string name="device_manager_session_rename_description">Собственные названия сессий помогут вам легче распознать свои девайсы.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Выйти из %1$d сессии</item>
<item quantity="few">Выйти из %1$d сессий</item>
<item quantity="many">Выйти из %1$d сессий</item>
<item quantity="other">Выйти из %1$d сессий</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Выйти</string>
<string name="device_manager_other_sessions_select">Выбрать сессии</string>
<string name="a11y_device_manager_filter">Фильтр</string>
<plurals name="device_manager_other_sessions_description_inactive">
<item quantity="one">Неактивен %1$d+ день (%2$s)</item>
<item quantity="few">Неактивен %1$d+ дней (%2$s)</item>
<item quantity="many">Неактивен %1$d+ дня (%2$s)</item>
<item quantity="other">Неактивен %1$d+ дня (%2$s)</item>
</plurals>
<string name="device_manager_verification_status_detail_other_session_unknown">Подтвердите текущую сессию, чтобы посмотреть её состояние подтверждения.</string>
<string name="device_manager_verification_status_unknown">Неизвестное состояние проверки</string>
<string name="labs_enable_element_call_permission_shortcuts_summary">Автоматически принимать виджеты Element Call и давать доступ к микрофону/камере</string>
<string name="labs_enable_element_call_permission_shortcuts">Включить ярлыки разрешений Element Call</string>
<string name="attachment_type_selector_text_formatting">Форматирование текста</string>
<string name="tooltip_attachment_voice_broadcast">Начать новую голосовую трансляцию</string>
<string name="live_location_not_enough_permission_dialog_description">Вам необходимо иметь нужные разрешения, чтобы делиться местоположением в реальном времени в этой комнате.</string>
<string name="live_location_not_enough_permission_dialog_title">У вас нет разрешения делиться местоположением в реальном времени</string>
<string name="labs_enable_msc3061_share_history_desc">При приглашении кого-то в зашифрованную комнату, которая делится историей, зашифрованная история будет видимой.</string>
<string name="error_voice_broadcast_already_in_progress_message">Вы уже записываете голосовую трансляцию. Пожалуйста закончите текущую голосовую трансляцию, чтобы начать новую.</string>
<string name="error_voice_broadcast_blocked_by_someone_else_message">Кто-то другой уже записывает голосовую трансляцию. Подождите пока их голосовая трансляция закончится, чтобы начать новую.</string>
<string name="error_voice_broadcast_permission_denied_message">У вас нет необходимых разрешений для начала голосовой трансляции в этой комнате. Свяжитесь с администратором комнаты, чтобы получить разрешения.</string>
<string name="error_voice_broadcast_unauthorized_title">Не получилось начать новую голосовую трансляцию</string>
<string name="a11y_voice_broadcast_fast_forward">Перемотать вперёд на 30 секунд</string>
<string name="a11y_voice_broadcast_fast_backward">Перемотать назад на 30 секунд</string>
<string name="a11y_voice_broadcast_buffering">Буферизация</string>
<string name="a11y_pause_voice_broadcast">Приостановить голосовую трансляцию</string>
<string name="a11y_play_voice_broadcast">Проиграть или продолжить голосовую трансляцию</string>
<string name="a11y_stop_voice_broadcast_record">Остановить запись голосовой трансляции</string>
<string name="a11y_pause_voice_broadcast_record">Приостановить запись голосовой трансляции</string>
<string name="a11y_resume_voice_broadcast_record">Продолжить запись голосовой трансляции</string>
<string name="voice_broadcast_live">Прямая трансляция</string>
<string name="key_authenticity_not_guaranteed">Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве.</string>
<string name="login_scan_qr_code">Сканировать QR-код</string>
<string name="send_your_first_msg_to_invite">Отправьте ваше первое сообщение, чтобы пригласить %s в переписку</string>
<string name="verify_invalid_qr_notice">Этот QR-код выглядит неправильно. Пожалуйста, попробуйте подтвердить другим способом.</string>
<string name="crosssigning_cannot_verify_this_session_desc">Вы не сможете получить доступ к истории зашифрованных сообщений. Сбросьте вашу защищённую резевную копию и ключи подтверждения, чтобы начать заново.</string>
<string name="ftue_auth_password_reset_confirmation">Сброс пароля</string>
<string name="ftue_auth_new_password_title">Выберите новый пароль</string>
<string name="ftue_auth_reset_password_email_subtitle">%s пришлёт вам ссылку для подтверждения</string>
<string name="ftue_auth_phone_subtitle">%s нуждается в подтверждении вашей учётной записи</string>
<string name="ftue_auth_email_subtitle">%s нуждается в подтверждении вашей учётной записи</string>
<string name="ftue_auth_choose_server_ems_cta">Связаться</string>
<string name="ftue_auth_choose_server_ems_subtitle">Element Matrix Services (EMS) — надёжная хостинговая служба для быстрой и безопасной связи в режиме реального времени. Узнайте больше на &lt;a href=\"${ftue_ems_url}\"&gt;element.io/ems&lt;/a&gt;</string>
<string name="a11y_open_spaces">Открыть список пространств</string>
<string name="push_gateway_item_enabled">Включено:</string>
<string name="error_check_network" tools:ignore="UnusedResources">Что-то пошло не так. Пожалуйста, проверьте соединение и попробуйте ещё раз.</string>
<string name="command_description_devtools">Открыть экран инструментов для разработчика</string>
<string name="timeline_error_room_not_found">Простите, эта комната не была найдена.
\nПожалуйста, попробуйте снова позже.%s</string>
<string name="some_devices_will_not_be_able_to_decrypt">В этой комнате есть неподтверждённые устройства, они не смогут расшифровывать сообщения, отправленные вами.</string>
<string name="grant_permission">Дать разрешение</string>
<string name="ftue_auth_create_account_username_entry_footer">Другие пользователи могут найти вас по %s</string>
<string name="voice_broadcast_recording_time_left">Осталось %1$s</string>
<string name="message_reply_to_sender_created_poll">создал опрос.</string>
<string name="message_reply_to_sender_sent_sticker">отправил наклейку.</string>
<string name="message_reply_to_sender_sent_video">отправил видео.</string>
<string name="message_reply_to_sender_sent_image">отправил изображение.</string>
<string name="message_reply_to_sender_sent_voice_message">отправил голосовое сообщение.</string>
<string name="message_reply_to_sender_sent_audio_file">отправил аудиофайл.</string>
<string name="message_reply_to_sender_sent_file">отправил файл.</string>
<string name="message_reply_to_prefix">В ответ на</string>
<string name="device_manager_other_sessions_hide_ip_address">Скрыть IP-адрес</string>
<string name="device_manager_other_sessions_show_ip_address">Показать IP-адрес</string>
<string name="quoting">Цитируя</string>
<string name="replying_to">В ответ на %s</string>
<string name="editing">Редактирование</string>
</resources> </resources>

View file

@ -2765,7 +2765,7 @@
<string name="labs_enable_new_app_layout_title">Zapnúť nové usporiadanie</string> <string name="labs_enable_new_app_layout_title">Zapnúť nové usporiadanie</string>
<string name="device_manager_learn_more_session_rename">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í. <string name="device_manager_learn_more_session_rename">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 \n
\nTo im poskytuje istotu, že sa s vami naozaj rozprávajú, ale zároveň to znamená, že vidia názov relácie, ktorý sem zadáte.</string> \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.</string>
<string name="device_manager_learn_more_session_rename_title">Premenovanie relácií</string> <string name="device_manager_learn_more_session_rename_title">Premenovanie relácií</string>
<string name="device_manager_learn_more_sessions_verified">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. <string name="device_manager_learn_more_sessions_verified">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 \n
@ -2899,4 +2899,27 @@
<string name="error_voice_broadcast_unauthorized_title">Nie je možné spustiť nové hlasové vysielanie</string> <string name="error_voice_broadcast_unauthorized_title">Nie je možné spustiť nové hlasové vysielanie</string>
<string name="a11y_voice_broadcast_fast_backward">Rýchle posunutie dozadu o 30 sekúnd</string> <string name="a11y_voice_broadcast_fast_backward">Rýchle posunutie dozadu o 30 sekúnd</string>
<string name="a11y_voice_broadcast_fast_forward">Rýchle posunutie dopredu o 30 sekúnd</string> <string name="a11y_voice_broadcast_fast_forward">Rýchle posunutie dopredu o 30 sekúnd</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Odhlásiť sa z %1$d relácie</item>
<item quantity="few">Odhlásiť sa z %1$d relácií</item>
<item quantity="other">Odhlásiť sa z %1$d relácií</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Odhlásiť sa</string>
<string name="voice_broadcast_recording_time_left">Ostáva %1$s</string>
<string name="quoting">Cituje</string>
<string name="message_reply_to_sender_created_poll">vytvoril/a anketu.</string>
<string name="message_reply_to_sender_sent_sticker">poslal/a nálepku.</string>
<string name="message_reply_to_sender_sent_video">poslal/a video.</string>
<string name="message_reply_to_sender_sent_image">poslal/a obrázok.</string>
<string name="message_reply_to_sender_sent_voice_message">poslal/a zvukovú správu.</string>
<string name="message_reply_to_sender_sent_audio_file">poslal/a zvukový súbor.</string>
<string name="message_reply_to_sender_sent_file">poslal súbor.</string>
<string name="message_reply_to_prefix">V odpovedi na</string>
<string name="device_manager_other_sessions_hide_ip_address">Skryť IP adresu</string>
<string name="device_manager_other_sessions_show_ip_address">Zobraziť IP adresu</string>
<string name="replying_to">Odpoveď na %s</string>
<string name="editing">Úprava</string>
</resources> </resources>

View file

@ -2822,4 +2822,33 @@
<string name="verify_invalid_qr_notice">Ky kod QR duket i formuar keq. Ju lutemi, provoni ta verifikoni me tjetër metodë.</string> <string name="verify_invalid_qr_notice">Ky kod QR duket i formuar keq. Ju lutemi, provoni ta verifikoni me tjetër metodë.</string>
<string name="room_settings_global_block_unverified_info_text">🔒 Keni aktivizuar fshehtëzim për sesionie të verifikuar vetëm për krejt dhomat, që nga Rregullime Sigurie.</string> <string name="room_settings_global_block_unverified_info_text">🔒 Keni aktivizuar fshehtëzim për sesionie të verifikuar vetëm për krejt dhomat, që nga Rregullime Sigurie.</string>
<string name="settings_autoplay_animated_images_summary">Luaj figura të animuara te rrjedha kohora sapo zënë të duken</string> <string name="settings_autoplay_animated_images_summary">Luaj figura të animuara te rrjedha kohora sapo zënë të duken</string>
<string name="message_reply_to_sender_created_poll">krijoi një pyetësor.</string>
<string name="message_reply_to_sender_sent_sticker">dërgoi një ngjitës.</string>
<string name="message_reply_to_sender_sent_video">dërgoi një video.</string>
<string name="message_reply_to_sender_sent_image">dërgoi një figurë.</string>
<string name="message_reply_to_sender_sent_voice_message">dërgoi një mesazh zanor.</string>
<string name="message_reply_to_sender_sent_audio_file">dërgoi një kartelë audio.</string>
<string name="message_reply_to_sender_sent_file">dërgoi një kartelë.</string>
<string name="message_reply_to_prefix">Në përgjigje të</string>
<string name="rich_text_editor_full_screen_toggle">Hyni/Dilni nga mënyra “Sa krejt ekrani”</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<string name="device_manager_other_sessions_hide_ip_address">Fshihe adresën IP</string>
<string name="device_manager_other_sessions_show_ip_address">Shfaq adresë IP</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Dilni nga %1$d sesion</item>
<item quantity="other">Dilni nga %1$d sesione</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Dilni</string>
<string name="attachment_type_selector_text_formatting">Formatim teksti</string>
<string name="voice_broadcast_recording_time_left">Edhe %1$s</string>
<string name="error_voice_broadcast_already_in_progress_message">Jeni duke incizuar tashmë një transmetim zanor. Ju lutemi, që të nisni një të ri, përfundoni transmetimin tuaj aktual zanor.</string>
<string name="error_voice_broadcast_blocked_by_someone_else_message">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.</string>
<string name="error_voice_broadcast_permission_denied_message">Skeni 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.</string>
<string name="error_voice_broadcast_unauthorized_title">Smund të niset një transmetim i ri zanor</string>
<string name="a11y_voice_broadcast_fast_forward">Shtyrje përpara 30 sekonda</string>
<string name="a11y_voice_broadcast_fast_backward">Kthim prapa 30 sekonda</string>
<string name="replying_to">Si përgjigje për %s</string>
<string name="labs_enable_deferred_dm_title">Aktivizo MD të lënë për më vonë</string>
</resources> </resources>

View file

@ -2836,4 +2836,20 @@
<item quantity="one">%1$d vald</item> <item quantity="one">%1$d vald</item>
<item quantity="other">%1$d valda</item> <item quantity="other">%1$d valda</item>
</plurals> </plurals>
<string name="rich_text_editor_full_screen_toggle">Växla fullskärmsläge</string>
<string name="device_manager_learn_more_sessions_verified_description">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.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Logga ut ur %1$d session</item>
<item quantity="other">Logga ut ur %1$d sessioner</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Logga ut</string>
<string name="attachment_type_selector_text_formatting">Textformatering</string>
<string name="error_voice_broadcast_already_in_progress_message">Du spelar redan in en röstsändning. Avsluta din nuvarande röstsändning för att starta en ny.</string>
<string name="error_voice_broadcast_blocked_by_someone_else_message">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.</string>
<string name="error_voice_broadcast_permission_denied_message">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.</string>
<string name="error_voice_broadcast_unauthorized_title">Kan inte starta en ny röstsändning</string>
<string name="a11y_voice_broadcast_fast_forward">Spola framåt 30 sekunder</string>
<string name="a11y_voice_broadcast_fast_backward">Spola tillbaka 30 sekunder</string>
</resources> </resources>

View file

@ -2839,12 +2839,12 @@
<string name="device_manager_session_rename">Перейменувати сеанс</string> <string name="device_manager_session_rename">Перейменувати сеанс</string>
<string name="device_manager_session_overview_signout">Вийти з цього сеансу</string> <string name="device_manager_session_overview_signout">Вийти з цього сеансу</string>
<string name="device_manager_other_sessions_description_unverified_current_session">Не звірений - Ваш поточний сеанс</string> <string name="device_manager_other_sessions_description_unverified_current_session">Не звірений - Ваш поточний сеанс</string>
<string name="tooltip_attachment_voice_broadcast">Розпочати трансляцію голосового повідомлення</string> <string name="tooltip_attachment_voice_broadcast">Розпочати голосову трансляцію</string>
<string name="key_authenticity_not_guaranteed">Справжність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої.</string> <string name="key_authenticity_not_guaranteed">Справжність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої.</string>
<string name="settings_security_incognito_keyboard_summary">Заборонити клавіатурі оновлювати будь-які персоналізовані дані, як-от історію набору тексту та словник, на основі того, що ви набрали в розмовах. Зверніть увагу, що деякі клавіатури можуть не дотримуватися цього налаштування.</string> <string name="settings_security_incognito_keyboard_summary">Заборонити клавіатурі оновлювати будь-які персоналізовані дані, як-от історію набору тексту та словник, на основі того, що ви набрали в розмовах. Зверніть увагу, що деякі клавіатури можуть не дотримуватися цього налаштування.</string>
<string name="settings_security_incognito_keyboard_title">Клавіатура інкогніто</string> <string name="settings_security_incognito_keyboard_title">Клавіатура інкогніто</string>
<string name="command_description_table_flip">Надсилає (╯°□°)╯︵ ┻━┻ на початку текстового повідомлення</string> <string name="command_description_table_flip">Надсилає (╯°□°)╯︵ ┻━┻ на початку текстового повідомлення</string>
<string name="attachment_type_voice_broadcast">Голосові повідомлення</string> <string name="attachment_type_voice_broadcast">Голосові трансляції</string>
<string name="command_description_devtools">Відкрийте інструменти розробника</string> <string name="command_description_devtools">Відкрийте інструменти розробника</string>
<string name="room_settings_global_block_unverified_info_text">🔒 Ви увімкнули шифрування лише для перевірених сеансів для всіх кімнат у налаштуваннях безпеки.</string> <string name="room_settings_global_block_unverified_info_text">🔒 Ви увімкнули шифрування лише для перевірених сеансів для всіх кімнат у налаштуваннях безпеки.</string>
<string name="some_devices_will_not_be_able_to_decrypt">У цій кімнаті є неперевірені пристрої, вони не зможуть розшифрувати повідомлення, які ви надсилаєте.</string> <string name="some_devices_will_not_be_able_to_decrypt">У цій кімнаті є неперевірені пристрої, вони не зможуть розшифрувати повідомлення, які ви надсилаєте.</string>
@ -2920,21 +2920,21 @@
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">Вхід з іншого пристрою вже виконано.</string> <string name="qr_code_login_header_failed_other_device_already_signed_in_description">Вхід з іншого пристрою вже виконано.</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">Під час налаштування захищеного обміну повідомленнями виникла проблема з безпекою. Можливо, порушено одне з таких налаштувань: Ваш домашній сервер; Ваше інтернет-з\'єднання; Ваш пристрій;</string> <string name="qr_code_login_header_failed_e2ee_security_issue_description">Під час налаштування захищеного обміну повідомленнями виникла проблема з безпекою. Можливо, порушено одне з таких налаштувань: Ваш домашній сервер; Ваше інтернет-з\'єднання; Ваш пристрій;</string>
<string name="qr_code_login_header_failed_other_description">Запит не виконаний.</string> <string name="qr_code_login_header_failed_other_description">Запит не виконаний.</string>
<string name="labs_enable_voice_broadcast_summary">Можливість записувати та надсилати голосові повідомлення до стрічки кімнати.</string> <string name="labs_enable_voice_broadcast_summary">Можливість записувати та надсилати голосові трансляції до стрічки кімнати.</string>
<string name="labs_enable_voice_broadcast_title">Увімкнути голосові повідомлення (в активній розробці)</string> <string name="labs_enable_voice_broadcast_title">Увімкнути голосові трансляції (в активній розробці)</string>
<string name="a11y_voice_broadcast_buffering">Буферизація</string> <string name="a11y_voice_broadcast_buffering">Буферизація</string>
<string name="a11y_pause_voice_broadcast">Призупинити голосове повідомлення</string> <string name="a11y_pause_voice_broadcast">Призупинити голосову трансляцію</string>
<string name="a11y_play_voice_broadcast">Відтворити або поновити відтворення голосового повідомлення</string> <string name="a11y_play_voice_broadcast">Відтворити або поновити відтворення голосової трансляції</string>
<string name="a11y_stop_voice_broadcast_record">Припинити запис голосового повідомлення</string> <string name="a11y_stop_voice_broadcast_record">Припинити запис голосової трансляції</string>
<string name="a11y_pause_voice_broadcast_record">Призупинити запис голосового повідомлення</string> <string name="a11y_pause_voice_broadcast_record">Призупинити запис голосової трансляції</string>
<string name="a11y_resume_voice_broadcast_record">Відновити запис голосового повідомлення</string> <string name="a11y_resume_voice_broadcast_record">Відновити запис голосової трансляції</string>
<string name="voice_broadcast_live">Наживо</string> <string name="voice_broadcast_live">Наживо</string>
<string name="device_manager_other_sessions_select">Вибрати сеанси</string> <string name="device_manager_other_sessions_select">Вибрати сеанси</string>
<string name="attachment_type_selector_contact">Контакт</string> <string name="attachment_type_selector_contact">Контакт</string>
<string name="attachment_type_selector_camera">Камера</string> <string name="attachment_type_selector_camera">Камера</string>
<string name="attachment_type_selector_location">Місце перебування</string> <string name="attachment_type_selector_location">Місце перебування</string>
<string name="attachment_type_selector_poll">Опитування</string> <string name="attachment_type_selector_poll">Опитування</string>
<string name="attachment_type_selector_voice_broadcast">Голосові повідомлення</string> <string name="attachment_type_selector_voice_broadcast">Голосові трансляції</string>
<string name="attachment_type_selector_file">Вкладення</string> <string name="attachment_type_selector_file">Вкладення</string>
<string name="attachment_type_selector_sticker">Наліпки</string> <string name="attachment_type_selector_sticker">Наліпки</string>
<string name="attachment_type_selector_gallery">Фотобібліотека</string> <string name="attachment_type_selector_gallery">Фотобібліотека</string>
@ -2948,10 +2948,34 @@
<string name="action_select_all">Вибрати все</string> <string name="action_select_all">Вибрати все</string>
<string name="rich_text_editor_full_screen_toggle">Перемкнути повноекранний режим</string> <string name="rich_text_editor_full_screen_toggle">Перемкнути повноекранний режим</string>
<string name="attachment_type_selector_text_formatting">Форматування тексту</string> <string name="attachment_type_selector_text_formatting">Форматування тексту</string>
<string name="error_voice_broadcast_already_in_progress_message">Ви вже записуєте голосове повідомлення. Завершіть поточну трансляцію, щоб розпочати нову.</string> <string name="error_voice_broadcast_already_in_progress_message">Ви вже записуєте голосову трансляцію. Завершіть поточну трансляцію, щоб розпочати нову.</string>
<string name="error_voice_broadcast_blocked_by_someone_else_message">Хтось інший вже записує голосове повідомлення. Зачекайте, поки закінчиться трансляція, щоб розпочати нову.</string> <string name="error_voice_broadcast_blocked_by_someone_else_message">Хтось інший вже записує голосову трансляцію. Зачекайте, поки вона завершиться, щоб розпочати нову.</string>
<string name="error_voice_broadcast_permission_denied_message">Ви не маєте необхідних дозволів для початку передавання голосового повідомлення в цю кімнату. Зверніться до адміністратора кімнати, щоб оновити ваші дозволи.</string> <string name="error_voice_broadcast_permission_denied_message">Ви не маєте необхідних дозволів для початку голосової трансляції в цю кімнату. Зверніться до адміністратора кімнати, щоб оновити ваші дозволи.</string>
<string name="error_voice_broadcast_unauthorized_title">Не вдалося розпочати передавання нового голосового повідомлення</string> <string name="error_voice_broadcast_unauthorized_title">Не вдалося розпочати нову голосову трансляцію</string>
<string name="a11y_voice_broadcast_fast_forward">Перемотати вперед на 30 секунд</string> <string name="a11y_voice_broadcast_fast_forward">Перемотати вперед на 30 секунд</string>
<string name="a11y_voice_broadcast_fast_backward">Перемотати назад на 30 секунд</string> <string name="a11y_voice_broadcast_fast_backward">Перемотати назад на 30 секунд</string>
<string name="device_manager_learn_more_sessions_verified_description">Звірені сеанси — це будь-який пристрій, на якому ви використовуєте цей обліковий запис після введення парольної фрази або підтвердження вашої особи за допомогою іншого звіреного сеансу.
\n
\nЦе означає, що ви маєте всі ключі, необхідні для розблокування ваших зашифрованих повідомлень і підтвердження іншим користувачам, що ви довіряєте цьому сеансу.</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="one">Вийти з %1$d сеансу</item>
<item quantity="few">Вийти з %1$d сеансів</item>
<item quantity="many">Вийти з %1$d сеансів</item>
<item quantity="other">Вийти з %1$d сеансів</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">Вийти</string>
<string name="voice_broadcast_recording_time_left">Залишилося %1$s</string>
<string name="message_reply_to_sender_sent_audio_file">надсилає аудіофайл.</string>
<string name="message_reply_to_sender_sent_file">відправив файл.</string>
<string name="message_reply_to_prefix">У відповідь на</string>
<string name="device_manager_other_sessions_hide_ip_address">Сховати IP-адресу</string>
<string name="message_reply_to_sender_created_poll">створив голосування.</string>
<string name="message_reply_to_sender_sent_sticker">відправив наліпку.</string>
<string name="message_reply_to_sender_sent_video">відправив відео.</string>
<string name="message_reply_to_sender_sent_image">відправив зображення.</string>
<string name="message_reply_to_sender_sent_voice_message">відправив голосове повідомлення.</string>
<string name="device_manager_other_sessions_show_ip_address">Показати IP-адресу</string>
<string name="quoting">Цитуючи</string>
<string name="replying_to">У відповідь на %s</string>
<string name="editing">Редагування</string>
</resources> </resources>

View file

@ -2789,4 +2789,25 @@
<string name="error_voice_broadcast_unauthorized_title">無法開始新的語音廣播</string> <string name="error_voice_broadcast_unauthorized_title">無法開始新的語音廣播</string>
<string name="a11y_voice_broadcast_fast_forward">快轉30秒</string> <string name="a11y_voice_broadcast_fast_forward">快轉30秒</string>
<string name="a11y_voice_broadcast_fast_backward">快退30秒</string> <string name="a11y_voice_broadcast_fast_backward">快退30秒</string>
<string name="device_manager_learn_more_sessions_verified_description">已驗證的工作階段是您輸入通關密語或透過另一個已驗證工作階段確認您的身份後使用此帳號的任何地方。
\n
\n這代表了您擁有解鎖加密訊息並向其他使用者確認您信任此工作階段所需的所有金鑰。</string>
<plurals name="device_manager_other_sessions_multi_signout_all">
<item quantity="other">登出 %1$d 個工作階段</item>
</plurals>
<string name="device_manager_other_sessions_multi_signout_selection">登出</string>
<string name="voice_broadcast_recording_time_left">剩餘 %1$s</string>
<string name="message_reply_to_sender_created_poll">已建立投票。</string>
<string name="message_reply_to_sender_sent_sticker">已傳送貼圖。</string>
<string name="message_reply_to_sender_sent_video">已傳送影片。</string>
<string name="message_reply_to_sender_sent_image">已傳送圖片。</string>
<string name="message_reply_to_sender_sent_voice_message">已傳送語音訊息。</string>
<string name="message_reply_to_sender_sent_audio_file">已傳送音訊檔。</string>
<string name="message_reply_to_sender_sent_file">已傳送檔案。</string>
<string name="message_reply_to_prefix">回覆給</string>
<string name="device_manager_other_sessions_hide_ip_address">隱藏 IP 位置</string>
<string name="device_manager_other_sessions_show_ip_address">顯示 IP 位置</string>
<string name="quoting">引用</string>
<string name="replying_to">回覆給 %s</string>
<string name="editing">正在編輯</string>
</resources> </resources>

View file

@ -1032,6 +1032,8 @@
<string name="settings_chat_effects_description">Use /confetti command or send a message containing ❄️ or 🎉</string> <string name="settings_chat_effects_description">Use /confetti command or send a message containing ❄️ or 🎉</string>
<string name="settings_autoplay_animated_images_title">Autoplay animated images</string> <string name="settings_autoplay_animated_images_title">Autoplay animated images</string>
<string name="settings_autoplay_animated_images_summary">Play animated images in the timeline as soon as they are visible</string> <string name="settings_autoplay_animated_images_summary">Play animated images in the timeline as soon as they are visible</string>
<string name="settings_enable_direct_share_title">Enable direct share</string>
<string name="settings_enable_direct_share_summary">Show recent chats in the system share menu</string>
<string name="settings_show_join_leave_messages">Show join and leave events</string> <string name="settings_show_join_leave_messages">Show join and leave events</string>
<string name="settings_show_join_leave_messages_summary">Invites, removes, and bans are unaffected.</string> <string name="settings_show_join_leave_messages_summary">Invites, removes, and bans are unaffected.</string>
<string name="settings_show_avatar_display_name_changes_messages">Show account events</string> <string name="settings_show_avatar_display_name_changes_messages">Show account events</string>
@ -1642,7 +1644,10 @@
<string name="error_user_already_logged_in">It looks like youre trying to connect to another homeserver. Do you want to sign out?</string> <string name="error_user_already_logged_in">It looks like youre trying to connect to another homeserver. Do you want to sign out?</string>
<string name="edit">Edit</string> <string name="edit">Edit</string>
<string name="editing">Editing</string>
<string name="reply">Reply</string> <string name="reply">Reply</string>
<string name="replying_to">Replying to %s</string>
<string name="quoting">Quoting</string>
<string name="reply_in_thread">Reply in thread</string> <string name="reply_in_thread">Reply in thread</string>
<string name="view_in_room">View In Room</string> <string name="view_in_room">View In Room</string>
@ -3089,12 +3094,13 @@
<string name="audio_message_file_size">(%1$s)</string> <string name="audio_message_file_size">(%1$s)</string>
<string name="voice_broadcast_live">Live</string> <string name="voice_broadcast_live">Live</string>
<!-- TODO Rename id to voice_broadcast_buffering -->
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string> <string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
<string name="a11y_pause_voice_broadcast_record">Pause voice broadcast record</string> <string name="a11y_pause_voice_broadcast_record">Pause voice broadcast record</string>
<string name="a11y_stop_voice_broadcast_record">Stop voice broadcast record</string> <string name="a11y_stop_voice_broadcast_record">Stop voice broadcast record</string>
<string name="a11y_play_voice_broadcast">Play or resume voice broadcast</string> <string name="a11y_play_voice_broadcast">Play or resume voice broadcast</string>
<string name="a11y_pause_voice_broadcast">Pause voice broadcast</string> <string name="a11y_pause_voice_broadcast">Pause voice broadcast</string>
<string name="a11y_voice_broadcast_buffering">Buffering</string>
<string name="a11y_voice_broadcast_fast_backward">Fast backward 30 seconds</string> <string name="a11y_voice_broadcast_fast_backward">Fast backward 30 seconds</string>
<string name="a11y_voice_broadcast_fast_forward">Fast forward 30 seconds</string> <string name="a11y_voice_broadcast_fast_forward">Fast forward 30 seconds</string>
<string name="error_voice_broadcast_unauthorized_title">Cant start a new voice broadcast</string> <string name="error_voice_broadcast_unauthorized_title">Cant start a new voice broadcast</string>
@ -3353,6 +3359,8 @@
<item quantity="one">Sign out of %1$d session</item> <item quantity="one">Sign out of %1$d session</item>
<item quantity="other">Sign out of %1$d sessions</item> <item quantity="other">Sign out of %1$d sessions</item>
</plurals> </plurals>
<string name="device_manager_other_sessions_show_ip_address">Show IP address</string>
<string name="device_manager_other_sessions_hide_ip_address">Hide IP address</string>
<string name="device_manager_session_overview_signout">Sign out of this session</string> <string name="device_manager_session_overview_signout">Sign out of this session</string>
<string name="device_manager_session_details_title">Session details</string> <string name="device_manager_session_details_title">Session details</string>
<string name="device_manager_session_details_description">Application, device, and activity information.</string> <string name="device_manager_session_details_description">Application, device, and activity information.</string>
@ -3461,4 +3469,13 @@
<string name="rich_text_editor_format_underline">Apply underline format</string> <string name="rich_text_editor_format_underline">Apply underline format</string>
<string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string> <string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string>
<!-- ReplyTo events -->
<string name="message_reply_to_prefix">In reply to</string>
<string name="message_reply_to_sender_sent_file">sent a file.</string>
<string name="message_reply_to_sender_sent_audio_file">sent an audio file.</string>
<string name="message_reply_to_sender_sent_voice_message">sent a voice message.</string>
<string name="message_reply_to_sender_sent_image">sent an image.</string>
<string name="message_reply_to_sender_sent_video">sent a video.</string>
<string name="message_reply_to_sender_sent_sticker">sent a sticker.</string>
<string name="message_reply_to_sender_created_poll">created a poll.</string>
</resources> </resources>

View file

@ -53,6 +53,7 @@
<dimen name="composer_attachment_margin">1dp</dimen> <dimen name="composer_attachment_margin">1dp</dimen>
<dimen name="rich_text_composer_corner_radius_single_line">28dp</dimen> <dimen name="rich_text_composer_corner_radius_single_line">28dp</dimen>
<dimen name="rich_text_composer_corner_radius_expanded">14dp</dimen> <dimen name="rich_text_composer_corner_radius_expanded">14dp</dimen>
<dimen name="rich_text_composer_menu_item_size">44dp</dimen>
<dimen name="chat_bubble_margin_start">28dp</dimen> <dimen name="chat_bubble_margin_start">28dp</dimen>
<dimen name="chat_bubble_margin_end">6dp</dimen> <dimen name="chat_bubble_margin_end">6dp</dimen>

View file

@ -4,7 +4,7 @@
<style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText"> <style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item> <item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item> <item name="android:inputType">textCapSentences|textMultiLine</item>
<item name="android:maxLines">12</item> <item name="android:maxLines">10</item>
<item name="android:minHeight">48dp</item> <item name="android:minHeight">48dp</item>
<item name="android:padding">8dp</item> <item name="android:padding">8dp</item>
<item name="android:textSize">15sp</item> <item name="android:textSize">15sp</item>
@ -14,9 +14,12 @@
<style name="Widget.Vector.EditText.RichTextComposer" parent="Widget.AppCompat.EditText"> <style name="Widget.Vector.EditText.RichTextComposer" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item> <item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item> <item name="android:inputType">textCapSentences|textMultiLine</item>
<item name="android:maxLines">12</item> <item name="android:maxLines">10</item>
<item name="android:minHeight">20dp</item> <item name="android:minHeight">40dp</item>
<item name="android:padding">0dp</item> <item name="android:paddingTop">10dp</item>
<item name="android:paddingBottom">10dp</item>
<item name="paddingStart">12dp</item>
<item name="android:clipToPadding">false</item>
<item name="android:textSize">15sp</item> <item name="android:textSize">15sp</item>
<item name="android:textColor">?vctr_message_text_color</item> <item name="android:textColor">?vctr_message_text_color</item>
</style> </style>

View file

@ -100,8 +100,8 @@ class FlowRoom(private val room: Room) {
return room.readService().getReadMarkerLive().asFlow() return room.readService().getReadMarkerLive().asFlow()
} }
fun liveReadReceipt(): Flow<Optional<String>> { fun liveReadReceipt(threadId: String?): Flow<Optional<String>> {
return room.readService().getMyReadReceiptLive().asFlow() return room.readService().getMyReadReceiptLive(threadId).asFlow()
} }
fun liveEventReadReceipts(eventId: String): Flow<List<ReadReceipt>> { fun liveEventReadReceipts(eventId: String): Flow<List<ReadReceipt>> {

View file

@ -62,7 +62,7 @@ android {
// that the app's state is completely cleared between tests. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.5.8\"" buildConfigField "String", "SDK_VERSION", "\"1.5.10\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""

View file

@ -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.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent 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.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -346,6 +347,10 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
assertTrue(registrationResult is RegistrationResult.Success) assertTrue(registrationResult is RegistrationResult.Success)
val session = (registrationResult as RegistrationResult.Success).session val session = (registrationResult as RegistrationResult.Success).session
session.open() session.open()
session.filterService().setSyncFilter(
SyncFilterBuilder()
.lazyLoadMembersForStateEvents(true)
)
if (sessionTestParams.withInitialSync) { if (sessionTestParams.withInitialSync) {
syncSession(session, 120_000) syncSession(session, 120_000)
} }

View file

@ -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<MessageContent>()?.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<MessageContent>()?.body
body2 shouldBeEqualTo "* Message 2, e2e edit"
}
}

View file

@ -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)
}
}

View file

@ -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<RealmConfiguration, Boolean> = 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) {
}
}
}
}
}

View file

@ -66,7 +66,7 @@ class PollAggregationTest : InstrumentedTest {
val aliceEventsListener = object : Timeline.Listener { val aliceEventsListener = object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
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 pollEventId = pollEvent.eventId
val pollContent = pollEvent.root.content?.toModel<MessagePollContent>() val pollContent = pollEvent.root.content?.toModel<MessagePollContent>()
val pollSummary = pollEvent.annotations?.pollResponseSummary val pollSummary = pollEvent.annotations?.pollResponseSummary

View file

@ -38,5 +38,4 @@ data class AggregatedAnnotation(
override val limited: Boolean? = false, override val limited: Boolean? = false,
override val count: Int? = 0, override val count: Int? = 0,
val chunk: List<RelationChunkInfo>? = null val chunk: List<RelationChunkInfo>? = null
) : UnsignedRelationInfo ) : UnsignedRelationInfo

View file

@ -19,7 +19,8 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
/** /**
* <code> * Server side relation aggregation.
* ```
* { * {
* "m.annotation": { * "m.annotation": {
* "chunk": [ * "chunk": [
@ -43,12 +44,13 @@ import com.squareup.moshi.JsonClass
* "count": 1 * "count": 1
* } * }
* } * }
* </code> * ```
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class AggregatedRelations( data class AggregatedRelations(
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null, @Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = 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 @Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null
) )

View file

@ -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,
)

View file

@ -26,13 +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.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.room.model.Membership 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.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.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent 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.MessageType
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent 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.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.model.relation.shouldRenderInThread
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.session.threads.ThreadDetails
@ -228,11 +227,14 @@ data class Event(
return when { return when {
isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
isFileMessage() -> "sent a file." isFileMessage() -> "sent a file."
isVoiceMessage() -> "sent a voice message."
isAudioMessage() -> "sent an audio file." isAudioMessage() -> "sent an audio file."
isImageMessage() -> "sent an image." isImageMessage() -> "sent an image."
isVideoMessage() -> "sent a video." isVideoMessage() -> "sent a video."
isSticker() -> "sent a sticker" isSticker() -> "sent a sticker."
isPoll() -> getPollQuestion() ?: "created a poll." isPoll() -> getPollQuestion() ?: "created a poll."
isLiveLocation() -> "Live location."
isLocationMessage() -> "has shared their location."
else -> text else -> text
} }
} }
@ -386,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.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? { fun Event.getRelationContent(): RelationDefaultContent? {
return if (isEncrypted()) { return if (isEncrypted()) {
content.toModel<EncryptedEventContent>()?.relatesTo content.toModel<EncryptedEventContent>()?.relatesTo
} else { } else {
content.toModel<MessageContent>()?.relatesTo ?: run { content.toModel<MessageContent>()?.relatesTo
// Special cases when there is only a local msgtype for some event types ?: getClearContent()?.get("m.relates_to")?.toContent().toModel() // Special cases when there is only a local msgtype for some event types
when (getClearType()) {
EventType.STICKER -> getClearContent().toModel<MessageStickerContent>()?.relatesTo
in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel<MessageBeaconLocationDataContent>()?.relatesTo
else -> getClearContent()?.get("m.relates_to")?.toContent().toModel()
}
}
} }
} }
@ -420,7 +416,7 @@ fun Event.getRelationContentForType(type: String): RelationDefaultContent? =
getRelationContent()?.takeIf { it.type == type } getRelationContent()?.takeIf { it.type == type }
fun Event.isReply(): Boolean { fun Event.isReply(): Boolean {
return getRelationContent()?.inReplyTo?.eventId != null return getRelationContent().isReply()
} }
fun Event.isReplyRenderedInThread(): Boolean { fun Event.isReplyRenderedInThread(): Boolean {
@ -443,11 +439,11 @@ fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE
fun Event.getPollContent(): MessagePollContent? { fun Event.getPollContent(): MessagePollContent? {
return content.toModel<MessagePollContent>() return getClearContent().toModel<MessagePollContent>()
} }
fun Event.supportsNotification() = 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() = fun Event.isContentReportable() =
this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values

View file

@ -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
)
}

View file

@ -49,11 +49,10 @@ object EventType {
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" 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 STATE_ROOM_BEACON_INFO = StableUnstableId(stable = "m.beacon_info", unstable = "org.matrix.msc3672.beacon_info")
val BEACON_LOCATION_DATA = listOf("org.matrix.msc3672.beacon", "m.beacon") 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_CHILD = "m.space.child"
const val STATE_SPACE_PARENT = "m.space.parent" const val STATE_SPACE_PARENT = "m.space.parent"
/** /**
@ -81,8 +80,7 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject" const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup" const val CALL_HANGUP = "m.call.hangup"
const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity" val CALL_ASSERTED_IDENTITY = StableUnstableId(stable = "m.call.asserted_identity", unstable = "org.matrix.call.asserted_identity")
const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity"
// This type is not processed by the client, just sent to the server // This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces" const val CALL_REPLACES = "m.call.replaces"
@ -90,10 +88,7 @@ object EventType {
// Key share events // Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request" const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
val ROOM_KEY_WITHHELD = StableUnstableId( val ROOM_KEY_WITHHELD = StableUnstableId(stable = "m.room_key.withheld", unstable = "org.matrix.room_key.withheld")
stable = "m.room_key.withheld",
unstable = "org.matrix.room_key.withheld"
)
const val REQUEST_SECRET = "m.secret.request" const val REQUEST_SECRET = "m.secret.request"
const val SEND_SECRET = "m.secret.send" const val SEND_SECRET = "m.secret.send"
@ -111,9 +106,9 @@ object EventType {
const val REACTION = "m.reaction" const val REACTION = "m.reaction"
// Poll // Poll
val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start") val POLL_START = StableUnstableId(stable = "m.poll.start", unstable = "org.matrix.msc3381.poll.start")
val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response") val POLL_RESPONSE = StableUnstableId(stable = "m.poll.response", unstable = "org.matrix.msc3381.poll.response")
val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end") val POLL_END = StableUnstableId(stable = "m.poll.end", unstable = "org.matrix.msc3381.poll.end")
// Emotes // Emotes
const val ROOM_EMOTES = "im.ponies.room_emotes" const val ROOM_EMOTES = "im.ponies.room_emotes"

View file

@ -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<MessageRelationContent?>()?.relatesTo
}

View file

@ -47,10 +47,9 @@ interface LocationSharingService {
/** /**
* Starts sharing live location in the room. * Starts sharing live location in the room.
* @param timeoutMillis timeout of the live in milliseconds * @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 * @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. * Stops sharing live location in the room.

View file

@ -15,10 +15,10 @@
*/ */
package org.matrix.android.sdk.api.session.room.model 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( 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) // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
val sourceEvents: List<String>, val sourceEvents: List<String>,
val localEchos: List<String>, val localEchos: List<String>,

View file

@ -18,5 +18,6 @@ package org.matrix.android.sdk.api.session.room.model
data class ReadReceipt( data class ReadReceipt(
val roomMember: RoomMemberSummary, val roomMember: RoomMemberSummary,
val originServerTs: Long val originServerTs: Long,
val threadId: String?
) )

View file

@ -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, * 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'. * 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. * Metadata about the image referred to in url.

View file

@ -28,3 +28,5 @@ data class RelationDefaultContent(
) : RelationContent ) : RelationContent
fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false
fun RelationDefaultContent?.isReply(): Boolean = this?.inReplyTo?.eventId != null

View file

@ -34,12 +34,14 @@ interface ReadService {
/** /**
* Force the read marker to be set on the latest event. * 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. * 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. * Set the read marker on the event with provided eventId.
@ -69,10 +71,10 @@ interface ReadService {
/** /**
* Returns a live read receipt id for the room. * Returns a live read receipt id for the room.
*/ */
fun getMyReadReceiptLive(): LiveData<Optional<String>> fun getMyReadReceiptLive(threadId: String?): LiveData<Optional<String>>
/** /**
* 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 * @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 * @return the eventId where the read receipt for the provided user is attached, or null if not found
@ -84,4 +86,8 @@ interface ReadService {
* @param eventId the event * @param eventId the event
*/ */
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
companion object {
const val THREAD_ID_MAIN = "main"
}
} }

View file

@ -33,7 +33,9 @@ object RoomSummaryConstants {
EventType.ENCRYPTED, EventType.ENCRYPTED,
EventType.STICKER, EventType.STICKER,
EventType.REACTION, EventType.REACTION,
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO ) +
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
// SC addition | this is the Element behaviour previous to Element v1.0.7 // SC addition | this is the Element behaviour previous to Element v1.0.7
val PREVIEWABLE_TYPES_ALL = listOf( val PREVIEWABLE_TYPES_ALL = listOf(
@ -53,7 +55,9 @@ object RoomSummaryConstants {
EventType.STICKER, EventType.STICKER,
EventType.REACTION, EventType.REACTION,
EventType.STATE_ROOM_CREATE EventType.STATE_ROOM_CREATE
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO ) +
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
// SC addition | no reactions in here // SC addition | no reactions in here
val PREVIEWABLE_ORIGINAL_CONTENT_TYPES = listOf( val PREVIEWABLE_ORIGINAL_CONTENT_TYPES = listOf(
@ -64,5 +68,7 @@ object RoomSummaryConstants {
EventType.CALL_ANSWER, EventType.CALL_ANSWER,
EventType.ENCRYPTED, EventType.ENCRYPTED,
EventType.STICKER EventType.STICKER
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO ) +
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
} }

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.timeline
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.orFalse 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.Event
import org.matrix.android.sdk.api.session.events.model.EventType 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.RelationType
@ -143,13 +144,21 @@ fun TimelineEvent.getEditedEventId(): String? {
fun TimelineEvent.getLastMessageContent(): MessageContent? { fun TimelineEvent.getLastMessageContent(): MessageContent? {
return when (root.getClearType()) { return when (root.getClearType()) {
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>() EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>() // XXX
in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconInfoContent>() // Polls/Beacon are not message contents like others as there is no msgtype subtype to discriminate moshi parsing
in EventType.BEACON_LOCATION_DATA -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconLocationDataContent>() // so toModel<MessageContent> won't parse them correctly
else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() // 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<MessagePollContent>()
in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconLocationDataContent>()
else -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
} }
} }
fun TimelineEvent.getLastEditNewContent(): Content? {
return annotations?.editSummary?.latestEdit?.getClearContent()?.toModel<MessageContent>()?.newContent
}
/** /**
* Returns true if it's a reply. * Returns true if it's a reply.
*/ */

View file

@ -16,19 +16,12 @@
package org.matrix.android.sdk.api.session.sync package org.matrix.android.sdk.api.session.sync
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
interface FilterService { interface FilterService {
enum class FilterPreset {
NoFilter,
/**
* Filter for Element, will include only known event type.
*/
ElementFilter
}
/** /**
* Configure the filter for the sync. * Configure the filter for the sync.
*/ */
fun setFilter(filterPreset: FilterPreset) suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder)
} }

View file

@ -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<String>? = null
private var listOfSupportedStateEventTypes: List<String>? = 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<String>) =
apply { this.listOfSupportedStateEventTypes = listOfSupportedStateEventTypes }
fun listOfSupportedTimelineEventTypes(listOfSupportedEventTypes: List<String>) =
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
}
}

View file

@ -83,9 +83,7 @@ internal class CrossSigningOlm @Inject constructor(
val signaturesMadeByMyKey = signatures[myUserID] // Signatures made by me val signaturesMadeByMyKey = signatures[myUserID] // Signatures made by me
?.get("ed25519:$pubKey") ?.get("ed25519:$pubKey")
if (signaturesMadeByMyKey.isNullOrBlank()) { require(signaturesMadeByMyKey.orEmpty().isNotBlank()) { "Not signed with my key $type" }
throw IllegalArgumentException("Not signed with my key $type")
}
// Check that Alice USK signature of Bob MSK is valid // Check that Alice USK signature of Bob MSK is valid
olmUtility.verifyEd25519Signature(signaturesMadeByMyKey, pubKey, JsonCanonicalizer.getCanonicalJson(Map::class.java, signable)) olmUtility.verifyEd25519Signature(signaturesMadeByMyKey, pubKey, JsonCanonicalizer.getCanonicalJson(Map::class.java, signable))

View file

@ -47,9 +47,8 @@ internal class DefaultEncryptEventTask @Inject constructor(
// don't want to wait for any query // don't want to wait for any query
// if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event // if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
val localEvent = params.event val localEvent = params.event
if (localEvent.eventId == null || localEvent.type == null) { require(localEvent.eventId != null)
throw IllegalArgumentException() require(localEvent.type != null)
}
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING) localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)

View file

@ -1140,28 +1140,25 @@ internal class DefaultVerificationService @Inject constructor(
override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceId: String, transactionId: String?): String? { override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceId: String, transactionId: String?): String? {
val txID = transactionId?.takeIf { it.isNotEmpty() } ?: createUniqueIDForTransaction(otherUserId, otherDeviceId) val txID = transactionId?.takeIf { it.isNotEmpty() } ?: createUniqueIDForTransaction(otherUserId, otherDeviceId)
// should check if already one (and cancel it) // should check if already one (and cancel it)
if (method == VerificationMethod.SAS) { require(method == VerificationMethod.SAS) { "Unknown verification method" }
val tx = DefaultOutgoingSASDefaultVerificationTransaction( val tx = DefaultOutgoingSASDefaultVerificationTransaction(
setDeviceVerificationAction, setDeviceVerificationAction,
userId, userId,
deviceId, deviceId,
cryptoStore, cryptoStore,
crossSigningService, crossSigningService,
outgoingKeyRequestManager, outgoingKeyRequestManager,
secretShareManager, secretShareManager,
myDeviceInfoHolder.get().myDevice.fingerprint()!!, myDeviceInfoHolder.get().myDevice.fingerprint()!!,
txID, txID,
otherUserId, otherUserId,
otherDeviceId otherDeviceId
) )
tx.transport = verificationTransportToDeviceFactory.createTransport(tx) tx.transport = verificationTransportToDeviceFactory.createTransport(tx)
addTransaction(tx) addTransaction(tx)
tx.start() tx.start()
return txID return txID
} else {
throw IllegalArgumentException("Unknown verification method")
}
} }
override fun requestKeyVerificationInDMs( override fun requestKeyVerificationInDMs(
@ -1343,28 +1340,25 @@ internal class DefaultVerificationService @Inject constructor(
otherUserId: String, otherUserId: String,
otherDeviceId: String otherDeviceId: String
): String { ): String {
if (method == VerificationMethod.SAS) { require(method == VerificationMethod.SAS) { "Unknown verification method" }
val tx = DefaultOutgoingSASDefaultVerificationTransaction( val tx = DefaultOutgoingSASDefaultVerificationTransaction(
setDeviceVerificationAction, setDeviceVerificationAction,
userId, userId,
deviceId, deviceId,
cryptoStore, cryptoStore,
crossSigningService, crossSigningService,
outgoingKeyRequestManager, outgoingKeyRequestManager,
secretShareManager, secretShareManager,
myDeviceInfoHolder.get().myDevice.fingerprint()!!, myDeviceInfoHolder.get().myDevice.fingerprint()!!,
transactionId, transactionId,
otherUserId, otherUserId,
otherDeviceId otherDeviceId
) )
tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx) tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
addTransaction(tx) addTransaction(tx)
tx.start() tx.start()
return transactionId return transactionId
} else {
throw IllegalArgumentException("Unknown verification method")
}
} }
override fun readyPendingVerificationInDMs( override fun readyPendingVerificationInDMs(

View file

@ -26,17 +26,17 @@ import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
internal fun <T> CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) { internal fun <T> CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: (realm: Realm) -> T) {
asyncTransaction(monarchy.realmConfiguration, transaction) asyncTransaction(monarchy.realmConfiguration, transaction)
} }
internal fun <T> CoroutineScope.asyncTransaction(realmConfiguration: RealmConfiguration, transaction: suspend (realm: Realm) -> T) { internal fun <T> CoroutineScope.asyncTransaction(realmConfiguration: RealmConfiguration, transaction: (realm: Realm) -> T) {
launch { launch {
awaitTransaction(realmConfiguration, transaction) awaitTransaction(realmConfiguration, transaction)
} }
} }
internal suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T { internal suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: (realm: Realm) -> T): T {
return withContext(Realm.WRITE_EXECUTOR.asCoroutineDispatcher()) { return withContext(Realm.WRITE_EXECUTOR.asCoroutineDispatcher()) {
Realm.getInstance(config).use { bgRealm -> Realm.getInstance(config).use { bgRealm ->
if (!bgRealm.isInTransaction) { if (!bgRealm.isInTransaction) {

View file

@ -66,6 +66,9 @@ 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.MigrateSessionTo040
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041 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.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.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import timber.log.Timber import timber.log.Timber
@ -89,7 +92,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val scSchemaVersion = 7L private val scSchemaVersion = 7L
private val scSchemaVersionOffset = (1L shl 12) private val scSchemaVersionOffset = (1L shl 12)
val schemaVersion = 42L + val schemaVersion = 45L +
scSchemaVersion * scSchemaVersionOffset scSchemaVersion * scSchemaVersionOffset
} }
@ -148,6 +151,9 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 40) MigrateSessionTo040(realm).perform() if (oldVersion < 40) MigrateSessionTo040(realm).perform()
if (oldVersion < 41) MigrateSessionTo041(realm).perform() if (oldVersion < 41) MigrateSessionTo041(realm).perform()
if (oldVersion < 42) MigrateSessionTo042(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()
if (oldScVersion <= 0) MigrateScSessionTo001(realm).perform() if (oldScVersion <= 0) MigrateScSessionTo001(realm).perform()
if (oldScVersion <= 1) MigrateScSessionTo002(realm).perform() if (oldScVersion <= 1) MigrateScSessionTo002(realm).perform()

View file

@ -84,7 +84,6 @@ internal fun ChunkEntity.addTimelineEvent(
this.eventId = eventId this.eventId = eventId
this.roomId = roomId this.roomId = roomId
this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
?.also { it.cleanUp(eventEntity.sender) }
this.readReceipts = readReceiptsSummaryEntity this.readReceipts = readReceiptsSummaryEntity
this.displayIndex = displayIndex this.displayIndex = displayIndex
this.ownedByThreadChunk = ownedByThreadChunk this.ownedByThreadChunk = ownedByThreadChunk
@ -134,7 +133,7 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE
val originServerTs = eventEntity.originServerTs val originServerTs = eventEntity.originServerTs
if (originServerTs != null) { if (originServerTs != null) {
val timestampOfEvent = originServerTs.toDouble() 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 the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) { if (timestampOfEvent > readReceiptOfSender.originServerTs) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst() val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()

View file

@ -65,11 +65,11 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
inThreadMessages = inThreadMessages, inThreadMessages = inThreadMessages,
latestMessageTimelineEventEntity = latestEventInThread latestMessageTimelineEventEntity = latestEventInThread
) )
}
}
if (shouldUpdateNotifications) { if (shouldUpdateNotifications) {
updateNotificationsNew(roomId, realm, currentUserId) updateThreadNotifications(roomId, realm, currentUserId, rootThreadEventId)
}
}
} }
} }
@ -273,8 +273,8 @@ internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm,
/** /**
* Find the read receipt for the current user. * Find the read receipt for the current user.
*/ */
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? = internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): String? =
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId) ReadReceiptEntity.where(realm, roomId = roomId, userId = userId, threadId = threadId)
.findFirst() .findFirst()
?.eventId ?.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 * Important: It will work only with the latest chunk, while read marker will be changed
* immediately so we should not display wrong notifications * immediately so we should not display wrong notifications
*/ */
internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) { internal fun updateThreadNotifications(roomId: String, realm: Realm, currentUserId: String, rootThreadEventId: String) {
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return val readReceipt = findMyReadReceipt(realm, roomId, currentUserId, threadId = rootThreadEventId) ?: return
val readReceiptChunk = ChunkEntity val readReceiptChunk = ChunkEntity
.findIncludingEvent(realm, readReceipt) ?: return .findIncludingEvent(realm, readReceipt) ?: return
val readReceiptChunkTimelineEvents = readReceiptChunk val readReceiptChunkThreadEvents = readReceiptChunk
.timelineEvents .timelineEvents
.where() .where()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId) .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll() ?: return .findAll() ?: return
val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt } val readReceiptChunkPosition = readReceiptChunkThreadEvents.indexOfFirst { it.eventId == readReceipt }
if (readReceiptChunkPosition == -1) return if (readReceiptChunkPosition == -1) return
if (readReceiptChunkPosition < readReceiptChunkTimelineEvents.lastIndex) { if (readReceiptChunkPosition < readReceiptChunkThreadEvents.lastIndex) {
// If the read receipt is found inside the chunk // If the read receipt is found inside the chunk
val threadEventsAfterReadReceipt = readReceiptChunkTimelineEvents val threadEventsAfterReadReceipt = readReceiptChunkThreadEvents
.slice(readReceiptChunkPosition..readReceiptChunkTimelineEvents.lastIndex) .slice(readReceiptChunkPosition..readReceiptChunkThreadEvents.lastIndex)
.filter { it.root?.isThread() == true } .filter { it.root?.isThread() == true }
// In order for the below code to work for old events, we should save the previous read receipt // 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 it.root?.rootThreadEventId
} }
// Find the root events in the new thread events // Update root thread event only if the user have participated in
val rootThreads = threadEventsAfterReadReceipt.distinctBy { it.root?.rootThreadEventId }.mapNotNull { it.root?.rootThreadEventId } 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 if (isUserParticipating) {
rootThreads.forEach { eventId -> rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE
val isUserParticipating = TimelineEventEntity.isUserParticipatingInThread( }
realm = realm,
roomId = roomId,
rootThreadEventId = eventId,
senderId = currentUserId
)
val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst()
if (isUserParticipating) { if (userMentionsList.contains(rootThreadEventId)) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE
}
if (userMentionsList.contains(eventId)) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE
}
} }
} }
} }

View file

@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.database.helper
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.createObject
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.CryptoService 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.MXCryptoError
@ -103,32 +102,6 @@ internal fun ThreadSummaryEntity.updateThreadSummaryLatestEvent(
} }
} }
private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap<String, RoomMemberContent?>): TimelineEventEntity {
val roomId = roomId
val eventId = eventId
val localId = TimelineEventEntity.nextId(realm)
val senderId = sender ?: ""
val timelineEventEntity = realm.createObject<TimelineEventEntity>().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( internal fun ThreadSummaryEntity.Companion.createOrUpdate(
threadSummaryType: ThreadSummaryUpdateType, threadSummaryType: ThreadSummaryUpdateType,
realm: Realm, realm: Realm,

View file

@ -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<EditionOfEvent> { 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
)
}
}

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.database.mapper 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.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary
@ -36,18 +35,7 @@ internal object EventAnnotationsSummaryMapper {
it.sourceLocalEcho.toList() it.sourceLocalEcho.toList()
) )
}, },
editSummary = annotationsSummary.editSummary editSummary = EditAggregatedSummaryEntityMapper.map(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
)
},
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let { referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
ReferencesAggregatedSummary( ReferencesAggregatedSummary(
ContentMapper.map(it.content), ContentMapper.map(it.content),

View file

@ -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<String>?.toRealmList(): RealmList<String>? {
return this?.toTypedArray()?.let { RealmList(*it) }
}
}

View file

@ -50,7 +50,7 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(
.mapNotNull { .mapNotNull {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst() val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
?: return@mapNotNull null ?: return@mapNotNull null
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong()) ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong(), it.threadId)
} }
} }
} }

View file

@ -25,11 +25,11 @@ internal class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8
override fun doMigrate(realm: DynamicRealm) { override fun doMigrate(realm: DynamicRealm) {
val editionOfEventSchema = realm.schema.create("EditionOfEvent") val editionOfEventSchema = realm.schema.create("EditionOfEvent")
.addField(EditionOfEventFields.CONTENT, String::class.java) .addField("content", String::class.java)
.addField(EditionOfEventFields.EVENT_ID, String::class.java) .addField(EditionOfEventFields.EVENT_ID, String::class.java)
.setRequired(EditionOfEventFields.EVENT_ID, true) .setRequired(EditionOfEventFields.EVENT_ID, true)
.addField(EditionOfEventFields.SENDER_ID, String::class.java) .addField("senderId", String::class.java)
.setRequired(EditionOfEventFields.SENDER_ID, true) .setRequired("senderId", true)
.addField(EditionOfEventFields.TIMESTAMP, Long::class.java) .addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
.addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java) .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)

View file

@ -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")
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -32,9 +32,8 @@ internal open class EditAggregatedSummaryEntity(
@RealmClass(embedded = true) @RealmClass(embedded = true)
internal open class EditionOfEvent( internal open class EditionOfEvent(
var senderId: String = "",
var eventId: String = "", var eventId: String = "",
var content: String? = null,
var timestamp: Long = 0, var timestamp: Long = 0,
var isLocalEcho: Boolean = false var isLocalEcho: Boolean = false,
var event: EventEntity? = null,
) : RealmObject() ) : RealmObject()

Some files were not shown because too many files have changed in this diff Show more