diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1ba71c1f61..4ff935fad1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,7 +25,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/develop' && format('integration-tests-develop-{0}-{1}', matrix.target, github.sha) || format('build-debug-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
@@ -49,7 +49,7 @@ jobs:
if: github.ref == 'refs/heads/main'
# Only runs on main, no concurrency.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index ee4a87293f..4e701faa44 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -7,5 +7,5 @@ jobs:
runs-on: ubuntu-latest
# No concurrency required, this is a prerequisite to other actions and should run every time.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 52c63d595a..d8c1bb6c49 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -13,52 +13,7 @@ env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
- -PallWarningsAsErrors=false
jobs:
- # Build Android Tests [Matrix SDK]
- build-android-test-matrix-sdk:
- name: Matrix SDK - Build Android Tests
- runs-on: macos-latest
- # No concurrency required, runs every time on a schedule.
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v2
- with:
- distribution: 'adopt'
- java-version: 11
- - uses: actions/cache@v2
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - name: Build Android Tests for matrix-sdk-android
- run: ./gradlew clean matrix-sdk-android:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
-
- # Build Android Tests [Matrix APP]
- build-android-test-app:
- name: App - Build Android Tests
- runs-on: macos-latest
- # No concurrency required, runs every time on a schedule.
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v2
- with:
- distribution: 'adopt'
- java-version: 11
- - uses: actions/cache@v2
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - name: Build Android Tests for vector
- run: ./gradlew clean vector:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
-
# Run Android Tests
integration-tests:
name: Matrix SDK - Running Integration Tests
@@ -69,7 +24,7 @@ jobs:
api-level: [ 28 ]
# No concurrency required, runs every time on a schedule.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
@@ -88,11 +43,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
- run: |
- pip install matrix-synapse
- curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
- chmod 777 start.sh
- ./start.sh --no-rate-limit
+ uses: michaelkaye/setup-matrix-synapse@v0.3.0
+ with:
+ uploadLogs: true
+ httpPort: 8080
+ disableRateLimiting: true
# package: org.matrix.android.sdk.session
- name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}]
uses: reactivecircus/android-emulator-runner@v2
@@ -261,7 +216,7 @@ jobs:
api-level: [ 28 ]
# No concurrency required, runs every time on a schedule.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
@@ -275,10 +230,11 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
- run: |
- pip install matrix-synapse
- curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
- | sed s/127.0.0.1/0.0.0.0/g | sed 's/http:\/\/localhost/http:\/\/10.0.2.2/g' | bash -s -- --no-rate-limit
+ uses: michaelkaye/setup-matrix-synapse@v0.3.0
+ with:
+ uploadLogs: true
+ httpPort: 8080
+ disableRateLimiting: true
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
@@ -298,7 +254,7 @@ jobs:
touch emulator.log
chmod 777 emulator.log
adb logcat >> emulator.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || (adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1 )
+ ./gradlew $CI_GRADLE_ARG_PROPERTIES connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || (adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1 )
- name: Upload Test Report Log
uses: actions/upload-artifact@v2
if: always()
@@ -311,7 +267,7 @@ jobs:
codecov-units:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
@@ -339,7 +295,7 @@ jobs:
needs:
- codecov-units
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
@@ -367,9 +323,6 @@ jobs:
needs:
- integration-tests
- ui-tests
-# - unit-tests
- - build-android-test-matrix-sdk
- - build-android-test-app
- sonarqube
if: always() && github.event_name != 'workflow_dispatch'
# No concurrency required, runs every time on a schedule.
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 02827e7f17..a588b91449 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -10,7 +10,7 @@ jobs:
name: Project Check Suite
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Run code quality check suite
run: ./tools/check/check_code_quality.sh
@@ -23,7 +23,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('ktlint-develop-{0}', github.sha) || format('ktlint-{0}', github.ref) }}
cancel-in-progress: true
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Run ktlint
run: |
./gradlew ktlintCheck --continue
@@ -96,7 +96,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('android-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('android-lint-develop-{0}', github.sha) || format('android-lint-{0}', github.ref) }}
cancel-in-progress: true
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
@@ -129,7 +129,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/develop' && format('apk-lint-develop-{0}-{1}', matrix.target, github.sha) || format('apk-lint-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml
index 55873c9112..d390c47696 100644
--- a/.github/workflows/sync-from-external-sources.yml
+++ b/.github/workflows/sync-from-external-sources.yml
@@ -11,7 +11,7 @@ jobs:
if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
@@ -38,7 +38,7 @@ jobs:
if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
@@ -64,7 +64,7 @@ jobs:
if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Run analytics import script
run: ./tools/import_analytic_plan.sh
- name: Create Pull Request for analytics plan
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3d24108084..587bf14488 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -12,6 +12,30 @@ env:
-Porg.gradle.parallel=false
jobs:
+ # Build Android Tests
+ build-android-tests:
+ name: Build Android Tests
+ runs-on: ubuntu-latest
+ concurrency:
+ group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }}
+ cancel-in-progress: true
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v2
+ with:
+ distribution: 'adopt'
+ java-version: 11
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+ - name: Build Android Tests
+ run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
+
unit-tests:
name: Run Unit Tests
runs-on: ubuntu-latest
@@ -20,7 +44,7 @@ jobs:
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('unit-tests-{0}', github.ref) }}
cancel-in-progress: true
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: actions/cache@v2
with:
path: |
@@ -30,7 +54,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Run unit tests
- run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false --stacktrace
+ run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES --stacktrace
- name: Format unit test results
if: always()
run: python3 ./tools/ci/render_test_output.py unit ./**/build/test-results/**/*.xml
@@ -41,3 +65,20 @@ jobs:
( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
with:
files: ./**/build/test-results/**/*.xml
+
+# Notify the channel about runs against develop or main that have failures, as PRs should have caught these first.
+ notify:
+ runs-on: ubuntu-latest
+ needs:
+ - unit-tests
+ - build-android-tests
+ if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }}
+ steps:
+ - uses: michaelkaye/matrix-hookshot-action@v0.3.0
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
+ matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
+ text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
+ html_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{icon conclusion }} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}"
+
diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml
index 4a786a9339..1cbf29cc8d 100644
--- a/.github/workflows/update-gradle-wrapper.yml
+++ b/.github/workflows/update-gradle-wrapper.yml
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1
diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index ed572b573f..85290e72df 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -11,6 +11,7 @@
emojiemojisfdroid
+ ganfragplayhmachomeserver
@@ -18,6 +19,7 @@
ktlintlinkifiedlinkify
+ manumegolmmsisdnmsisdns
diff --git a/CHANGES.md b/CHANGES.md
index 318290107a..c411593627 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,49 @@
+Changes in Element v1.4.4 (2022-03-09)
+======================================
+
+Features ✨
+----------
+ - Adds animated typing indicator to the bottom of the timeline ([#3296](https://github.com/vector-im/element-android/issues/3296))
+ - Removes the topic and typing information from the room's top bar ([#4642](https://github.com/vector-im/element-android/issues/4642))
+ - Add possibility to save media from Gallery + reorder choices in message context menu ([#5005](https://github.com/vector-im/element-android/issues/5005))
+ - Improves settings error dialog messaging when changing avatar or display name fails ([#5418](https://github.com/vector-im/element-android/issues/5418))
+
+Bugfixes 🐛
+----------
+ - Open direct message screen when clicking on DM button in the space members list ([#4319](https://github.com/vector-im/element-android/issues/4319))
+ - Fix incorrect media cache size in settings ([#5394](https://github.com/vector-im/element-android/issues/5394))
+ - Setting an avatar when creating a room had no effect ([#5402](https://github.com/vector-im/element-android/issues/5402))
+ - Fix reactions summary crash when reopening a room ([#5463](https://github.com/vector-im/element-android/issues/5463))
+ - Fixing room titles overlapping the room image in the room toolbar ([#5468](https://github.com/vector-im/element-android/issues/5468))
+
+In development 🚧
+----------------
+ - Starts the FTUE account personalisation flow by adding an account created screen behind a feature flag ([#5158](https://github.com/vector-im/element-android/issues/5158))
+
+SDK API changes ⚠️
+------------------
+ - Change name of getTimeLineEvent and getTimeLineEventLive methods to getTimelineEvent and getTimelineEventLive. ([#5330](https://github.com/vector-im/element-android/issues/5330))
+
+Other changes
+-------------
+ - Improve Bubble layouts rendering ([#5303](https://github.com/vector-im/element-android/issues/5303))
+ - Continue improving realm usage (potentially helping with storage and RAM usage) ([#5330](https://github.com/vector-im/element-android/issues/5330))
+ - Update reaction button layout. ([#5313](https://github.com/vector-im/element-android/issues/5313))
+ - Adds forceLoginFallback feature flag and usages to FTUE login and registration ([#5325](https://github.com/vector-im/element-android/issues/5325))
+ - Override task affinity to prevent unknown activities running in our app tasks. ([#4498](https://github.com/vector-im/element-android/issues/4498))
+ - Tentatively fixing the UI sanity test being unable to click on the space menu items ([#5269](https://github.com/vector-im/element-android/issues/5269))
+ - Moves attachment-viewer, diff-match-patch, and multipicker modules to subfolders under library ([#5309](https://github.com/vector-im/element-android/issues/5309))
+ - Log the `since` token used and `next_batch` token returned when doing an incremental sync. ([#5312](https://github.com/vector-im/element-android/issues/5312), [#5318](https://github.com/vector-im/element-android/issues/5318))
+ - Upgrades material dependency version from 1.4.0 to 1.5.0 ([#5392](https://github.com/vector-im/element-android/issues/5392))
+ - Using app name instead of hardcoded "Element" for exported keys filename ([#5326](https://github.com/vector-im/element-android/issues/5326))
+ - Upgrade the plugin which generate strings with template from 1.2.2 to 2.0.0 ([#5348](https://github.com/vector-im/element-android/issues/5348))
+ - Remove about 700 unused strings and their translations ([#5352](https://github.com/vector-im/element-android/issues/5352))
+ - Creates dedicated VectorOverrides for forcing behaviour for local testing/development ([#5361](https://github.com/vector-im/element-android/issues/5361))
+ - Cleanup unused threads build configurations ([#5379](https://github.com/vector-im/element-android/issues/5379))
+ - Notify element-android channel each time a nightly build completes. ([#5314](https://github.com/vector-im/element-android/issues/5314))
+ - Iterate on badge / unread indicator color ([#5456](https://github.com/vector-im/element-android/issues/5456))
+
+
Changes in Element v1.4.2 (2022-02-22 Palindrome Day!)
======================================================
diff --git a/changelog.d/3296.bugfix b/changelog.d/3296.bugfix
deleted file mode 100644
index e5f8799f21..0000000000
--- a/changelog.d/3296.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Typing notifications moved from the header to the bottom of the timeline.
\ No newline at end of file
diff --git a/changelog.d/4319.bugfix b/changelog.d/4319.bugfix
deleted file mode 100644
index da42c864c6..0000000000
--- a/changelog.d/4319.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Open direct message screen when clicking on DM button in the space members list
diff --git a/changelog.d/4498.misc b/changelog.d/4498.misc
deleted file mode 100644
index 78493b5d77..0000000000
--- a/changelog.d/4498.misc
+++ /dev/null
@@ -1 +0,0 @@
-Override task affinity to prevent unknown activities running in our app tasks.
\ No newline at end of file
diff --git a/changelog.d/4533.misc b/changelog.d/4533.misc
new file mode 100644
index 0000000000..1137a1c43c
--- /dev/null
+++ b/changelog.d/4533.misc
@@ -0,0 +1 @@
+Improve headers UI in Rooms/Messages lists
diff --git a/changelog.d/4642.bugfix b/changelog.d/4642.bugfix
deleted file mode 100644
index 2a5ea97196..0000000000
--- a/changelog.d/4642.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Update the top bar in a room: remove topic and typing information
\ No newline at end of file
diff --git a/changelog.d/4860.bugfix b/changelog.d/4860.bugfix
new file mode 100644
index 0000000000..32049face4
--- /dev/null
+++ b/changelog.d/4860.bugfix
@@ -0,0 +1 @@
+Add colors for shield vector drawable
\ No newline at end of file
diff --git a/changelog.d/5005.feature b/changelog.d/5005.feature
deleted file mode 100644
index ce3b2ad1f9..0000000000
--- a/changelog.d/5005.feature
+++ /dev/null
@@ -1 +0,0 @@
-Add possibility to save media from Gallery + reorder choices in message context menu
diff --git a/changelog.d/5158.wip b/changelog.d/5158.wip
deleted file mode 100644
index 67a3d83a7a..0000000000
--- a/changelog.d/5158.wip
+++ /dev/null
@@ -1 +0,0 @@
-Starts the FTUE account personalisation flow by adding an account created screen behind a feature flag
\ No newline at end of file
diff --git a/changelog.d/5260.misc b/changelog.d/5260.misc
new file mode 100644
index 0000000000..36812e2c83
--- /dev/null
+++ b/changelog.d/5260.misc
@@ -0,0 +1 @@
+Number of unread messages on space badge now include number of unread DMs
\ No newline at end of file
diff --git a/changelog.d/5269.misc b/changelog.d/5269.misc
deleted file mode 100644
index 699ddfd3dd..0000000000
--- a/changelog.d/5269.misc
+++ /dev/null
@@ -1 +0,0 @@
-Tentatively fixing the UI sanity test being unable to click on the space menu items
\ No newline at end of file
diff --git a/changelog.d/5270.misc b/changelog.d/5270.misc
new file mode 100644
index 0000000000..9bbe41af59
--- /dev/null
+++ b/changelog.d/5270.misc
@@ -0,0 +1 @@
+Amend spaces menu to be consistent with iOS version
\ No newline at end of file
diff --git a/changelog.d/5303.misc b/changelog.d/5303.misc
deleted file mode 100644
index dbad0b738d..0000000000
--- a/changelog.d/5303.misc
+++ /dev/null
@@ -1 +0,0 @@
-Improve Bubble layouts rendering.
\ No newline at end of file
diff --git a/changelog.d/5309.misc b/changelog.d/5309.misc
deleted file mode 100644
index 83771995af..0000000000
--- a/changelog.d/5309.misc
+++ /dev/null
@@ -1 +0,0 @@
-Moves attachment-viewer, diff-match-patch, and multipicker modules to subfolders under library
\ No newline at end of file
diff --git a/changelog.d/5312.misc b/changelog.d/5312.misc
deleted file mode 100644
index d724f1ba3f..0000000000
--- a/changelog.d/5312.misc
+++ /dev/null
@@ -1 +0,0 @@
-Log the `since` token used and `next_batch` token returned when doing an incremental sync.
diff --git a/changelog.d/5313.misc b/changelog.d/5313.misc
deleted file mode 100644
index efc225a0a4..0000000000
--- a/changelog.d/5313.misc
+++ /dev/null
@@ -1 +0,0 @@
-Update reaction button layout.
\ No newline at end of file
diff --git a/changelog.d/5314.misc b/changelog.d/5314.misc
deleted file mode 100644
index 35fed08a61..0000000000
--- a/changelog.d/5314.misc
+++ /dev/null
@@ -1 +0,0 @@
-Notify element-android channel each time a nightly build completes.
diff --git a/changelog.d/5318.misc b/changelog.d/5318.misc
deleted file mode 100644
index d724f1ba3f..0000000000
--- a/changelog.d/5318.misc
+++ /dev/null
@@ -1 +0,0 @@
-Log the `since` token used and `next_batch` token returned when doing an incremental sync.
diff --git a/changelog.d/5325.feature b/changelog.d/5325.feature
deleted file mode 100644
index 23754c790d..0000000000
--- a/changelog.d/5325.feature
+++ /dev/null
@@ -1 +0,0 @@
-Adds forceLoginFallback feature flag and usages to FTUE login and registration
\ No newline at end of file
diff --git a/changelog.d/5326.misc b/changelog.d/5326.misc
deleted file mode 100644
index 5ffa732d53..0000000000
--- a/changelog.d/5326.misc
+++ /dev/null
@@ -1 +0,0 @@
-[Export e2ee keys] use appName instead of element
\ No newline at end of file
diff --git a/changelog.d/5330.misc b/changelog.d/5330.misc
deleted file mode 100644
index 6315ad536c..0000000000
--- a/changelog.d/5330.misc
+++ /dev/null
@@ -1 +0,0 @@
-Continue improving realm usage.
\ No newline at end of file
diff --git a/changelog.d/5330.sdk b/changelog.d/5330.sdk
deleted file mode 100644
index 3f6d46401c..0000000000
--- a/changelog.d/5330.sdk
+++ /dev/null
@@ -1 +0,0 @@
-Change name of getTimeLineEvent and getTimeLineEventLive methods to getTimelineEvent and getTimelineEventLive.
\ No newline at end of file
diff --git a/changelog.d/5340.bugfix b/changelog.d/5340.bugfix
new file mode 100644
index 0000000000..4c53f0088c
--- /dev/null
+++ b/changelog.d/5340.bugfix
@@ -0,0 +1 @@
+Support both stable and unstable prefixes for Events about Polls and Location
\ No newline at end of file
diff --git a/changelog.d/5346.misc b/changelog.d/5346.misc
new file mode 100644
index 0000000000..f979c180ef
--- /dev/null
+++ b/changelog.d/5346.misc
@@ -0,0 +1 @@
+Selected space highlight changed in left panel
\ No newline at end of file
diff --git a/changelog.d/5348.misc b/changelog.d/5348.misc
deleted file mode 100644
index f5ee8627ce..0000000000
--- a/changelog.d/5348.misc
+++ /dev/null
@@ -1 +0,0 @@
-Upgrade the plugin which generate strings with template from 1.2.2 to 2.0.0
\ No newline at end of file
diff --git a/changelog.d/5352.misc b/changelog.d/5352.misc
deleted file mode 100644
index 956de682d8..0000000000
--- a/changelog.d/5352.misc
+++ /dev/null
@@ -1 +0,0 @@
-Remove about 700 unused strings and their translations
\ No newline at end of file
diff --git a/changelog.d/5361.misc b/changelog.d/5361.misc
deleted file mode 100644
index d49554c7e7..0000000000
--- a/changelog.d/5361.misc
+++ /dev/null
@@ -1 +0,0 @@
-Creates dedicated VectorOverrides for forcing behaviour for local testing/development
\ No newline at end of file
diff --git a/changelog.d/5375.wip b/changelog.d/5375.wip
new file mode 100644
index 0000000000..352b2385a9
--- /dev/null
+++ b/changelog.d/5375.wip
@@ -0,0 +1 @@
+Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities
\ No newline at end of file
diff --git a/changelog.d/5379.misc b/changelog.d/5379.misc
deleted file mode 100644
index d485636f10..0000000000
--- a/changelog.d/5379.misc
+++ /dev/null
@@ -1 +0,0 @@
-Cleanup unused threads build configurations
\ No newline at end of file
diff --git a/changelog.d/5384.misc b/changelog.d/5384.misc
new file mode 100644
index 0000000000..dca87422bb
--- /dev/null
+++ b/changelog.d/5384.misc
@@ -0,0 +1 @@
+Add top margin before our first message
diff --git a/changelog.d/5392.misc b/changelog.d/5392.misc
deleted file mode 100644
index 54d7dba992..0000000000
--- a/changelog.d/5392.misc
+++ /dev/null
@@ -1 +0,0 @@
-Upgrades material dependency version from 1.4.0 to 1.5.0
diff --git a/changelog.d/5394.bugfix b/changelog.d/5394.bugfix
deleted file mode 100644
index f8c5311492..0000000000
--- a/changelog.d/5394.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix incorrect media cache size in settings
\ No newline at end of file
diff --git a/changelog.d/5395.feature b/changelog.d/5395.feature
new file mode 100644
index 0000000000..eb16c6cd81
--- /dev/null
+++ b/changelog.d/5395.feature
@@ -0,0 +1 @@
+Add a custom view to display a picker for share location options
diff --git a/changelog.d/5402.bugfix b/changelog.d/5402.bugfix
deleted file mode 100644
index fde9e7e74f..0000000000
--- a/changelog.d/5402.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-[Create room] Setting an avatar when creating a room had no effect
\ No newline at end of file
diff --git a/changelog.d/5418.feature b/changelog.d/5418.feature
deleted file mode 100644
index 5e1efc8718..0000000000
--- a/changelog.d/5418.feature
+++ /dev/null
@@ -1 +0,0 @@
-Improves settings error dialog messaging when changing avatar or display name fails
\ No newline at end of file
diff --git a/changelog.d/5443.misc b/changelog.d/5443.misc
new file mode 100644
index 0000000000..f9fd715403
--- /dev/null
+++ b/changelog.d/5443.misc
@@ -0,0 +1 @@
+Adds stable room hierarchy endpoint with a fallback to the unstable one
diff --git a/changelog.d/5448.bugfix b/changelog.d/5448.bugfix
new file mode 100644
index 0000000000..c4e8fb4a49
--- /dev/null
+++ b/changelog.d/5448.bugfix
@@ -0,0 +1 @@
+Fix missing messages when loading messages forwards
diff --git a/changelog.d/5501.misc b/changelog.d/5501.misc
new file mode 100644
index 0000000000..6c46a105b7
--- /dev/null
+++ b/changelog.d/5501.misc
@@ -0,0 +1 @@
+Use ColorPrimary for attachmentGalleryButton tint
\ No newline at end of file
diff --git a/changelog.d/5514.bugfix b/changelog.d/5514.bugfix
new file mode 100644
index 0000000000..0dfbca6e9a
--- /dev/null
+++ b/changelog.d/5514.bugfix
@@ -0,0 +1 @@
+Read receipt in wrong order
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 87b8e3c12f..1f2a08b6a6 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -58,6 +58,7 @@ ext.libs = [
'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle",
+ 'lifecycleRuntimeKtx' : "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle",
'datastore' : "androidx.datastore:datastore:1.0.0",
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
@@ -141,4 +142,4 @@ ext.libs = [
'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1",
'junit' : "junit:junit:4.13.2"
]
-]
\ No newline at end of file
+]
diff --git a/fastlane/metadata/android/de-DE/changelogs/40104000.txt b/fastlane/metadata/android/de-DE/changelogs/40104000.txt
new file mode 100644
index 0000000000..37de3cb4a2
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40104000.txt
@@ -0,0 +1,2 @@
+Neues: Erstelle Threads, damit dein Chatverlauf nicht zugespammt wird. Nachrichtenblasen.
+Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.4.0
diff --git a/fastlane/metadata/android/de-DE/changelogs/40104020.txt b/fastlane/metadata/android/de-DE/changelogs/40104020.txt
new file mode 100644
index 0000000000..6693401a24
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40104020.txt
@@ -0,0 +1,2 @@
+Neues: Unterstützung für @room, Verbesserungen der Abstimmungen und weitere kleine Änderungen
+Ganzer Changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.2
diff --git a/fastlane/metadata/android/en-US/changelogs/40104040.txt b/fastlane/metadata/android/en-US/changelogs/40104040.txt
new file mode 100644
index 0000000000..d36b10c390
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40104040.txt
@@ -0,0 +1,2 @@
+Main changes in this version: typing indicator UI updates. Various bug fixes and stability improvements.
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.4
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103170.txt b/fastlane/metadata/android/fr-FR/changelogs/40103170.txt
new file mode 100644
index 0000000000..c264ea3703
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40103170.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : envoyer votre position dans n'importe quel salon. Éditer un sondage.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.17
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103180.txt b/fastlane/metadata/android/fr-FR/changelogs/40103180.txt
new file mode 100644
index 0000000000..0b8a9542a5
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40103180.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : envoyer votre position dans n'importe quel salon. Éditer un sondage.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.18
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104000.txt b/fastlane/metadata/android/fr-FR/changelogs/40104000.txt
new file mode 100644
index 0000000000..eaced9e3f4
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40104000.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : Implémentation initial des fils de discussion. Bulles de messages.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.0
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104020.txt b/fastlane/metadata/android/fr-FR/changelogs/40104020.txt
new file mode 100644
index 0000000000..068b4aac43
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40104020.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : Ajout du support pour @room et des sondages non terminé parmi plein d'autres changements mineurs.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.2
diff --git a/fastlane/metadata/android/hu-HU/full_description.txt b/fastlane/metadata/android/hu-HU/full_description.txt
index 899b4cd978..0791eed7ba 100644
--- a/fastlane/metadata/android/hu-HU/full_description.txt
+++ b/fastlane/metadata/android/hu-HU/full_description.txt
@@ -1,43 +1,42 @@
-Element egy biztonságos üzenetküldő és csapatmunka támogató alkalmazás ami ideális távoli munkavégzés közben csoportos csevegéshez. Az alkalmazás végpontok közötti titkosítást használ videó konferenciához, fájl megosztáshoz és videó hivásokhoz.
+Az Element egy biztonságos üzenetküldő, és egy csapatmunka app, amely távoli munkavégzéshez is alkalmas lehet. Az alkalmazás végponti titkosítás használatával biztosít videó konferencia, fájlmegosztás, és audio hívás lehetőségeket.
-Element tulajdonságai:
-- Fejlett online kommunikációs eszköz
-- Teljesen titkosított üzenetküldés biztonságos céges kommunikációt kínál még a távdolgozóknak is
-- Elosztott csevegés a Matrix nyílt forráskódú keretrendszer felhasználásával
-- Bizontságos fájl megosztás titkosítottan projektek kezeléséhez
-- Videó hívás VoIP-pal és képernyőmegosztással
-- Könnyen integrálható a kedvenc online kollaborációs eszközöddel, projekt menedzsment eszközzel, VoIP szolgáltatással vagy más csoport üzenetküldő alkalmazással
+Az Element funkciói többek között:
+- Fejlett online kommunikációs eszközök
+- Titkosított üzenetek a biztonságos céges kommunikációhoz, otthonról dolgozóknak is
+- Decentralizált chat a nyílt forráskódú Matrix protokoll használatával
+- Biztonságos fájlmegosztáss a projektek kezeléséhez
+- Videochat, VoIP, és képernyőmegosztási lehetőséggel
+- Egyszerű integráció a kedvenc online kollaborációs eszközeiddel, projektkezelési eszközökkel, VoIP szolgáltatásokkal, és más csoportos üzenetküldő alkalmazásokkal
-Element teljesen más mint a többi üzenetküldő alkalmazás. Matrixot használ, egy nyílt hálózatot a decentralizált biztonságos kommunikációhoz. Lehetőséget ad saját szerver üzemeltetésére ami maximális tulajdont és kontrollt biztosít az adatok fölött.
+Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages.
-Magánélet védelme és titkosított üzenetküldés
-Element megóv a kéretlen hirdetésektől, adatbányászattól és a különböző szigetszerű megoldásoktól. Minden adatot biztonságba helyez, egy az egybe videó és hang kommunikáció végpontok között titkosítva ahol az eszközök hitelesítve vannak.
+Privacy and encrypted messaging
+Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification.
-Element a kezedbe adja az adatvédelmi irányítást miközben bárkivel kommunikálhatsz a Matrix hálózatban vagy más üzleti kollaborációs eszközzel ami integrálva van, mint amilyen a Slack.
+Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack.
-Element futtatható saját szerveren
+Element can be self-hosted
+To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility.
-Azért, hogy az érzékeny adatok és beszélgetések minnél inkább az irányításod alatt lehessen az Elementet saját magadnak üzemeltetheted vagy választhatsz bármely Matrixon alapuló - szabványos nyílt forráskódú és decentralizált kommunikáció - szoláltató közül. Element adatvédelmet, biztonságot és rugalmas integrációkat biztosít.
+Own your data
+You decide where to keep your data and messages. Without the risk of data mining or access from third parties.
-A te adatod a tiéd
-Te döntöd el, hogy hol tárolod az adataidat és üzeneteidet. Adatbányászat vagy harmadik fél hozzáférésének kockázata nélkül.
+Element puts you in control in different ways:
+1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers
+2. Self-host your account by running a server on your own IT infrastructure
+3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform
-Element többféle képpen adja vissza az irányítást:
-1. Szerezz egy ingyenes hozzáférést a matrix.org nyilvános szerverre amit a Matrix fejlesztők üzemeltetnek vagy válassz a több ezer önkéntesek által üzemeltetett nyilvános szerverből
-2. Üzemeltess szerver magadnak a saját infrastruktúrádon
-3. Iratkozz fel egy egyedi szerverre az Element Matrix Services platformon
+Open messaging and collaboration
+You can chat with anyone on the Matrix network, whether they’re using Element, another Matrix app or even if they are using a different messaging app.
-Nyílt üzenetküldés és kollaboráció
-Bárkivel beszélgethetsz a Matrix hálózaton, akár az Elementet használja akár egy másik Matrix alkalmazást használ vagy akár egy eltérő üzenetküldőt.
+Super secure
+Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signed device verification.
-Fantasztikusan biztonságos
-Igazi végpontok között titkosítás (csak a beszélgetésben résztvevők tudják visszafejteni) és hitelesítés eszközök közötti aláírásokkal.
+Complete communication and integration
+Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
-Teljes kommunikáció és integráció
-Üzenetküldés, hang és videóhívás, fájl megosztás, képernyő megosztás és egy csomó integráció, botok és kisalkalmazások. Építs szobákat, közösségeket, maradj kapcsolatban és végezz el dolgokat.
+Pick up where you left off
+Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io
-Vedd fel a fonalat
-Maradj kapcsolatban bárhol minden eszközödön a szinkronizált üzenetekkel és a weben a https://app.element.io oldallal
-
-Nyílt forráskód
-Element Android egy nyílt forráskódú projekt a GitHubon. Küldj hibajegyet és/vagy vegyél részt a fejlesztésében itt: https://github.com/vector-im/element-android
+Open source
+Element Android is an open source project, hosted by GitHub. Please report bugs and/or contribute to its development at https://github.com/vector-im/element-android
diff --git a/fastlane/metadata/android/hu-HU/short_description.txt b/fastlane/metadata/android/hu-HU/short_description.txt
index 2dfe14c516..51be689331 100644
--- a/fastlane/metadata/android/hu-HU/short_description.txt
+++ b/fastlane/metadata/android/hu-HU/short_description.txt
@@ -1 +1 @@
-Csoportos üzenetküldő - titkosított üzenetek, videó hívások
+Csoportos üzenetküldő - titkosított üzenetek és videó hívások
diff --git a/fastlane/metadata/android/hu-HU/title.txt b/fastlane/metadata/android/hu-HU/title.txt
index 907f907f99..c463dea393 100644
--- a/fastlane/metadata/android/hu-HU/title.txt
+++ b/fastlane/metadata/android/hu-HU/title.txt
@@ -1 +1 @@
-Element
+Element - Biztonságos üzenetküldő
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100100.txt b/fastlane/metadata/android/ja-JP/changelogs/40100100.txt
index 48af96d216..0f9fc720a9 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100100.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100100.txt
@@ -1,2 +1,2 @@
今回の新バージョンでは、主にバグの修正と改善が行われています。メッセージの送信がより速くなりました。
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.10
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.10
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100110.txt b/fastlane/metadata/android/ja-JP/changelogs/40100110.txt
index b8b9798fcd..d67486a147 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100110.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100110.txt
@@ -1,2 +1,2 @@
今回の新バージョンでは、主にUI(ユーザーインターフェース)とUX(ユーザーエクスペリエンス)の向上が図られています。友達を招待したり、QRコードを読み取って素早くDMを作成できるようになりました。
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.11
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100120.txt b/fastlane/metadata/android/ja-JP/changelogs/40100120.txt
index 01c33c5d52..1e10e5f2e3 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100120.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100120.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.12
+このバージョンの主な変更点:URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100130.txt b/fastlane/metadata/android/ja-JP/changelogs/40100130.txt
index 941a052239..0e5ef9b8eb 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100130.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100130.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.13
+このバージョンの主な変更点:URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100140.txt b/fastlane/metadata/android/ja-JP/changelogs/40100140.txt
index 6dc536cdcf..8fa9848d0b 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100140.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100140.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: 部屋の許可、自動のテーマ切替、そして多くのバグを修正しました。
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.14
+このバージョンの主な変更点:部屋の許可、自動のテーマ切替、そして多くのバグを修正しました。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100150.txt b/fastlane/metadata/android/ja-JP/changelogs/40100150.txt
index caded1b8ed..c94330b70b 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100150.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100150.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: ソーシャルログインに対応しました。
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15
+このバージョンの主な変更点:ソーシャルログインに対応しました。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt
index 1b1a2092b0..ae947f1781 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
+このバージョンの主な変更点:パフォーマンスの向上と、バグを修正しました!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100170.txt b/fastlane/metadata/android/ja-JP/changelogs/40100170.txt
index a0cc7b107d..01b742a9a2 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100170.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100170.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: バグの修正!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.17
+このバージョンの主な変更点:バグを修正しました!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt
index d0900f38c2..0c09cee3dd 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.0
+このバージョンの主な変更点:パフォーマンスの向上と、バグを修正しました!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101010.txt b/fastlane/metadata/android/ja-JP/changelogs/40101010.txt
index cb204e5696..25ac73b449 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101010.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101010.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.1
+このバージョンの主な変更点:パフォーマンスの向上と、バグを修正しました!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101020.txt b/fastlane/metadata/android/ja-JP/changelogs/40101020.txt
index bb6ab66525..762879a281 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101020.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101020.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.2
+このバージョンの主な変更点:パフォーマンスの向上と、バグを修正しました!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101030.txt b/fastlane/metadata/android/ja-JP/changelogs/40101030.txt
index e7ecc05a0f..3c641c09ac 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101030.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101030.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.3
+このバージョンの主な変更点:パフォーマンスの向上と、バグを修正しました!
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101040.txt b/fastlane/metadata/android/ja-JP/changelogs/40101040.txt
new file mode 100644
index 0000000000..2dc1cdb781
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101040.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:パフォーマンスの向上と不具合の修正
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101100.txt b/fastlane/metadata/android/ja-JP/changelogs/40101100.txt
new file mode 100644
index 0000000000..2f720498ec
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101100.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:テーマ、スタイルの更新と、スペースに関する新機能。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.10
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101160.txt b/fastlane/metadata/android/ja-JP/changelogs/40101160.txt
index 985ea10510..3e37e353d7 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101160.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101160.txt
@@ -1,2 +1,2 @@
-このバージョンの主な変更点:ルームにて誰かがログアウトした際に発生するエラーを修正しました。
-全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.16
+このバージョンの主な変更点:ルームにて誰かがログアウトした際に発生するエラーを修正しました。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.1.16
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103070.txt b/fastlane/metadata/android/ja-JP/changelogs/40103070.txt
new file mode 100644
index 0000000000..09c44e990d
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103070.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:主に通知に関する不具合の修正。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103080.txt b/fastlane/metadata/android/ja-JP/changelogs/40103080.txt
new file mode 100644
index 0000000000..7c37f5a756
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103080.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:不具合の修正
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.8
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103090.txt b/fastlane/metadata/android/ja-JP/changelogs/40103090.txt
new file mode 100644
index 0000000000..580b49e6d9
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103090.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:音声メッセージの下書き機能の追加。不具合の修正。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.9
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103100.txt b/fastlane/metadata/android/ja-JP/changelogs/40103100.txt
new file mode 100644
index 0000000000..0527756005
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103100.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:アンケート機能のサポート(実験的)。URL プレビューの新規デザイン。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.10
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103110.txt b/fastlane/metadata/android/ja-JP/changelogs/40103110.txt
new file mode 100644
index 0000000000..5295af5833
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103110.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:不具合の修正
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.11
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103120.txt b/fastlane/metadata/android/ja-JP/changelogs/40103120.txt
new file mode 100644
index 0000000000..3859bee8d5
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103120.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:不具合の修正
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.12
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103130.txt b/fastlane/metadata/android/ja-JP/changelogs/40103130.txt
new file mode 100644
index 0000000000..19d04a9b99
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103130.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:登録時の表示に関する変更(Analyticsへのオプトインなど)。数学に関するイベントをラボに追加。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.13
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103140.txt b/fastlane/metadata/android/ja-JP/changelogs/40103140.txt
new file mode 100644
index 0000000000..c9f5062c5b
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103140.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:登録時の表示に関する変更(Analyticsへのオプトインなど)。数学に関するイベントをラボに追加。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.14
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103150.txt b/fastlane/metadata/android/ja-JP/changelogs/40103150.txt
new file mode 100644
index 0000000000..89c3117cf5
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103150.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:登録時の表示に関する変更(Analyticsへのオプトインなど)。数学に関するイベントをラボに追加。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.15
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104000.txt b/fastlane/metadata/android/ja-JP/changelogs/40104000.txt
new file mode 100644
index 0000000000..22a205dc37
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40104000.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:スレッド機能の実装、吹き出しメッセージ。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.4.0
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104020.txt b/fastlane/metadata/android/ja-JP/changelogs/40104020.txt
new file mode 100644
index 0000000000..e792008faf
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40104020.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:@roomの対応、非公開の投票など。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.4.2
diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt
index 6014938cce..ce1550acb0 100644
--- a/fastlane/metadata/android/ja-JP/full_description.txt
+++ b/fastlane/metadata/android/ja-JP/full_description.txt
@@ -1,42 +1,42 @@
-Elementは、安全なメッセンジャー、リモートワーク中のグループチャットに適したチームコラボレーションアプリです。エンドツーエンドの暗号化を使用して、強力なビデオ会議、ファイル共有、音声通話を提供します。
+Elementは、安全なメッセージングアプリ、リモートワーク中のグループチャットに適したチームコラボレーションアプリです。エンド・ツー・エンドの暗号化技術を使用して、強力なビデオ会議、ファイル共有、音声通話を提供します。
Elementの特徴
- 高度なオンラインコミュニケーションツール
-- 完全に暗号化されたメッセージにより、リモートワーカーでも、より安全な企業コミュニケーションが可能
-- Matrixオープンソースフレームワークをベースにした分散型のチャット
-- プロジェクトを管理しながら、暗号化されたデータで安全にファイル共有
+- メッセージの完全な暗号化。リモートワーカーでも、より安全な企業コミュニケーションが可能
+- Matrixオープンソースフレームワークに基づく、分散型のチャット
+- プロジェクトの管理と並行して、データの暗号化によりファイルを安全に共有することが可能
- Voice over IPによるビデオチャットと画面共有
-- お気に入りのオンラインコラボレーションツール、プロジェクト管理ツール、VoIPサービス、その他のチームメッセージングアプリと簡単に統合可能
+- お気に入りのオンラインコラボレーションツールや、プロジェクト管理ツール、VoIPサービス、その他のチームメッセージングアプリと簡単に統合可能
-Elementは他のメッセージングアプリやコラボレーションアプリとは全く異なります。安全なメッセージングと分散型(非中央集権)コミュニケーションのためのオープンネットワークであるMatrixで動作します。ユーザーが自分のデータやメッセージを最大限にコントロールできるように、セルフホスティングも可能です。
+Elementは、他のメッセージングアプリやコラボレーションアプリとは全く異なります。安全なメッセージングと分散型(非中央集権型)コミュニケーションのためのオープンネットワークであるMatrixで動作します。自分のデータやメッセージを最大限にコントロールするために、あなた自身がサーバーを運営することもできます。
プライバシーと暗号化されたコミュニケーション
-Elementは、望ましくない広告、データマイニング、ウォールドガーデンからユーザーを保護します。また、エンド・ツー・エンドの暗号化と相互署名された端末の検証により、全てのデータ、1対1のビデオおよび音声通信を保護します。
+Elementは、望ましくない広告、データマイニング、囲い込みからユーザーを守ります。また、エンド・ツー・エンドの暗号化と、相互署名による端末の認証に基づき、全てのデータ、ビデオ会議、音声通信を保護します。
-Elementは、Slackなどのアプリと統合することで、Matrixネットワーク上の誰とでも安全にコミュニケーションを取ることができると同時に、プライバシーをコントロールすることができます。
+Elementでは、Matrixネットワークにいる誰とでもコミュニケーションが行えるだけでなく、Slackなどのアプリと連携すれば、他のネットワークともコミュニケーションを行うとともに、プライバシーをコントロールすることができます。
-Elementはセルフホスティングが可能
-機密データや会話の管理を強化するために、Elementはセルフホスティングが可能です。または、オープンソースの分散型コミュニケーションの標準であるMatrixベースのホストを選択することもできます。Elementは、プライバシー、セキュリティーコンプライアンス、および統合の柔軟性を提供します。
+セルフホスティングが可能
+機密データや会話の管理を強化するために、Elementはセルフホスティングが可能です。または、オープンソースの分散型コミュニケーションの標準であるMatrixに基づくサーバーを選ぶこともできます。Elementは、プライバシー、セキュリティーコンプライアンス、および柔軟な機能統合を提供します。
自分のデータを所有する
-データやメッセージをどこに保管するかは、ユーザー自身が決めることができます。データマイニングやサードパーティからのアクセスのリスクはありません。
+データやメッセージを保管する場所を自分で決めることができます。データマイニングや第三者へのデータ流出のリスクはありません。
-Elementでは、どのサーバーを使うかを、ご自身で決めることができます。
-1. 開発者がホストする matrix.org のパブリックサーバーで無料アカウントを取得するか、ボランティアがホストしているパブリックサーバーから選択する。
+Elementでは、どのサーバーを使うかをご自身で決めることができます。
+1. 開発者が運営する matrix.org の公開サーバーで無料アカウントを取得するか、ボランティアが管理している運営サーバーから選ぶ。
2. あなた自身がサーバーを運営し、アカウントを管理する。
-3. Element Matrix Servicesのホスティングプラットフォームに加入し、カスタムサーバー上でアカウントを作る。
+3. Element Matrix Servicesの運営プラットフォームに加入し、カスタムサーバー上でアカウントを作る。
オープンなメッセージングとコラボレーション
-Matrixネットワーク上の誰とでも、相手がElementや他のMatrixアプリを使っているか、さらには他のメッセージングアプリを使っているかに関わらず、チャットをすることができます。
+相手がElement、他のMatrixアプリ、さらには他のメッセージングアプリを使っているかに関わらず、Matrixネットワーク上の誰とでもチャットをすることができます。
非常に安全
-本物のエンド・ツー・エンドの暗号化(会話に参加している人だけがメッセージを復号化できる)と、相互署名された端末の検証を行います。
+本物のエンド・ツー・エンドの暗号化(会話に参加している人だけがメッセージを復号化できます)と、クロス署名による端末の認証が可能です。
包括的なコミュニケーションと統合
-メッセージング、音声およびビデオ通話、ファイル共有、画面共有、その他多くのインテグレーション、ボット、ウィジェットを提供します。ルームやコミュニティーを立ち上げて連絡を取り合い、物事をスムーズに成し遂げることができます。
+メッセージング、音声およびビデオ通話、ファイル共有、画面共有、その他多くの機能統合、ボット、ウィジェットを提供します。ルームやコミュニティーを立ち上げて連絡を取り合い、物事をスムーズに成し遂げましょう。
-中断からの再開
-メッセージの履歴は全ての端末とウェブ(https://app.element.io)で完全に同期されるので、どこからでも連絡を取り合うことができます。
+いつでも、どこにいても
+メッセージの履歴は、全ての端末とウェブ(https://app.element.io)で完全に同期されるので、どこからでも連絡を取り合うことができます。
オープンソース
-Element AndroidはGitHubで開発されているオープンソースのプロジェクトです。 バグの報告や開発への貢献は https://github.com/vector-im/element-android にて受け付けています。
+Element Androidは、GitHubで開発されているオープンソースのプロジェクトです。 不具合の報告や開発への貢献は https://github.com/vector-im/element-android にて受け付けています。
diff --git a/fastlane/metadata/android/sq/changelogs/40104000.txt b/fastlane/metadata/android/sq/changelogs/40104000.txt
new file mode 100644
index 0000000000..f917c7c0cb
--- /dev/null
+++ b/fastlane/metadata/android/sq/changelogs/40104000.txt
@@ -0,0 +1,2 @@
+Ndryshime kryesore në këtë version: Sendërtimi fillestar i mesazheve në rrjedha. Flluska mesazhesh.
+Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.0
diff --git a/fastlane/metadata/android/sq/changelogs/40104020.txt b/fastlane/metadata/android/sq/changelogs/40104020.txt
new file mode 100644
index 0000000000..2fbe4f2bf6
--- /dev/null
+++ b/fastlane/metadata/android/sq/changelogs/40104020.txt
@@ -0,0 +1,2 @@
+Ndryshimet kryesore në këtë version: shtim mbulimi për @room dhe për pyetësorë jopublikë, mes mjaft ndryshimesh të tjera të vockla.
+Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.2
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104000.txt b/fastlane/metadata/android/sv-SE/changelogs/40104000.txt
new file mode 100644
index 0000000000..6bce52ba36
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104000.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Initial implementation av trådmeddelanden. Meddelandebubblor.
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.0
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104020.txt b/fastlane/metadata/android/sv-SE/changelogs/40104020.txt
new file mode 100644
index 0000000000..e3b5d4cd1c
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104020.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: lägg till stöd för @room och slutna omröstningar, och många andra små ändringar.
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.2
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index dcf5e2cb7b..db3bccc1f9 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
+distributionSha256Sum=a9a7b7baba105f6557c9dcf9c3c6e8f7e57e6b49889c5f1d133f015d0727e4be
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt
index 227ac2a71d..00d66645e6 100644
--- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt
+++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt
@@ -20,7 +20,6 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.view.ContextMenu
-import android.view.Menu
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
@@ -77,10 +76,7 @@ internal abstract class ValueItem : EpoxyModelWithHolder() {
menuInfo: ContextMenu.ContextMenuInfo?
) {
if (copyValue != null) {
- val menuItem = menu?.add(
- Menu.NONE, R.id.copy_value,
- Menu.NONE, R.string.copy_value
- )
+ val menuItem = menu?.add(R.string.copy_value)
val clipService =
v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
menuItem?.setOnMenuItemClickListener {
diff --git a/library/jsonviewer/src/main/res/menu/jv_menu_item.xml b/library/jsonviewer/src/main/res/menu/jv_menu_item.xml
deleted file mode 100644
index 4da69b5117..0000000000
--- a/library/jsonviewer/src/main/res/menu/jv_menu_item.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/library/jsonviewer/src/main/res/values/strings.xml b/library/jsonviewer/src/main/res/values/strings.xml
index cc4b8726b4..fbd67256f5 100644
--- a/library/jsonviewer/src/main/res/values/strings.xml
+++ b/library/jsonviewer/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
+
Copy Value
diff --git a/library/ui-styles/src/debug/res/layout/activity_debug_button_styles.xml b/library/ui-styles/src/debug/res/layout/activity_debug_button_styles.xml
index 0f129fb406..cc15bb1b3b 100644
--- a/library/ui-styles/src/debug/res/layout/activity_debug_button_styles.xml
+++ b/library/ui-styles/src/debug/res/layout/activity_debug_button_styles.xml
@@ -71,19 +71,6 @@
android:enabled="false"
android:text="Destructive disabled" />
-
-
-
-
-
-
-
-
diff --git a/library/ui-styles/src/main/res/color/button_social_google_background_selector_dark.xml b/library/ui-styles/src/main/res/color/button_social_google_background_selector_dark.xml
deleted file mode 100644
index 3893ce3e34..0000000000
--- a/library/ui-styles/src/main/res/color/button_social_google_background_selector_dark.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values-land/styles_dial_pad.xml b/library/ui-styles/src/main/res/values-land/styles_dial_pad.xml
deleted file mode 100644
index 39c5bf9aa6..0000000000
--- a/library/ui-styles/src/main/res/values-land/styles_dial_pad.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index 770b001893..75b03a7d2e 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -9,10 +9,6 @@
#2f9edb?colorError
-
- #14368BD6
- @color/palette_azure
-
@color/palette_azure@color/palette_melon
@@ -22,7 +18,6 @@
#99000000#27303A
- #FF61708B#1E61708B
@@ -56,6 +51,12 @@
+
+
+
@android:color/white
@@ -77,16 +78,6 @@
#BF000000#BF000000
-
- #FFFFFFFF
- #FF22262E
- #FF090A0C
-
-
- #FFE9EDF1
- #FF22262E
- #FF090A0C
-
#EBEFF5#27303A
@@ -101,9 +92,7 @@
#AAAAAAAA#55555555
- #EEEEEE
- #61708B#FFF3F8FD
@@ -133,4 +122,14 @@
@color/palette_gray_100@color/palette_gray_450
+
+
+ @color/palette_prune
+ @color/palette_prune
+
+
+ #0DBD8B
+ #17191C
+ #FF4B55
+
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index db42cfa12c..6737f4faf1 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -9,20 +9,11 @@
32dp50dp
- 16dp
- 196dp
- 44dp
- 72dp16dp32dp
- 40dp
- 60dp
-
- 4dp8dp
- 8dp0.75
@@ -70,4 +61,7 @@
0.150.05
-
\ No newline at end of file
+
+
+ 10dp
+
diff --git a/library/ui-styles/src/main/res/values/palette.xml b/library/ui-styles/src/main/res/values/palette.xml
index e37fd8a7c6..e6cee80b59 100644
--- a/library/ui-styles/src/main/res/values/palette.xml
+++ b/library/ui-styles/src/main/res/values/palette.xml
@@ -1,8 +1,12 @@
-
+
-
+
#368BD6
@@ -15,6 +19,7 @@
#0DBD8B#FFFFFF#FF5B55
+
#7E69FF#2DC2C5#5C56F5
@@ -27,6 +32,7 @@
#8D97A5#737D8C#17191C
+
#F4F9FD
diff --git a/library/ui-styles/src/main/res/values/palette_mobile.xml b/library/ui-styles/src/main/res/values/palette_mobile.xml
index c22b9705c7..ec2f1d0814 100644
--- a/library/ui-styles/src/main/res/values/palette_mobile.xml
+++ b/library/ui-styles/src/main/res/values/palette_mobile.xml
@@ -35,7 +35,6 @@
@color/palette_gray_25@color/palette_black_950
- @color/palette_black_950@color/palette_white@color/palette_black_800
diff --git a/library/ui-styles/src/main/res/values/stylable_location_sharing_option_picker_view.xml b/library/ui-styles/src/main/res/values/stylable_location_sharing_option_picker_view.xml
new file mode 100644
index 0000000000..25b2687fed
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_location_sharing_option_picker_view.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/ui-styles/src/main/res/values/styles_attachments.xml b/library/ui-styles/src/main/res/values/styles_attachments.xml
deleted file mode 100644
index 18c2e3f95f..0000000000
--- a/library/ui-styles/src/main/res/values/styles_attachments.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/styles_buttons.xml b/library/ui-styles/src/main/res/values/styles_buttons.xml
index d09d0a399d..004aca5aaa 100644
--- a/library/ui-styles/src/main/res/values/styles_buttons.xml
+++ b/library/ui-styles/src/main/res/values/styles_buttons.xml
@@ -33,24 +33,6 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/styles_dial_pad.xml b/library/ui-styles/src/main/res/values/styles_dial_pad.xml
index 34e128c56d..77dc0b3081 100644
--- a/library/ui-styles/src/main/res/values/styles_dial_pad.xml
+++ b/library/ui-styles/src/main/res/values/styles_dial_pad.xml
@@ -1,5 +1,8 @@
-
+
+
+
-
-
diff --git a/library/ui-styles/src/main/res/values/theme_black.xml b/library/ui-styles/src/main/res/values/theme_black.xml
index 44d4206d43..6e5ce80c19 100644
--- a/library/ui-styles/src/main/res/values/theme_black.xml
+++ b/library/ui-styles/src/main/res/values/theme_black.xml
@@ -11,8 +11,6 @@
@color/vctr_fab_label_stroke_black@color/vctr_fab_label_color_black@color/vctr_touch_guard_bg_black
- @color/vctr_attachment_selector_background_black
- @color/vctr_attachment_selector_border_black@color/vctr_room_active_widgets_banner_bg_black@color/vctr_room_active_widgets_banner_text_black@color/vctr_reaction_background_off_black
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index 31e64184bc..06670ccd68 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -21,8 +21,6 @@
@color/vctr_fab_label_color_dark@color/vctr_touch_guard_bg_dark@color/vctr_keys_backup_banner_accent_color_dark
- @color/vctr_attachment_selector_background_dark
- @color/vctr_attachment_selector_border_dark@color/vctr_room_active_widgets_banner_bg_dark@color/vctr_room_active_widgets_banner_text_dark@color/vctr_reaction_background_off_dark
@@ -46,11 +44,12 @@
@color/vctr_presence_indicator_offline_dark
-
+
?vctr_system?vctr_content_quinary?vctr_system?vctr_system
+ ?vctr_content_tertiary@color/element_accent_dark
@@ -144,6 +143,8 @@
@style/Widget.Vector.ActionButton
+
+ @color/vctr_live_location_dark
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index e53bc510da..c184464320 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -21,8 +21,6 @@
@color/vctr_fab_label_color_light@color/vctr_touch_guard_bg_light@color/vctr_keys_backup_banner_accent_color_light
- @color/vctr_attachment_selector_background_light
- @color/vctr_attachment_selector_border_light@color/vctr_room_active_widgets_banner_bg_light@color/vctr_room_active_widgets_banner_text_light@color/vctr_reaction_background_off_light
@@ -46,11 +44,12 @@
@color/vctr_presence_indicator_offline_light
-
+
?vctr_system?vctr_content_quinary?vctr_system?vctr_system
+ ?vctr_content_tertiary@color/element_accent_light
@@ -145,6 +144,8 @@
@style/Widget.Vector.ActionButton
+
+ @color/vctr_live_location_light
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 3e301eebb9..2b2c38e22a 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -31,12 +31,11 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.4.4\""
+ buildConfigField "String", "SDK_VERSION", "\"1.4.6\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
- resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
- resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
- resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
+ buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
+ buildConfigField "String", "GIT_SDK_REVISION_DATE", "\"${gitRevisionDate()}\""
defaultConfig {
consumerProguardFiles 'proguard-rules.pro'
@@ -167,7 +166,7 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3'
@@ -175,7 +174,7 @@ dependencies {
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk
testImplementation libs.tests.kluent
- implementation libs.jetbrains.coroutinesAndroid
+ testImplementation libs.jetbrains.coroutinesTest
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
// Transitively required for mocking realm as monarchy doesn't expose Rx
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 031d0a8bcf..ac4ccf56d1 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -71,7 +71,7 @@ class CommonTestHelper(context: Context) {
)
)
}
- matrix = TestMatrix.getInstance(context)
+ matrix = TestMatrix.getInstance()
}
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt
index 5c9b79361e..0f79896b2c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt
@@ -23,7 +23,7 @@ object TestConstants {
const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080"
// Time out to use when waiting for server response.
- private const val AWAIT_TIME_OUT_MILLIS = 30_000
+ private const val AWAIT_TIME_OUT_MILLIS = 60_000
// Time out to use when waiting for server response, when the debugger is connected. 10 minutes
private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt
index e92232a7c5..fa44167a8f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt
@@ -105,16 +105,9 @@ internal class TestMatrix constructor(context: Context, matrixConfiguration: Mat
}
}
- fun getInstance(context: Context): TestMatrix {
- if (isInit.compareAndSet(false, true)) {
- val appContext = context.applicationContext
- if (appContext is MatrixConfiguration.Provider) {
- val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
- instance = TestMatrix(appContext, matrixConfiguration)
- } else {
- throw IllegalStateException("Matrix is not initialized properly." +
- " You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
- }
+ fun getInstance(): TestMatrix {
+ if (isInit.compareAndSet(false, false)) {
+ throw IllegalStateException("Matrix is not initialized properly. You should call TestMatrix.initialize first")
}
return instance
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
new file mode 100644
index 0000000000..41ec69cdc5
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
@@ -0,0 +1,649 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto
+
+import android.util.Log
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.delay
+import org.amshove.kluent.fail
+import org.amshove.kluent.internal.assertEquals
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestHelper
+import org.matrix.android.sdk.common.SessionTestParams
+import org.matrix.android.sdk.common.TestConstants
+import org.matrix.android.sdk.common.TestMatrixCallback
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
+import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
+import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
+import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
+import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
+import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+@LargeTest
+class E2eeSanityTests : InstrumentedTest {
+
+ private val testHelper = CommonTestHelper(context())
+ private val cryptoTestHelper = CryptoTestHelper(testHelper)
+
+ /**
+ * Simple test that create an e2ee room.
+ * Some new members are added, and a message is sent.
+ * We check that the message is e2e and can be decrypted.
+ *
+ * Additional users join, we check that they can't decrypt history
+ *
+ * Alice sends a new message, then check that the new one can be decrypted
+ */
+ @Test
+ fun testSendingE2EEMessages() {
+ val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
+ val aliceSession = cryptoTestData.firstSession
+ val e2eRoomID = cryptoTestData.roomId
+
+ val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
+
+ // add some more users and invite them
+ val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
+ .map {
+ testHelper.createAccount(it, SessionTestParams(true))
+ }
+
+ Log.v("#E2E TEST", "All accounts created")
+ // we want to invite them in the room
+ otherAccounts.forEach {
+ testHelper.runBlockingTest {
+ Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
+ aliceRoomPOV.invite(it.myUserId)
+ }
+ }
+
+ // All user should accept invite
+ otherAccounts.forEach { otherSession ->
+ waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
+ Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
+ }
+
+ // check that alice see them as joined (not really necessary?)
+ ensureMembersHaveJoined(aliceSession, otherAccounts, e2eRoomID)
+
+ Log.v("#E2E TEST", "All users have joined the room")
+ Log.v("#E2E TEST", "Alice is sending the message")
+
+ val text = "This is my message"
+ val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text)
+ // val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
+ Assert.assertTrue("Message should be sent", sentEventId != null)
+
+ // All should be able to decrypt
+ otherAccounts.forEach { otherSession ->
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
+ timelineEvent != null &&
+ timelineEvent.isEncrypted() &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE
+ }
+ }
+ }
+
+ // Add a new user to the room, and check that he can't decrypt
+ val newAccount = listOf("adam") // , "adam", "manu")
+ .map {
+ testHelper.createAccount(it, SessionTestParams(true))
+ }
+
+ newAccount.forEach {
+ testHelper.runBlockingTest {
+ Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
+ aliceRoomPOV.invite(it.myUserId)
+ }
+ }
+
+ newAccount.forEach {
+ waitForAndAcceptInviteInRoom(it, e2eRoomID)
+ }
+
+ ensureMembersHaveJoined(aliceSession, newAccount, e2eRoomID)
+
+ // wait a bit
+ testHelper.runBlockingTest {
+ delay(3_000)
+ }
+
+ // check that messages are encrypted (uisi)
+ newAccount.forEach { otherSession ->
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
+ Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
+ }
+ timelineEvent != null &&
+ timelineEvent.root.getClearType() == EventType.ENCRYPTED &&
+ timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
+ }
+ }
+ }
+
+ // Let alice send a new message
+ Log.v("#E2E TEST", "Alice sends a new message")
+
+ val secondMessage = "2 This is my message"
+ val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage)
+
+ // new members should be able to decrypt it
+ newAccount.forEach { otherSession ->
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
+ Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
+ }
+ timelineEvent != null &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE &&
+ secondMessage == timelineEvent.root.getClearContent().toModel()?.body
+ }
+ }
+ }
+
+ otherAccounts.forEach {
+ testHelper.signOutAndClose(it)
+ }
+ newAccount.forEach { testHelper.signOutAndClose(it) }
+
+ cryptoTestData.cleanUp(testHelper)
+ }
+
+ /**
+ * Quick test for basic key backup
+ * 1. Create e2e between Alice and Bob
+ * 2. Alice sends 3 messages, using 3 different sessions
+ * 3. Ensure bob can decrypt
+ * 4. Create backup for bob and upload keys
+ *
+ * 5. Sign out alice and bob to ensure no gossiping will happen
+ *
+ * 6. Let bob sign in with a new session
+ * 7. Ensure history is UISI
+ * 8. Import backup
+ * 9. Check that new session can decrypt
+ */
+ @Test
+ fun testBasicBackupImport() {
+ val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession!!
+ val e2eRoomID = cryptoTestData.roomId
+
+ Log.v("#E2E TEST", "Create and start key backup for bob ...")
+ val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
+ val keyBackupPassword = "FooBarBaz"
+ val megolmBackupCreationInfo = testHelper.doSync {
+ bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
+ }
+ val version = testHelper.doSync {
+ bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
+ }
+ Log.v("#E2E TEST", "... Key backup started and enabled for bob")
+ // Bob session should now have
+
+ val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
+
+ // let's send a few message to bob
+ val sentEventIds = mutableListOf()
+ val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
+ messagesText.forEach { text ->
+ val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
+ sentEventIds.add(it)
+ }
+
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
+ timelineEvent != null &&
+ timelineEvent.isEncrypted() &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE
+ }
+ }
+ // we want more so let's discard the session
+ aliceSession.cryptoService().discardOutboundSession(e2eRoomID)
+
+ testHelper.runBlockingTest {
+ delay(1_000)
+ }
+ }
+ Log.v("#E2E TEST", "Bob received all and can decrypt")
+
+ // Let's wait a bit to be sure that bob has backed up the session
+
+ Log.v("#E2E TEST", "Force key backup for Bob...")
+ testHelper.waitWithLatch { latch ->
+ bobKeysBackupService.backupAllGroupSessions(
+ null,
+ TestMatrixCallback(latch, true)
+ )
+ }
+ Log.v("#E2E TEST", "... Key backup done for Bob")
+
+ // Now lets logout both alice and bob to ensure that we won't have any gossiping
+
+ val bobUserId = bobSession.myUserId
+ Log.v("#E2E TEST", "Logout alice and bob...")
+ testHelper.signOutAndClose(aliceSession)
+ testHelper.signOutAndClose(bobSession)
+ Log.v("#E2E TEST", "..Logout alice and bob...")
+
+ testHelper.runBlockingTest {
+ delay(1_000)
+ }
+
+ // Create a new session for bob
+ Log.v("#E2E TEST", "Create a new session for Bob")
+ val newBobSession = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
+
+ // check that bob can't currently decrypt
+ Log.v("#E2E TEST", "check that bob can't currently decrypt")
+ sentEventIds.forEach { sentEventId ->
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
+ Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
+ }
+ timelineEvent != null &&
+ timelineEvent.root.getClearType() == EventType.ENCRYPTED
+ }
+ }
+ }
+ // after initial sync events are not decrypted, so we have to try manually
+ ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
+
+ // Let's now import keys from backup
+
+ newBobSession.cryptoService().keysBackupService().let { keysBackupService ->
+ val keyVersionResult = testHelper.doSync {
+ keysBackupService.getVersion(version.version, it)
+ }
+
+ val importedResult = testHelper.doSync {
+ keysBackupService.restoreKeyBackupWithPassword(keyVersionResult!!,
+ keyBackupPassword,
+ null,
+ null,
+ null, it)
+ }
+
+ assertEquals(3, importedResult.totalNumberOfKeys)
+ }
+
+ // ensure bob can now decrypt
+ ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
+
+ testHelper.signOutAndClose(newBobSession)
+ }
+
+ /**
+ * Check that a new verified session that was not supposed to get the keys initially will
+ * get them from an older one.
+ */
+ @Test
+ fun testSimpleGossip() {
+ val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
+ val aliceSession = cryptoTestData.firstSession
+ val bobSession = cryptoTestData.secondSession!!
+ val e2eRoomID = cryptoTestData.roomId
+
+ val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
+
+ cryptoTestHelper.initializeCrossSigning(bobSession)
+
+ // let's send a few message to bob
+ val sentEventIds = mutableListOf()
+ val messagesText = listOf("1. Hello", "2. Bob")
+
+ Log.v("#E2E TEST", "Alice sends some messages")
+ messagesText.forEach { text ->
+ val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
+ sentEventIds.add(it)
+ }
+
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
+ timelineEvent != null &&
+ timelineEvent.isEncrypted() &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE
+ }
+ }
+ }
+
+ // Ensure bob can decrypt
+ ensureIsDecrypted(sentEventIds, bobSession, e2eRoomID)
+
+ // Let's now add a new bob session
+ // Create a new session for bob
+ Log.v("#E2E TEST", "Create a new session for Bob")
+ val newBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
+
+ // check that new bob can't currently decrypt
+ Log.v("#E2E TEST", "check that new bob can't currently decrypt")
+
+ ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
+
+ // Try to request
+ sentEventIds.forEach { sentEventId ->
+ val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
+ newBobSession.cryptoService().requestRoomKeyForEvent(event)
+ }
+
+ // wait a bit
+ testHelper.runBlockingTest {
+ delay(10_000)
+ }
+
+ // Ensure that new bob still can't decrypt (keys must have been withheld)
+ ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.KEYS_WITHHELD)
+
+ // Now mark new bob session as verified
+
+ bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
+ newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId!!)
+
+ // now let new session re-request
+ sentEventIds.forEach { sentEventId ->
+ val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
+ newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
+ }
+
+ // wait a bit
+ testHelper.runBlockingTest {
+ delay(10_000)
+ }
+
+ ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
+
+ cryptoTestData.cleanUp(testHelper)
+ testHelper.signOutAndClose(newBobSession)
+ }
+
+ /**
+ * Test that if a better key is forwarded (lower index, it is then used)
+ */
+ @Test
+ fun testForwardBetterKey() {
+ val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
+ val aliceSession = cryptoTestData.firstSession
+ val bobSessionWithBetterKey = cryptoTestData.secondSession!!
+ val e2eRoomID = cryptoTestData.roomId
+
+ val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
+
+ cryptoTestHelper.initializeCrossSigning(bobSessionWithBetterKey)
+
+ // let's send a few message to bob
+ var firstEventId: String
+ val firstMessage = "1. Hello"
+
+ Log.v("#E2E TEST", "Alice sends some messages")
+ firstMessage.let { text ->
+ firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
+
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
+ timelineEvent != null &&
+ timelineEvent.isEncrypted() &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE
+ }
+ }
+ }
+
+ // Ensure bob can decrypt
+ ensureIsDecrypted(listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID)
+
+ // Let's add a new unverified session from bob
+ val newBobSession = testHelper.logIntoAccount(bobSessionWithBetterKey.myUserId, SessionTestParams(true))
+
+ // check that new bob can't currently decrypt
+ Log.v("#E2E TEST", "check that new bob can't currently decrypt")
+ ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
+
+ // Now let alice send a new message. this time the new bob session will be able to decrypt
+ var secondEventId: String
+ val secondMessage = "2. New Device?"
+
+ Log.v("#E2E TEST", "Alice sends some messages")
+ secondMessage.let { text ->
+ secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
+
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
+ timelineEvent != null &&
+ timelineEvent.isEncrypted() &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE
+ }
+ }
+ }
+
+ // check that both messages have same sessionId (it's just that we don't have index 0)
+ val firstEventNewBobPov = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
+ val secondEventNewBobPov = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
+
+ val firstSessionId = firstEventNewBobPov!!.root.content.toModel()!!.sessionId!!
+ val secondSessionId = secondEventNewBobPov!!.root.content.toModel()!!.sessionId!!
+
+ Assert.assertTrue("Should be the same session id", firstSessionId == secondSessionId)
+
+ // Confirm we can decrypt one but not the other
+ testHelper.runBlockingTest {
+ try {
+ newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
+ fail("Should not be able to decrypt event")
+ } catch (error: MXCryptoError) {
+ val errorType = (error as? MXCryptoError.Base)?.errorType
+ assertEquals(MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, errorType)
+ }
+ }
+
+ testHelper.runBlockingTest {
+ try {
+ newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
+ } catch (error: MXCryptoError) {
+ fail("Should be able to decrypt event")
+ }
+ }
+
+ // Now let's verify bobs session, and re-request keys
+ bobSessionWithBetterKey.cryptoService()
+ .verificationService()
+ .markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
+
+ newBobSession.cryptoService()
+ .verificationService()
+ .markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!)
+
+ // now let new session request
+ newBobSession.cryptoService().requestRoomKeyForEvent(firstEventNewBobPov.root)
+
+ // wait a bit
+ testHelper.runBlockingTest {
+ delay(10_000)
+ }
+
+ // old session should have shared the key at earliest known index now
+ // we should be able to decrypt both
+ testHelper.runBlockingTest {
+ try {
+ newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
+ } catch (error: MXCryptoError) {
+ fail("Should be able to decrypt first event now $error")
+ }
+ }
+ testHelper.runBlockingTest {
+ try {
+ newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
+ } catch (error: MXCryptoError) {
+ fail("Should be able to decrypt event $error")
+ }
+ }
+
+ cryptoTestData.cleanUp(testHelper)
+ testHelper.signOutAndClose(newBobSession)
+ }
+
+ private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
+ aliceRoomPOV.sendTextMessage(text)
+ var sentEventId: String? = null
+ testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
+ val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
+ timeline.start()
+
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val decryptedMsg = timeline.getSnapshot()
+ .filter { it.root.getClearType() == EventType.MESSAGE }
+ .also { list ->
+ val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
+ Log.v("#E2E TEST", "Timeline snapshot is $message")
+ }
+ .filter { it.root.sendState == SendState.SYNCED }
+ .firstOrNull { it.root.getClearContent().toModel()?.body?.startsWith(text) == true }
+ sentEventId = decryptedMsg?.eventId
+ decryptedMsg != null
+ }
+
+ timeline.dispose()
+ }
+ return sentEventId
+ }
+
+ private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List, e2eRoomID: String) {
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ otherAccounts.map {
+ aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership
+ }.all {
+ it == Membership.JOIN
+ }
+ }
+ }
+ }
+
+ private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) {
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val roomSummary = otherSession.getRoomSummary(e2eRoomID)
+ (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
+ if (it) {
+ Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
+ }
+ }
+ }
+ }
+
+ testHelper.runBlockingTest(60_000) {
+ Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
+ try {
+ otherSession.joinRoom(e2eRoomID)
+ } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
+ // it's ok we will wait after
+ }
+ }
+
+ Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
+ testHelper.waitWithLatch {
+ testHelper.retryPeriodicallyWithLatch(it) {
+ val roomSummary = otherSession.getRoomSummary(e2eRoomID)
+ roomSummary != null && roomSummary.membership == Membership.JOIN
+ }
+ }
+ }
+
+ private fun ensureCanDecrypt(sentEventIds: MutableList, session: Session, e2eRoomID: String, messagesText: List) {
+ sentEventIds.forEachIndexed { index, sentEventId ->
+ testHelper.waitWithLatch { latch ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
+ testHelper.runBlockingTest {
+ try {
+ session.cryptoService().decryptEvent(event, "").let { result ->
+ event.mxDecryptionResult = OlmDecryptionResult(
+ payload = result.clearEvent,
+ senderKey = result.senderCurve25519Key,
+ keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
+ forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+ )
+ }
+ } catch (error: MXCryptoError) {
+ // nop
+ }
+ }
+ event.getClearType() == EventType.MESSAGE &&
+ messagesText[index] == event.getClearContent()?.toModel()?.body
+ }
+ }
+ }
+ }
+
+ private fun ensureIsDecrypted(sentEventIds: List, session: Session, e2eRoomID: String) {
+ testHelper.waitWithLatch { latch ->
+ sentEventIds.forEach { sentEventId ->
+ testHelper.retryPeriodicallyWithLatch(latch) {
+ val timelineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
+ timelineEvent != null &&
+ timelineEvent.isEncrypted() &&
+ timelineEvent.root.getClearType() == EventType.MESSAGE
+ }
+ }
+ }
+ }
+
+ private fun ensureCannotDecrypt(sentEventIds: List, newBobSession: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType?) {
+ sentEventIds.forEach { sentEventId ->
+ val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
+ testHelper.runBlockingTest {
+ try {
+ newBobSession.cryptoService().decryptEvent(event, "")
+ fail("Should not be able to decrypt event")
+ } catch (error: MXCryptoError) {
+ val errorType = (error as? MXCryptoError.Base)?.errorType
+ if (expectedError == null) {
+ Assert.assertNotNull(errorType)
+ } else {
+ assertEquals(expectedError, errorType, "Message expected to be UISI")
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
index a7a81bacf5..46c1dacf78 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
@@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.FixMethodOrder
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -41,7 +40,6 @@ class PreShareKeysTest : InstrumentedTest {
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test
- @Ignore("This test will be ignored until it is fixed")
fun ensure_outbound_session_happy_path() {
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = testData.roomId
@@ -92,7 +90,7 @@ class PreShareKeysTest : InstrumentedTest {
// Just send a real message as test
val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
- assertEquals(megolmSessionId, sentEvent.root.content.toModel()?.sessionId, "Unexpected megolm session")
+ assertEquals("Unexpected megolm session", megolmSessionId, sentEvent.root.content.toModel()?.sessionId,)
testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index 0a8ce67680..fb5d58b127 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -21,7 +21,6 @@ import org.amshove.kluent.shouldBe
import org.junit.Assert
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -85,7 +84,6 @@ class UnwedgingTest : InstrumentedTest {
* -> This is automatically fixed after SDKs restarted the olm session
*/
@Test
- @Ignore("This test will be ignored until it is fixed")
fun testUnwedging() {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
@@ -94,9 +92,7 @@ class UnwedgingTest : InstrumentedTest {
val bobSession = cryptoTestData.secondSession!!
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
-
- // bobSession.cryptoService().setWarnOnUnknownDevices(false)
- // aliceSession.cryptoService().setWarnOnUnknownDevices(false)
+ val olmDevice = (aliceSession.cryptoService() as DefaultCryptoService).olmDeviceForTest
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
@@ -175,6 +171,7 @@ class UnwedgingTest : InstrumentedTest {
Timber.i("## CRYPTO | testUnwedging: wedge the session now. Set crypto state like after the first message")
aliceCryptoStore.storeSession(OlmSessionWrapper(deserializeFromRealm(oldSession)!!), bobSession.cryptoService().getMyDevice().identityKey()!!)
+ olmDevice.clearOlmSessionCache()
Thread.sleep(6_000)
// Force new session, and key share
@@ -227,8 +224,10 @@ class UnwedgingTest : InstrumentedTest {
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
// we should get back the key and be able to decrypt
- val result = tryOrNull {
- bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
+ val result = testHelper.runBlockingTest {
+ tryOrNull {
+ bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
+ }
}
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
result != null
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 82aee454eb..cd20ab477c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -97,7 +97,9 @@ class KeyShareTests : InstrumentedTest {
assert(receivedEvent!!.isEncrypted())
try {
- aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
+ commonTestHelper.runBlockingTest {
+ aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
+ }
fail("should fail")
} catch (failure: Throwable) {
}
@@ -152,7 +154,9 @@ class KeyShareTests : InstrumentedTest {
}
try {
- aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
+ commonTestHelper.runBlockingTest {
+ aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
+ }
fail("should fail")
} catch (failure: Throwable) {
}
@@ -189,7 +193,9 @@ class KeyShareTests : InstrumentedTest {
}
try {
- aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
+ commonTestHelper.runBlockingTest {
+ aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
+ }
} catch (failure: Throwable) {
fail("should have been able to decrypt")
}
@@ -384,7 +390,11 @@ class KeyShareTests : InstrumentedTest {
val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
- var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
+ var dRes = tryOrNull {
+ commonTestHelper.runBlockingTest {
+ bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "")
+ }
+ }
assert(dRes == null)
@@ -395,7 +405,11 @@ class KeyShareTests : InstrumentedTest {
Thread.sleep(3_000)
// With the bug the first session would have improperly reshare that key :/
- dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
+ dRes = tryOrNull {
+ commonTestHelper.runBlockingTest {
+ bobSession.cryptoService().decryptEvent(beforeJoin.root, "")
+ }
+ }
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel()?.body}")
assert(dRes?.clearEvent == null)
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 9fda21763a..65c65660b5 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -93,7 +93,9 @@ class WithHeldTests : InstrumentedTest {
// Bob should not be able to decrypt because the keys is withheld
try {
// .. might need to wait a bit for stability?
- bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
+ testHelper.runBlockingTest {
+ bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
+ }
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@@ -118,7 +120,9 @@ class WithHeldTests : InstrumentedTest {
// Previous message should still be undecryptable (partially withheld session)
try {
// .. might need to wait a bit for stability?
- bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
+ testHelper.runBlockingTest {
+ bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
+ }
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@@ -165,7 +169,9 @@ class WithHeldTests : InstrumentedTest {
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
try {
// .. might need to wait a bit for stability?
- bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
+ testHelper.runBlockingTest {
+ bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
+ }
Assert.fail("This session should not be able to decrypt")
} catch (failure: Throwable) {
val type = (failure as MXCryptoError.Base).errorType
@@ -233,7 +239,11 @@ class WithHeldTests : InstrumentedTest {
testHelper.retryPeriodicallyWithLatch(latch) {
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
// try to decrypt and force key request
- tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
+ tryOrNull {
+ testHelper.runBlockingTest {
+ bobSecondSession.cryptoService().decryptEvent(it.root, "")
+ }
+ }
}
sessionId = timeLineEvent?.root?.content?.toModel()?.sessionId
timeLineEvent != null
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
index 35c5a4dab9..2c96568102 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -39,6 +40,7 @@ import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
+@Ignore("This test is flaky ; see issue #5449")
class VerificationTest : InstrumentedTest {
data class ExpectedResult(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
index e3f00a24b6..65f69e17c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
@@ -121,7 +121,7 @@ interface CryptoService {
fun discardOutboundSession(roomId: String)
@Throws(MXCryptoError::class)
- fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
+ suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index df57ca5681..c0ca40bc73 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -349,7 +349,7 @@ fun Event.isAttachmentMessage(): Boolean {
}
}
-fun Event.isPoll(): Boolean = getClearType() == EventType.POLL_START || getClearType() == EventType.POLL_END
+fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
@@ -372,7 +372,7 @@ fun Event.getRelationContent(): RelationDefaultContent? {
* Returns the poll question or null otherwise
*/
fun Event.getPollQuestion(): String? =
- getPollContent()?.pollCreationInfo?.question?.question
+ getPollContent()?.getBestPollCreationInfo()?.question?.getBestQuestion()
/**
* Returns the relation content for a specific type or null otherwise
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
index 0c77b574e7..22fb9bcbe2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
@@ -103,9 +103,9 @@ object EventType {
const val REACTION = "m.reaction"
// Poll
- const val POLL_START = "org.matrix.msc3381.poll.start"
- const val POLL_RESPONSE = "org.matrix.msc3381.poll.response"
- const val POLL_END = "org.matrix.msc3381.poll.end"
+ val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start")
+ val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response")
+ val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end")
// Unwedging
internal const val DUMMY = "m.dummy"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
index bca432320d..f506b147df 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
+import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership
@@ -216,6 +217,11 @@ interface RoomService {
pagedListConfig: PagedList.Config = defaultPagedListConfig,
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
+ /**
+ * Retrieve a flow on the number of rooms.
+ */
+ fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow
+
/**
* TODO Doc
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
index b83f57f5ef..db87f913b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
@@ -22,10 +22,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
interface UpdatableLivePageResult {
val livePagedList: LiveData>
-
- fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
-
val liveBoundaries: LiveData
+ var queryParams: RoomSummaryQueryParams
}
data class ResultBoundaries(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
index d07bd2d73a..84bf5cf7b7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
@@ -39,37 +39,46 @@ data class MessageLocationContent(
*/
@Json(name = "geo_uri") val geoUri: String,
+ @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
+ @Json(name = "m.new_content") override val newContent: Content? = null,
/**
* See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md
*/
- @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null,
-
- @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
- @Json(name = "m.new_content") override val newContent: Content? = null,
-
+ @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null,
+ @Json(name = "m.location") val locationInfo: LocationInfo? = null,
+ /**
+ * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
+ */
+ @Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null,
+ @Json(name = "m.ts") val ts: Long? = null,
+ @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
+ @Json(name = "m.text") val text: String? = null,
/**
* m.asset defines a generic asset that can be used for location tracking but also in other places like
* inventories, geofencing, checkins/checkouts etc.
* It should contain a mandatory namespaced type key defining what particular asset is being referred to.
* For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid.
*/
- @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
-
- /**
- * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
- */
- @Json(name = "org.matrix.msc3488.ts") val ts: Long? = null,
-
- @Json(name = "org.matrix.msc1767.text") val text: String? = null
+ @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset? = null,
+ @Json(name = "m.asset") val locationAsset: LocationAsset? = null
) : MessageContent {
- fun getBestGeoUri() = locationInfo?.geoUri ?: geoUri
+ fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
+
+ fun getBestTs() = ts ?: unstableTs
+
+ fun getBestText() = text ?: unstableText
+
+ fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
+
+ fun getBestGeoUri() = getBestLocationInfo()?.geoUri ?: geoUri
/**
* @return true if the location asset is a user location, not a generic one.
*/
fun isSelfLocation(): Boolean {
// Should behave like m.self if locationAsset is null
+ val locationAsset = getBestLocationAsset()
return locationAsset?.type == null || locationAsset.type == LocationAssetType.SELF
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
index a4e1317290..43c0c90068 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
@@ -31,5 +31,9 @@ data class MessagePollContent(
@Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
- @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
-) : MessageContent
+ @Json(name = "org.matrix.msc3381.poll.start") val unstablePollCreationInfo: PollCreationInfo? = null,
+ @Json(name = "m.poll.start") val pollCreationInfo: PollCreationInfo? = null
+) : MessageContent {
+
+ fun getBestPollCreationInfo() = pollCreationInfo ?: unstablePollCreationInfo
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
index f3b4e3dc23..022915ed69 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
@@ -31,5 +31,9 @@ data class MessagePollResponseContent(
@Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
- @Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null
-) : MessageContent
+ @Json(name = "org.matrix.msc3381.poll.response") val unstableResponse: PollResponse? = null,
+ @Json(name = "m.response") val response: PollResponse? = null
+) : MessageContent {
+
+ fun getBestResponse() = response ?: unstableResponse
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt
index 8f5ff53c85..34614d9d15 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollAnswer.kt
@@ -22,5 +22,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollAnswer(
@Json(name = "id") val id: String? = null,
- @Json(name = "org.matrix.msc1767.text") val answer: String? = null
-)
+ @Json(name = "org.matrix.msc1767.text") val unstableAnswer: String? = null,
+ @Json(name = "m.text") val answer: String? = null
+) {
+
+ fun getBestAnswer() = answer ?: unstableAnswer
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt
index a82c01b159..81b034a809 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt
@@ -21,8 +21,8 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollCreationInfo(
- @Json(name = "question") val question: PollQuestion? = null,
- @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED,
- @Json(name = "max_selections") val maxSelections: Int = 1,
- @Json(name = "answers") val answers: List? = null
+ @Json(name = "question") val question: PollQuestion? = null,
+ @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE,
+ @Json(name = "max_selections") val maxSelections: Int = 1,
+ @Json(name = "answers") val answers: List? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt
index 76025f745e..df9517892b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollQuestion.kt
@@ -21,5 +21,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollQuestion(
- @Json(name = "org.matrix.msc1767.text") val question: String? = null
-)
+ @Json(name = "org.matrix.msc1767.text") val unstableQuestion: String? = null,
+ @Json(name = "m.text") val question: String? = null
+) {
+
+ fun getBestQuestion() = question ?: unstableQuestion
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt
index 3a8066b9bc..54801e698d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt
@@ -25,11 +25,17 @@ enum class PollType {
* Voters should see results as soon as they have voted.
*/
@Json(name = "org.matrix.msc3381.poll.disclosed")
+ DISCLOSED_UNSTABLE,
+
+ @Json(name = "m.poll.disclosed")
DISCLOSED,
/**
* Results should be only revealed when the poll is ended.
*/
@Json(name = "org.matrix.msc3381.poll.undisclosed")
+ UNDISCLOSED_UNSTABLE,
+
+ @Json(name = "m.poll.undisclosed")
UNDISCLOSED
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
index 3bba2deae5..eaed9053ea 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
@@ -32,7 +32,6 @@ object RoomSummaryConstants {
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
EventType.STICKER,
- EventType.REACTION,
- EventType.POLL_START
- )
+ EventType.REACTION
+ ) + EventType.POLL_START
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 6f8bae876b..f9398ac7b8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -134,9 +134,9 @@ fun TimelineEvent.getEditedEventId(): String? {
*/
fun TimelineEvent.getLastMessageContent(): MessageContent? {
return when (root.getClearType()) {
- EventType.STICKER -> root.getClearContent().toModel()
- EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
- else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
+ EventType.STICKER -> root.getClearContent().toModel()
+ in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
+ else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
index 6152069644..46433f387d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
@@ -38,7 +38,7 @@ interface TimelineService {
/**
* Returns a snapshot of TimelineEvent event with eventId.
- * At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case.
+ * At the opposite of getTimelineEventLive which will be updated when local echo event is synced, it will return null in this case.
* @param eventId the eventId to get the TimelineEvent
*/
fun getTimelineEvent(eventId: String): TimelineEvent?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 0646e4d2b8..db44abc36f 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -434,6 +434,14 @@ internal class DefaultCryptoService @Inject constructor(
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
+
+ // unwedge if needed
+ try {
+ eventDecryptor.unwedgeDevicesIfNeeded()
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
+ }
+
// There is a limit of to_device events returned per sync.
// If we are in a case of such limited to_device sync we can't try to generate/upload
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
@@ -723,7 +731,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXCryptoError::class)
- override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return internalDecryptEvent(event, timeline)
}
@@ -746,7 +754,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return the MXEventDecryptionResult data, or null in case of error
*/
@Throws(MXCryptoError::class)
- private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return eventDecryptor.decryptEvent(event, timeline)
}
@@ -1364,6 +1372,9 @@ internal class DefaultCryptoService @Inject constructor(
@VisibleForTesting
val cryptoStoreForTesting = cryptoStore
+ @VisibleForTesting
+ val olmDeviceForTest = olmDevice
+
companion object {
const val CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS = 3_600_000 // one hour
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index 57381eacfb..00efd3d6a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -21,14 +21,13 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
-import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
-import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@@ -40,6 +39,8 @@ import javax.inject.Inject
private const val SEND_TO_DEVICE_RETRY_COUNT = 3
+private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
+
@SessionScope
internal class EventDecryptor @Inject constructor(
private val cryptoCoroutineScope: CoroutineScope,
@@ -47,13 +48,22 @@ internal class EventDecryptor @Inject constructor(
private val roomDecryptorProvider: RoomDecryptorProvider,
private val messageEncrypter: MessageEncrypter,
private val sendToDeviceTask: SendToDeviceTask,
+ private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val cryptoStore: IMXCryptoStore
) {
- // The date of the last time we forced establishment
- // of a new session for each user:device.
- private val lastNewSessionForcedDates = MXUsersDevicesMap()
+ /**
+ * Rate limit unwedge attempt, should we persist that?
+ */
+ private val lastNewSessionForcedDates = mutableMapOf()
+
+ data class WedgedDeviceInfo(
+ val userId: String,
+ val senderKey: String?
+ )
+
+ private val wedgedDevices = mutableListOf()
/**
* Decrypt an event
@@ -63,7 +73,7 @@ internal class EventDecryptor @Inject constructor(
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXCryptoError::class)
- fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return internalDecryptEvent(event, timeline)
}
@@ -91,38 +101,32 @@ internal class EventDecryptor @Inject constructor(
* @return the MXEventDecryptionResult data, or null in case of error
*/
@Throws(MXCryptoError::class)
- private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val eventContent = event.content
if (eventContent == null) {
- Timber.e("## CRYPTO | decryptEvent : empty event content")
+ Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
} else {
val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
- Timber.e("## CRYPTO | decryptEvent() : $reason")
+ Timber.tag(loggerTag.value).e("decryptEvent() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else {
try {
return alg.decryptEvent(event, timeline)
} catch (mxCryptoError: MXCryptoError) {
- Timber.v("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
+ Timber.tag(loggerTag.value).d("internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
if (algorithm == MXCRYPTO_ALGORITHM_OLM) {
if (mxCryptoError is MXCryptoError.Base &&
mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {
// need to find sending device
- cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- val olmContent = event.content.toModel()
- cryptoStore.getUserDevices(event.senderId ?: "")
- ?.values
- ?.firstOrNull { it.identityKey() == olmContent?.senderKey }
- ?.let {
- markOlmSessionForUnwedging(event.senderId ?: "", it)
- }
- ?: run {
- Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging")
- }
+ val olmContent = event.content.toModel()
+ if (event.senderId != null && olmContent?.senderKey != null) {
+ markOlmSessionForUnwedging(event.senderId, olmContent.senderKey)
+ } else {
+ Timber.tag(loggerTag.value).d("Can't mark as wedge malformed")
}
}
}
@@ -132,53 +136,91 @@ internal class EventDecryptor @Inject constructor(
}
}
- // coroutineDispatchers.crypto scope
- private fun markOlmSessionForUnwedging(senderId: String, deviceInfo: CryptoDeviceInfo) {
- val deviceKey = deviceInfo.identityKey()
+ private fun markOlmSessionForUnwedging(senderId: String, senderKey: String) {
+ val info = WedgedDeviceInfo(senderId, senderKey)
+ if (!wedgedDevices.contains(info)) {
+ Timber.tag(loggerTag.value).d("Marking device from $senderId key:$senderKey as wedged")
+ wedgedDevices.add(info)
+ }
+ }
- val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
+ // coroutineDispatchers.crypto scope
+ suspend fun unwedgeDevicesIfNeeded() {
+ // handle wedged devices
+ // Some olm decryption have failed and some device are wedged
+ // we should force start a new session for those
+ Timber.tag(loggerTag.value).v("Unwedging: ${wedgedDevices.size} are wedged")
+ // get the one that should be retried according to rate limit
val now = System.currentTimeMillis()
- if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
- Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
+ val toUnwedge = wedgedDevices.filter {
+ val lastForcedDate = lastNewSessionForcedDates[it] ?: 0
+ if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
+ Timber.tag(loggerTag.value).d("Unwedging, New session for $it already forced with device at $lastForcedDate")
+ return@filter false
+ }
+ // let's already mark that we tried now
+ lastNewSessionForcedDates[it] = now
+ true
+ }
+
+ if (toUnwedge.isEmpty()) {
+ Timber.tag(loggerTag.value).v("Nothing to unwedge")
return
}
+ Timber.tag(loggerTag.value).d("Unwedging, trying to create new session for ${toUnwedge.size} devices")
- Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
- lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
-
- // offload this from crypto thread (?)
- cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
- runCatching { ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) }.fold(
- onSuccess = { sendDummyToDevice(ensured = it, deviceInfo, senderId) },
- onFailure = {
- Timber.e("## CRYPTO | markOlmSessionForUnwedging() : failed to ensure device info ${senderId}${deviceInfo.deviceId}")
+ toUnwedge
+ .chunked(100) // safer to chunk if we ever have lots of wedged devices
+ .forEach { wedgedList ->
+ val groupedByUserId = wedgedList.groupBy { it.userId }
+ // lets download keys if needed
+ withContext(coroutineDispatchers.io) {
+ deviceListManager.downloadKeys(groupedByUserId.keys.toList(), false)
}
- )
- }
- }
- private suspend fun sendDummyToDevice(ensured: MXUsersDevicesMap, deviceInfo: CryptoDeviceInfo, senderId: String) {
- Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}")
+ // find the matching devices
+ groupedByUserId
+ .map { groupedByUser ->
+ val userId = groupedByUser.key
+ val wedgeSenderKeysForUser = groupedByUser.value.map { it.senderKey }
+ val knownDevices = cryptoStore.getUserDevices(userId)?.values.orEmpty()
+ userId to wedgeSenderKeysForUser.mapNotNull { senderKey ->
+ knownDevices.firstOrNull { it.identityKey() == senderKey }
+ }
+ }
+ .toMap()
+ .let { deviceList ->
+ try {
+ // force creating new outbound session and mark them as most recent to
+ // be used for next encryption (dummy)
+ val sessionToUse = ensureOlmSessionsForDevicesAction.handle(deviceList, true)
+ Timber.tag(loggerTag.value).d("Unwedging, found ${sessionToUse.map.size} to send dummy to")
- // Now send a blank message on that session so the other side knows about it.
- // (The keyshare request is sent in the clear so that won't do)
- // We send this first such that, as long as the toDevice messages arrive in the
- // same order we sent them, the other end will get this first, set up the new session,
- // then get the keyshare request and send the key over this new session (because it
- // is the session it has most recently received a message on).
- val payloadJson = mapOf("type" to EventType.DUMMY)
+ // Now send a dummy message on that session so the other side knows about it.
+ val payloadJson = mapOf(
+ "type" to EventType.DUMMY
+ )
+ val sendToDeviceMap = MXUsersDevicesMap()
+ sessionToUse.map.values
+ .flatMap { it.values }
+ .map { it.deviceInfo }
+ .forEach { deviceInfo ->
+ Timber.tag(loggerTag.value).v("encrypting dummy to ${deviceInfo.deviceId}")
+ val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
+ sendToDeviceMap.setObject(deviceInfo.userId, deviceInfo.deviceId, encodedPayload)
+ }
- val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
- val sendToDeviceMap = MXUsersDevicesMap()
- sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
- Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}")
- withContext(coroutineDispatchers.io) {
- val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
- try {
- sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT)
- } catch (failure: Throwable) {
- Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}")
- }
- }
+ // now let's send that
+ val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT)
+ }
+ } catch (failure: Throwable) {
+ deviceList.flatMap { it.value }.joinToString { it.shortDebugString() }.let {
+ Timber.tag(loggerTag.value).e(failure, "## Failed to unwedge devices: $it}")
+ }
+ }
+ }
+ }
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
index e7a46750b0..34bef61c98 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
@@ -19,8 +19,10 @@ package org.matrix.android.sdk.internal.crypto
import android.util.LruCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import timber.log.Timber
@@ -28,6 +30,13 @@ import java.util.Timer
import java.util.TimerTask
import javax.inject.Inject
+data class InboundGroupSessionHolder(
+ val wrapper: OlmInboundGroupSessionWrapper2,
+ val mutex: Mutex = Mutex()
+)
+
+private val loggerTag = LoggerTag("InboundGroupSessionStore", LoggerTag.CRYPTO)
+
/**
* Allows to cache and batch store operations on inbound group session store.
* Because it is used in the decrypt flow, that can be called quite rapidly
@@ -42,12 +51,13 @@ internal class InboundGroupSessionStore @Inject constructor(
val senderKey: String
)
- private val sessionCache = object : LruCache(30) {
- override fun entryRemoved(evicted: Boolean, key: CacheKey?, oldValue: OlmInboundGroupSessionWrapper2?, newValue: OlmInboundGroupSessionWrapper2?) {
- if (evicted && oldValue != null) {
+ private val sessionCache = object : LruCache(100) {
+ override fun entryRemoved(evicted: Boolean, key: CacheKey?, oldValue: InboundGroupSessionHolder?, newValue: InboundGroupSessionHolder?) {
+ if (oldValue != null) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- Timber.v("## Inbound: entryRemoved ${oldValue.roomId}-${oldValue.senderKey}")
- store.storeInboundGroupSessions(listOf(oldValue))
+ Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}")
+ store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper })
+ oldValue.wrapper.olmInboundGroupSession?.releaseSession()
}
}
}
@@ -59,27 +69,50 @@ internal class InboundGroupSessionStore @Inject constructor(
private val dirtySession = mutableListOf()
@Synchronized
- fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? {
- synchronized(sessionCache) {
- val known = sessionCache[CacheKey(sessionId, senderKey)]
- Timber.v("## Inbound: getInboundGroupSession in cache ${known != null}")
- return known ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
- Timber.v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
- sessionCache.put(CacheKey(sessionId, senderKey), it)
- }
- }
+ fun clear() {
+ sessionCache.evictAll()
}
@Synchronized
- fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2, sessionId: String, senderKey: String) {
- Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
+ fun getInboundGroupSession(sessionId: String, senderKey: String): InboundGroupSessionHolder? {
+ val known = sessionCache[CacheKey(sessionId, senderKey)]
+ Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession $sessionId in cache ${known != null}")
+ return known
+ ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
+ Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
+ sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
+ }?.let {
+ InboundGroupSessionHolder(it)
+ }
+ }
+
+ @Synchronized
+ fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
+ Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
+ dirtySession.remove(old.wrapper)
+ store.removeInboundGroupSession(sessionId, senderKey)
+ sessionCache.remove(CacheKey(sessionId, senderKey))
+
+ // release removed session
+ old.wrapper.olmInboundGroupSession?.releaseSession()
+
+ internalStoreGroupSession(new, sessionId, senderKey)
+ }
+
+ @Synchronized
+ fun storeInBoundGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
+ internalStoreGroupSession(holder, sessionId, senderKey)
+ }
+
+ private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
+ Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}")
// We want to batch this a bit for performances
- dirtySession.add(wrapper)
+ dirtySession.add(holder.wrapper)
if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
// first time seen, put it in memory cache while waiting for batch insert
// If it's already known, no need to update cache it's already there
- sessionCache.put(CacheKey(sessionId, senderKey), wrapper)
+ sessionCache.put(CacheKey(sessionId, senderKey), holder)
}
timerTask?.cancel()
@@ -96,7 +129,7 @@ internal class InboundGroupSessionStore @Inject constructor(
val toSave = mutableListOf().apply { addAll(dirtySession) }
dirtySession.clear()
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
- Timber.v("## Inbound: getInboundGroupSession batching save of ${dirtySession.size}")
+ Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}")
tryOrNull {
store.storeInboundGroupSessions(toSave)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index e1a706df79..501fb42db2 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -16,6 +16,11 @@
package org.matrix.android.sdk.internal.crypto
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
import org.matrix.android.sdk.api.util.JsonDict
@@ -40,6 +45,8 @@ import timber.log.Timber
import java.net.URLEncoder
import javax.inject.Inject
+private val loggerTag = LoggerTag("MXOlmDevice", LoggerTag.CRYPTO)
+
// The libolm wrapper.
@SessionScope
internal class MXOlmDevice @Inject constructor(
@@ -47,9 +54,12 @@ internal class MXOlmDevice @Inject constructor(
* The store where crypto data is saved.
*/
private val store: IMXCryptoStore,
+ private val olmSessionStore: OlmSessionStore,
private val inboundGroupSessionStore: InboundGroupSessionStore
) {
+ val mutex = Mutex()
+
/**
* @return the Curve25519 key for the account.
*/
@@ -93,26 +103,26 @@ internal class MXOlmDevice @Inject constructor(
try {
store.getOrCreateOlmAccount()
} catch (e: Exception) {
- Timber.e(e, "MXOlmDevice : cannot initialize olmAccount")
+ Timber.tag(loggerTag.value).e(e, "MXOlmDevice : cannot initialize olmAccount")
}
try {
olmUtility = OlmUtility()
} catch (e: Exception) {
- Timber.e(e, "## MXOlmDevice : OlmUtility failed with error")
+ Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : OlmUtility failed with error")
olmUtility = null
}
try {
- deviceCurve25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY]
+ deviceCurve25519Key = store.doWithOlmAccount { it.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY] }
} catch (e: Exception) {
- Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error")
+ Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_IDENTITY_KEY} with error")
}
try {
- deviceEd25519Key = store.getOlmAccount().identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY]
+ deviceEd25519Key = store.doWithOlmAccount { it.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY] }
} catch (e: Exception) {
- Timber.e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error")
+ Timber.tag(loggerTag.value).e(e, "## MXOlmDevice : cannot find ${OlmAccount.JSON_KEY_FINGER_PRINT_KEY} with error")
}
}
@@ -121,9 +131,9 @@ internal class MXOlmDevice @Inject constructor(
*/
fun getOneTimeKeys(): Map>? {
try {
- return store.getOlmAccount().oneTimeKeys()
+ return store.doWithOlmAccount { it.oneTimeKeys() }
} catch (e: Exception) {
- Timber.e(e, "## getOneTimeKeys() : failed")
+ Timber.tag(loggerTag.value).e(e, "## getOneTimeKeys() : failed")
}
return null
@@ -133,7 +143,7 @@ internal class MXOlmDevice @Inject constructor(
* @return The maximum number of one-time keys the olm account can store.
*/
fun getMaxNumberOfOneTimeKeys(): Long {
- return store.getOlmAccount().maxOneTimeKeys()
+ return store.doWithOlmAccount { it.maxOneTimeKeys() }
}
/**
@@ -143,9 +153,9 @@ internal class MXOlmDevice @Inject constructor(
*/
fun getFallbackKey(): MutableMap>? {
try {
- return store.getOlmAccount().fallbackKey()
+ return store.doWithOlmAccount { it.fallbackKey() }
} catch (e: Exception) {
- Timber.e("## getFallbackKey() : failed")
+ Timber.tag(loggerTag.value).e("## getFallbackKey() : failed")
}
return null
}
@@ -158,12 +168,14 @@ internal class MXOlmDevice @Inject constructor(
fun generateFallbackKeyIfNeeded(): Boolean {
try {
if (!hasUnpublishedFallbackKey()) {
- store.getOlmAccount().generateFallbackKey()
- store.saveOlmAccount()
+ store.doWithOlmAccount {
+ it.generateFallbackKey()
+ store.saveOlmAccount()
+ }
return true
}
} catch (e: Exception) {
- Timber.e("## generateFallbackKey() : failed")
+ Timber.tag(loggerTag.value).e("## generateFallbackKey() : failed")
}
return false
}
@@ -174,10 +186,12 @@ internal class MXOlmDevice @Inject constructor(
fun forgetFallbackKey() {
try {
- store.getOlmAccount().forgetFallbackKey()
- store.saveOlmAccount()
+ store.doWithOlmAccount {
+ it.forgetFallbackKey()
+ store.saveOlmAccount()
+ }
} catch (e: Exception) {
- Timber.e("## forgetFallbackKey() : failed")
+ Timber.tag(loggerTag.value).e("## forgetFallbackKey() : failed")
}
}
@@ -190,6 +204,8 @@ internal class MXOlmDevice @Inject constructor(
it.groupSession.releaseSession()
}
outboundGroupSessionCache.clear()
+ inboundGroupSessionStore.clear()
+ olmSessionStore.clear()
}
/**
@@ -200,9 +216,9 @@ internal class MXOlmDevice @Inject constructor(
*/
fun signMessage(message: String): String? {
try {
- return store.getOlmAccount().signMessage(message)
+ return store.doWithOlmAccount { it.signMessage(message) }
} catch (e: Exception) {
- Timber.e(e, "## signMessage() : failed")
+ Timber.tag(loggerTag.value).e(e, "## signMessage() : failed")
}
return null
@@ -213,10 +229,12 @@ internal class MXOlmDevice @Inject constructor(
*/
fun markKeysAsPublished() {
try {
- store.getOlmAccount().markOneTimeKeysAsPublished()
- store.saveOlmAccount()
+ store.doWithOlmAccount {
+ it.markOneTimeKeysAsPublished()
+ store.saveOlmAccount()
+ }
} catch (e: Exception) {
- Timber.e(e, "## markKeysAsPublished() : failed")
+ Timber.tag(loggerTag.value).e(e, "## markKeysAsPublished() : failed")
}
}
@@ -227,10 +245,12 @@ internal class MXOlmDevice @Inject constructor(
*/
fun generateOneTimeKeys(numKeys: Int) {
try {
- store.getOlmAccount().generateOneTimeKeys(numKeys)
- store.saveOlmAccount()
+ store.doWithOlmAccount {
+ it.generateOneTimeKeys(numKeys)
+ store.saveOlmAccount()
+ }
} catch (e: Exception) {
- Timber.e(e, "## generateOneTimeKeys() : failed")
+ Timber.tag(loggerTag.value).e(e, "## generateOneTimeKeys() : failed")
}
}
@@ -243,12 +263,14 @@ internal class MXOlmDevice @Inject constructor(
* @return the session id for the outbound session.
*/
fun createOutboundSession(theirIdentityKey: String, theirOneTimeKey: String): String? {
- Timber.v("## createOutboundSession() ; theirIdentityKey $theirIdentityKey theirOneTimeKey $theirOneTimeKey")
+ Timber.tag(loggerTag.value).d("## createOutboundSession() ; theirIdentityKey $theirIdentityKey theirOneTimeKey $theirOneTimeKey")
var olmSession: OlmSession? = null
try {
olmSession = OlmSession()
- olmSession.initOutboundSession(store.getOlmAccount(), theirIdentityKey, theirOneTimeKey)
+ store.doWithOlmAccount { olmAccount ->
+ olmSession.initOutboundSession(olmAccount, theirIdentityKey, theirOneTimeKey)
+ }
val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
@@ -257,14 +279,14 @@ internal class MXOlmDevice @Inject constructor(
// this session
olmSessionWrapper.onMessageReceived()
- store.storeSession(olmSessionWrapper, theirIdentityKey)
+ olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey)
val sessionIdentifier = olmSession.sessionIdentifier()
- Timber.v("## createOutboundSession() ; olmSession.sessionIdentifier: $sessionIdentifier")
+ Timber.tag(loggerTag.value).v("## createOutboundSession() ; olmSession.sessionIdentifier: $sessionIdentifier")
return sessionIdentifier
} catch (e: Exception) {
- Timber.e(e, "## createOutboundSession() failed")
+ Timber.tag(loggerTag.value).e(e, "## createOutboundSession() failed")
olmSession?.releaseSession()
}
@@ -281,34 +303,38 @@ internal class MXOlmDevice @Inject constructor(
* @return {{payload: string, session_id: string}} decrypted payload, and session id of new session.
*/
fun createInboundSession(theirDeviceIdentityKey: String, messageType: Int, ciphertext: String): Map? {
- Timber.v("## createInboundSession() : theirIdentityKey: $theirDeviceIdentityKey")
+ Timber.tag(loggerTag.value).d("## createInboundSession() : theirIdentityKey: $theirDeviceIdentityKey")
var olmSession: OlmSession? = null
try {
try {
olmSession = OlmSession()
- olmSession.initInboundSessionFrom(store.getOlmAccount(), theirDeviceIdentityKey, ciphertext)
+ store.doWithOlmAccount { olmAccount ->
+ olmSession.initInboundSessionFrom(olmAccount, theirDeviceIdentityKey, ciphertext)
+ }
} catch (e: Exception) {
- Timber.e(e, "## createInboundSession() : the session creation failed")
+ Timber.tag(loggerTag.value).e(e, "## createInboundSession() : the session creation failed")
return null
}
- Timber.v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}")
+ Timber.tag(loggerTag.value).v("## createInboundSession() : sessionId: ${olmSession.sessionIdentifier()}")
try {
- store.getOlmAccount().removeOneTimeKeys(olmSession)
- store.saveOlmAccount()
+ store.doWithOlmAccount { olmAccount ->
+ olmAccount.removeOneTimeKeys(olmSession)
+ store.saveOlmAccount()
+ }
} catch (e: Exception) {
- Timber.e(e, "## createInboundSession() : removeOneTimeKeys failed")
+ Timber.tag(loggerTag.value).e(e, "## createInboundSession() : removeOneTimeKeys failed")
}
- Timber.v("## createInboundSession() : ciphertext: $ciphertext")
+ Timber.tag(loggerTag.value).v("## createInboundSession() : ciphertext: $ciphertext")
try {
val sha256 = olmUtility!!.sha256(URLEncoder.encode(ciphertext, "utf-8"))
- Timber.v("## createInboundSession() :ciphertext: SHA256: $sha256")
+ Timber.tag(loggerTag.value).v("## createInboundSession() :ciphertext: SHA256: $sha256")
} catch (e: Exception) {
- Timber.e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
+ Timber.tag(loggerTag.value).e(e, "## createInboundSession() :ciphertext: cannot encode ciphertext")
}
val olmMessage = OlmMessage()
@@ -324,9 +350,9 @@ internal class MXOlmDevice @Inject constructor(
// This counts as a received message: set last received message time to now
olmSessionWrapper.onMessageReceived()
- store.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
+ olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
} catch (e: Exception) {
- Timber.e(e, "## createInboundSession() : decryptMessage failed")
+ Timber.tag(loggerTag.value).e(e, "## createInboundSession() : decryptMessage failed")
}
val res = HashMap()
@@ -343,7 +369,7 @@ internal class MXOlmDevice @Inject constructor(
return res
} catch (e: Exception) {
- Timber.e(e, "## createInboundSession() : OlmSession creation failed")
+ Timber.tag(loggerTag.value).e(e, "## createInboundSession() : OlmSession creation failed")
olmSession?.releaseSession()
}
@@ -357,8 +383,8 @@ internal class MXOlmDevice @Inject constructor(
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
* @return a list of known session ids for the device.
*/
- fun getSessionIds(theirDeviceIdentityKey: String): List? {
- return store.getDeviceSessionIds(theirDeviceIdentityKey)
+ fun getSessionIds(theirDeviceIdentityKey: String): List {
+ return olmSessionStore.getDeviceSessionIds(theirDeviceIdentityKey)
}
/**
@@ -368,7 +394,7 @@ internal class MXOlmDevice @Inject constructor(
* @return the session id, or null if no established session.
*/
fun getSessionId(theirDeviceIdentityKey: String): String? {
- return store.getLastUsedSessionId(theirDeviceIdentityKey)
+ return olmSessionStore.getLastUsedSessionId(theirDeviceIdentityKey)
}
/**
@@ -379,30 +405,30 @@ internal class MXOlmDevice @Inject constructor(
* @param payloadString the payload to be encrypted and sent
* @return the cipher text
*/
- fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map? {
- var res: MutableMap? = null
- val olmMessage: OlmMessage
+ suspend fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map? {
val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId)
if (olmSessionWrapper != null) {
try {
- Timber.v("## encryptMessage() : olmSession.sessionIdentifier: $sessionId")
- // Timber.v("## encryptMessage() : payloadString: " + payloadString);
+ Timber.tag(loggerTag.value).v("## encryptMessage() : olmSession.sessionIdentifier: $sessionId")
- olmMessage = olmSessionWrapper.olmSession.encryptMessage(payloadString)
- store.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
- res = HashMap()
-
- res["body"] = olmMessage.mCipherText
- res["type"] = olmMessage.mType
- } catch (e: Exception) {
- Timber.e(e, "## encryptMessage() : failed")
+ val olmMessage = olmSessionWrapper.mutex.withLock {
+ olmSessionWrapper.olmSession.encryptMessage(payloadString)
+ }
+ return mapOf(
+ "body" to olmMessage.mCipherText,
+ "type" to olmMessage.mType,
+ ).also {
+ olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
+ }
+ } catch (e: Throwable) {
+ Timber.tag(loggerTag.value).e(e, "## encryptMessage() : failed to encrypt olm with device|session:$theirDeviceIdentityKey|$sessionId")
+ return null
}
} else {
- Timber.e("## encryptMessage() : Failed to encrypt unknown session $sessionId")
+ Timber.tag(loggerTag.value).e("## encryptMessage() : Failed to encrypt unknown session $sessionId")
+ return null
}
-
- return res
}
/**
@@ -414,7 +440,8 @@ internal class MXOlmDevice @Inject constructor(
* @param sessionId the id of the active session.
* @return the decrypted payload.
*/
- fun decryptMessage(ciphertext: String, messageType: Int, sessionId: String, theirDeviceIdentityKey: String): String? {
+ @kotlin.jvm.Throws
+ suspend fun decryptMessage(ciphertext: String, messageType: Int, sessionId: String, theirDeviceIdentityKey: String): String? {
var payloadString: String? = null
val olmSessionWrapper = getSessionForDevice(theirDeviceIdentityKey, sessionId)
@@ -424,13 +451,13 @@ internal class MXOlmDevice @Inject constructor(
olmMessage.mCipherText = ciphertext
olmMessage.mType = messageType.toLong()
- try {
- payloadString = olmSessionWrapper.olmSession.decryptMessage(olmMessage)
- olmSessionWrapper.onMessageReceived()
- store.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
- } catch (e: Exception) {
- Timber.e(e, "## decryptMessage() : decryptMessage failed")
- }
+ payloadString =
+ olmSessionWrapper.mutex.withLock {
+ olmSessionWrapper.olmSession.decryptMessage(olmMessage).also {
+ olmSessionWrapper.onMessageReceived()
+ }
+ }
+ olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
}
return payloadString
@@ -469,7 +496,7 @@ internal class MXOlmDevice @Inject constructor(
store.storeCurrentOutboundGroupSessionForRoom(roomId, session)
return session.sessionIdentifier()
} catch (e: Exception) {
- Timber.e(e, "createOutboundGroupSession")
+ Timber.tag(loggerTag.value).e(e, "createOutboundGroupSession")
session?.releaseSession()
}
@@ -521,7 +548,7 @@ internal class MXOlmDevice @Inject constructor(
try {
return outboundGroupSessionCache[sessionId]!!.groupSession.sessionKey()
} catch (e: Exception) {
- Timber.e(e, "## getSessionKey() : failed")
+ Timber.tag(loggerTag.value).e(e, "## getSessionKey() : failed")
}
}
return null
@@ -550,8 +577,8 @@ internal class MXOlmDevice @Inject constructor(
if (sessionId.isNotEmpty() && payloadString.isNotEmpty()) {
try {
return outboundGroupSessionCache[sessionId]!!.groupSession.encryptMessage(payloadString)
- } catch (e: Exception) {
- Timber.e(e, "## encryptGroupMessage() : failed")
+ } catch (e: Throwable) {
+ Timber.tag(loggerTag.value).e(e, "## encryptGroupMessage() : failed")
}
}
return null
@@ -578,52 +605,64 @@ internal class MXOlmDevice @Inject constructor(
forwardingCurve25519KeyChain: List,
keysClaimed: Map,
exportFormat: Boolean): Boolean {
- val session = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
- runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
- .fold(
- {
- // If we already have this session, consider updating it
- Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
+ val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
+ val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
+ val existingSession = existingSessionHolder?.wrapper
+ // If we have an existing one we should check if the new one is not better
+ if (existingSession != null) {
+ Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
+ try {
+ val existingFirstKnown = existingSession.firstKnownIndex ?: return false.also {
+ // This is quite unexpected, could throw if native was released?
+ Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
+ candidateSession.olmInboundGroupSession?.releaseSession()
+ // Probably should discard it?
+ }
+ val newKnownFirstIndex = candidateSession.firstKnownIndex
+ // If our existing session is better we keep it
+ if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
+ Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
+ candidateSession.olmInboundGroupSession?.releaseSession()
+ return false
+ }
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
+ candidateSession.olmInboundGroupSession?.releaseSession()
+ return false
+ }
+ }
- val existingFirstKnown = it.firstKnownIndex!!
- val newKnownFirstIndex = session.firstKnownIndex
+ Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId")
- // If our existing session is better we keep it
- if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
- session.olmInboundGroupSession?.releaseSession()
- return false
- }
- },
- {
- // Nothing to do in case of error
- }
- )
-
- // sanity check
- if (null == session.olmInboundGroupSession) {
- Timber.e("## addInboundGroupSession : invalid session")
+ // sanity check on the new session
+ val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
+ if (null == candidateOlmInboundSession) {
+ Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session ")
return false
}
try {
- if (session.olmInboundGroupSession!!.sessionIdentifier() != sessionId) {
- Timber.e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
- session.olmInboundGroupSession!!.releaseSession()
+ if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
+ Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
+ candidateOlmInboundSession.releaseSession()
return false
}
- } catch (e: Exception) {
- session.olmInboundGroupSession?.releaseSession()
- Timber.e(e, "## addInboundGroupSession : sessionIdentifier() failed")
+ } catch (e: Throwable) {
+ candidateOlmInboundSession.releaseSession()
+ Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
return false
}
- session.senderKey = senderKey
- session.roomId = roomId
- session.keysClaimed = keysClaimed
- session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
+ candidateSession.senderKey = senderKey
+ candidateSession.roomId = roomId
+ candidateSession.keysClaimed = keysClaimed
+ candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
- inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
-// store.storeInboundGroupSessions(listOf(session))
+ if (existingSession != null) {
+ inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
+ } else {
+ inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
+ }
return true
}
@@ -638,57 +677,70 @@ internal class MXOlmDevice @Inject constructor(
val sessions = ArrayList(megolmSessionsData.size)
for (megolmSessionData in megolmSessionsData) {
- val sessionId = megolmSessionData.sessionId
- val senderKey = megolmSessionData.senderKey
+ val sessionId = megolmSessionData.sessionId ?: continue
+ val senderKey = megolmSessionData.senderKey ?: continue
val roomId = megolmSessionData.roomId
- var session: OlmInboundGroupSessionWrapper2? = null
+ var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null
try {
- session = OlmInboundGroupSessionWrapper2(megolmSessionData)
+ candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData)
} catch (e: Exception) {
- Timber.e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
+ Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
}
// sanity check
- if (session?.olmInboundGroupSession == null) {
- Timber.e("## importInboundGroupSession : invalid session")
+ if (candidateSessionToImport?.olmInboundGroupSession == null) {
+ Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session")
continue
}
+ val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession
try {
- if (session.olmInboundGroupSession?.sessionIdentifier() != sessionId) {
- Timber.e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
- if (session.olmInboundGroupSession != null) session.olmInboundGroupSession!!.releaseSession()
+ if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) {
+ Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
+ candidateOlmInboundGroupSession?.releaseSession()
continue
}
} catch (e: Exception) {
- Timber.e(e, "## importInboundGroupSession : sessionIdentifier() failed")
- session.olmInboundGroupSession!!.releaseSession()
+ Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed")
+ candidateOlmInboundGroupSession?.releaseSession()
continue
}
- runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
- .fold(
- {
- // If we already have this session, consider updating it
- Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
+ val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
+ val existingSession = existingSessionHolder?.wrapper
- // For now we just ignore updates. TODO: implement something here
- if (it.firstKnownIndex!! <= session.firstKnownIndex!!) {
- // Ignore this, keep existing
- session.olmInboundGroupSession!!.releaseSession()
- } else {
- sessions.add(session)
- }
- Unit
- },
- {
- // Session does not already exist, add it
- sessions.add(session)
- }
+ if (existingSession == null) {
+ // Session does not already exist, add it
+ Timber.tag(loggerTag.value).d("## importInboundGroupSession() : importing new megolm session $senderKey/$sessionId")
+ sessions.add(candidateSessionToImport)
+ } else {
+ Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
+ val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex }
+ val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex }
- )
+ if (existingFirstKnown == null || candidateFirstKnownIndex == null) {
+ // should not happen?
+ candidateSessionToImport.olmInboundGroupSession?.releaseSession()
+ Timber.tag(loggerTag.value)
+ .w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex")
+ } else {
+ if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) {
+ // Ignore this, keep existing
+ candidateOlmInboundGroupSession.releaseSession()
+ } else {
+ // update cache with better session
+ inboundGroupSessionStore.replaceGroupSession(
+ existingSessionHolder,
+ InboundGroupSessionHolder(candidateSessionToImport),
+ sessionId,
+ senderKey
+ )
+ sessions.add(candidateSessionToImport)
+ }
+ }
+ }
}
store.storeInboundGroupSessions(sessions)
@@ -696,18 +748,6 @@ internal class MXOlmDevice @Inject constructor(
return sessions
}
- /**
- * Remove an inbound group session
- *
- * @param sessionId the session identifier.
- * @param sessionKey base64-encoded secret key.
- */
- fun removeInboundGroupSession(sessionId: String?, sessionKey: String?) {
- if (null != sessionId && null != sessionKey) {
- store.removeInboundGroupSession(sessionId, sessionKey)
- }
- }
-
/**
* Decrypt a received message with an inbound group session.
*
@@ -719,19 +759,24 @@ internal class MXOlmDevice @Inject constructor(
* @return the decrypting result. Nil if the sessionId is unknown.
*/
@Throws(MXCryptoError::class)
- fun decryptGroupMessage(body: String,
- roomId: String,
- timeline: String?,
- sessionId: String,
- senderKey: String): OlmDecryptionResult {
- val session = getInboundGroupSession(sessionId, senderKey, roomId)
+ suspend fun decryptGroupMessage(body: String,
+ roomId: String,
+ timeline: String?,
+ sessionId: String,
+ senderKey: String): OlmDecryptionResult {
+ val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
+ val wrapper = sessionHolder.wrapper
+ val inboundGroupSession = wrapper.olmInboundGroupSession
+ ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
- if (roomId == session.roomId) {
+ if (roomId == wrapper.roomId) {
val decryptResult = try {
- session.olmInboundGroupSession!!.decryptMessage(body)
+ sessionHolder.mutex.withLock {
+ inboundGroupSession.decryptMessage(body)
+ }
} catch (e: OlmException) {
- Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
+ Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed")
throw MXCryptoError.OlmError(e)
}
@@ -742,32 +787,32 @@ internal class MXOlmDevice @Inject constructor(
if (timelineSet.contains(messageIndexKey)) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
- Timber.e("## decryptGroupMessage() : $reason")
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
}
timelineSet.add(messageIndexKey)
}
- inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
+ inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
- Timber.e("## decryptGroupMessage() : fails to parse the payload")
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}
return OlmDecryptionResult(
payload,
- session.keysClaimed,
+ wrapper.keysClaimed,
senderKey,
- session.forwardingCurve25519KeyChain
+ wrapper.forwardingCurve25519KeyChain
)
} else {
- val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
- Timber.e("## decryptGroupMessage() : $reason")
+ val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
}
}
@@ -819,7 +864,7 @@ internal class MXOlmDevice @Inject constructor(
private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? {
// sanity check
return if (theirDeviceIdentityKey.isEmpty() || sessionId.isEmpty()) null else {
- store.getDeviceSession(sessionId, theirDeviceIdentityKey)
+ olmSessionStore.getDeviceSession(sessionId, theirDeviceIdentityKey)
}
}
@@ -832,25 +877,26 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session.
*/
- fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper2 {
+ fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder {
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
}
- val session = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
+ val holder = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
+ val session = holder?.wrapper
if (session != null) {
// Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room.
if (roomId != session.roomId) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
- Timber.e("## getInboundGroupSession() : $errorDescription")
+ Timber.tag(loggerTag.value).e("## getInboundGroupSession() : $errorDescription")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
} else {
- return session
+ return holder
}
} else {
- Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
+ Timber.tag(loggerTag.value).w("## getInboundGroupSession() : UISI $sessionId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
}
}
@@ -866,4 +912,9 @@ internal class MXOlmDevice @Inject constructor(
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
}
+
+ @VisibleForTesting
+ fun clearOlmSessionCache() {
+ olmSessionStore.clear()
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
new file mode 100644
index 0000000000..f4fbca6a0f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto
+
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.olm.OlmSession
+import timber.log.Timber
+import javax.inject.Inject
+
+private val loggerTag = LoggerTag("OlmSessionStore", LoggerTag.CRYPTO)
+
+/**
+ * Keep the used olm session in memory and load them from the data layer when needed
+ * Access is synchronized for thread safety
+ */
+internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoStore) {
+ /**
+ * map of device key to list of olm sessions (it is possible to have several active sessions with a device)
+ */
+ private val olmSessions = HashMap>()
+
+ /**
+ * Store a session between our own device and another device.
+ * This will be called after the session has been created but also every time it has been used
+ * in order to persist the correct state for next run
+ * @param olmSessionWrapper the end-to-end session.
+ * @param deviceKey the public key of the other device.
+ */
+ @Synchronized
+ fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) {
+ // This could be a newly created session or one that was just created
+ // Anyhow we should persist ratchet state for future app lifecycle
+ addNewSessionInCache(olmSessionWrapper, deviceKey)
+ store.storeSession(olmSessionWrapper, deviceKey)
+ }
+
+ /**
+ * Get all the Olm Sessions we are sharing with the given device.
+ *
+ * @param deviceKey the public key of the other device.
+ * @return A set of sessionId, or empty if device is not known
+ */
+ @Synchronized
+ fun getDeviceSessionIds(deviceKey: String): List {
+ // we need to get the persisted ids first
+ val persistedKnownSessions = store.getDeviceSessionIds(deviceKey)
+ .orEmpty()
+ .toMutableList()
+ // Do we have some in cache not yet persisted?
+ olmSessions.getOrPut(deviceKey) { mutableListOf() }.forEach { cached ->
+ getSafeSessionIdentifier(cached.olmSession)?.let { cachedSessionId ->
+ if (!persistedKnownSessions.contains(cachedSessionId)) {
+ persistedKnownSessions.add(cachedSessionId)
+ }
+ }
+ }
+ return persistedKnownSessions
+ }
+
+ /**
+ * Retrieve an end-to-end session between our own device and another
+ * device.
+ *
+ * @param sessionId the session Id.
+ * @param deviceKey the public key of the other device.
+ * @return the session wrapper if found
+ */
+ @Synchronized
+ fun getDeviceSession(sessionId: String, deviceKey: String): OlmSessionWrapper? {
+ // get from cache or load and add to cache
+ return internalGetSession(sessionId, deviceKey)
+ }
+
+ /**
+ * Retrieve the last used sessionId, regarding `lastReceivedMessageTs`, or null if no session exist
+ *
+ * @param deviceKey the public key of the other device.
+ * @return last used sessionId, or null if not found
+ */
+ @Synchronized
+ fun getLastUsedSessionId(deviceKey: String): String? {
+ // We want to avoid to load in memory old session if possible
+ val lastPersistedUsedSession = store.getLastUsedSessionId(deviceKey)
+ var candidate = lastPersistedUsedSession?.let { internalGetSession(it, deviceKey) }
+ // we should check if we have one in cache with a higher last message received?
+ olmSessions[deviceKey].orEmpty().forEach { inCache ->
+ if (inCache.lastReceivedMessageTs > (candidate?.lastReceivedMessageTs ?: 0L)) {
+ candidate = inCache
+ }
+ }
+
+ return candidate?.olmSession?.sessionIdentifier()
+ }
+
+ /**
+ * Release all sessions and clear cache
+ */
+ @Synchronized
+ fun clear() {
+ olmSessions.entries.onEach { entry ->
+ entry.value.onEach { it.olmSession.releaseSession() }
+ }
+ olmSessions.clear()
+ }
+
+ private fun internalGetSession(sessionId: String, deviceKey: String): OlmSessionWrapper? {
+ return getSessionInCache(sessionId, deviceKey)
+ ?: // deserialize from store
+ return store.getDeviceSession(sessionId, deviceKey)?.also {
+ addNewSessionInCache(it, deviceKey)
+ }
+ }
+
+ private fun getSessionInCache(sessionId: String, deviceKey: String): OlmSessionWrapper? {
+ return olmSessions[deviceKey]?.firstOrNull {
+ getSafeSessionIdentifier(it.olmSession) == sessionId
+ }
+ }
+
+ private fun getSafeSessionIdentifier(session: OlmSession): String? {
+ return try {
+ session.sessionIdentifier()
+ } catch (throwable: Throwable) {
+ Timber.tag(loggerTag.value).w("Failed to load sessionId from loaded olm session")
+ null
+ }
+ }
+
+ private fun addNewSessionInCache(session: OlmSessionWrapper, deviceKey: String) {
+ val sessionId = getSafeSessionIdentifier(session.olmSession) ?: return
+ olmSessions.getOrPut(deviceKey) { mutableListOf() }.let {
+ val existing = it.firstOrNull { getSafeSessionIdentifier(it.olmSession) == sessionId }
+ it.add(session)
+ // remove and release if was there but with different instance
+ if (existing != null && existing.olmSession != session.olmSession) {
+ // mm not sure when this could happen
+ // anyhow we should remove and release the one known
+ it.remove(existing)
+ existing.olmSession.releaseSession()
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
index ab2ed04dfb..87c176612d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt
@@ -16,14 +16,18 @@
package org.matrix.android.sdk.internal.crypto.actions
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.internal.crypto.model.toDebugString
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
+import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject
@@ -31,90 +35,90 @@ private const val ONE_TIME_KEYS_RETRY_COUNT = 3
private val loggerTag = LoggerTag("EnsureOlmSessionsForDevicesAction", LoggerTag.CRYPTO)
+@SessionScope
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
private val olmDevice: MXOlmDevice,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
+ private val ensureMutex = Mutex()
+
+ /**
+ * We want to synchronize a bit here, because we are iterating to check existing olm session and
+ * also adding some
+ */
suspend fun handle(devicesByUser: Map>, force: Boolean = false): MXUsersDevicesMap {
- val devicesWithoutSession = ArrayList()
+ ensureMutex.withLock {
+ val results = MXUsersDevicesMap()
+ val deviceList = devicesByUser.flatMap { it.value }
+ Timber.tag(loggerTag.value)
+ .d("ensure olm forced:$force for ${deviceList.joinToString { it.shortDebugString() }}")
+ val devicesToCreateSessionWith = mutableListOf()
+ if (force) {
+ // we take all devices and will query otk for them
+ devicesToCreateSessionWith.addAll(deviceList)
+ } else {
+ // only peek devices without active session
+ deviceList.forEach { deviceInfo ->
+ val deviceId = deviceInfo.deviceId
+ val userId = deviceInfo.userId
+ val key = deviceInfo.identityKey() ?: return@forEach Unit.also {
+ Timber.tag(loggerTag.value).w("Ignoring device ${deviceInfo.shortDebugString()} without identity key")
+ }
- val results = MXUsersDevicesMap()
-
- for ((userId, deviceList) in devicesByUser) {
- for (deviceInfo in deviceList) {
- val deviceId = deviceInfo.deviceId
- val key = deviceInfo.identityKey()
- if (key == null) {
- Timber.w("## CRYPTO | Ignoring device (${deviceInfo.userId}|$deviceId) without identity key")
- continue
- }
-
- val sessionId = olmDevice.getSessionId(key)
-
- if (sessionId.isNullOrEmpty() || force) {
- Timber.tag(loggerTag.value).d("Found no existing olm session (${deviceInfo.userId}|$deviceId) (force=$force)")
- devicesWithoutSession.add(deviceInfo)
- } else {
- Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
- }
-
- val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
- results.setObject(userId, deviceId, olmSessionResult)
- }
- }
-
- Timber.tag(loggerTag.value).d("Devices without olm session (count:${devicesWithoutSession.size}) :" +
- " ${devicesWithoutSession.joinToString { "${it.userId}|${it.deviceId}" }}")
- if (devicesWithoutSession.size == 0) {
- return results
- }
-
- // Prepare the request for claiming one-time keys
- val usersDevicesToClaim = MXUsersDevicesMap()
-
- val oneTimeKeyAlgorithm = MXKey.KEY_SIGNED_CURVE_25519_TYPE
-
- for (device in devicesWithoutSession) {
- usersDevicesToClaim.setObject(device.userId, device.deviceId, oneTimeKeyAlgorithm)
- }
-
- // TODO: this has a race condition - if we try to send another message
- // while we are claiming a key, we will end up claiming two and setting up
- // two sessions.
- //
- // That should eventually resolve itself, but it's poor form.
-
- Timber.tag(loggerTag.value).i("claimOneTimeKeysForUsersDevices() : ${usersDevicesToClaim.toDebugString()}")
-
- val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
- val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT)
- Timber.tag(loggerTag.value).v("claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
- for ((userId, deviceInfos) in devicesByUser) {
- for (deviceInfo in deviceInfos) {
- var oneTimeKey: MXKey? = null
- val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
- if (null != deviceIds) {
- for (deviceId in deviceIds) {
- val olmSessionResult = results.getObject(userId, deviceId)
- if (olmSessionResult?.sessionId != null && !force) {
- // We already have a result for this device
- continue
- }
- val key = oneTimeKeys.getObject(userId, deviceId)
- if (key?.type == oneTimeKeyAlgorithm) {
- oneTimeKey = key
- }
- if (oneTimeKey == null) {
- Timber.tag(loggerTag.value).d("No one time key for $userId|$deviceId")
- continue
- }
- // Update the result for this device in results
- olmSessionResult?.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
+ // is there a session that as been already used?
+ val sessionId = olmDevice.getSessionId(key)
+ if (sessionId.isNullOrEmpty()) {
+ Timber.tag(loggerTag.value).d("Found no existing olm session ${deviceInfo.shortDebugString()} add to claim list")
+ devicesToCreateSessionWith.add(deviceInfo)
+ } else {
+ Timber.tag(loggerTag.value).d("using olm session $sessionId for (${deviceInfo.userId}|$deviceId)")
+ val olmSessionResult = MXOlmSessionResult(deviceInfo, sessionId)
+ results.setObject(userId, deviceId, olmSessionResult)
}
}
}
+
+ if (devicesToCreateSessionWith.isEmpty()) {
+ // no session to create
+ return results
+ }
+ val usersDevicesToClaim = MXUsersDevicesMap().apply {
+ devicesToCreateSessionWith.forEach {
+ setObject(it.userId, it.deviceId, MXKey.KEY_SIGNED_CURVE_25519_TYPE)
+ }
+ }
+
+ // Let's now claim one time keys
+ val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
+ val oneTimeKeys = withContext(coroutineDispatchers.io) {
+ oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
+ }
+
+ // let now start olm session using the new otks
+ devicesToCreateSessionWith.forEach { deviceInfo ->
+ val userId = deviceInfo.userId
+ val deviceId = deviceInfo.deviceId
+ // Did we get an OTK
+ val oneTimeKey = oneTimeKeys.getObject(userId, deviceId)
+ if (oneTimeKey == null) {
+ Timber.tag(loggerTag.value).d("No otk for ${deviceInfo.shortDebugString()}")
+ } else if (oneTimeKey.type != MXKey.KEY_SIGNED_CURVE_25519_TYPE) {
+ Timber.tag(loggerTag.value).d("Bad otk type (${oneTimeKey.type}) for ${deviceInfo.shortDebugString()}")
+ } else {
+ val olmSessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
+ if (olmSessionId != null) {
+ val olmSessionResult = MXOlmSessionResult(deviceInfo, olmSessionId)
+ results.setObject(userId, deviceId, olmSessionResult)
+ } else {
+ Timber
+ .tag(loggerTag.value)
+ .d("## CRYPTO | cant unwedge failed to create outbound ${deviceInfo.shortDebugString()}")
+ }
+ }
+ }
+ return results
}
- return results
}
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt
index 165f200bac..4e158602c8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto.actions
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_OLM
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
@@ -28,6 +29,8 @@ import org.matrix.android.sdk.internal.util.convertToUTF8
import timber.log.Timber
import javax.inject.Inject
+private val loggerTag = LoggerTag("MessageEncrypter", LoggerTag.CRYPTO)
+
internal class MessageEncrypter @Inject constructor(
@UserId
private val userId: String,
@@ -42,7 +45,7 @@ internal class MessageEncrypter @Inject constructor(
* @param deviceInfos list of device infos to encrypt for.
* @return the content for an m.room.encrypted event.
*/
- fun encryptMessage(payloadFields: Content, deviceInfos: List): EncryptedMessage {
+ suspend fun encryptMessage(payloadFields: Content, deviceInfos: List): EncryptedMessage {
val deviceInfoParticipantKey = deviceInfos.associateBy { it.identityKey()!! }
val payloadJson = payloadFields.toMutableMap()
@@ -66,7 +69,7 @@ internal class MessageEncrypter @Inject constructor(
val sessionId = olmDevice.getSessionId(deviceKey)
if (!sessionId.isNullOrEmpty()) {
- Timber.v("Using sessionid $sessionId for device $deviceKey")
+ Timber.tag(loggerTag.value).d("Using sessionid $sessionId for device $deviceKey")
payloadJson["recipient"] = deviceInfo.userId
payloadJson["recipient_keys"] = mapOf("ed25519" to deviceInfo.fingerprint()!!)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
index 79c7608cbf..b6c1d99aa5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
@@ -36,7 +36,7 @@ internal interface IMXDecrypting {
* @return the decryption information, or an error
*/
@Throws(MXCryptoError::class)
- fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
+ suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
/**
* Handle a key event.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt
index 1fd5061a65..6f488def0a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt
@@ -45,7 +45,7 @@ internal interface IMXGroupEncryption {
*
* @return true in case of success
*/
- suspend fun reshareKey(sessionId: String,
+ suspend fun reshareKey(groupSessionId: String,
userId: String,
deviceId: String,
senderKey: String): Boolean
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index 2ee24dfbb0..e94daa0e76 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@@ -71,7 +72,7 @@ internal class MXMegolmDecryption(private val userId: String,
// private var pendingEvents: MutableMap>> = HashMap()
@Throws(MXCryptoError::class)
- override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
// If cross signing is enabled, we don't send request until the keys are trusted
// There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
@@ -79,7 +80,7 @@ internal class MXMegolmDecryption(private val userId: String,
}
@Throws(MXCryptoError::class)
- private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
+ private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
Timber.tag(loggerTag.value).v("decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
@@ -345,7 +346,22 @@ internal class MXMegolmDecryption(private val userId: String,
return
}
val userId = request.userId ?: return
+
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
+ val body = request.requestBody
+ val sessionHolder = try {
+ olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice: failed to get session for request $body")
+ return@launch
+ }
+
+ val export = sessionHolder.mutex.withLock {
+ sessionHolder.wrapper.exportKeys()
+ } ?: return@launch Unit.also {
+ Timber.tag(loggerTag.value).e("shareKeysWithDevice: failed to export group session ${body.sessionId}")
+ }
+
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.mapCatching {
val deviceId = request.deviceId
@@ -355,7 +371,6 @@ internal class MXMegolmDecryption(private val userId: String,
} else {
val devicesByUser = mapOf(userId to listOf(deviceInfo))
val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
- val body = request.requestBody
val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
@@ -365,19 +380,10 @@ internal class MXMegolmDecryption(private val userId: String,
}
Timber.tag(loggerTag.value).i("shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId")
- val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY)
- runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) }
- .fold(
- {
- // TODO
- payloadJson["content"] = it.exportKeys() ?: ""
- },
- {
- // TODO
- Timber.tag(loggerTag.value).e(it, "shareKeysWithDevice: failed to get session for request $body")
- }
-
- )
+ val payloadJson = mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to export
+ )
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index 389036a1f8..cf9733dc2d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@@ -88,7 +90,7 @@ internal class MXMegolmEncryption(
Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
val devices = getDevicesInRoom(userIds)
Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
- Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
+ Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
val outboundSession = ensureOutboundSession(devices.allowedDevices)
return encryptContent(outboundSession, eventType, eventContent)
@@ -142,8 +144,9 @@ internal class MXMegolmEncryption(
Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() ")
val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
- val keysClaimedMap = HashMap()
- keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!
+ val keysClaimedMap = mapOf(
+ "ed25519" to olmDevice.deviceEd25519Key!!
+ )
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
emptyList(), keysClaimedMap, false)
@@ -303,11 +306,13 @@ internal class MXMegolmEncryption(
Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
try {
- sendToDeviceTask.execute(sendToDeviceParams)
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.execute(sendToDeviceParams)
+ }
Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
} catch (failure: Throwable) {
// What to do here...
- Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
+ Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>")
}
} else {
Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key")
@@ -346,9 +351,12 @@ internal class MXMegolmEncryption(
}
)
try {
- sendToDeviceTask.execute(params)
+ withContext(coroutineDispatchers.io) {
+ sendToDeviceTask.execute(params)
+ }
} catch (failure: Throwable) {
- Timber.tag(loggerTag.value).e("notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
+ Timber.tag(loggerTag.value)
+ .e("notifyKeyWithHeld() :$sessionId Failed to send withheld ${targets.map { "${it.userId}|${it.deviceId}" }}")
}
}
@@ -432,20 +440,20 @@ internal class MXMegolmEncryption(
}
}
- override suspend fun reshareKey(sessionId: String,
+ override suspend fun reshareKey(groupSessionId: String,
userId: String,
deviceId: String,
senderKey: String): Boolean {
- Timber.tag(loggerTag.value).i("process reshareKey for $sessionId to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("process reshareKey for $groupSessionId to $userId:$deviceId")
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false
.also { Timber.tag(loggerTag.value).w("reshareKey: Device not found") }
// Get the chain index of the key we previously sent this device
- val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, deviceInfo)
+ val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, groupSessionId, deviceInfo)
if (!wasSessionSharedWithUser.found) {
// This session was never shared with this user
// Send a room key with held
- notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
+ notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), groupSessionId, senderKey, WithHeldCode.UNAUTHORISED)
Timber.tag(loggerTag.value).w("reshareKey: ERROR : Never shared megolm with this device")
return false
}
@@ -456,42 +464,47 @@ internal class MXMegolmEncryption(
}
val devicesByUser = mapOf(userId to listOf(deviceInfo))
- val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
- val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
- olmSessionResult?.sessionId // no session with this device, probably because there were no one-time keys.
- // ensureOlmSessionsForDevicesAction has already done the logging, so just skip it.
- ?: return false.also {
- Timber.tag(loggerTag.value).w("reshareKey: no session with this device, probably because there were no one-time keys")
- }
+ val usersDeviceMap = try {
+ ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+ } catch (failure: Throwable) {
+ null
+ }
+ val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId)
+ if (olmSessionResult?.sessionId == null) {
+ Timber.tag(loggerTag.value).w("reshareKey: no session with this device, probably because there were no one-time keys")
+ return false
+ }
+ Timber.tag(loggerTag.value).i(" reshareKey: $groupSessionId:$chainIndex with device $userId:$deviceId using session ${olmSessionResult.sessionId}")
- Timber.tag(loggerTag.value).i(" reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId")
+ val sessionHolder = try {
+ olmDevice.getInboundGroupSession(groupSessionId, senderKey, roomId)
+ } catch (failure: Throwable) {
+ Timber.tag(loggerTag.value).e(failure, "shareKeysWithDevice: failed to get session $groupSessionId")
+ return false
+ }
- val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY)
+ val export = sessionHolder.mutex.withLock {
+ sessionHolder.wrapper.exportKeys()
+ } ?: return false.also {
+ Timber.tag(loggerTag.value).e("shareKeysWithDevice: failed to export group session $groupSessionId")
+ }
- runCatching { olmDevice.getInboundGroupSession(sessionId, senderKey, roomId) }
- .fold(
- {
- // TODO
- payloadJson["content"] = it.exportKeys(chainIndex.toLong()) ?: ""
- },
- {
- // TODO
- Timber.tag(loggerTag.value).e(it, "reshareKey: failed to get session $sessionId|$senderKey|$roomId")
- }
-
- )
+ val payloadJson = mapOf(
+ "type" to EventType.FORWARDED_ROOM_KEY,
+ "content" to export
+ )
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
- Timber.tag(loggerTag.value).i("reshareKey() : sending session $sessionId to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("reshareKey() : sending session $groupSessionId to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
return try {
sendToDeviceTask.execute(sendToDeviceParams)
- Timber.tag(loggerTag.value).i("reshareKey() : successfully send <$sessionId> to $userId:$deviceId")
+ Timber.tag(loggerTag.value).i("reshareKey() : successfully send <$groupSessionId> to $userId:$deviceId")
true
} catch (failure: Throwable) {
- Timber.tag(loggerTag.value).e(failure, "reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
+ Timber.tag(loggerTag.value).e(failure, "reshareKey() : fail to send <$groupSessionId> to $userId:$deviceId")
false
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
index f1bca4fbc6..afa249801d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.internal.crypto.algorithms.olm
+import kotlinx.coroutines.sync.withLock
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -30,6 +32,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.convertFromUTF8
import timber.log.Timber
+private val loggerTag = LoggerTag("MXOlmDecryption", LoggerTag.CRYPTO)
internal class MXOlmDecryption(
// The olm device interface
private val olmDevice: MXOlmDevice,
@@ -38,27 +41,27 @@ internal class MXOlmDecryption(
IMXDecrypting {
@Throws(MXCryptoError::class)
- override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
+ override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val olmEventContent = event.content.toModel() ?: run {
- Timber.e("## decryptEvent() : bad event format")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : bad event format")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
}
val cipherText = olmEventContent.ciphertext ?: run {
- Timber.e("## decryptEvent() : missing cipher text")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : missing cipher text")
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
}
val senderKey = olmEventContent.senderKey ?: run {
- Timber.e("## decryptEvent() : missing sender key")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : missing sender key")
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
}
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
- Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
}
@@ -69,7 +72,7 @@ internal class MXOlmDecryption(
val decryptedPayload = decryptMessage(message, senderKey)
if (decryptedPayload == null) {
- Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
+ Timber.tag(loggerTag.value).e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
}
val payloadString = convertFromUTF8(decryptedPayload)
@@ -78,30 +81,30 @@ internal class MXOlmDecryption(
val payload = adapter.fromJson(payloadString)
if (payload == null) {
- Timber.e("## decryptEvent failed : null payload")
+ Timber.tag(loggerTag.value).e("## decryptEvent failed : null payload")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)
}
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
- Timber.e("## decryptEvent() : bad olmPayloadContent format")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : bad olmPayloadContent format")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}
if (olmPayloadContent.recipient.isNullOrBlank()) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
- Timber.e("## decryptEvent() : $reason")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
}
if (olmPayloadContent.recipient != userId) {
- Timber.e("## decryptEvent() : Event ${event.eventId}:" +
+ Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}:" +
" Intended recipient ${olmPayloadContent.recipient} does not match our id $userId")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
}
val recipientKeys = olmPayloadContent.recipientKeys ?: run {
- Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
+ Timber.tag(loggerTag.value).e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys'" +
" property; cannot prevent unknown-key attack")
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
@@ -110,31 +113,34 @@ internal class MXOlmDecryption(
val ed25519 = recipientKeys["ed25519"]
if (ed25519 != olmDevice.deviceEd25519Key) {
- Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
+ Timber.tag(loggerTag.value).e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.BAD_RECIPIENT_KEY_REASON)
}
if (olmPayloadContent.sender.isNullOrBlank()) {
- Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
+ Timber.tag(loggerTag.value)
+ .e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
}
if (olmPayloadContent.sender != event.senderId) {
- Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
+ Timber.tag(loggerTag.value)
+ .e("Event ${event.eventId}: sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
}
if (olmPayloadContent.roomId != event.roomId) {
- Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
+ Timber.tag(loggerTag.value)
+ .e("## decryptEvent() : Event ${event.eventId}: room ${olmPayloadContent.roomId} does not match reported room ${event.roomId}")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.roomId))
}
val keys = olmPayloadContent.keys ?: run {
- Timber.e("## decryptEvent failed : null keys")
+ Timber.tag(loggerTag.value).e("## decryptEvent failed : null keys")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)
}
@@ -153,8 +159,8 @@ internal class MXOlmDecryption(
* @param message message object, with 'type' and 'body' fields.
* @return payload, if decrypted successfully.
*/
- private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
- val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey).orEmpty()
+ private suspend fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
+ val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey)
val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) {
@@ -166,11 +172,32 @@ internal class MXOlmDecryption(
// Try each session in turn
// decryptionErrors = {};
+
+ val isPreKey = messageType == 0
+ // we want to synchronize on prekey if not we could end up create two olm sessions
+ // Not very clear but it looks like the js-sdk for consistency
+ return if (isPreKey) {
+ olmDevice.mutex.withLock {
+ reallyDecryptMessage(sessionIds, messageBody, messageType, theirDeviceIdentityKey)
+ }
+ } else {
+ reallyDecryptMessage(sessionIds, messageBody, messageType, theirDeviceIdentityKey)
+ }
+ }
+
+ private suspend fun reallyDecryptMessage(sessionIds: List, messageBody: String, messageType: Int, theirDeviceIdentityKey: String): String? {
+ Timber.tag(loggerTag.value).d("decryptMessage() try to decrypt olm message type:$messageType from ${sessionIds.size} known sessions")
for (sessionId in sessionIds) {
- val payload = olmDevice.decryptMessage(messageBody, messageType, sessionId, theirDeviceIdentityKey)
+ val payload = try {
+ olmDevice.decryptMessage(messageBody, messageType, sessionId, theirDeviceIdentityKey)
+ } catch (throwable: Exception) {
+ // As we are trying one by one, we don't really care of the error here
+ Timber.tag(loggerTag.value).d("decryptMessage() failed with session $sessionId")
+ null
+ }
if (null != payload) {
- Timber.v("## decryptMessage() : Decrypted Olm message from $theirDeviceIdentityKey with session $sessionId")
+ Timber.tag(loggerTag.value).v("## decryptMessage() : Decrypted Olm message from $theirDeviceIdentityKey with session $sessionId")
return payload
} else {
val foundSession = olmDevice.matchesSession(theirDeviceIdentityKey, sessionId, messageType, messageBody)
@@ -178,7 +205,7 @@ internal class MXOlmDecryption(
if (foundSession) {
// Decryption failed, but it was a prekey message matching this
// session, so it should have worked.
- Timber.e("## decryptMessage() : Error decrypting prekey message with existing session id $sessionId:TODO")
+ Timber.tag(loggerTag.value).e("## decryptMessage() : Error decrypting prekey message with existing session id $sessionId:TODO")
return null
}
}
@@ -189,9 +216,9 @@ internal class MXOlmDecryption(
// didn't work.
if (sessionIds.isEmpty()) {
- Timber.e("## decryptMessage() : No existing sessions")
+ Timber.tag(loggerTag.value).e("## decryptMessage() : No existing sessions")
} else {
- Timber.e("## decryptMessage() : Error decrypting non-prekey message with existing sessions")
+ Timber.tag(loggerTag.value).e("## decryptMessage() : Error decrypting non-prekey message with existing sessions")
}
return null
@@ -199,14 +226,17 @@ internal class MXOlmDecryption(
// prekey message which doesn't match any existing sessions: make a new
// session.
+ // XXXX Possible races here? if concurrent access for same prekey message, we might create 2 sessions?
+ Timber.tag(loggerTag.value).d("## decryptMessage() : Create inbound group session from prekey sender:$theirDeviceIdentityKey")
+
val res = olmDevice.createInboundSession(theirDeviceIdentityKey, messageType, messageBody)
if (null == res) {
- Timber.e("## decryptMessage() : Error decrypting non-prekey message with existing sessions")
+ Timber.tag(loggerTag.value).e("## decryptMessage() : Error decrypting non-prekey message with existing sessions")
return null
}
- Timber.v("## decryptMessage() : Created new inbound Olm session get id ${res["session_id"]} with $theirDeviceIdentityKey")
+ Timber.tag(loggerTag.value).v("## decryptMessage() : Created new inbound Olm session get id ${res["session_id"]} with $theirDeviceIdentityKey")
return res["payload"]
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt
index 5cd647ff6f..9325355d28 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt
@@ -96,7 +96,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
if (userList.isNotEmpty()) {
// Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user,
// or a new device?) So we check all again :/
- Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}")
+ Timber.v("## CrossSigning - Updating trust for users: ${userList.logLimit()}")
updateTrust(userList)
}
@@ -148,7 +148,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
myUserId -> myTrustResult
else -> {
crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also {
- Timber.d("## CrossSigning - user:${entry.key} result:$it")
+ Timber.v("## CrossSigning - user:${entry.key} result:$it")
}
}
}
@@ -178,7 +178,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
// Update trust if needed
devicesEntities?.forEach { device ->
val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified()
- Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}")
+ Timber.v("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}")
if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) {
Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified")
// need to save
@@ -216,7 +216,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
.equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true)
.findFirst()
?.let { roomSummary ->
- Timber.d("## CrossSigning - Check shield state for room $roomId")
+ Timber.v("## CrossSigning - Check shield state for room $roomId")
val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds()
try {
val updatedTrust = computeRoomShield(
@@ -277,7 +277,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
cryptoRealm: Realm,
activeMemberUserIds: List,
roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel {
- Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}")
+ Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}")
// The set of “all users” depends on the type of room:
// For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room
// For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
index b20168eaa3..954c2dbe43 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
@@ -671,7 +671,6 @@ internal class DefaultKeysBackupService @Inject constructor(
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
throw InvalidParameterException("Invalid recovery key")
}
-
// Get a PK decryption instance
pkDecryptionFromRecoveryKey(recoveryKey)
}
@@ -681,6 +680,10 @@ internal class DefaultKeysBackupService @Inject constructor(
throw InvalidParameterException("Invalid recovery key")
}
+ // Save for next time and for gossiping
+ // Save now as it's valid, don't wait for the import as it could take long.
+ saveBackupRecoveryKey(recoveryKey, keysVersionResult.version)
+
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
// Get backed up keys from the homeserver
@@ -729,8 +732,6 @@ internal class DefaultKeysBackupService @Inject constructor(
if (backUp) {
maybeBackupKeys()
}
- // Save for next time and for gossiping
- saveBackupRecoveryKey(recoveryKey, keysVersionResult.version)
result
}
}.foldToCallback(callback)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
index 5e7744853a..b3638dc414 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
@@ -70,6 +70,8 @@ data class CryptoDeviceInfo(
keys?.let { map["keys"] = it }
return map
}
+
+ fun shortDebugString() = "$userId|$deviceId"
}
internal fun CryptoDeviceInfo.toRest(): DeviceKeys {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
index 15b92f105a..263cb3b036 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto.model
+import kotlinx.coroutines.sync.Mutex
import org.matrix.olm.OlmSession
/**
@@ -25,7 +26,10 @@ data class OlmSessionWrapper(
// The associated olm session.
val olmSession: OlmSession,
// Timestamp at which the session last received a message.
- var lastReceivedMessageTs: Long = 0) {
+ var lastReceivedMessageTs: Long = 0,
+
+ val mutex: Mutex = Mutex()
+) {
/**
* Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index 96ea5c03fa..e662ff74e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -54,7 +54,7 @@ internal interface IMXCryptoStore {
/**
* @return the olm account
*/
- fun getOlmAccount(): OlmAccount
+ fun doWithOlmAccount(block: (OlmAccount) -> T): T
fun getOrCreateOlmAccount(): OlmAccount
@@ -261,7 +261,7 @@ internal interface IMXCryptoStore {
fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String)
/**
- * Retrieve the end-to-end session ids between the logged-in user and another
+ * Retrieve all end-to-end session ids between our own device and another
* device.
*
* @param deviceKey the public key of the other device.
@@ -270,7 +270,7 @@ internal interface IMXCryptoStore {
fun getDeviceSessionIds(deviceKey: String): List?
/**
- * Retrieve an end-to-end session between the logged-in user and another
+ * Retrieve an end-to-end session between our own device and another
* device.
*
* @param sessionId the session Id.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index a07827c033..585b3d2d25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -104,7 +104,6 @@ import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-import kotlin.collections.set
@SessionScope
internal class RealmCryptoStore @Inject constructor(
@@ -124,12 +123,6 @@ internal class RealmCryptoStore @Inject constructor(
// The olm account
private var olmAccount: OlmAccount? = null
- // Cache for OlmSession, to release them properly
- private val olmSessionsToRelease = HashMap()
-
- // Cache for InboundGroupSession, to release them properly
- private val inboundGroupSessionToRelease = HashMap()
-
private val newSessionListeners = ArrayList()
override fun addNewSessionListener(listener: NewSessionListener) {
@@ -213,16 +206,6 @@ internal class RealmCryptoStore @Inject constructor(
monarchyWriteAsyncExecutor.awaitTermination(1, TimeUnit.MINUTES)
}
- olmSessionsToRelease.forEach {
- it.value.olmSession.releaseSession()
- }
- olmSessionsToRelease.clear()
-
- inboundGroupSessionToRelease.forEach {
- it.value.olmInboundGroupSession?.releaseSession()
- }
- inboundGroupSessionToRelease.clear()
-
olmAccount?.releaseAccount()
realmLocker?.close()
@@ -247,10 +230,18 @@ internal class RealmCryptoStore @Inject constructor(
}
}
- override fun getOlmAccount(): OlmAccount {
- return olmAccount!!
+ /**
+ * Olm account access should be synchronized
+ */
+ override fun doWithOlmAccount(block: (OlmAccount) -> T): T {
+ return olmAccount!!.let { olmAccount ->
+ synchronized(olmAccount) {
+ block.invoke(olmAccount)
+ }
+ }
}
+ @Synchronized
override fun getOrCreateOlmAccount(): OlmAccount {
doRealmTransaction(realmConfiguration) {
val metaData = it.where().findFirst()
@@ -680,13 +671,6 @@ internal class RealmCryptoStore @Inject constructor(
if (sessionIdentifier != null) {
val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey)
- // Release memory of previously known session, if it is not the same one
- if (olmSessionsToRelease[key]?.olmSession != olmSessionWrapper.olmSession) {
- olmSessionsToRelease[key]?.olmSession?.releaseSession()
- }
-
- olmSessionsToRelease[key] = olmSessionWrapper
-
doRealmTransaction(realmConfiguration) {
val realmOlmSession = OlmSessionEntity().apply {
primaryKey = key
@@ -703,23 +687,18 @@ internal class RealmCryptoStore @Inject constructor(
override fun getDeviceSession(sessionId: String, deviceKey: String): OlmSessionWrapper? {
val key = OlmSessionEntity.createPrimaryKey(sessionId, deviceKey)
-
- // If not in cache (or not found), try to read it from realm
- if (olmSessionsToRelease[key] == null) {
- doRealmQueryAndCopy(realmConfiguration) {
- it.where()
- .equalTo(OlmSessionEntityFields.PRIMARY_KEY, key)
- .findFirst()
- }
- ?.let {
- val olmSession = it.getOlmSession()
- if (olmSession != null && it.sessionId != null) {
- olmSessionsToRelease[key] = OlmSessionWrapper(olmSession, it.lastReceivedMessageTs)
- }
- }
+ return doRealmQueryAndCopy(realmConfiguration) {
+ it.where()
+ .equalTo(OlmSessionEntityFields.PRIMARY_KEY, key)
+ .findFirst()
}
-
- return olmSessionsToRelease[key]
+ ?.let {
+ val olmSession = it.getOlmSession()
+ if (olmSession != null && it.sessionId != null) {
+ return@let OlmSessionWrapper(olmSession, it.lastReceivedMessageTs)
+ }
+ null
+ }
}
override fun getLastUsedSessionId(deviceKey: String): String? {
@@ -761,13 +740,6 @@ internal class RealmCryptoStore @Inject constructor(
if (sessionIdentifier != null) {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey)
- // Release memory of previously known session, if it is not the same one
- if (inboundGroupSessionToRelease[key] != session) {
- inboundGroupSessionToRelease[key]?.olmInboundGroupSession?.releaseSession()
- }
-
- inboundGroupSessionToRelease[key] = session
-
val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
primaryKey = key
sessionId = sessionIdentifier
@@ -784,20 +756,12 @@ internal class RealmCryptoStore @Inject constructor(
override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
- // If not in cache (or not found), try to read it from realm
- if (inboundGroupSessionToRelease[key] == null) {
- doWithRealm(realmConfiguration) {
- it.where()
- .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
- .findFirst()
- ?.getInboundGroupSession()
- }
- ?.let {
- inboundGroupSessionToRelease[key] = it
- }
+ return doWithRealm(realmConfiguration) {
+ it.where()
+ .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
+ .findFirst()
+ ?.getInboundGroupSession()
}
-
- return inboundGroupSessionToRelease[key]
}
override fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? {
@@ -853,10 +817,6 @@ internal class RealmCryptoStore @Inject constructor(
override fun removeInboundGroupSession(sessionId: String, senderKey: String) {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
- // Release memory of previously known session
- inboundGroupSessionToRelease[key]?.olmInboundGroupSession?.releaseSession()
- inboundGroupSessionToRelease.remove(key)
-
doRealmTransaction(realmConfiguration) {
it.where()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
index 2820b66886..62f90f563e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.legacy.riot;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
@@ -196,6 +197,7 @@ public class LoginStorage {
/**
* Clear the stored values
*/
+ @SuppressLint("ApplySharedPref")
public void clear() {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
index 1ab1042129..5aec7db66c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
@@ -21,6 +21,7 @@ internal object NetworkConstants {
private const val URI_API_PREFIX_PATH = "_matrix/client"
const val URI_API_PREFIX_PATH_ = "$URI_API_PREFIX_PATH/"
const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/"
+ const val URI_API_PREFIX_PATH_V1 = "$URI_API_PREFIX_PATH/v1/"
const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/"
// Media
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
index 8b05d2ea62..8ae203c2b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
@@ -56,7 +56,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
when (event.type) {
- EventType.POLL_START,
+ in EventType.POLL_START,
EventType.MESSAGE,
EventType.REDACTION,
EventType.ENCRYPTED,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
index 4a02c55db0..0d78489fbd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
+import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService
@@ -109,6 +110,10 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
+ override fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow {
+ return roomSummaryDataSource.getCountFlow(queryParams)
+ }
+
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
return roomSummaryDataSource.getNotificationCountForRooms(queryParams)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 1e0eb8b497..8159da844f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -86,11 +86,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// TODO Add ?
// EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY,
- EventType.ENCRYPTED,
- EventType.POLL_START,
- EventType.POLL_RESPONSE,
- EventType.POLL_END
- )
+ EventType.ENCRYPTED
+ ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
return allowedTypes.contains(eventType)
@@ -156,7 +153,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
// A replace!
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
- } else if (event.getClearType() == EventType.POLL_RESPONSE) {
+ } else if (event.getClearType() in EventType.POLL_RESPONSE) {
event.getClearContent().toModel(catchError = true)?.let { pollResponseContent ->
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
@@ -177,12 +174,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
handleVerification(realm, event, roomId, isLocalEcho, it)
}
}
- EventType.POLL_RESPONSE -> {
+ in EventType.POLL_RESPONSE -> {
event.getClearContent().toModel(catchError = true)?.let {
handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId)
}
}
- EventType.POLL_END -> {
+ in EventType.POLL_END -> {
event.content.toModel(catchError = true)?.let {
handleEndPoll(realm, event, it, roomId, isLocalEcho)
}
@@ -217,7 +214,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
}
- EventType.POLL_START -> {
+ in EventType.POLL_START -> {
val content: MessagePollContent? = event.content.toModel()
if (content?.relatesTo?.type == RelationType.REPLACE) {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
@@ -225,12 +222,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
handleReplace(realm, event, content, roomId, isLocalEcho)
}
}
- EventType.POLL_RESPONSE -> {
+ in EventType.POLL_RESPONSE -> {
event.content.toModel(catchError = true)?.let {
handleResponse(realm, event, it, roomId, isLocalEcho)
}
}
- EventType.POLL_END -> {
+ in EventType.POLL_END -> {
event.content.toModel(catchError = true)?.let {
handleEndPoll(realm, event, it, roomId, isLocalEcho)
}
@@ -407,12 +404,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
return
}
- val option = content.response?.answers?.first() ?: return Unit.also {
+ val option = content.getBestResponse()?.answers?.first() ?: return Unit.also {
Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}")
}
// Check if this option is in available options
- if (!targetPollContent.pollCreationInfo?.answers?.map { it.id }?.contains(option).orFalse()) {
+ if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(option).orFalse()) {
Timber.v("## POLL $targetEventId doesn't contain option $option")
return
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt
index ee52fe574b..4753e12157 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt
@@ -71,7 +71,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
when (typeToPrune) {
EventType.ENCRYPTED,
EventType.MESSAGE,
- EventType.POLL_START -> {
+ in EventType.POLL_START -> {
Timber.d("REDACTION for message ${eventToPrune.eventId}")
val unsignedData = EventMapper.map(eventToPrune).unsignedData
?: UnsignedData(null, null)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index e0d501c515..cd06d47f05 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -156,7 +156,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
* Invoke the event decryption mechanism for a specific event
*/
- private fun decryptIfNeeded(event: Event, roomId: String) {
+ private suspend fun decryptIfNeeded(event: Event, roomId: String) {
try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index 3c36d58710..8d32c53604 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -137,16 +137,11 @@ internal class LocalEchoEventFactory @Inject constructor(
options: List,
pollType: PollType): MessagePollContent {
return MessagePollContent(
- pollCreationInfo = PollCreationInfo(
- question = PollQuestion(
- question = question
- ),
+ unstablePollCreationInfo = PollCreationInfo(
+ question = PollQuestion(unstableQuestion = question),
kind = pollType,
answers = options.map { option ->
- PollAnswer(
- id = UUID.randomUUID().toString(),
- answer = option
- )
+ PollAnswer(id = UUID.randomUUID().toString(), unstableAnswer = option)
}
)
)
@@ -167,7 +162,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
- type = EventType.POLL_START,
+ type = EventType.POLL_START.first(),
content = newContent.toContent()
)
}
@@ -179,11 +174,9 @@ internal class LocalEchoEventFactory @Inject constructor(
body = answerId,
relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE,
- eventId = pollEventId),
- response = PollResponse(
- answers = listOf(answerId)
- )
-
+ eventId = pollEventId
+ ),
+ unstableResponse = PollResponse(answers = listOf(answerId))
)
val localId = LocalEcho.createLocalEchoId()
return Event(
@@ -191,7 +184,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
- type = EventType.POLL_RESPONSE,
+ type = EventType.POLL_RESPONSE.first(),
content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId))
}
@@ -207,7 +200,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
- type = EventType.POLL_START,
+ type = EventType.POLL_START.first(),
content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId))
}
@@ -226,7 +219,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
- type = EventType.POLL_END,
+ type = EventType.POLL_END.first(),
content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId))
}
@@ -239,15 +232,10 @@ internal class LocalEchoEventFactory @Inject constructor(
val content = MessageLocationContent(
geoUri = geoUri,
body = geoUri,
- locationInfo = LocationInfo(
- geoUri = geoUri,
- description = geoUri
- ),
- locationAsset = LocationAsset(
- type = LocationAssetType.SELF
- ),
- ts = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()),
- text = geoUri
+ unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
+ unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF),
+ unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()),
+ unstableText = geoUri
)
return createMessageEvent(roomId, content)
}
@@ -638,7 +626,9 @@ internal class LocalEchoEventFactory @Inject constructor(
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
- MessageType.MSGTYPE_POLL_START -> return TextContent((content as? MessagePollContent)?.pollCreationInfo?.question?.question ?: "")
+ MessageType.MSGTYPE_POLL_START -> {
+ return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "")
+ }
else -> return TextContent(content?.body ?: "")
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index c9fc3c9575..ea4f102fa5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -25,7 +25,13 @@ import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmQuery
+import io.realm.kotlin.toFlow
import io.realm.kotlin.where
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.isNormalized
@@ -42,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@@ -55,8 +62,10 @@ import javax.inject.Inject
internal class RoomSummaryDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
+ private val realmSessionProvider: RealmSessionProvider,
private val roomSummaryMapper: RoomSummaryMapper,
- private val queryStringValueProcessor: QueryStringValueProcessor
+ private val queryStringValueProcessor: QueryStringValueProcessor,
+ private val coroutineDispatchers: MatrixCoroutineDispatchers
) {
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
@@ -219,17 +228,29 @@ internal class RoomSummaryDataSource @Inject constructor(
return object : UpdatableLivePageResult {
override val livePagedList: LiveData> = mapped
- override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) {
- realmDataSourceFactory.updateQuery {
- roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
- }
- }
-
override val liveBoundaries: LiveData
get() = boundaries
+
+ override var queryParams: RoomSummaryQueryParams = queryParams
+ set(value) {
+ field = value
+ realmDataSourceFactory.updateQuery {
+ roomSummariesQuery(it, value).process(sortOrder)
+ }
+ }
}
}
+ fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow =
+ realmSessionProvider
+ .withRealm { realm -> roomSummariesQuery(realm, queryParams).findAllAsync() }
+ .toFlow()
+ // need to create the flow on a context dispatcher with a thread with attached Looper
+ .flowOn(coroutineDispatchers.main)
+ .map { it.size }
+ .flowOn(coroutineDispatchers.io)
+ .distinctUntilChanged()
+
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
var notificationCount: RoomAggregateNotificationCount? = null
monarchy.doWithRealm { realm ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 1c1d59fb3d..c9712c5721 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.summary
import io.realm.Realm
import io.realm.kotlin.createObject
+import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -165,7 +166,9 @@ internal class RoomSummaryUpdater @Inject constructor(
Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
// mmm i want to decrypt now or is it ok to do it async?
tryOrNull {
- eventDecryptor.decryptEvent(root.asDomain(), "")
+ runBlocking {
+ eventDecryptor.decryptEvent(root.asDomain(), "")
+ }
}
?.let { root.setDecryptionResult(it) }
}
@@ -411,8 +414,6 @@ internal class RoomSummaryUpdater @Inject constructor(
realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
- // also we do not count DM in here, because home space will already show them
- .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
.findAll().forEach {
highlightCount += it.highlightCount
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
index b7a2cf2fce..1262c09d97 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
@@ -66,7 +66,9 @@ internal class RealmSendingEventsDataSource(
private fun updateFrozenResults(sendingEvents: RealmList?) {
// Makes sure to close the previous frozen realm
- frozenSendingTimelineEvents?.realm?.close()
+ if (frozenSendingTimelineEvents?.isValid == true) {
+ frozenSendingTimelineEvents?.realm?.close()
+ }
frozenSendingTimelineEvents = sendingEvents?.freeze()
}
@@ -74,13 +76,15 @@ internal class RealmSendingEventsDataSource(
val builtSendingEvents = mutableListOf()
uiEchoManager.getInMemorySendingEvents()
.addWithUiEcho(builtSendingEvents)
- frozenSendingTimelineEvents
- ?.filter { timelineEvent ->
- builtSendingEvents.none { it.eventId == timelineEvent.eventId }
- }
- ?.map {
- timelineEventMapper.map(it)
- }?.addWithUiEcho(builtSendingEvents)
+ if (frozenSendingTimelineEvents?.isValid == true) {
+ frozenSendingTimelineEvents
+ ?.filter { timelineEvent ->
+ builtSendingEvents.none { it.eventId == timelineEvent.eventId }
+ }
+ ?.map {
+ timelineEventMapper.map(it)
+ }?.addWithUiEcho(builtSendingEvents)
+ }
return builtSendingEvents
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index c0dc31fcf8..77f210aa9a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -144,14 +144,14 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
val offsetCount = count - loadFromStorage.numberOfEvents
- return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) {
+ return if (offsetCount == 0) {
+ LoadMoreResult.SUCCESS
+ } else if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) {
LoadMoreResult.REACHED_END
} else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) {
LoadMoreResult.REACHED_END
} else if (timelineSettings.isThreadTimeline() && loadFromStorage.threadReachedEnd) {
LoadMoreResult.REACHED_END
- } else if (offsetCount == 0) {
- LoadMoreResult.SUCCESS
} else {
delegateLoadMore(fetchOnServerIfNeeded, offsetCount, direction)
}
@@ -508,13 +508,18 @@ private fun RealmQuery.offsets(
count: Int,
startDisplayIndex: Int
): RealmQuery {
- sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
- if (direction == Timeline.Direction.BACKWARDS) {
+ return if (direction == Timeline.Direction.BACKWARDS) {
lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex)
+ sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
+ limit(count.toLong())
} else {
greaterThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, startDisplayIndex)
+ // We need to sort ascending first so limit works in the right direction
+ sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
+ limit(count.toLong())
+ // Result is expected to be sorted descending
+ sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
}
- return limit(count.toLong())
}
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 49a8a8b55a..bacac58d84 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm
import io.realm.RealmConfiguration
+import kotlinx.coroutines.runBlocking
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.events.model.Event
@@ -99,7 +100,9 @@ internal class TimelineEventDecryptor @Inject constructor(
}
executor?.execute {
Realm.getInstance(realmConfiguration).use { realm ->
- processDecryptRequest(request, realm)
+ runBlocking {
+ processDecryptRequest(request, realm)
+ }
}
}
}
@@ -115,7 +118,7 @@ internal class TimelineEventDecryptor @Inject constructor(
threadsAwarenessHandler.makeEventThreadAware(realm, event.roomId, decryptedEvent, eventEntity)
}
}
- private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
+ private suspend fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
val event = request.event
val timelineId = request.timelineId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt
index 7ac34e80e9..e5213c4696 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.signout
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
@@ -65,7 +66,13 @@ internal class DefaultSignOutTask @Inject constructor(
// Logout from identity server if any
runCatching { identityDisconnectTask.execute(Unit) }
- .onFailure { Timber.w(it, "Unable to disconnect identity server") }
+ .onFailure {
+ if (it is IdentityServiceError.NoIdentityServerConfigured) {
+ Timber.i("No identity server configured to disconnect")
+ } else {
+ Timber.w(it, "Unable to disconnect identity server")
+ }
+ }
Timber.d("SignOut: cleanup session...")
cleanupSession.cleanup()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
index c18055e089..e764ab551a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -113,71 +113,108 @@ internal class DefaultSpaceService @Inject constructor(
return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
}
- override suspend fun querySpaceChildren(spaceId: String,
- suggestedOnly: Boolean?,
- limit: Int?,
- from: String?,
- knownStateList: List?): SpaceHierarchyData {
- return resolveSpaceInfoTask.execute(
- ResolveSpaceInfoTask.Params(
- spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly
- )
- ).let { response ->
- val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId }
- val root = RoomSummary(
- roomId = spaceDesc?.roomId ?: spaceId,
- roomType = spaceDesc?.roomType,
- name = spaceDesc?.name ?: "",
- displayName = spaceDesc?.name ?: "",
- topic = spaceDesc?.topic ?: "",
- joinedMembersCount = spaceDesc?.numJoinedMembers,
- avatarUrl = spaceDesc?.avatarUrl ?: "",
- encryptionEventTs = null,
- typingUsers = emptyList(),
- isEncrypted = false,
- flattenParentIds = emptyList(),
- canonicalAlias = spaceDesc?.canonicalAlias,
- joinRules = RoomJoinRules.PUBLIC.takeIf { spaceDesc?.worldReadable == true }
- )
- val children = response.rooms
- ?.filter { it.roomId != spaceId }
- ?.flatMap { childSummary ->
- (spaceDesc?.childrenState ?: knownStateList)
- ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
- ?.mapNotNull { childStateEv ->
- // create a child entry for everytime this room is the child of a space
- // beware that a room could appear then twice in this list
- childStateEv.content.toModel()?.let { childStateEvContent ->
- SpaceChildInfo(
- childRoomId = childSummary.roomId,
- isKnown = true,
- roomType = childSummary.roomType,
- name = childSummary.name,
- topic = childSummary.topic,
- avatarUrl = childSummary.avatarUrl,
- order = childStateEvContent.order,
-// autoJoin = childStateEvContent.autoJoin ?: false,
- viaServers = childStateEvContent.via.orEmpty(),
- activeMemberCount = childSummary.numJoinedMembers,
- parentRoomId = childStateEv.roomId,
- suggested = childStateEvContent.suggested,
- canonicalAlias = childSummary.canonicalAlias,
- aliases = childSummary.aliases,
- worldReadable = childSummary.worldReadable
- )
- }
- }.orEmpty()
- }
- .orEmpty()
- SpaceHierarchyData(
- rootSummary = root,
- children = children,
- childrenState = spaceDesc?.childrenState.orEmpty(),
- nextToken = response.nextBatch
- )
- }
+ override suspend fun querySpaceChildren(
+ spaceId: String,
+ suggestedOnly: Boolean?,
+ limit: Int?,
+ from: String?,
+ knownStateList: List?
+ ): SpaceHierarchyData {
+ val spacesResponse = getSpacesResponse(spaceId, suggestedOnly, limit, from)
+ val spaceRootResponse = spacesResponse.getRoot(spaceId)
+ val spaceRoot = spaceRootResponse?.toRoomSummary() ?: createBlankRoomSummary(spaceId)
+ val spaceChildren = spacesResponse.rooms.mapSpaceChildren(spaceId, spaceRootResponse, knownStateList)
+
+ return SpaceHierarchyData(
+ rootSummary = spaceRoot,
+ children = spaceChildren,
+ childrenState = spaceRootResponse?.childrenState.orEmpty(),
+ nextToken = spacesResponse.nextBatch
+ )
}
+ private suspend fun getSpacesResponse(spaceId: String, suggestedOnly: Boolean?, limit: Int?, from: String?) =
+ resolveSpaceInfoTask.execute(
+ ResolveSpaceInfoTask.Params(spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly)
+ )
+
+ private fun SpacesResponse.getRoot(spaceId: String) = rooms?.firstOrNull { it.roomId == spaceId }
+
+ private fun SpaceChildSummaryResponse.toRoomSummary() = RoomSummary(
+ roomId = roomId,
+ roomType = roomType,
+ name = name ?: "",
+ displayName = name ?: "",
+ topic = topic ?: "",
+ joinedMembersCount = numJoinedMembers,
+ avatarUrl = avatarUrl ?: "",
+ encryptionEventTs = null,
+ typingUsers = emptyList(),
+ isEncrypted = false,
+ flattenParentIds = emptyList(),
+ canonicalAlias = canonicalAlias,
+ joinRules = RoomJoinRules.PUBLIC.takeIf { isWorldReadable }
+ )
+
+ private fun createBlankRoomSummary(spaceId: String) = RoomSummary(
+ roomId = spaceId,
+ joinedMembersCount = null,
+ encryptionEventTs = null,
+ typingUsers = emptyList(),
+ isEncrypted = false,
+ flattenParentIds = emptyList(),
+ canonicalAlias = null,
+ joinRules = null
+ )
+
+ private fun List?.mapSpaceChildren(
+ spaceId: String,
+ spaceRootResponse: SpaceChildSummaryResponse?,
+ knownStateList: List?,
+ ) = this?.filterIdIsNot(spaceId)
+ ?.toSpaceChildInfoList(spaceId, spaceRootResponse, knownStateList)
+ .orEmpty()
+
+ private fun List.filterIdIsNot(spaceId: String) = filter { it.roomId != spaceId }
+
+ private fun List.toSpaceChildInfoList(
+ spaceId: String,
+ rootRoomResponse: SpaceChildSummaryResponse?,
+ knownStateList: List?,
+ ) = flatMap { spaceChildSummary ->
+ (rootRoomResponse?.childrenState ?: knownStateList)
+ ?.filter { it.isChildOf(spaceChildSummary) }
+ ?.mapNotNull { childStateEvent -> childStateEvent.toSpaceChildInfo(spaceId, spaceChildSummary) }
+ .orEmpty()
+ }
+
+ private fun Event.isChildOf(space: SpaceChildSummaryResponse) = stateKey == space.roomId && type == EventType.STATE_SPACE_CHILD
+
+ private fun Event.toSpaceChildInfo(spaceId: String, summary: SpaceChildSummaryResponse) = content.toModel()?.let { content ->
+ createSpaceChildInfo(spaceId, summary, content)
+ }
+
+ private fun createSpaceChildInfo(
+ spaceId: String,
+ summary: SpaceChildSummaryResponse,
+ content: SpaceChildContent
+ ) = SpaceChildInfo(
+ childRoomId = summary.roomId,
+ isKnown = true,
+ roomType = summary.roomType,
+ name = summary.name,
+ topic = summary.topic,
+ avatarUrl = summary.avatarUrl,
+ order = content.order,
+ viaServers = content.via.orEmpty(),
+ activeMemberCount = summary.numJoinedMembers,
+ parentRoomId = spaceId,
+ suggested = content.suggested,
+ canonicalAlias = summary.canonicalAlias,
+ aliases = summary.aliases,
+ worldReadable = summary.isWorldReadable
+ )
+
override suspend fun joinSpace(spaceIdOrAlias: String,
reason: String?,
viaServers: List): JoinSpaceResult {
@@ -192,10 +229,6 @@ internal class DefaultSpaceService @Inject constructor(
leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason))
}
-// override fun getSpaceParentsOfRoom(roomId: String): List {
-// return spaceSummaryDataSource.getParentsOfRoom(roomId)
-// }
-
override suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List) {
// Should we perform some validation here?,
// and if client want to bypass, it could use sendStateEvent directly?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
index 2a396d6ee7..d59ca06c2c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.space
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
+import retrofit2.HttpException
import javax.inject.Inject
internal interface ResolveSpaceInfoTask : Task {
@@ -28,7 +29,6 @@ internal interface ResolveSpaceInfoTask : Task
+ // IMPORTANT nothing should be suspend here as we are accessing the realm instance (thread local)
measureTimeMillis {
Timber.v("Handle rooms")
reportSubtask(reporter, InitSyncStep.ImportingAccountRoom, 1, 0.7f) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index f299d3effa..9ae7b82777 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -38,7 +38,7 @@ private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService,
private val verificationService: DefaultVerificationService) {
- fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) {
+ suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) {
val total = toDevice.events?.size ?: 0
toDevice.events?.forEachIndexed { index, event ->
progressReporter?.reportProgress(index * 100F / total)
@@ -66,7 +66,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
* @param timelineId the timeline identifier
* @return true if the event has been decrypted
*/
- private fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
+ private suspend fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
Timber.v("## CRYPTO | decryptToDeviceEvent")
if (event.getClearType() == EventType.ENCRYPTED) {
var result: MXEventDecryptionResult? = null
@@ -80,6 +80,8 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
it.identityKey() == senderKey
}?.deviceId ?: senderKey
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
+ } catch (failure: Throwable) {
+ Timber.e(failure, "## CRYPTO | Failed to decrypt to device event from ${event.senderId}")
}
if (null != result) {
@@ -91,7 +93,9 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
)
return true
} else {
- // should not happen
+ // Could happen for to device events
+ // None of the known session could decrypt the message
+ // In this case unwedging process might have been started (rate limited)
Timber.e("## CRYPTO | ERROR NULL DECRYPTION RESULT from ${event.senderId}")
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 99e6521eb7..a5ad19bbf8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
import dagger.Lazy
import io.realm.Realm
import io.realm.kotlin.createObject
+import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -379,7 +380,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
if (event.isEncrypted() && !isInitialSync) {
- decryptIfNeeded(event, roomId)
+ runBlocking {
+ decryptIfNeeded(event, roomId)
+ }
}
var contentToInject: String? = null
if (!isInitialSync) {
@@ -455,7 +458,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
return chunkEntity
}
- private fun decryptIfNeeded(event: Event, roomId: String) {
+ private suspend fun decryptIfNeeded(event: Event, roomId: String) {
try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt
new file mode 100644
index 0000000000..f80c0f06d0
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.space
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
+import okhttp3.ResponseBody.Companion.toResponseBody
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
+import org.matrix.android.sdk.test.fakes.FakeSpaceApi
+import org.matrix.android.sdk.test.fixtures.SpacesResponseFixture.aSpacesResponse
+import retrofit2.HttpException
+import retrofit2.Response
+
+@ExperimentalCoroutinesApi
+internal class DefaultResolveSpaceInfoTaskTest {
+
+ private val spaceApi = FakeSpaceApi()
+ private val globalErrorReceiver = FakeGlobalErrorReceiver()
+ private val resolveSpaceInfoTask = DefaultResolveSpaceInfoTask(spaceApi.instance, globalErrorReceiver)
+
+ @Test
+ fun `given stable endpoint works, when execute, then return stable api data`() = runBlockingTest {
+ spaceApi.givenStableEndpointReturns(response)
+
+ val result = resolveSpaceInfoTask.execute(spaceApi.params)
+
+ result shouldBeEqualTo response
+ }
+
+ @Test
+ fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runBlockingTest {
+ spaceApi.givenStableEndpointThrows(httpException)
+ spaceApi.givenUnstableEndpointReturns(response)
+
+ val result = resolveSpaceInfoTask.execute(spaceApi.params)
+
+ result shouldBeEqualTo response
+ }
+
+ companion object {
+ private val response = aSpacesResponse()
+ private val httpException = HttpException(Response.error(500, "".toResponseBody()))
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSpaceApi.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSpaceApi.kt
new file mode 100644
index 0000000000..d4fc986791
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSpaceApi.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes
+
+import io.mockk.coEvery
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.space.SpaceApi
+import org.matrix.android.sdk.internal.session.space.SpacesResponse
+import org.matrix.android.sdk.test.fixtures.ResolveSpaceInfoTaskParamsFixture
+
+internal class FakeSpaceApi {
+
+ val instance: SpaceApi = mockk()
+ val params = ResolveSpaceInfoTaskParamsFixture.aResolveSpaceInfoTaskParams()
+
+ fun givenStableEndpointReturns(response: SpacesResponse) {
+ coEvery { instance.getSpaceHierarchy(params.spaceId, params.suggestedOnly, params.limit, params.maxDepth, params.from) } returns response
+ }
+
+ fun givenStableEndpointThrows(throwable: Throwable) {
+ coEvery { instance.getSpaceHierarchy(params.spaceId, params.suggestedOnly, params.limit, params.maxDepth, params.from) } throws throwable
+ }
+
+ fun givenUnstableEndpointReturns(response: SpacesResponse) {
+ coEvery { instance.getSpaceHierarchyUnstable(params.spaceId, params.suggestedOnly, params.limit, params.maxDepth, params.from) } returns response
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/ResolveSpaceInfoTaskParamsFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/ResolveSpaceInfoTaskParamsFixture.kt
new file mode 100644
index 0000000000..28f8c3637d
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/ResolveSpaceInfoTaskParamsFixture.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fixtures
+
+import org.matrix.android.sdk.internal.session.space.ResolveSpaceInfoTask
+
+internal object ResolveSpaceInfoTaskParamsFixture {
+ fun aResolveSpaceInfoTaskParams(
+ spaceId: String = "",
+ limit: Int? = null,
+ maxDepth: Int? = null,
+ from: String? = null,
+ suggestedOnly: Boolean? = null,
+ ) = ResolveSpaceInfoTask.Params(
+ spaceId,
+ limit,
+ maxDepth,
+ from,
+ suggestedOnly,
+ )
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SpacesResponseFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SpacesResponseFixture.kt
new file mode 100644
index 0000000000..0a08331114
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/SpacesResponseFixture.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fixtures
+
+import org.matrix.android.sdk.internal.session.space.SpaceChildSummaryResponse
+import org.matrix.android.sdk.internal.session.space.SpacesResponse
+
+internal object SpacesResponseFixture {
+ fun aSpacesResponse(
+ nextBatch: String? = null,
+ rooms: List? = null,
+ ) = SpacesResponse(
+ nextBatch,
+ rooms,
+ )
+}
diff --git a/vector/build.gradle b/vector/build.gradle
index c4c9436e13..2d9c097da8 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -18,7 +18,7 @@ ext.versionMinor = 4
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
-ext.versionPatch = 4
+ext.versionPatch = 6
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@@ -355,6 +355,7 @@ dependencies {
// Lifecycle
implementation libs.androidx.lifecycleLivedata
implementation libs.androidx.lifecycleProcess
+ implementation libs.androidx.lifecycleRuntimeKtx
implementation libs.androidx.datastore
implementation libs.androidx.datastorepreferences
@@ -367,7 +368,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
// FlowBinding
implementation libs.github.flowBinding
diff --git a/vector/lint.xml b/vector/lint.xml
index 22da6adfa9..e219ac1eed 100644
--- a/vector/lint.xml
+++ b/vector/lint.xml
@@ -15,24 +15,14 @@
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt
index 4394f5436e..5e16182f3c 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt
@@ -22,13 +22,17 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
+import im.vector.app.features.HomeserverCapabilitiesOverride
import im.vector.app.features.VectorOverrides
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.extensions.orFalse
private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_overrides")
private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display")
private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback")
+private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name")
+private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar")
class DebugVectorOverrides(private val context: Context) : VectorOverrides {
@@ -40,6 +44,13 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
preferences[keyForceLoginFallback].orFalse()
}
+ override val forceHomeserverCapabilities = context.dataStore.data.map { preferences ->
+ HomeserverCapabilitiesOverride(
+ canChangeDisplayName = preferences[forceCanChangeDisplayName],
+ canChangeAvatar = preferences[forceCanChangeAvatar]
+ )
+ }
+
suspend fun setForceDialPadDisplay(force: Boolean) {
context.dataStore.edit { settings ->
settings[keyForceDialPadDisplay] = force
@@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
settings[keyForceLoginFallback] = force
}
}
+
+ suspend fun setHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) {
+ val capabilitiesOverride = block(forceHomeserverCapabilities.firstOrNull() ?: HomeserverCapabilitiesOverride(null, null))
+ context.dataStore.edit { settings ->
+ when (capabilitiesOverride.canChangeDisplayName) {
+ null -> settings.remove(forceCanChangeDisplayName)
+ else -> settings[forceCanChangeDisplayName] = capabilitiesOverride.canChangeDisplayName
+ }
+ when (capabilitiesOverride.canChangeAvatar) {
+ null -> settings.remove(forceCanChangeAvatar)
+ else -> settings[forceCanChangeAvatar] = capabilitiesOverride.canChangeAvatar
+ }
+ }
+ }
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
index b54d776901..38253fe7c2 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt
@@ -50,6 +50,12 @@ class DebugPrivateSettingsFragment : VectorBaseFragment
+ viewModel.handle(DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride(option))
+ }
+ views.forceChangeAvatarCapability.bind(it.homeserverCapabilityOverrides.avatar) { option ->
+ viewModel.handle(DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride(option))
+ }
views.forceLoginFallback.isChecked = it.forceLoginFallback
}
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
index 1c76cf6fb2..5dea3dce64 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt
@@ -18,7 +18,9 @@ package im.vector.app.features.debug.settings
import im.vector.app.core.platform.VectorViewModelAction
-sealed class DebugPrivateSettingsViewActions : VectorViewModelAction {
- data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions()
- data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions()
+sealed interface DebugPrivateSettingsViewActions : VectorViewModelAction {
+ data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions
+ data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions
+ data class SetDisplayNameCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions
+ data class SetAvatarCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
index 8d040d4773..62871023bc 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
@@ -22,9 +22,12 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.debug.features.DebugVectorOverrides
+import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride
+import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride
import kotlinx.coroutines.launch
class DebugPrivateSettingsViewModel @AssistedInject constructor(
@@ -40,10 +43,10 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
init {
- observeVectorDataStore()
+ observeVectorOverrides()
}
- private fun observeVectorDataStore() {
+ private fun observeVectorOverrides() {
debugVectorOverrides.forceDialPad.setOnEach {
copy(
dialPadVisible = it
@@ -52,13 +55,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
debugVectorOverrides.forceLoginFallback.setOnEach {
copy(forceLoginFallback = it)
}
+ debugVectorOverrides.forceHomeserverCapabilities.setOnEach {
+ val activeDisplayNameOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeDisplayName)
+ val activeAvatarOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeAvatar)
+ copy(homeserverCapabilityOverrides = homeserverCapabilityOverrides.copy(
+ displayName = homeserverCapabilityOverrides.displayName.copy(activeOption = activeDisplayNameOption),
+ avatar = homeserverCapabilityOverrides.avatar.copy(activeOption = activeAvatarOption),
+ ))
+ }
}
override fun handle(action: DebugPrivateSettingsViewActions) {
when (action) {
is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action)
- }
+ is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action)
+ is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action)
+ }.exhaustive
}
private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
@@ -72,4 +85,18 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
debugVectorOverrides.setForceLoginFallback(action.force)
}
}
+
+ private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) {
+ viewModelScope.launch {
+ val forceDisplayName = action.option.toBoolean()
+ debugVectorOverrides.setHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) }
+ }
+ }
+
+ private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) {
+ viewModelScope.launch {
+ val forceAvatar = action.option.toBoolean()
+ debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) }
+ }
+ }
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
index 7fca29af8c..749b11a744 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt
@@ -17,8 +17,23 @@
package im.vector.app.features.debug.settings
import com.airbnb.mvrx.MavericksState
+import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdown
data class DebugPrivateSettingsViewState(
val dialPadVisible: Boolean = false,
val forceLoginFallback: Boolean = false,
+ val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides()
) : MavericksState
+
+data class HomeserverCapabilityOverrides(
+ val displayName: OverrideDropdown = OverrideDropdown(
+ label = "Override display name capability",
+ activeOption = null,
+ options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled)
+ ),
+ val avatar: OverrideDropdown = OverrideDropdown(
+ label = "Override avatar capability",
+ activeOption = null,
+ options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled)
+ )
+)
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt
new file mode 100644
index 0000000000..48ec44f909
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.LinearLayout
+import im.vector.app.R
+import im.vector.app.databinding.ViewBooleanDropdownBinding
+
+class OverrideDropdownView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+
+ private val binding = ViewBooleanDropdownBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ init {
+ orientation = HORIZONTAL
+ gravity = Gravity.CENTER_VERTICAL
+ }
+
+ fun bind(feature: OverrideDropdown, listener: Listener) {
+ binding.overrideLabel.text = feature.label
+
+ binding.overrideOptions.apply {
+ val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item)
+ val options = listOf("Inactive") + feature.options.map { it.label }
+ arrayAdapter.addAll(options)
+ adapter = arrayAdapter
+
+ feature.activeOption?.let {
+ setSelection(options.indexOf(it.label), false)
+ }
+
+ onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ when (position) {
+ 0 -> listener.onOverrideSelected(option = null)
+ else -> listener.onOverrideSelected(feature.options[position - 1])
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ fun interface Listener {
+ fun onOverrideSelected(option: T?)
+ }
+
+ data class OverrideDropdown(
+ val label: String,
+ val options: List,
+ val activeOption: T?,
+ )
+}
+
+interface OverrideOption {
+ val label: String
+}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt
new file mode 100644
index 0000000000..316e8fb901
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.debug.settings
+
+sealed interface BooleanHomeserverCapabilitiesOverride : OverrideOption {
+
+ companion object {
+ fun from(value: Boolean?) = when (value) {
+ null -> null
+ true -> ForceEnabled
+ false -> ForceDisabled
+ }
+ }
+
+ object ForceEnabled : BooleanHomeserverCapabilitiesOverride {
+ override val label = "Force enabled"
+ }
+
+ object ForceDisabled : BooleanHomeserverCapabilitiesOverride {
+ override val label = "Force disabled"
+ }
+}
+
+fun BooleanHomeserverCapabilitiesOverride?.toBoolean() = when (this) {
+ null -> null
+ BooleanHomeserverCapabilitiesOverride.ForceDisabled -> false
+ BooleanHomeserverCapabilitiesOverride.ForceEnabled -> true
+}
diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml
index 6760c68169..c42ad68dce 100644
--- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml
+++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml
@@ -31,6 +31,24 @@
android:layout_height="wrap_content"
android:text="Force login and registration fallback" />
+
+
+
+
diff --git a/vector/src/debug/res/layout/view_boolean_dropdown.xml b/vector/src/debug/res/layout/view_boolean_dropdown.xml
new file mode 100644
index 0000000000..5018d61047
--- /dev/null
+++ b/vector/src/debug/res/layout/view_boolean_dropdown.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
index b1df29ebc6..28c1587b1a 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
@@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
fun TimelineEvent.canReact(): Boolean {
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
- return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER, EventType.POLL_START) &&
+ return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START &&
root.sendState == SendState.SYNCED &&
!root.isRedacted()
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
index 54fcac42d1..f9ca8cb57c 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
@@ -23,6 +23,7 @@ import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import androidx.annotation.AttrRes
+import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
@@ -70,6 +71,15 @@ fun View.setAttributeTintedBackground(@DrawableRes drawableRes: Int, @AttrRes ti
background = drawable
}
+fun View.tintBackground(@ColorInt tintColor: Int) {
+ val bkg = background?.let {
+ val backgroundDrawable = DrawableCompat.wrap(background)
+ DrawableCompat.setTint(backgroundDrawable, tintColor)
+ backgroundDrawable
+ }
+ background = bkg
+}
+
fun ImageView.setAttributeTintedImageResource(@DrawableRes drawableRes: Int, @AttrRes tint: Int) {
val drawable = ContextCompat.getDrawable(context, drawableRes)!!
DrawableCompat.setTint(drawable, ThemeUtils.getColor(context, tint))
diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt
index 4128fdbe3c..daa0d9e0bd 100644
--- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt
+++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt
@@ -22,9 +22,16 @@ import kotlinx.coroutines.flow.flowOf
interface VectorOverrides {
val forceDialPad: Flow
val forceLoginFallback: Flow
+ val forceHomeserverCapabilities: Flow?
}
+data class HomeserverCapabilitiesOverride(
+ val canChangeDisplayName: Boolean?,
+ val canChangeAvatar: Boolean?
+)
+
class DefaultVectorOverrides : VectorOverrides {
override val forceDialPad = flowOf(false)
override val forceLoginFallback = flowOf(false)
+ override val forceHomeserverCapabilities: Flow? = null
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
index ea03b833ac..b052758201 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
@@ -457,7 +457,7 @@ class HomeDetailFragment @Inject constructor(
backgroundColor = if (highlight) {
ThemeUtils.getColor(requireContext(), R.attr.colorError)
} else {
- ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary)
+ ThemeUtils.getColor(requireContext(), R.attr.vctr_unread_background)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index 662af3d546..c638666cd7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -1189,7 +1189,7 @@ class TimelineFragment @Inject constructor(
val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong())
getString(R.string.voice_message_reply_content, formattedDuration)
} else if (messageContent is MessagePollContent) {
- messageContent.pollCreationInfo?.question?.question
+ messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
} else {
messageContent?.body ?: ""
}
@@ -2165,7 +2165,7 @@ class TimelineFragment @Inject constructor(
timelineViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
}
is EventSharedAction.Edit -> {
- if (action.eventType == EventType.POLL_START) {
+ if (action.eventType in EventType.POLL_START) {
navigator.openCreatePoll(requireContext(), timelineArgs.roomId, action.eventId, PollMode.EDIT)
} else if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString()))
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index 43fa9e0c2e..fb47fb5136 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
@@ -415,7 +416,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
partialState = partialState,
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
callback = callback,
- eventsGroup = timelineEventsGroup
+ eventsGroup = timelineEventsGroup,
+ reactionsSummaryEvents = ReactionsSummaryEvents(
+ onAddMoreClicked = { reactionListFactory.onAddMoreClicked(callback, event) },
+ onShowLessClicked = { reactionListFactory.onShowLessClicked(event.eventId) },
+ onShowMoreClicked = { reactionListFactory.onShowMoreClicked(event.eventId) }
+ )
)
modelCache[position] = buildCacheItem(params)
numberOfEventsToBuild++
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 5575d9b7f6..b99dbcc220 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -181,7 +181,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
} else {
when (timelineEvent.root.getClearType()) {
EventType.MESSAGE,
- EventType.STICKER -> {
+ EventType.STICKER -> {
val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val html = messageContent.formattedBody
@@ -207,13 +207,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
EventType.CALL_INVITE,
EventType.CALL_CANDIDATES,
EventType.CALL_HANGUP,
- EventType.CALL_ANSWER -> {
+ EventType.CALL_ANSWER -> {
noticeEventFormatter.format(timelineEvent, room?.roomSummary()?.isDirect.orFalse())
}
- EventType.POLL_START -> {
- timelineEvent.root.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question ?: ""
+ in EventType.POLL_START -> {
+ timelineEvent.root.getClearContent().toModel(catchError = true)
+ ?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: ""
}
- else -> null
+ else -> null
}
}
} catch (failure: Throwable) {
@@ -373,7 +374,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
if (canRedact(timelineEvent, actionPermissions)) {
- if (timelineEvent.root.getClearType() == EventType.POLL_START) {
+ if (timelineEvent.root.getClearType() in EventType.POLL_START) {
add(EventSharedAction.Redact(
eventId,
askForReason = informationData.senderId != session.myUserId,
@@ -425,7 +426,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun canReply(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
// Only EventType.MESSAGE and EventType.POLL_START event types are supported for the moment
- if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.POLL_START)) return false
+ if (event.root.getClearType() !in EventType.POLL_START + EventType.MESSAGE) return false
if (!actionPermissions.canSendMessage) return false
return when (messageContent?.msgType) {
MessageType.MSGTYPE_TEXT,
@@ -511,7 +512,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
- if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.POLL_START)) return false
+ if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START) return false
// Message sent by the current user can always be redacted
if (event.root.senderId == session.myUserId) return true
// Check permission for messages sent by other users
@@ -526,13 +527,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun canViewReactions(event: TimelineEvent): Boolean {
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
- if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.POLL_START)) return false
+ if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START) return false
return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
}
private fun canEdit(event: TimelineEvent, myUserId: String, actionPermissions: ActionPermissions): Boolean {
// Only event of type EventType.MESSAGE and EventType.POLL_START are supported for the moment
- if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.POLL_START)) return false
+ if (event.root.getClearType() !in listOf(EventType.MESSAGE) + EventType.POLL_START) return false
if (!actionPermissions.canSendMessage) return false
// TODO if user is admin or moderator
val messageContent = event.root.getClearContent().toModel()
@@ -578,13 +579,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
private fun canEndPoll(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
- return event.root.getClearType() == EventType.POLL_START &&
+ return event.root.getClearType() in EventType.POLL_START &&
canRedact(event, actionPermissions) &&
event.annotations?.pollResponseSummary?.closedTime == null
}
private fun canEditPoll(event: TimelineEvent): Boolean {
- return event.root.getClearType() == EventType.POLL_START &&
+ return event.root.getClearType() in EventType.POLL_START &&
event.annotations?.pollResponseSummary?.closedTime == null &&
event.annotations?.pollResponseSummary?.aggregatedContent?.totalVotes ?: 0 == 0
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
index 0161f0b55d..a5d6f75387 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt
@@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttrib
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -61,7 +62,8 @@ class CallItemFactory @Inject constructor(
highlight = params.isHighlighted,
informationData = informationData,
isStillActive = callEventGrouper.isInCall(),
- formattedDuration = callEventGrouper.formattedDuration()
+ formattedDuration = callEventGrouper.formattedDuration(),
+ reactionsSummaryEvents = params.reactionsSummaryEvents
)
} else {
null
@@ -78,7 +80,8 @@ class CallItemFactory @Inject constructor(
highlight = params.isHighlighted,
informationData = informationData,
isStillActive = callEventGrouper.isRinging(),
- formattedDuration = callEventGrouper.formattedDuration()
+ formattedDuration = callEventGrouper.formattedDuration(),
+ reactionsSummaryEvents = params.reactionsSummaryEvents
)
} else {
null
@@ -94,7 +97,8 @@ class CallItemFactory @Inject constructor(
highlight = params.isHighlighted,
informationData = informationData,
isStillActive = false,
- formattedDuration = callEventGrouper.formattedDuration()
+ formattedDuration = callEventGrouper.formattedDuration(),
+ reactionsSummaryEvents = params.reactionsSummaryEvents
)
}
EventType.CALL_HANGUP -> {
@@ -111,7 +115,8 @@ class CallItemFactory @Inject constructor(
highlight = params.isHighlighted,
informationData = informationData,
isStillActive = false,
- formattedDuration = callEventGrouper.formattedDuration()
+ formattedDuration = callEventGrouper.formattedDuration(),
+ reactionsSummaryEvents = params.reactionsSummaryEvents
)
}
else -> null
@@ -133,10 +138,11 @@ class CallItemFactory @Inject constructor(
highlight: Boolean,
isStillActive: Boolean,
formattedDuration: String,
- callback: TimelineEventController.Callback?
+ callback: TimelineEventController.Callback?,
+ reactionsSummaryEvents: ReactionsSummaryEvents?
): CallTileTimelineItem? {
val userOfInterest = roomSummary.toMatrixItem()
- val attributes = messageItemAttributesFactory.create(null, informationData, callback).let {
+ val attributes = messageItemAttributesFactory.create(null, informationData, callback, reactionsSummaryEvents).let {
CallTileTimelineItem.Attributes(
callId = callId,
callKind = callKind,
@@ -151,7 +157,8 @@ class CallItemFactory @Inject constructor(
readReceiptsCallback = it.readReceiptsCallback,
userOfInterest = userOfInterest,
callback = callback,
- isStillActive = isStillActive
+ isStillActive = isStillActive,
+ reactionsSummaryEvents = reactionsSummaryEvents
)
}
return CallTileTimelineItem_()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
index bc2497392c..2b04600af2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
@@ -111,7 +111,9 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
messageContent = event.root.content.toModel(),
informationData = informationData,
callback = params.callback,
- threadDetails = threadDetails)
+ threadDetails = threadDetails,
+ reactionsSummaryEvents = params.reactionsSummaryEvents
+ )
return MessageTextItem_()
.layout(informationData.messageLayout.layoutRes)
.leftGuideline(avatarSizeProvider.leftGuideline)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
index 0ff786d504..0cb86a5c1c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
@@ -46,7 +46,7 @@ class EncryptionItemFactory @Inject constructor(
}
val algorithm = event.root.content.toModel()?.algorithm
val informationData = informationDataFactory.create(params)
- val attributes = messageItemAttributesFactory.create(null, informationData, params.callback)
+ val attributes = messageItemAttributesFactory.create(null, informationData, params.callback, params.reactionsSummaryEvents)
val isSafeAlgorithm = algorithm == MXCRYPTO_ALGORITHM_MEGOLM
val title: String
@@ -80,7 +80,8 @@ class EncryptionItemFactory @Inject constructor(
itemClickListener = attributes.itemClickListener,
itemLongClickListener = attributes.itemLongClickListener,
reactionPillCallback = attributes.reactionPillCallback,
- readReceiptsCallback = attributes.readReceiptsCallback
+ readReceiptsCallback = attributes.readReceiptsCallback,
+ reactionsSummaryEvents = attributes.reactionsSummaryEvents
)
)
.highlighted(params.isHighlighted)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index aa1758dd6c..659cef2b37 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -155,7 +155,7 @@ class MessageItemFactory @Inject constructor(
if (event.root.isRedacted()) {
// message is redacted
- val attributes = messageItemAttributesFactory.create(null, informationData, callback, threadDetails)
+ val attributes = messageItemAttributesFactory.create(null, informationData, callback, params.reactionsSummaryEvents)
return buildRedactedItem(attributes, highlight)
}
@@ -177,7 +177,7 @@ class MessageItemFactory @Inject constructor(
}
// always hide summary when we are on thread timeline
- val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, threadDetails)
+ val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
// val all = event.root.toContent()
// val ev = all.toModel()
@@ -247,7 +247,7 @@ class MessageItemFactory @Inject constructor(
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
val isPollSent = informationData.sendState.isSent()
- val isPollUndisclosed = pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED
+ val isPollUndisclosed = pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED_UNSTABLE
val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
when {
@@ -262,13 +262,13 @@ class MessageItemFactory @Inject constructor(
}
}
- pollContent.pollCreationInfo?.answers?.forEach { option ->
+ pollContent.getBestPollCreationInfo()?.answers?.forEach { option ->
val voteSummary = pollResponseSummary?.votes?.get(option.id)
val isMyVote = pollResponseSummary?.myVote == option.id
val voteCount = voteSummary?.total ?: 0
val votePercentage = voteSummary?.percentage ?: 0.0
val optionId = option.id ?: ""
- val optionAnswer = option.answer ?: ""
+ val optionAnswer = option.getBestAnswer() ?: ""
optionViewStates.add(
if (!isPollSent) {
@@ -291,7 +291,7 @@ class MessageItemFactory @Inject constructor(
)
}
- val question = pollContent.pollCreationInfo?.question?.question ?: ""
+ val question = pollContent.getBestPollCreationInfo()?.question?.getBestQuestion() ?: ""
return PollItem_()
.attributes(attributes)
@@ -413,7 +413,8 @@ class MessageItemFactory @Inject constructor(
itemClickListener = attributes.itemClickListener,
reactionPillCallback = attributes.reactionPillCallback,
readReceiptsCallback = attributes.readReceiptsCallback,
- emojiTypeFace = attributes.emojiTypeFace
+ emojiTypeFace = attributes.emojiTypeFace,
+ reactionsSummaryEvents = attributes.reactionsSummaryEvents
)
)
.callback(callback)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
index e66dd4b043..ed3cc8df53 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
@@ -38,8 +38,7 @@ class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: Av
.map {
ReadReceiptData(it.roomMember.userId, it.roomMember.avatarUrl, it.roomMember.displayName, it.originServerTs)
}
- .toList()
-
+ .sortedByDescending { it.timestamp }
return ReadReceiptsItem_()
.id("read_receipts_$eventId")
.eventId(eventId)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index b41e1d8f25..f9d2613e27 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -94,7 +94,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
when (event.root.getClearType()) {
// Message itemsX
EventType.STICKER,
- EventType.POLL_START,
+ in EventType.POLL_START,
EventType.MESSAGE -> messageItemFactory.create(params)
EventType.REDACTION,
EventType.KEY_VERIFICATION_ACCEPT,
@@ -107,8 +107,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.CALL_SELECT_ANSWER,
EventType.CALL_NEGOTIATE,
EventType.REACTION,
- EventType.POLL_RESPONSE,
- EventType.POLL_END -> noticeItemFactory.create(params)
+ in EventType.POLL_RESPONSE,
+ in EventType.POLL_END -> noticeItemFactory.create(params)
// Calls
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
index 46ae01a794..7c02b6f058 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
@@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.factory
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
data class TimelineItemFactoryParams(
@@ -29,6 +30,7 @@ data class TimelineItemFactoryParams(
val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(),
val lastSentEventIdWithoutReadReceipts: String? = null,
val callback: TimelineEventController.Callback? = null,
+ val reactionsSummaryEvents: ReactionsSummaryEvents? = null,
val eventsGroup: TimelineEventsGroup? = null
) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
index bdc6906593..16cf73cbb0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
@@ -71,10 +71,10 @@ class VerificationItemFactory @Inject constructor(
// If it's not a request ignore this event
// if (refEvent.root.getClearContent().toModel() == null) return ignoredConclusion(event, highlight, callback)
- val referenceInformationData = messageInformationDataFactory.create(TimelineItemFactoryParams(refEvent))
+ val referenceInformationData = messageInformationDataFactory.create(TimelineItemFactoryParams(event = refEvent))
val informationData = messageInformationDataFactory.create(params)
- val attributes = messageItemAttributesFactory.create(null, informationData, params.callback)
+ val attributes = messageItemAttributesFactory.create(null, informationData, params.callback, params.reactionsSummaryEvents)
when (event.root.getClearType()) {
EventType.KEY_VERIFICATION_CANCEL -> {
@@ -100,7 +100,8 @@ class VerificationItemFactory @Inject constructor(
itemClickListener = attributes.itemClickListener,
itemLongClickListener = attributes.itemLongClickListener,
reactionPillCallback = attributes.reactionPillCallback,
- readReceiptsCallback = attributes.readReceiptsCallback
+ readReceiptsCallback = attributes.readReceiptsCallback,
+ reactionsSummaryEvents = attributes.reactionsSummaryEvents
)
)
.highlighted(params.isHighlighted)
@@ -133,7 +134,8 @@ class VerificationItemFactory @Inject constructor(
itemClickListener = attributes.itemClickListener,
itemLongClickListener = attributes.itemLongClickListener,
reactionPillCallback = attributes.reactionPillCallback,
- readReceiptsCallback = attributes.readReceiptsCallback
+ readReceiptsCallback = attributes.readReceiptsCallback,
+ reactionsSummaryEvents = attributes.reactionsSummaryEvents
)
)
.highlighted(params.isHighlighted)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt
index a08383315c..647b34c626 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt
@@ -88,7 +88,8 @@ class WidgetItemFactory @Inject constructor(
userOfInterest = userOfInterest,
callback = params.callback,
isStillActive = isCallStillActive,
- formattedDuration = ""
+ formattedDuration = "",
+ reactionsSummaryEvents = params.reactionsSummaryEvents
)
return CallTileTimelineItem_()
.attributes(attributes)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
index d5f3a74e4e..1c339e6cf4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
@@ -120,14 +120,14 @@ class DisplayableEventFormatter @Inject constructor(
EventType.CALL_CANDIDATES -> {
span { }
}
- EventType.POLL_START -> {
- timelineEvent.root.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question
+ in EventType.POLL_START -> {
+ timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion()
?: stringProvider.getString(R.string.sent_a_poll)
}
- EventType.POLL_RESPONSE -> {
+ in EventType.POLL_RESPONSE -> {
stringProvider.getString(R.string.poll_response_room_list_preview)
}
- EventType.POLL_END -> {
+ in EventType.POLL_END -> {
stringProvider.getString(R.string.poll_end_room_list_preview)
}
else -> {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
index c7be395693..a20c1e5f97 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
@@ -106,8 +106,8 @@ class NoticeEventFormatter @Inject constructor(
EventType.STATE_SPACE_PARENT,
EventType.REDACTION,
EventType.STICKER,
- EventType.POLL_RESPONSE,
- EventType.POLL_END -> formatDebug(timelineEvent.root)
+ in EventType.POLL_RESPONSE,
+ in EventType.POLL_END -> formatDebug(timelineEvent.root)
else -> {
Timber.v("Type $type not handled by this formatter")
null
@@ -196,8 +196,8 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatDebug(event: Event): CharSequence {
- val threadPrefix = if (event.isThread()) "thread" else ""
- return "Debug: $threadPrefix event type \"${event.getClearType()}\""
+ val threadPrefix = if (event.isThread()) "thread" else ""
+ return "Debug: $threadPrefix event type \"${event.getClearType()}\""
}
private fun formatRoomCreateEvent(event: Event, isDm: Boolean): CharSequence? {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
index 0cf30c8c01..7262284c95 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
@@ -19,7 +19,9 @@ package im.vector.app.features.home.room.detail.timeline.helper
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
+import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import im.vector.app.R
@@ -37,7 +39,8 @@ class LocationPinProvider @Inject constructor(
private val context: Context,
private val activeSessionHolder: ActiveSessionHolder,
private val dimensionConverter: DimensionConverter,
- private val avatarRenderer: AvatarRenderer
+ private val avatarRenderer: AvatarRenderer,
+ private val matrixItemColorProvider: MatrixItemColorProvider
) {
private val cache = mutableMapOf()
@@ -61,35 +64,42 @@ class LocationPinProvider @Inject constructor(
return
}
- activeSessionHolder.getActiveSession().getUser(userId)?.toMatrixItem()?.let {
- val size = dimensionConverter.dpToPx(44)
- avatarRenderer.render(glideRequests, it, object : CustomTarget(size, size) {
- override fun onResourceReady(resource: Drawable, transition: Transition?) {
- Timber.d("## Location: onResourceReady")
- val pinDrawable = createPinDrawable(resource)
- cache[userId] = pinDrawable
- callback(pinDrawable)
- }
+ activeSessionHolder
+ .getActiveSession()
+ .getUser(userId)
+ ?.toMatrixItem()
+ ?.let { userItem ->
+ val size = dimensionConverter.dpToPx(44)
+ val bgTintColor = matrixItemColorProvider.getColor(userItem)
+ avatarRenderer.render(glideRequests, userItem, object : CustomTarget(size, size) {
+ override fun onResourceReady(resource: Drawable, transition: Transition?) {
+ Timber.d("## Location: onResourceReady")
+ val pinDrawable = createPinDrawable(resource, bgTintColor)
+ cache[userId] = pinDrawable
+ callback(pinDrawable)
+ }
- override fun onLoadCleared(placeholder: Drawable?) {
- // Is it possible? Put placeholder instead?
- // FIXME The doc says it has to be implemented and should free resources
- Timber.d("## Location: onLoadCleared")
- }
+ override fun onLoadCleared(placeholder: Drawable?) {
+ // Is it possible? Put placeholder instead?
+ // FIXME The doc says it has to be implemented and should free resources
+ Timber.d("## Location: onLoadCleared")
+ }
- override fun onLoadFailed(errorDrawable: Drawable?) {
- Timber.w("## Location: onLoadFailed")
- errorDrawable ?: return
- val pinDrawable = createPinDrawable(errorDrawable)
- cache[userId] = pinDrawable
- callback(pinDrawable)
+ override fun onLoadFailed(errorDrawable: Drawable?) {
+ Timber.w("## Location: onLoadFailed")
+ errorDrawable ?: return
+ val pinDrawable = createPinDrawable(errorDrawable, bgTintColor)
+ cache[userId] = pinDrawable
+ callback(pinDrawable)
+ }
+ })
}
- })
- }
}
- private fun createPinDrawable(drawable: Drawable): Drawable {
+ private fun createPinDrawable(drawable: Drawable, @ColorInt bgTintColor: Int): Drawable {
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
+ // use mutate on drawable to avoid sharing the color when we have multiple different user pins
+ DrawableCompat.setTint(bgUserPin.mutate(), bgTintColor)
val layerDrawable = LayerDrawable(arrayOf(bgUserPin, drawable))
val horizontalInset = dimensionConverter.dpToPx(4)
val topInset = dimensionConverter.dpToPx(4)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 59b39d17ef..97b3a8f445 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -93,7 +93,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
avatarUrl = event.senderInfo.avatarUrl,
memberName = event.senderInfo.disambiguatedDisplayName,
messageLayout = messageLayout,
- reactionsSummary = reactionsSummaryFactory.create(event, params.callback),
+ reactionsSummary = reactionsSummaryFactory.create(event),
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
PollResponseData(
myVote = it.aggregatedContent?.myVote,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt
index 845b765101..426561054b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt
@@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
import org.matrix.android.sdk.api.session.threads.ThreadDetails
import javax.inject.Inject
@@ -38,6 +39,7 @@ class MessageItemAttributesFactory @Inject constructor(
fun create(messageContent: Any?,
informationData: MessageInformationData,
callback: TimelineEventController.Callback?,
+ reactionsSummaryEvents: ReactionsSummaryEvents?,
threadDetails: ThreadDetails? = null): AbsMessageItem.Attributes {
return AbsMessageItem.Attributes(
avatarSize = avatarSizeProvider.avatarSize,
@@ -60,6 +62,7 @@ class MessageItemAttributesFactory @Inject constructor(
emojiTypeFace = emojiCompatFontProvider.typeface,
decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message),
threadDetails = threadDetails,
+ reactionsSummaryEvents = reactionsSummaryEvents,
areThreadMessagesEnabled = preferencesProvider.areThreadMessagesEnabled()
)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt
index fcc98ff729..3ba5997ee3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt
@@ -34,7 +34,7 @@ class ReactionsSummaryFactory @Inject constructor() {
return eventsRequestingBuild.remove(event.eventId)
}
- fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): ReactionsSummaryData {
+ fun create(event: TimelineEvent): ReactionsSummaryData {
val eventId = event.eventId
val showAllStates = showAllReactionsByEvent.contains(eventId)
val reactions = event.annotations?.reactionsSummary
@@ -43,21 +43,24 @@ class ReactionsSummaryFactory @Inject constructor() {
}
return ReactionsSummaryData(
reactions = reactions,
- showAll = showAllStates,
- onShowMoreClicked = {
- showAllReactionsByEvent.add(eventId)
- onRequestBuild(eventId)
- },
- onShowLessClicked = {
- showAllReactionsByEvent.remove(eventId)
- onRequestBuild(eventId)
- },
- onAddMoreClicked = {
- callback?.onAddMoreReaction(event)
- }
+ showAll = showAllStates
)
}
+ fun onAddMoreClicked(callback: TimelineEventController.Callback?, event: TimelineEvent) {
+ callback?.onAddMoreReaction(event)
+ }
+
+ fun onShowMoreClicked(eventId: String) {
+ showAllReactionsByEvent.add(eventId)
+ onRequestBuild(eventId)
+ }
+
+ fun onShowLessClicked(eventId: String) {
+ showAllReactionsByEvent.remove(eventId)
+ onRequestBuild(eventId)
+ }
+
private fun onRequestBuild(eventId: String) {
eventsRequestingBuild.add(eventId)
onRequestBuild?.invoke()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
index 53a9fbbaea..96a2ca4609 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
@@ -50,9 +50,8 @@ object TimelineDisplayableEvents {
EventType.STATE_ROOM_TOMBSTONE,
EventType.STATE_ROOM_JOIN_RULES,
EventType.KEY_VERIFICATION_DONE,
- EventType.KEY_VERIFICATION_CANCEL,
- EventType.POLL_START
- )
+ EventType.KEY_VERIFICATION_CANCEL
+ ) + EventType.POLL_START
}
fun TimelineEvent.canBeMerged(): Boolean {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 430e0970bb..4f08c9d05f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -121,18 +121,24 @@ abstract class AbsBaseMessageItem : BaseEventItem
val showReactionsTextView = createReactionTextView(holder)
if (reactionsSummary.showAll) {
showReactionsTextView.setText(R.string.message_reaction_show_less)
- showReactionsTextView.onClick { reactionsSummary.onShowLessClicked() }
+ showReactionsTextView.onClick {
+ baseAttributes.reactionsSummaryEvents?.onShowLessClicked?.invoke()
+ }
} else {
val moreCount = reactions.count() - MAX_REACTIONS_TO_SHOW
showReactionsTextView.text = holder.view.resources.getQuantityString(R.plurals.message_reaction_show_more, moreCount, moreCount)
- showReactionsTextView.onClick { reactionsSummary.onShowMoreClicked() }
+ showReactionsTextView.onClick {
+ baseAttributes.reactionsSummaryEvents?.onShowMoreClicked?.invoke()
+ }
}
holder.reactionsContainer.addView(showReactionsTextView)
}
val addMoreReactionsTextView = createReactionTextView(holder)
addMoreReactionsTextView.text = holder.view.context.getDrawableAsSpannable(R.drawable.ic_add_reaction_small)
- addMoreReactionsTextView.onClick { reactionsSummary.onAddMoreClicked() }
+ addMoreReactionsTextView.onClick {
+ baseAttributes.reactionsSummaryEvents?.onAddMoreClicked?.invoke()
+ }
holder.reactionsContainer.addView(addMoreReactionsTextView)
holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
}
@@ -180,6 +186,7 @@ abstract class AbsBaseMessageItem : BaseEventItem
// val memberClickListener: ClickListener?
val reactionPillCallback: TimelineEventController.ReactionPillCallback?
+ val reactionsSummaryEvents: ReactionsSummaryEvents?
// val avatarCallback: TimelineEventController.AvatarCallback?
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback?
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
index 9e8f86c26e..2fac9df665 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -105,6 +105,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem
} else {
holder.timeView.isVisible = false
}
+
// Render send state indicator
holder.sendStateImageView.render(attributes.informationData.sendStateDecoration)
holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA
@@ -184,7 +185,8 @@ abstract class AbsMessageItem : AbsBaseMessageItem
val emojiTypeFace: Typeface? = null,
val decryptionErrorMessage: String? = null,
val threadDetails: ThreadDetails? = null,
- val areThreadMessagesEnabled: Boolean = false
+ val areThreadMessagesEnabled: Boolean = false,
+ override val reactionsSummaryEvents: ReactionsSummaryEvents? = null,
) : AbsBaseMessageItem.Attributes {
// Have to override as it's used to diff epoxy items
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
index 6db0b0c380..ea130901b1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
@@ -263,7 +263,8 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem? = null,
- val showAll: Boolean = false,
+ val showAll: Boolean = false
+) : Parcelable
+
+data class ReactionsSummaryEvents(
val onShowMoreClicked: () -> Unit,
val onShowLessClicked: () -> Unit,
val onAddMoreClicked: () -> Unit
-) : Parcelable
+)
@Parcelize
data class ReactionInfoData(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
index fdde087b44..2d9119f14c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
@@ -93,7 +93,8 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem {
marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
@@ -141,22 +173,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
}
}
+ private fun TimelineMessageLayout.Bubble.setAdditionalTopSpace() = apply {
+ views.additionalTopSpace.isVisible = addTopMargin
+ }
+
private fun TimelineMessageLayout.Bubble.CornersRadius.toFloatArray(): FloatArray {
return floatArrayOf(topStartRadius, topStartRadius, topEndRadius, topEndRadius, bottomEndRadius, bottomEndRadius, bottomStartRadius, bottomStartRadius)
}
-
- private fun updateDrawables(messageLayout: TimelineMessageLayout.Bubble) {
- val shapeAppearanceModel = messageLayout.cornersRadius.shapeAppearanceModel()
- bubbleDrawable.apply {
- this.shapeAppearanceModel = shapeAppearanceModel
- this.fillColor = if (messageLayout.isPseudoBubble) {
- ColorStateList.valueOf(Color.TRANSPARENT)
- } else {
- val backgroundColorAttr = if (isIncoming) R.attr.vctr_message_bubble_inbound else R.attr.vctr_message_bubble_outbound
- val backgroundColor = ThemeUtils.getColor(context, backgroundColorAttr)
- ColorStateList.valueOf(backgroundColor)
- }
- }
- rippleMaskDrawable.shapeAppearanceModel = shapeAppearanceModel
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt
index bec3ccc643..6057072e41 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomCategoryItem.kt
@@ -33,6 +33,7 @@ import im.vector.app.features.themes.ThemeUtils
abstract class RoomCategoryItem : VectorEpoxyModel() {
@EpoxyAttribute lateinit var title: String
+ @EpoxyAttribute var itemCount: Int = 0
@EpoxyAttribute var expanded: Boolean = false
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false
@@ -46,14 +47,16 @@ abstract class RoomCategoryItem : VectorEpoxyModel() {
DrawableCompat.setTint(it, tintColor)
}
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
- holder.titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
holder.titleView.text = title
+ holder.counterView.text = itemCount.takeIf { it > 0 }?.toString().orEmpty()
+ holder.counterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
holder.rootView.onClick(listener)
}
class Holder : VectorEpoxyHolder() {
val unreadCounterBadgeView by bind(R.id.roomCategoryUnreadCounterBadgeView)
val titleView by bind(R.id.roomCategoryTitleView)
+ val counterView by bind(R.id.roomCategoryCounterView)
val rootView by bind(R.id.roomCategoryRootView)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
index 28849204c4..4265eebe62 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
@@ -23,6 +23,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
@@ -50,8 +52,10 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -287,6 +291,7 @@ class RoomListFragment @Inject constructor(
))
checkEmptyState()
}
+ observeItemCount(section, sectionAdapter)
section.notificationCount.observe(viewLifecycleOwner) { counts ->
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
notificationCount = counts.totalCount,
@@ -310,6 +315,7 @@ class RoomListFragment @Inject constructor(
))
checkEmptyState()
}
+ observeItemCount(section, sectionAdapter)
section.isExpanded.observe(viewLifecycleOwner) { _ ->
refreshCollapseStates()
}
@@ -326,6 +332,7 @@ class RoomListFragment @Inject constructor(
isLoading = false))
checkEmptyState()
}
+ observeItemCount(section, sectionAdapter)
section.notificationCount.observe(viewLifecycleOwner) { counts ->
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
notificationCount = counts.totalCount,
@@ -373,6 +380,18 @@ class RoomListFragment @Inject constructor(
}
}
+ private fun observeItemCount(section: RoomsSection, sectionAdapter: SectionHeaderAdapter) {
+ lifecycleScope.launch {
+ section.itemCount
+ .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
+ .collect { count ->
+ sectionAdapter.updateSection(
+ sectionAdapter.roomsSectionData.copy(itemCount = count)
+ )
+ }
+ }
+ }
+
private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
when (quickAction) {
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
index 77f61149f8..ec7915ba34 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
@@ -28,6 +28,7 @@ import im.vector.app.features.invite.showInvites
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -72,7 +73,18 @@ class RoomListSectionBuilderGroup(
session.getFilteredPagedRoomSummariesLive(qpm)
.let { updatableFilterLivePageResult ->
onUpdatable(updatableFilterLivePageResult)
- sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList))
+
+ val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
+ .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
+ .distinctUntilChanged()
+
+ sections.add(
+ RoomsSection(
+ sectionName = name,
+ livePages = updatableFilterLivePageResult.livePagedList,
+ itemCount = itemCountFlow
+ )
+ )
}
}
)
@@ -109,9 +121,7 @@ class RoomListSectionBuilderGroup(
.onEach { groupingMethod ->
val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
activeGroupAwareQueries.onEach { updater ->
- updater.updateQuery { query ->
- query.copy(activeGroupId = selectedGroupId)
- }
+ updater.queryParams = updater.queryParams.copy(activeGroupId = selectedGroupId)
}
}.launchIn(coroutineScope)
@@ -265,7 +275,8 @@ class RoomListSectionBuilderGroup(
RoomsSection(
sectionName = name,
livePages = livePagedList,
- notifyOfLocalEcho = notifyOfLocalEcho
+ notifyOfLocalEcho = notifyOfLocalEcho,
+ itemCount = session.getRoomCountFlow(roomQueryParams)
)
)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
index 296e61690b..f82dbd43e1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
@@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
@@ -91,7 +92,18 @@ class RoomListSectionBuilderSpace(
session.getFilteredPagedRoomSummariesLive(qpm)
.let { updatableFilterLivePageResult ->
onUpdatable(updatableFilterLivePageResult)
- sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList))
+
+ val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
+ .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
+ .distinctUntilChanged()
+
+ sections.add(
+ RoomsSection(
+ sectionName = name,
+ livePages = updatableFilterLivePageResult.livePagedList,
+ itemCount = itemCountFlow
+ )
+ )
}
}
)
@@ -261,7 +273,8 @@ class RoomListSectionBuilderSpace(
RoomsSection(
sectionName = stringProvider.getString(R.string.suggested_header),
liveSuggested = liveSuggestedRooms,
- notifyOfLocalEcho = false
+ notifyOfLocalEcho = false,
+ itemCount = suggestedRoomsFlow.map { suggestions -> suggestions.size }
)
)
}
@@ -338,11 +351,9 @@ class RoomListSectionBuilderSpace(
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) {
- it.updateQuery {
- it.copy(
- activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
- )
- }
+ it.queryParams = roomQueryParams.copy(
+ activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
+ )
}
})
}
@@ -350,17 +361,13 @@ class RoomListSectionBuilderSpace(
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
override fun updateForSpaceId(roomId: String?) {
if (roomId != null) {
- it.updateQuery {
- it.copy(
- activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
- )
- }
+ it.queryParams = roomQueryParams.copy(
+ activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
+ )
} else {
- it.updateQuery {
- it.copy(
- activeSpaceFilter = ActiveSpaceFilter.None
- )
- }
+ it.queryParams = roomQueryParams.copy(
+ activeSpaceFilter = ActiveSpaceFilter.None
+ )
}
}
})
@@ -390,11 +397,19 @@ class RoomListSectionBuilderSpace(
.flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
+ val itemCountFlow = livePagedList.asFlow()
+ .flatMapLatest {
+ val queryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
+ session.getRoomCountFlow(queryParams)
+ }
+ .distinctUntilChanged()
+
sections.add(
RoomsSection(
sectionName = name,
livePages = livePagedList,
- notifyOfLocalEcho = notifyOfLocalEcho
+ notifyOfLocalEcho = notifyOfLocalEcho,
+ itemCount = itemCountFlow
)
)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
index 4a81a8b526..ec8b01876b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
@@ -192,8 +192,8 @@ class RoomListViewModel @AssistedInject constructor(
roomFilter = action.filter
)
}
- updatableQuery?.updateQuery {
- it.copy(
+ updatableQuery?.apply {
+ queryParams = queryParams.copy(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED)
)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt
index 5eaae262a6..357df5ecd3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.paging.PagedList
+import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@@ -29,6 +30,7 @@ data class RoomsSection(
val liveList: LiveData>? = null,
val liveSuggested: LiveData? = null,
val isExpanded: MutableLiveData = MutableLiveData(true),
+ val itemCount: Flow,
val notificationCount: MutableLiveData = MutableLiveData(RoomAggregateNotificationCount(0, 0)),
val notifyOfLocalEcho: Boolean = false
)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
index 560e0d00a3..2e6436d21d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
@@ -33,6 +33,7 @@ class SectionHeaderAdapter constructor(
data class RoomsSectionData(
val name: String,
+ val itemCount: Int = 0,
val isExpanded: Boolean = true,
val notificationCount: Int = 0,
val isHighlighted: Boolean = false,
@@ -85,8 +86,9 @@ class SectionHeaderAdapter constructor(
val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
DrawableCompat.setTint(it, tintColor)
}
+ binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
+ binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty()
binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted))
- binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
}
companion object {
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
index a7e93a3f6c..b1033f2797 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
@@ -30,6 +30,10 @@ import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentLocationSharingBinding
+import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
+import im.vector.app.features.location.option.LocationSharingOption
+import org.matrix.android.sdk.api.util.MatrixItem
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -37,7 +41,9 @@ import javax.inject.Inject
* We should consider using SupportMapFragment for a out of the box lifecycle handling
*/
class LocationSharingFragment @Inject constructor(
- private val urlMapProvider: UrlMapProvider
+ private val urlMapProvider: UrlMapProvider,
+ private val avatarRenderer: AvatarRenderer,
+ private val matrixItemColorProvider: MatrixItemColorProvider
) : VectorBaseFragment() {
private val viewModel: LocationSharingViewModel by fragmentViewModel()
@@ -45,6 +51,8 @@ class LocationSharingFragment @Inject constructor(
// Keep a ref to handle properly the onDestroy callback
private var mapView: WeakReference? = null
+ private var hasRenderedUserAvatar = false
+
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
return FragmentLocationSharingBinding.inflate(inflater, container, false)
}
@@ -59,9 +67,7 @@ class LocationSharingFragment @Inject constructor(
views.mapView.initialize(urlMapProvider.getMapUrl())
}
- views.shareLocationContainer.debouncedClicks {
- viewModel.handle(LocationSharingAction.OnShareLocation)
- }
+ initOptionsPicker()
viewModel.observeViewEvents {
when (it) {
@@ -107,6 +113,12 @@ class LocationSharingFragment @Inject constructor(
super.onDestroy()
}
+ override fun invalidate() = withState(viewModel) { state ->
+ views.mapView.render(state.toMapState())
+ views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null
+ updateUserAvatar(state.userItem)
+ }
+
private fun handleLocationNotAvailableError() {
MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.location_not_available_dialog_title)
@@ -118,8 +130,28 @@ class LocationSharingFragment @Inject constructor(
.show()
}
- override fun invalidate() = withState(viewModel) { state ->
- views.mapView.render(state.toMapState())
- views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null
+ private fun initOptionsPicker() {
+ // TODO
+ // change the options dynamically depending on the current chosen location
+ views.shareLocationOptionsPicker.render(LocationSharingOption.USER_CURRENT)
+ views.shareLocationOptionsPicker.optionPinned.debouncedClicks {
+ // TODO
+ }
+ views.shareLocationOptionsPicker.optionUserCurrent.debouncedClicks {
+ viewModel.handle(LocationSharingAction.OnShareLocation)
+ }
+ views.shareLocationOptionsPicker.optionUserLive.debouncedClicks {
+ // TODO
+ }
+ }
+
+ private fun updateUserAvatar(userItem: MatrixItem.UserItem?) {
+ userItem?.takeUnless { hasRenderedUserAvatar }
+ ?.let {
+ hasRenderedUserAvatar = true
+ avatarRenderer.render(it, views.shareLocationOptionsPicker.optionUserCurrent.iconView)
+ val tintColor = matrixItemColorProvider.getColor(it)
+ views.shareLocationOptionsPicker.optionUserCurrent.setIconBackgroundTint(tintColor)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
index f4e1fd0281..989ec255e5 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
@@ -26,6 +26,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.util.toMatrixItem
class LocationSharingViewModel @AssistedInject constructor(
@Assisted private val initialState: LocationSharingViewState,
@@ -45,9 +46,14 @@ class LocationSharingViewModel @AssistedInject constructor(
init {
locationTracker.start(this)
+ setUserItem()
createPin()
}
+ private fun setUserItem() {
+ setState { copy(userItem = session.getUser(session.myUserId)?.toMatrixItem()) }
+ }
+
private fun createPin() {
locationPinProvider.create(session.myUserId) {
setState {
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt
index a9a24094eb..e63206f515 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt
@@ -20,6 +20,7 @@ import android.graphics.drawable.Drawable
import androidx.annotation.StringRes
import com.airbnb.mvrx.MavericksState
import im.vector.app.R
+import org.matrix.android.sdk.api.util.MatrixItem
enum class LocationSharingMode(@StringRes val titleRes: Int) {
STATIC_SHARING(R.string.location_activity_title_static_sharing),
@@ -29,6 +30,7 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) {
data class LocationSharingViewState(
val roomId: String,
val mode: LocationSharingMode,
+ val userItem: MatrixItem.UserItem? = null,
val lastKnownLocation: LocationData? = null,
val pinDrawable: Drawable? = null
) : MavericksState {
diff --git a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
index adb5c27a02..3e4e16861e 100644
--- a/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/location/UrlMapProvider.kt
@@ -39,7 +39,7 @@ class UrlMapProvider @Inject constructor(
suspend fun getMapUrl(): String {
val upstreamMapUrl = tryOrNull { rawService.getElementWellknown(session.sessionParams) }
- ?.mapTileServerConfig
+ ?.getBestMapTileServerConfig()
?.mapStyleUrl
return upstreamMapUrl ?: fallbackMapUrl
}
diff --git a/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOption.kt b/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOption.kt
new file mode 100644
index 0000000000..ebf9bde5f6
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOption.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.location.option
+
+enum class LocationSharingOption {
+ /**
+ * Current user's location.
+ */
+ USER_CURRENT,
+
+ /**
+ * User's location during a certain amount of time.
+ */
+ USER_LIVE,
+
+ /**
+ * Static location pinned by the user.
+ */
+ PINNED
+}
diff --git a/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOptionPickerView.kt b/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOptionPickerView.kt
new file mode 100644
index 0000000000..1aea1ff613
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOptionPickerView.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.location.option
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import im.vector.app.R
+import im.vector.app.databinding.ViewLocationSharingOptionPickerBinding
+
+/**
+ * Custom view to display the location sharing option picker.
+ */
+class LocationSharingOptionPickerView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ val optionPinned: LocationSharingOptionView
+ get() = binding.locationSharingOptionPinned
+
+ val optionUserCurrent: LocationSharingOptionView
+ get() = binding.locationSharingOptionUserCurrent
+
+ val optionUserLive: LocationSharingOptionView
+ get() = binding.locationSharingOptionUserLive
+
+ private val divider1: View
+ get() = binding.locationSharingOptionsDivider1
+
+ private val divider2: View
+ get() = binding.locationSharingOptionsDivider2
+
+ private val binding = ViewLocationSharingOptionPickerBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ init {
+ applyBackground()
+ }
+
+ fun render(vararg options: LocationSharingOption) {
+ val optionsNumber = options.toSet().size
+ val isPinnedVisible = options.contains(LocationSharingOption.PINNED)
+ val isUserCurrentVisible = options.contains(LocationSharingOption.USER_CURRENT)
+ val isUserLiveVisible = options.contains(LocationSharingOption.USER_LIVE)
+
+ optionPinned.isVisible = isPinnedVisible
+ divider1.isVisible = isPinnedVisible && optionsNumber > 1
+ optionUserCurrent.isVisible = isUserCurrentVisible
+ divider2.isVisible = isUserCurrentVisible && isUserLiveVisible
+ optionUserLive.isVisible = isUserLiveVisible
+ }
+
+ private fun applyBackground() {
+ val outValue = TypedValue()
+ context.theme.resolveAttribute(
+ R.attr.colorSurface,
+ outValue,
+ true
+ )
+ binding.root.background = ContextCompat.getDrawable(
+ context,
+ outValue.resourceId
+ )
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOptionView.kt b/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOptionView.kt
new file mode 100644
index 0000000000..d11ff00261
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/option/LocationSharingOptionView.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.location.option
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.ImageView
+import androidx.annotation.ColorInt
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.setPadding
+import im.vector.app.R
+import im.vector.app.core.extensions.tintBackground
+import im.vector.app.databinding.ViewLocationSharingOptionBinding
+
+/**
+ * Custom view to display a location sharing option.
+ */
+class LocationSharingOptionView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ val iconView: ImageView
+ get() = binding.shareLocationOptionIcon
+
+ private val binding = ViewLocationSharingOptionBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ init {
+ context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.LocationSharingOptionView,
+ 0,
+ 0
+ ).run {
+ try {
+ setIcon(this)
+ setTitle(this)
+ } finally {
+ recycle()
+ }
+ }
+ }
+
+ fun setIconBackgroundTint(@ColorInt color: Int) {
+ binding.shareLocationOptionIcon.tintBackground(color)
+ }
+
+ private fun setIcon(typedArray: TypedArray) {
+ val icon = typedArray.getDrawable(R.styleable.LocationSharingOptionView_locShareIcon)
+ val background = typedArray.getDrawable(R.styleable.LocationSharingOptionView_locShareIconBackground)
+ val backgroundTint = typedArray.getColor(
+ R.styleable.LocationSharingOptionView_locShareIconBackgroundTint,
+ ContextCompat.getColor(context, android.R.color.transparent)
+ )
+ val padding = typedArray.getDimensionPixelOffset(
+ R.styleable.LocationSharingOptionView_locShareIconPadding,
+ context.resources.getDimensionPixelOffset(R.dimen.location_sharing_option_default_padding)
+ )
+ val description = typedArray.getString(R.styleable.LocationSharingOptionView_locShareIconDescription)
+
+ iconView.setImageDrawable(icon)
+ iconView.background = background
+ iconView.tintBackground(backgroundTint)
+ iconView.setPadding(padding)
+ iconView.contentDescription = description
+ }
+
+ private fun setTitle(typedArray: TypedArray) {
+ val title = typedArray.getString(R.styleable.LocationSharingOptionView_locShareTitle)
+ binding.shareLocationOptionTitle.text = title
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt
index 0cf586afea..0897eace8c 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt
@@ -191,7 +191,7 @@ class NotifiableEventResolver @Inject constructor(
}
}
- private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
+ private suspend fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
if (root.isEncrypted() && root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
// for now decrypt sync
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
index b35c110892..4f16231747 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
@@ -75,6 +75,7 @@ sealed class OnboardingAction : VectorViewModelAction {
data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction()
+ object PersonalizeProfile : OnboardingAction()
data class UpdateDisplayName(val displayName: String) : OnboardingAction()
object UpdateDisplayNameSkipped : OnboardingAction()
data class ProfilePictureSelected(val uri: Uri) : OnboardingAction()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt
index 8a09879b15..82ee48411d 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt
@@ -51,9 +51,8 @@ sealed class OnboardingViewEvents : VectorViewEvents {
object OnAccountCreated : OnboardingViewEvents()
object OnAccountSignedIn : OnboardingViewEvents()
object OnTakeMeHome : OnboardingViewEvents()
- object OnPersonalizeProfile : OnboardingViewEvents()
- object OnDisplayNameUpdated : OnboardingViewEvents()
- object OnDisplayNameSkipped : OnboardingViewEvents()
+ object OnChooseDisplayName : OnboardingViewEvents()
+ object OnChooseProfilePicture : OnboardingViewEvents()
object OnPersonalizationComplete : OnboardingViewEvents()
object OnBack : OnboardingViewEvents()
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index 413745f98c..36020fbe61 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -48,6 +48,7 @@ import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode
import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.auth.AuthenticationService
@@ -156,12 +157,13 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.ResetAction -> handleResetAction(action)
is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory()
- is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
is OnboardingAction.UpdateDisplayName -> updateDisplayName(action.displayName)
- OnboardingAction.UpdateDisplayNameSkipped -> _viewEvents.post(OnboardingViewEvents.OnDisplayNameSkipped)
- OnboardingAction.UpdateProfilePictureSkipped -> _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete)
+ OnboardingAction.UpdateDisplayNameSkipped -> handleDisplayNameStepComplete()
+ OnboardingAction.UpdateProfilePictureSkipped -> completePersonalization()
+ OnboardingAction.PersonalizeProfile -> handlePersonalizeProfile()
is OnboardingAction.ProfilePictureSelected -> handleProfilePictureSelected(action)
OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture()
+ is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
}.exhaustive
}
@@ -762,15 +764,33 @@ class OnboardingViewModel @AssistedInject constructor(
authenticationService.reset()
session.configureAndStart(applicationContext)
- setState {
- copy(
- asyncLoginAction = Success(Unit)
- )
- }
when (isAccountCreated) {
- true -> _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
- false -> _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
+ true -> {
+ val personalizationState = createPersonalizationState(session, state)
+ setState {
+ copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState)
+ }
+ _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
+ }
+ false -> {
+ setState { copy(asyncLoginAction = Success(Unit)) }
+ _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
+ }
+ }
+ }
+
+ private suspend fun createPersonalizationState(session: Session, state: OnboardingViewState): PersonalizationState {
+ return when {
+ vectorFeatures.isOnboardingPersonalizeEnabled() -> {
+ val homeServerCapabilities = session.getHomeServerCapabilities()
+ val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities?.firstOrNull()
+ state.personalizationState.copy(
+ supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName,
+ supportsChangingProfilePicture = capabilityOverrides?.canChangeAvatar ?: homeServerCapabilities.canChangeAvatar
+ )
+ }
+ else -> state.personalizationState
}
}
@@ -910,7 +930,7 @@ class OnboardingViewModel @AssistedInject constructor(
personalizationState = personalizationState.copy(displayName = displayName)
)
}
- _viewEvents.post(OnboardingViewEvents.OnDisplayNameUpdated)
+ handleDisplayNameStepComplete()
} catch (error: Throwable) {
setState { copy(asyncDisplayName = Fail(error)) }
_viewEvents.post(OnboardingViewEvents.Failure(error))
@@ -918,12 +938,37 @@ class OnboardingViewModel @AssistedInject constructor(
}
}
+ private fun handlePersonalizeProfile() {
+ withPersonalisationState {
+ when {
+ it.supportsChangingDisplayName -> _viewEvents.post(OnboardingViewEvents.OnChooseDisplayName)
+ it.supportsChangingProfilePicture -> _viewEvents.post(OnboardingViewEvents.OnChooseProfilePicture)
+ else -> {
+ throw IllegalStateException("It should not be possible to personalize without supporting display name or avatar changing")
+ }
+ }
+ }
+ }
+
+ private fun handleDisplayNameStepComplete() {
+ withPersonalisationState {
+ when {
+ it.supportsChangingProfilePicture -> _viewEvents.post(OnboardingViewEvents.OnChooseProfilePicture)
+ else -> completePersonalization()
+ }
+ }
+ }
+
private fun handleProfilePictureSelected(action: OnboardingAction.ProfilePictureSelected) {
setState {
copy(personalizationState = personalizationState.copy(selectedPictureUri = action.uri))
}
}
+ private fun withPersonalisationState(block: (PersonalizationState) -> Unit) {
+ withState { block(it.personalizationState) }
+ }
+
private fun updateProfilePicture() {
withState { state ->
when (val pictureUri = state.personalizationState.selectedPictureUri) {
@@ -955,6 +1000,10 @@ class OnboardingViewModel @AssistedInject constructor(
}
private fun onProfilePictureSaved() {
+ completePersonalization()
+ }
+
+ private fun completePersonalization() {
_viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
index bd5d93ae4d..8747de6da8 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
@@ -22,7 +22,6 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.PersistState
-import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ServerType
@@ -83,10 +82,6 @@ data class OnboardingViewState(
asyncDisplayName is Loading ||
asyncProfilePicture is Loading
}
-
- fun isAuthTaskCompleted(): Boolean {
- return asyncLoginAction is Success
- }
}
enum class OnboardingFlow {
@@ -97,6 +92,11 @@ enum class OnboardingFlow {
@Parcelize
data class PersonalizationState(
+ val supportsChangingDisplayName: Boolean = false,
+ val supportsChangingProfilePicture: Boolean = false,
val displayName: String? = null,
val selectedPictureUri: Uri? = null
-) : Parcelable
+) : Parcelable {
+
+ fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt
index d021fd2813..ccfb863a5b 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt
@@ -20,11 +20,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.databinding.FragmentFtueAccountCreatedBinding
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewEvents
+import im.vector.app.features.onboarding.OnboardingViewState
import javax.inject.Inject
class FtueAuthAccountCreatedFragment @Inject constructor(
@@ -42,8 +44,15 @@ class FtueAuthAccountCreatedFragment @Inject constructor(
private fun setupViews() {
views.accountCreatedSubtitle.text = getString(R.string.ftue_account_created_subtitle, activeSessionHolder.getActiveSession().myUserId)
- views.accountCreatedPersonalize.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnPersonalizeProfile)) }
+ views.accountCreatedPersonalize.debouncedClicks { viewModel.handle(OnboardingAction.PersonalizeProfile) }
views.accountCreatedTakeMeHome.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) }
+ views.accountCreatedTakeMeHomeCta.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) }
+ }
+
+ override fun updateWithState(state: OnboardingViewState) {
+ val canPersonalize = state.personalizationState.supportsPersonalization()
+ views.personalizeButtonGroup.isVisible = canPersonalize
+ views.takeMeHomeButtonGroup.isVisible = !canPersonalize
}
override fun resetViewModel() {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt
index bc1bf0c8bc..81300932db 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseProfilePictureFragment.kt
@@ -22,6 +22,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
+import androidx.core.view.isInvisible
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
@@ -70,6 +71,8 @@ class FtueAuthChooseProfilePictureFragment @Inject constructor(
}
override fun updateWithState(state: OnboardingViewState) {
+ views.profilePictureToolbar.isInvisible = !state.personalizationState.supportsChangingDisplayName
+
val hasSetPicture = state.personalizationState.selectedPictureUri != null
views.profilePictureSubmit.isEnabled = hasSetPicture
views.changeProfilePictureIcon.setImageResource(if (hasSetPicture) R.drawable.ic_edit else R.drawable.ic_camera_plain)
@@ -93,4 +96,14 @@ class FtueAuthChooseProfilePictureFragment @Inject constructor(
override fun resetViewModel() {
// Nothing to do
}
+
+ override fun onBackPressed(toolbarButton: Boolean): Boolean {
+ return when (withState(viewModel) { it.personalizationState.supportsChangingDisplayName }) {
+ true -> super.onBackPressed(toolbarButton)
+ false -> {
+ viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
+ true
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
index e336419e3f..2008726ac3 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
@@ -122,17 +122,9 @@ class FtueAuthVariant(
private fun updateWithState(viewState: OnboardingViewState) {
isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled
- views.loginLoading.isVisible = shouldShowLoading(viewState)
+ views.loginLoading.isVisible = viewState.isLoading()
}
- private fun shouldShowLoading(viewState: OnboardingViewState) =
- if (vectorFeatures.isOnboardingPersonalizeEnabled()) {
- viewState.isLoading()
- } else {
- // Keep loading when during success because of the delay when switching to the next Activity
- viewState.isLoading() || viewState.isAuthTaskCompleted()
- }
-
override fun setIsLoading(isLoading: Boolean) = Unit
private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) {
@@ -230,12 +222,11 @@ class FtueAuthVariant(
FtueAuthUseCaseFragment::class.java,
option = commonOption)
}
- OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
+ is OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
- OnboardingViewEvents.OnPersonalizeProfile -> onPersonalizeProfile()
+ OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName()
OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true)
- OnboardingViewEvents.OnDisplayNameUpdated -> onDisplayNameUpdated()
- OnboardingViewEvents.OnDisplayNameSkipped -> onDisplayNameUpdated()
+ OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture()
OnboardingViewEvents.OnPersonalizationComplete -> navigateToHome(createdAccount = true)
OnboardingViewEvents.OnBack -> activity.popBackstack()
}.exhaustive
@@ -399,15 +390,11 @@ class FtueAuthVariant(
}
private fun onAccountCreated() {
- if (vectorFeatures.isOnboardingPersonalizeEnabled()) {
- activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
- activity.replaceFragment(
- views.loginFragmentContainer,
- FtueAuthAccountCreatedFragment::class.java,
- )
- } else {
- navigateToHome(createdAccount = true)
- }
+ activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+ activity.replaceFragment(
+ views.loginFragmentContainer,
+ FtueAuthAccountCreatedFragment::class.java
+ )
}
private fun navigateToHome(createdAccount: Boolean) {
@@ -416,14 +403,14 @@ class FtueAuthVariant(
activity.finish()
}
- private fun onPersonalizeProfile() {
+ private fun onChooseDisplayName() {
activity.addFragmentToBackstack(views.loginFragmentContainer,
FtueAuthChooseDisplayNameFragment::class.java,
option = commonOption
)
}
- private fun onDisplayNameUpdated() {
+ private fun onChooseProfilePicture() {
activity.addFragmentToBackstack(views.loginFragmentContainer,
FtueAuthChooseProfilePictureFragment::class.java,
option = commonOption
diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt
index b4f61dbc1f..0ef92e4d2f 100644
--- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt
@@ -60,9 +60,9 @@ class CreatePollController @Inject constructor(
pollTypeChangedListener { _, id ->
host.callback?.onPollTypeChanged(
if (id == R.id.openPollTypeRadioButton) {
- PollType.DISCLOSED
+ PollType.DISCLOSED_UNSTABLE
} else {
- PollType.UNDISCLOSED
+ PollType.UNDISCLOSED_UNSTABLE
}
)
}
diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt
index 5c7ef72297..2358f7f9a0 100644
--- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt
@@ -71,9 +71,10 @@ class CreatePollViewModel @AssistedInject constructor(
val event = room.getTimelineEvent(eventId) ?: return
val content = event.getLastMessageContent() as? MessagePollContent ?: return
- val pollType = content.pollCreationInfo?.kind ?: PollType.DISCLOSED
- val question = content.pollCreationInfo?.question?.question ?: ""
- val options = content.pollCreationInfo?.answers?.mapNotNull { it.answer } ?: List(MIN_OPTIONS_COUNT) { "" }
+ val pollCreationInfo = content.getBestPollCreationInfo()
+ val pollType = pollCreationInfo?.kind ?: PollType.DISCLOSED_UNSTABLE
+ val question = pollCreationInfo?.question?.getBestQuestion() ?: ""
+ val options = pollCreationInfo?.answers?.mapNotNull { it.getBestAnswer() } ?: List(MIN_OPTIONS_COUNT) { "" }
setState {
copy(
diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
index 175d1b0116..fc3b746f32 100644
--- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt
@@ -27,7 +27,7 @@ data class CreatePollViewState(
val options: List = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
val canCreatePoll: Boolean = false,
val canAddMoreOptions: Boolean = true,
- val pollType: PollType = PollType.DISCLOSED
+ val pollType: PollType = PollType.DISCLOSED_UNSTABLE
) : MavericksState {
constructor(args: CreatePollArgs) : this(
diff --git a/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt b/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt
index 1b24a70cb9..0736c236b5 100644
--- a/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/create/PollTypeSelectionItem.kt
@@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollType
abstract class PollTypeSelectionItem : VectorEpoxyModel() {
@EpoxyAttribute
- var pollType: PollType = PollType.DISCLOSED
+ var pollType: PollType = PollType.DISCLOSED_UNSTABLE
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var pollTypeChangedListener: RadioGroup.OnCheckedChangeListener? = null
@@ -38,8 +38,8 @@ abstract class PollTypeSelectionItem : VectorEpoxyModel R.id.openPollTypeRadioButton
- PollType.UNDISCLOSED -> R.id.closedPollTypeRadioButton
+ PollType.DISCLOSED_UNSTABLE, PollType.DISCLOSED -> R.id.openPollTypeRadioButton
+ PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED -> R.id.closedPollTypeRadioButton
}
)
diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt
index 0ae2a16b71..91b0f4d2f7 100644
--- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt
+++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt
@@ -38,8 +38,13 @@ data class ElementWellKnown(
val riotE2E: E2EWellKnownConfig? = null,
@Json(name = "org.matrix.msc3488.tile_server")
+ val unstableMapTileServerConfig: MapTileServerConfig? = null,
+
+ @Json(name = "m.tile_server")
val mapTileServerConfig: MapTileServerConfig? = null
-)
+) {
+ fun getBestMapTileServerConfig() = mapTileServerConfig ?: unstableMapTileServerConfig
+}
@JsonClass(generateAdapter = true)
data class E2EWellKnownConfig(
diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt
index 3164daf634..c2d63aa8d3 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/manage/AddRoomListController.kt
@@ -94,6 +94,12 @@ class AddRoomListController @Inject constructor(
}
var totalSize: Int = 0
+ set(value) {
+ if (value != field) {
+ field = value
+ requestForcedModelBuild()
+ }
+ }
var selectedItems: Map = emptyMap()
set(value) {
@@ -120,7 +126,8 @@ class AddRoomListController @Inject constructor(
add(
RoomCategoryItem_().apply {
id("header")
- title(host.sectionName ?: "")
+ title(host.sectionName.orEmpty())
+ itemCount(host.totalSize)
expanded(host.expanded)
listener {
host.expanded = !host.expanded
diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt
index bcf0a8a949..8d6a351013 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt
@@ -22,6 +22,8 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
@@ -35,9 +37,12 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceAddRoomsBinding
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import reactivecircus.flowbinding.appcompat.queryTextChanges
import javax.inject.Inject
@@ -169,48 +174,63 @@ class SpaceAddRoomFragment @Inject constructor(
}
private fun setupRecyclerView() {
- val concatAdapter = ConcatAdapter()
- spaceEpoxyController.sectionName = getString(R.string.spaces_header)
- roomEpoxyController.sectionName = getString(R.string.rooms_header)
- spaceEpoxyController.listener = this
- roomEpoxyController.listener = this
+ setupSpaceSection()
+ setupRoomSection()
+ setupDmSection()
- viewModel.updatableLiveSpacePageResult.liveBoundaries.observe(viewLifecycleOwner) {
+ views.roomList.adapter = ConcatAdapter().apply {
+ addAdapter(roomEpoxyController.adapter)
+ addAdapter(spaceEpoxyController.adapter)
+ addAdapter(dmEpoxyController.adapter)
+ }
+ }
+
+ private fun setupSpaceSection() {
+ spaceEpoxyController.sectionName = getString(R.string.spaces_header)
+ spaceEpoxyController.listener = this
+ viewModel.spaceUpdatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
spaceEpoxyController.boundaryChange(it)
}
- viewModel.updatableLiveSpacePageResult.livePagedList.observe(viewLifecycleOwner) {
- spaceEpoxyController.totalSize = it.size
+ viewModel.spaceUpdatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
spaceEpoxyController.submitList(it)
}
+ listenItemCount(viewModel.spaceCountFlow) { spaceEpoxyController.totalSize = it }
+ }
- viewModel.updatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
+ private fun setupRoomSection() {
+ roomEpoxyController.sectionName = getString(R.string.rooms_header)
+ roomEpoxyController.listener = this
+
+ viewModel.roomUpdatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
roomEpoxyController.boundaryChange(it)
}
- viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
- roomEpoxyController.totalSize = it.size
+ viewModel.roomUpdatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
roomEpoxyController.submitList(it)
}
-
+ listenItemCount(viewModel.roomCountFlow) { roomEpoxyController.totalSize = it }
views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
views.roomList.setHasFixedSize(true)
+ }
- concatAdapter.addAdapter(roomEpoxyController.adapter)
- concatAdapter.addAdapter(spaceEpoxyController.adapter)
-
+ private fun setupDmSection() {
// This controller can be disabled depending on the space type (public or not)
- viewModel.updatableDMLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
- dmEpoxyController.boundaryChange(it)
- }
- viewModel.updatableDMLivePageResult.livePagedList.observe(viewLifecycleOwner) {
- dmEpoxyController.totalSize = it.size
- dmEpoxyController.submitList(it)
- }
dmEpoxyController.sectionName = getString(R.string.direct_chats_header)
dmEpoxyController.listener = this
+ viewModel.dmUpdatableLivePageResult.liveBoundaries.observe(viewLifecycleOwner) {
+ dmEpoxyController.boundaryChange(it)
+ }
+ viewModel.dmUpdatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
+ dmEpoxyController.submitList(it)
+ }
+ listenItemCount(viewModel.dmCountFlow) { dmEpoxyController.totalSize = it }
+ }
- concatAdapter.addAdapter(dmEpoxyController.adapter)
-
- views.roomList.adapter = concatAdapter
+ private fun listenItemCount(itemCountFlow: Flow, onEachAction: (Int) -> Unit) {
+ lifecycleScope.launch {
+ itemCountFlow
+ .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
+ .collect { count -> onEachAction(count) }
+ }
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt
index 8fa269d439..7d99c53f23 100644
--- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt
@@ -17,7 +17,7 @@
package im.vector.app.features.spaces.manage
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.asFlow
import androidx.paging.PagedList
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@@ -30,6 +30,9 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
@@ -60,7 +63,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
- val updatableLiveSpacePageResult: UpdatableLivePageResult by lazy {
+ val spaceUpdatableLivePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
@@ -79,7 +82,13 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
)
}
- val updatableLivePageResult: UpdatableLivePageResult by lazy {
+ val spaceCountFlow: Flow by lazy {
+ spaceUpdatableLivePageResult.livePagedList.asFlow()
+ .flatMapLatest { session.getRoomCountFlow(spaceUpdatableLivePageResult.queryParams) }
+ .distinctUntilChanged()
+ }
+
+ val roomUpdatableLivePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
@@ -99,7 +108,13 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
)
}
- val updatableDMLivePageResult: UpdatableLivePageResult by lazy {
+ val roomCountFlow: Flow by lazy {
+ roomUpdatableLivePageResult.livePagedList.asFlow()
+ .flatMapLatest { session.getRoomCountFlow(roomUpdatableLivePageResult.queryParams) }
+ .distinctUntilChanged()
+ }
+
+ val dmUpdatableLivePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
@@ -119,6 +134,12 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
)
}
+ val dmCountFlow: Flow by lazy {
+ dmUpdatableLivePageResult.livePagedList.asFlow()
+ .flatMapLatest { session.getRoomCountFlow(dmUpdatableLivePageResult.queryParams) }
+ .distinctUntilChanged()
+ }
+
private val selectionList = mutableMapOf()
val selectionListLiveData = MutableLiveDataغُرفة فارِغة (كانت %s)%1$s، %2$s و%3$s
@@ -192,7 +191,6 @@
احذفغيّر الاسمأبلِغ عن المحتوى
-
اخرجمكالمة صوتيةمكالمة صورية
@@ -209,7 +207,6 @@
متراسلو «ماترِكس» فقطلا نتائجالغرف
-
أرسِل السجلاتأرسِل سجلات الانهيارأرسِل لقطة شاشة
@@ -234,7 +231,6 @@
أنسيت كلمة السر؟يجب إدخال عنوان البريد الإلكتروني المرتبط بحسابك.فشل تأكيد عنوان البريد: تحقق من نقر الرابط في البريد
-
أدخِل مسارا صالحالم يحتوي JSON صالحأُرسلت الكثير من الطلبات
@@ -261,7 +257,6 @@
اخرجالبصمة (%s):تعذّر التحقق من معرّف الخادوم البعيد.
-
لا نتائجصورة اللاحةاسم العرض
@@ -325,7 +320,6 @@
استخدم الكمرة الأصيلةاضبطه كعنوان رئيسيألغِ ضبطه كعنوان رئيسي
-
السمةخطأ في فكّ التعميةاسم الجهاز
@@ -335,7 +329,6 @@
صدّر المفاتيح إلى ملف محليأدخِل عبارة المرورأكّد عبارة المرور
-
استورد مفاتيح الطرفين لغرفةاستورد مفاتيح الغرفةاستورد المفاتيح من ملف محلي
@@ -363,9 +356,7 @@
بلاغ علةيبدو أنك تهزّ الهاتف وأنت مُحبط. أتريد إرسال بلاغ علة؟التقدم (%s٪)
-
يحمّل…
-
لا أعضاءعضو واحد
@@ -384,7 +375,6 @@
%d رسالة جديدة%d رسالة جديدة
-
أولوية منخفضةالمجتمعاتهزّ الجهاز بجنون يُرسل بلاغًا بعلة
@@ -396,7 +386,6 @@
أمتأكد من بدء محادثة صوتية؟أمتأكد من بدء محادثة صورية؟عنوان البريد مُعرّف بالفعل.
-
يريد خادوم المنزل هذا التأكد من أنك لست أحد الآليينJSON معطوب
@@ -407,19 +396,14 @@
%d تغييرا على العضوية%d تغيير على العضوية
-
يحتاج Element تصريحا منك للوصول إلى المِكرفون لإجراء المكالمات الصوتية.
-
يحتاج Element تصريحا منك للوصول إلى الكمرة والمِكرفون لإجراء المكالمات الصورية.
رجاءً اسمح بالوصول في المنبثقة التالية لتقدر على إرسال إجراء المكالمات الصورية.
-
-
ادعُانضم إلى الغرفةالأصليلم يُجب الطرف البعيد.
-
الدردشات المباشرةأشِر إليهلن تستطيع العودة عن هذا التغيير إذ أنك تمنح المستخدم نفس مستوى السلطة الذي لك.
@@ -482,7 +466,6 @@
هُزّ الجهاز عند الإشارة إليّالتحاليل%1$s في %2$s
-
تخويلوالج كَخادوم المنزل
@@ -493,7 +476,6 @@
الميلمعطّلمزعج
-
لا ترسل من هذا الجهاز الرسائل المعمّاة إلى الأجهزة غير المؤكّدةعمِّ إلى الأجهزة المؤكّدة فقط<b>غير<b/> مؤكّدة
@@ -504,7 +486,6 @@
قد يعني هذا بأن أحدهم يعترض الاتصال بعدوانية، أو أن هاتفك لا يثق بالشهادة التي قدّمها الخادوم البعيد.إن قال مدير الخادوم بأن هذا متوقع، فتأكد من أن البصمة أدناه تطابق البصمة التي وفّرها.تغيّرت الشهادة من شهادة كنت تثق بها إلى شهادة لا تثق بها. لربما جدّد الخادوم شهادته. راسل إدارة الخادوم واسألهم عن البصمة المتوقعة.
-
أضِف اختصارا إلى الشاشة الرئيسيةشاشة معلومات التطبيق في النظامدعوات المكالمات
@@ -545,10 +526,6 @@
رجاءً اكتب كلمة السر.رجاءً اكتب الوصف بالإنجليزية إن أمكن.تنبيهات النظام
-
-
-
-
لا شيء محدّدواحدة محدّدة
@@ -566,14 +543,11 @@
%d رسالة إخطار غير مقروءة%d رسالة إخطار غير مقروءة
-
يمنع المستخدم حسب المعرّف المعطىيُلغي المنع عن المستخدم حسب المعرّف المعطىيُحدّد مستوى قدرة المستخدميدعو المستخدم حسب المعرّف المعطى إلى الغرفة الحاليةشغّل/عطّل ماركداون
-
-
ألغِ تفعيل الحساباستُبدلت هذه الغرفة ولم تعد نشطة بعد الآنهذه الغرفة هي استمرار لمحادثة أخرى
@@ -610,8 +584,8 @@
طردالإعدادات المتقدمة للإشعاراتعند تسجيل الخروج الآن ستخسر مفاتيحك
- النسخ الاحتياطي المفاتيح ما زال جاريا. في حال خروجك الآن لن تتمكن لاحقا من قراءة الرسائل المشفرة.
- تأكد من تفعيل النسخ الاحتياطي للمفاتيح على كل أجهزتك كي لا تخسر رسائلك المشفرة
+ النسخ الاحتياطي المفاتيح ما زال جاريا. في حال خروجك الآن لن تتمكن لاحقا من قراءة الرسائل المعماة.
+ تأكد من تفعيل النسخ الاحتياطي للمفاتيح على كل أجهزتك كي لا تخسر رسائلك المعماةينسخ احتياطيا المفاتيح…ليس لديك تصريح لبدء إجتماعليس لديك تصريح لبدء إجتماع في هذه الغرفة
@@ -622,10 +596,10 @@
إفصلأبّطللاشيء
- ستفقد الوصول إلى رسائلك المشفرة إلا إذا أخذت نسخة إحتياطية من مفاتيحك قبل تسجيلك للخروج.
+ ستفقد الوصول إلى رسائلك المعماة إلا إذا أخذت نسخة إحتياطية من مفاتيحك قبل تسجيلك للخروج.نسخة إحتياطيةهل أنت متأكد؟
- لا أريد رسائلي المشفرة
+ لا أريد رسائلي المعماةنسخ إحتياطي للمفتاحإفتراضي النظامغير %1$s عنوان الغرفة الى %2$s.
@@ -840,4 +814,12 @@
يستورد المفاتيح…ينزّل المفاتيح…لا يسمح لك بالانضمام لهذه الغرفة
+ فشلت مكالمة ${app_name}
+ انسخ رابط النقاش
+ اعرضه في الغرفة
+ أقبل
+ اعرض النقاشات
+ فشلت إزالة الودجة
+ فشلت إضافة الودجة
+ يستمع للإشعارات
\ No newline at end of file
diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 9d0220f3a6..cf022172c2 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -38,7 +38,6 @@
Telefonní čísloPozvání do místnosti%1$s a %2$s
-
Prázdná místnost%s povýšili tuto místnost.%1$s zrušili pozvánku do místnosti pro %2$s
@@ -260,7 +259,6 @@
Trvalý odkazZobrazit dešifrovaný zdrojNahlásit obsah
-
OdhlásitHlasový hovorVideo hovor
@@ -281,7 +279,6 @@
Pouze kontakty MatrixŽádné výsledkyMístnosti
-
KomunityOdeslat záznamyOdeslat záznamy zřícení
@@ -334,8 +331,6 @@
\nPřejete si nějaké přidat nyní\?Omlouváme se, ale nebyla nalezena žádná externí aplikace pro dokončení této akce.Šifrovaná zpráva
-
-
Prosím, přečtěte si a souhlaste s pravidly tohoto serveru:Neobsahuje platný JSONZnovu požádat o šifrovací klíče z vašich ostatních relací.
@@ -360,10 +355,7 @@
Hovor probíhá…Protější strana hovor nepřijala.Informace
-
-
${app_name} potřebuje oprávnění pro přístup k Vašemu mikrofonu pro uskutečnění hlasových hovorů.
-
ANONEPokračovat
@@ -373,16 +365,11 @@
OdmítnoutZobrazit členyPřejít na nepřečtené
-
%d člen%d členové%d členů
-
-
-
-
Opustit místnostOpravdu chcete opustit tuto místnost\?PŘÍMÉ KONVERZACE
@@ -431,8 +418,6 @@
${app_name} potřebuje oprávnění pro přístup k Vaší kameře a mikrofonu pro uskutečnění video hovoru.
\n
\nProsím, povolte přístup na následující hlášce abyste mohli uskutečnit hovor.
-
-
Tuto změnu nelze zvrátit, protože povyšujete uživatele na stejnou úroveň, jakou máte vy.
\nOpravdu to chcete udělat\?Toto by mohlo znamenat, že někdo škodlivě zachytává Vaši komunikaci nebo že Váš telefon nedůvěřuje certifikátu poskytnutému vzdáleným serverem.
@@ -440,12 +425,9 @@
Certifikát se změnil z toho, kterému Váš telefon důvěřoval. Toto je VELMI NEOBVYKLÉ. Je doporučeno, abyste NEPŘIJALI tento nový certifikát.Certifikát se změnil z původně důvěryhodného na nyní nedůvěryhodný. Server patrně obnovil svůj certifikát. Kontaktujte administrátora kvůli očekávanému otisku.Přijměte certifikát pouze pokud administrátor serveru publikoval otisk, který odpovídá tomu uvedenému výše.
-
VyhledatFiltrovat členy místnostiŽádné výsledky
-
-
Všechny zprávyPřidat na domovskou obrazovkuObrázek profilu
@@ -462,7 +444,6 @@
Označit za přečtenéŽádnýZrušit
-
Přihlásit se se single sign-onTo není platná adresa Matrix serveruDomovský server není dostupný na této adrese, zkontrolujte ji prosím
@@ -553,7 +534,6 @@
Neobdržíte oznámení o příchozích zprávách, je-li aplikace na pozadí.Start při zaváděníČas požadavku na sync vypršel
-
Prodleva mezi jednotlivými syncyVerzeVerze olm
@@ -603,7 +583,6 @@
Deaktivovat můj účetObjevováníSpráva Vašich nastavení pro objevování.
-
AnalýzaOdeslat analytická data${app_name} sbírá anonymní analytická data pro vylepšení aplikace.
@@ -612,7 +591,6 @@
Aktualizovat veřejné jménoViděn naposledy%1$s @ %2$s
-
OvěřeníPřihlášen jakoDomovský server
@@ -661,7 +639,6 @@
Toto jsou experimentální funkce, které mohou selhat neočekávanými způsoby. Použijte obezřetně.Nastavit jako hlavní adresuOdebrat jako hlavní adresu
-
Motiv vzhleduChyba dešifrováníVeřejné jméno
@@ -672,7 +649,6 @@
Export klíčů do místního souboruExportProsím, vytvořte frázi k zašifrování exportovaných klíčů. Pro import klíčů budete muset zadat stejnou přístupovou frázi.
-
Obnovení zašifrovaných zprávSpráva zálohy klíčůImport E2E klíčů místností
@@ -721,7 +697,6 @@
Importovat e2e klíče ze souboru \"%1$s\".Potvrďte porovnáním následujícího s nastavením uživatele ve svých dalších relacích:Pokud se neshodují, zabezpečení Vaší komunikace může být ohroženo.
-
Vybrat adresář místnostíNázev serveruVšechny místnosti na serveru %s
@@ -731,7 +706,6 @@
%d nepřečtené oznámené zprávy%d nepřečtených oznámených zpráv
-
%d místnost%d místnosti
@@ -834,8 +808,6 @@
ÚvodMístnostiPozvaní
-
-
%2$s Vás vykopnul z %1$s%2$s Vám zakázal %1$sDůvod: %1$s
@@ -899,7 +871,6 @@
Uložit klíč obnovySdíletUložit jako soubor
-
Záloha již existuje na Vašem domovském serveruVypadá to, že jste již nastavili zálohu klíče z jiné relace. Chcete ji nahradit zálohou, již právě provádíte\?Nahradit
@@ -942,7 +913,6 @@
Kontroluji stav zálohySmazat zálohuSmazat Vaše zálohované šifrovací klíče ze serveru\? Ke čtení šifrované historie zpráv již nebude moci použít klíč obnovy.
-
Nikdy neztraťte šifrované zprávyPoužíjte zálohu klíčeNový bezpečný klíč zpráv
@@ -950,11 +920,8 @@
VerzeAlgoritmusPodpis
-
Ověřeno!Rozumím
-
-
Žádost na ověření%s chce ověřit Vaši relaciNeznámá chyba
@@ -1107,7 +1074,6 @@
Obsah byl nahlášen jako nepatřičný.
\n
\nPokud si dále nepřejete vidět obsah tohoto uživatele, můžete jej ignorovat a tím skrýt jejich zprávy.
-
Ignorovat uživateleVšechny zprávy (hlučné)Všechny zprávy
@@ -1295,7 +1261,6 @@
Ověřit %sOvěřeno %sČekám na %s…
-
Zprávy v této místnosti nejsou koncově šifrovány.Zprávy v této místnosti jsou koncově šifrovány.
\n
@@ -1443,7 +1408,6 @@
Vytiskněte a uložte na bezpečném místěUložte je na USB nebo zálohový diskNahrajte do svého osobního úložiště v cloudu
-
Šifrování zapnutoZprávy v této místnosti jsou koncově šifrovány. Zjistěte více a ověřte uživatele v jejich profilech.Šifrování není zapnuto
@@ -1855,7 +1819,6 @@
Skrýt pokročiléUkázat pokročilé%1$d z %2$d
-
Udělit souhlasZrušit můj souhlasUdělili jste souhlas pro odeslání emailových adres a telefonních čísel na tento server pro identity za účelem nalezení dalších uživatelů podle svých kontaktů.
@@ -1948,8 +1911,6 @@
Při přepojování hovoru došlo k chyběPřipojitNejprve se poraďte
-
-
Probíhající hovor (%1$s)Při vyhledávání telefonního čísla došlo k chyběČíselník
@@ -2149,7 +2110,6 @@
Zadejte název nového serveru, který chcete prozkoumat.Přidat nový serverVáš server
-
Omlouváme se, došlo k chybě během pokusu o přistoupení: %sAdresa prostoruProhlédnout a spravovat adresy tohoto prostoru.
@@ -2354,7 +2314,6 @@
Otázka nebo téma hlasováníVytvořit hlasováníHlasování
-
Odeslat e-maily a telefonní čísla na %sVaše kontakty jsou soukromé. Pro zjištění uživatelů z vašich kontaktů, potřebujeme vaše svolení k odeslání informací o kontaktech na váš server identit.Relace byla odhlášena!
@@ -2505,4 +2464,6 @@
%1$d dalšíchZobrazit méně
+ %1$s, %2$s a další
+ %1$s a %2$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index bf72827085..7a8ac9c71b 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -39,7 +39,6 @@
Raumeinladung%1$s und %2$sLeerer Raum
-
%s hat diesen Raum aufgewertet.Sende eine Nachricht…Erste Synchronisation:
@@ -244,7 +243,6 @@
LöschenUmbenennenInhalt melden
-
oderEinladenAbmelden
@@ -267,7 +265,6 @@
Nur Matrix-KontakteKeine ErgebnisseRäume
-
Logdateien übermittelnAbsturzberichte übermittelnScreenshot übermitteln
@@ -298,7 +295,6 @@
Dieser Homeserver möchte sicherstellen, dass du kein Roboter bistDie E-Mail-Adresse, die mit deinem Account verknüpft ist, muss eingegeben werden.Verifizierung der E-Mail-Adresse ist fehlgeschlagen. Stelle sicher, dass du den Link in der E-Mail geöffnet hast
-
Bitte eine gültige URL eingebenFehlerhaftes JSONEnthielt kein gültiges JSON
@@ -314,13 +310,10 @@
Anruf aktiv…Die Gegenseite hat den Anruf nicht angenommen.Information
-
${app_name} benötigt die Berechtigung, auf dein Mikrofon zugreifen zu können, um (Sprach-)Anrufe tätigen zu können.
-
${app_name} benötigt die Berechtigung, auf Kamera und Mikrofon zu zugreifen, um Video-Anrufe durchzuführen.
\n
\nBitte erlaube den Zugriff im nächsten Dialog, um den Anruf durchzuführen.
-
JaNeinFortsetzen
@@ -328,7 +321,6 @@
BetretenAblehnenZu ungelesenen Nachrichten springen
-
Raum verlassenRaum wirklich verlassen\?DIREKT-CHATS
@@ -341,8 +333,8 @@
Du wirst diese Änderung nicht rückgängig machen können, da die Person dieselbe Berechtigungsstufe wie du erhalten wird.
\nBist du sicher\?%s schreibt…
- %1$s & %2$s schreiben…
- %1$s, %2$s & andere schreiben…
+ %1$s und %2$s schreiben…
+ %1$s, %2$s und andere schreiben…Du bist nicht berechtigt, in diesen Raum zu schreiben.VertrauenNicht vertrauen
@@ -355,7 +347,6 @@
Das Zertifikat unterscheidet sich von dem Zertifikat, dem dein Gerät ursprünglich vertraut hat. Dies ist SEHR UNGEWÖHNLICH. Es wird empfohlen, dass du dieses neue Zertifikat NICHT AKZEPTIERST.Das Zertifikat hat sich von einem ursprünglich vertrauenswürdigem Zertifikat in ein nicht vertrauenswürdiges Zertifikat geändert. Eventuell wurde das Zertifikat des Servers erneuert. Bitte erkundige dich beim Server-Administrator, welcher Fingerprint als vertrauenswürdig gilt.Akzeptiere das Zertifikat nur dann, wenn der Server-Administrator einen Fingerprint veröffentlicht hat, der mit dem obigen übereinstimmt.
-
SuchenRaummitglieder filternKeine Suchergebnisse
@@ -400,7 +391,6 @@
Öffentlichen Namen aktualisierenZuletzt gesehen%1$s @ %2$s
-
AuthentifizierungAngemeldet alsHeimserver
@@ -441,7 +431,6 @@
ExportierenPassphrase eingebenPassphrase bestätigen
-
Ende-zu-Ende-Raumschlüssel importierenRaumschlüssel importierenSchlüssel aus lokaler Datei importieren
@@ -453,7 +442,6 @@
BestätigenVergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des anderen Nutzers und bestätige:Falls sie nicht übereinstimmen, wurde die Kommunikation vielleicht kompromittiert.
-
Raumverzeichnis auswählenServer-NameAlle Räume auf dem %s-Server
@@ -531,7 +519,6 @@
Community-AvatareSchütteln, um einen Fehler zu meldenMitglieder auflisten
-
%d Mitglied%d Mitglieder
@@ -540,13 +527,10 @@
%d neue Nachricht%d neue Nachrichten
-
-
%d ungelesene Nachricht%d ungelesene Nachrichten
-
%d Raum%d Räume
@@ -606,16 +590,10 @@
Die Konversation wird hier fortgesetztDieser Raum ist die Fortsetzung einer anderen KonversationKlicke hier um die älteren Nachrichten zu sehen
-
-
-
-
%d ausgewählt%d ausgewählt
-
-
Systembenachrichtigungenkontaktiere deinen Service-AdministratorDieser Homeserver hat eine seiner Ressourcen-Grenzen erreicht, sodass einige Nutzer sich nicht anmelden können.
@@ -707,7 +685,6 @@
Token-RegistrierungWenn ein Benutzer ein abgestecktes Gerät mit ausgeschaltetem Bildschirm eine Weile nicht bewegt, wechselt es in den Bereitschaftsmodus. Dies hindert Apps daran, auf das Netzwerk zuzugreifen und verzögert die Ausführung von Aufgaben, Synchronisierungen und Standard-Alarmen.Ignoriere Optimierungen
-
Keine validen Google-Play-Dienste gefunden. Benachrichtigungen könnten nicht richtig funktionieren.Videogespräch aktiv…Schlüsselsicherung
@@ -756,7 +733,6 @@
Nachrichten in verschlüsselten Räumen sind mit Ende-zu-Ende-Verschlüsselung gesichert. Nur du und der Empfänger haben die Schlüssel um diese Nachrichten zu lesen.
\n
\nSichere deine Schlüssel, um sie nicht zu verlieren.
-
Wiederherstellungsschlüssel aus Passphrase generieren. Dies kann mehrere Sekunden brauchen.Du verlierst möglicherweise den Zugang zu deinen Nachrichten, wenn du dich abmeldest oder das Gerät verlierst.Rufe Backup-Version ab…
@@ -817,7 +793,6 @@
Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort auf, wie z.B. einem Passwortmanager (oder Tresor) aufIch habe eine Kopie angefertigtTeilen
-
Verliere nie wieder verschlüsselte NachrichtenBenutze SchlüsselsicherungNeue sichere Schlüssel für Nachrichten
@@ -839,7 +814,6 @@
Nachricht mit Eingabetaste sendenEingabetaste der Bildschirmtastatur schickt die Nachricht ab, statt einen Zeilenumbruch zu erzeugenDas Passwort ist ungültig
-
MedienStandard-KomprimierungWählen
@@ -879,7 +853,6 @@
Überprüfe SicherungsstatusVerifiziert!Verstanden
-
Verifizierungsanfrage%s möchte deine Sitzung verifizierenUnbekannter Fehler
@@ -949,7 +922,6 @@
Link in die Zwischenablage kopiertRaum erstellen…Bearbeitungsverlauf anzeigen
-
E2E-Schlüssel aus der Datei \"%1$s\" importieren.Vielen Dank, der Vorschlag wurde erfolgreich gesendetDer Vorschlag konnte nicht gesendet werden (%s)
@@ -986,7 +958,6 @@
Erkennungsoptionen werden angezeigt, sobald du eine E-Mail hinzugefügt hast.Gib einen neuen Identitätsserver einKonnte keine Verbindung zum Homeserver herstellen
-
Dies ist keine Adresse eines MatrixserversKann Homeserver nicht unter dieser URL erreichen. Bitte überprüfenHintergrund-Synchronisierungsmodus
@@ -994,7 +965,6 @@
\nAbhängig vom Ressourcen-Status deines Geräts kann dein System die Synchronisierung verschieben.${app_name} wird sich im Hintergrund periodisch zu einem bestimmten Zeitpunkt synchronisieren (konfigurierbar).
\nDies wird Funk- und Akkunutzung beeinflussen. Es wird eine permanente Benachrichtigung geben, die sagt, dass ${app_name} auf Ereignisse lauscht.
-
IntegrationenBenutze einen Integrationsmanager um Bots, Brücken, Widgets und Stickerpakete zu verwalten.
\nIntegrationsmanager erhalten Rauminformationen und können so Widgets verändern, Einladungen senden und in deinem Namen Berechtigungslevel setzen.
@@ -1078,7 +1048,6 @@
Dieser Inhalt wurde als unangebracht gemeldet.
\n
\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden.
-
Nutzer ignorierenAlle Nachrichten (laut)Alle Nachrichten
@@ -1101,13 +1070,13 @@
Premium-Hosting für OrganisationenMehr erfahrenAndere
- Benutzerdefinierte & erweiterte Einstellungen
+ Benutzerdefinierte und erweiterte EinstellungenFortfahrenEine Trennung von deinem Identitätsserver würde bedeuten, dass du weder von anderen Nutzern gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst.Du teilst deine E-Mail-Adressen oder Telefonnummern momentan auf dem Identitätsserver %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören.Stimme den Nutzungsbedingungen des Identitätsservers (%s) zu, um zu erlauben per E-Mail oder Telefonnummer gefunden zu werden.Zu teilende Daten nicht verarbeitbar
- Erweitere & individualisiere dein Benutzererlebnis
+ Erweitere und individualisiere dein BenutzererlebnisMit %1$s verbindenMit Element Matrix Services verbindenMit einem individuellen Server verbinden
@@ -1271,7 +1240,6 @@
%s verifizieren%s verifiziertWarte auf %s…
-
Nachrichten in diesem Raum sind nicht Ende-zu-Ende-verschlüsselt.Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt.
\n
@@ -1295,7 +1263,7 @@
NutzerAdmin in %1$sModeration in %1$s
- Springen & als gelesen markieren
+ Springen und als gelesen markieren${app_name} kann keine Ereignisse vom Typ \'%1$s\'${app_name} ist beim Verarbeiten des Ereignisinhalts mit der ID \'%1$s\' auf ein Problem gestoßenNicht ignorieren
@@ -1392,7 +1360,7 @@
\n- Dieses Gerät, oder das andere Gerät
\n- Die Internetverbindung, die von den Geräten genutzt wird
\n
-\nWir empfehlen dir dein Passwort & Wiederherstellungsschlüssel in den Einstellungen sofort zu ändern.
+\nWir empfehlen dir dein Passwort und den Wiederherstellungsschlüssel in den Einstellungen sofort zu ändern.Verifizierung abgebrochen. Du kannst sie erneut starten.Verifizierung abgebrochenGib dein %s ein um fortzufahren.
@@ -1409,7 +1377,7 @@
Synchronisiere BenutzerschlüsselSynchronisiere selbstsignierenden SchlüsselRichte Schlüsselbackup ein
- Deine %2$s & %1$s sind nun eingerichtet.
+ Deine %2$s und %1$s sind nun eingerichtet.
\n
\nBewahre sie sicher auf! Du wirst sie benötigen, um verschlüsselte Nachrichten und sichere Informationen freizuschalten, wenn du alle deine aktive Sitzungen verlierst.Speichere ihn auf einem USB-Stick oder auf einem Sicherungslaufwerk
@@ -1426,7 +1394,7 @@
Verschlüsselung ist nicht aktiviertRaumupgradesVerschlüsselung aktiviert
- Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr & verifiziere Benutzer in deren Profil.
+ Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr und verifiziere Benutzer in deren Profil.Die Verschlüsselung in diesem Raum wird nicht unterstütztWarte auf %s…Fehlerbehebung
@@ -1435,12 +1403,11 @@
Fast geschafft! Warte auf Bestätigung…Verschlüsselte DirektnachrichtenNachricht…
- Verifiziere dich & andere, um eure Chats zu schützen
+ Verifiziere dich und andere, um eure Chats zu schützenGib zum Fortfahren deinen %s einDatei benutzenDies ist kein gültiger WiederherstellungsschlüsselBitte gib deinen Wiederherstellungsschlüssel ein
-
Verschlüsselungsupgrade verfügbarÜberprüfe WiederherstellungsschlüsselÜberprüfe Sicherungsstatus (%s)
@@ -1472,7 +1439,7 @@
UnverschlüsseltVerschlüsselt von einem unbekannten GerätÜberprüfe, wo du angemeldet bist
- Verifiziere alle deine Sitzungen, um sicherzustellen, dass dein Konto & deine Nachrichten sicher sind
+ Verifiziere alle deine Sitzungen, um sicherzustellen, dass dein Konto und deine Nachrichten sicher sindBestätige neue Anmeldung zu deinem Konto: %1$sVerifiziere manuell mit einem TextAnmeldung verifizieren
@@ -1543,7 +1510,7 @@
Backup einrichtenBackup zurücksetzenAuf diesem Gerät einrichten
- Verlust verschlüsselter Nachrichten & Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden.
+ Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden.Generiere einen neuen Sicherheitsschlüssel oder setze eine neue Sicherheitspassphrase für dein existierendes Backup.Dieses wird deinen aktuellen Schlüssel oder deine aktuelle Phrase ersetzen.Integrationen sind deaktiviert
@@ -1589,9 +1556,9 @@
Dein Serveradministrator hat in privaten Räumen und Direktnachrichten Ende-zu-Ende-Verschlüsselung standardmäßig deaktiviert.Flugzeugmodus ist aktivGib eine Sicherheitsphrase ein, die nur du kennst. Diese wird benutzt um deine Daten auf dem Server geheim zu halten.
- Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten & Daten verlieren.
+ Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten und Daten verlieren.
\n
-\nDu kannst auch ein Backup einrichten & deine Schlüssel in den Einstellungen verwalten.
+\nDu kannst auch ein Backup einrichten und deine Schlüssel in den Einstellungen verwalten.Du hast den Raum erstellt und konfiguriert.Dieser Account ist deaktiviert worden.Konnte Mediendatei nicht speichern
@@ -1620,7 +1587,7 @@
Stoppe KameraStarte KameraBackup
- Verlust verschlüsselter Nachrichten & Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden.
+ Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden.Sicherheitsschlüssel benutzenGeneriere einen Sicherheitsschlüssel, welcher z.B. in einem Passwortmanager oder in einem Tresor sicher aufbewahrt werden sollte.Eine Sicherheitsphrase benutzen
@@ -1786,7 +1753,7 @@
RaumnamePrüfung exportierenDirektnachricht
- Geschichte der Anfragen von Schlüsselfreigaben senden
+ Verlauf der Anfragen von Schlüsselfreigaben sendenKeine weiteren ErgebnisseStarte die DiskussionAutorisieren
@@ -1841,7 +1808,6 @@
Aktivieren, wenn der Raum nur von Mitgliedern deines Homeservers zur internen Kommunikation verwendet wird. Das kann später nicht mehr geändert werden.Begrenze Zugang zu diesem Raum (für immer!) auf Mitglieder von %s%1$d von %2$d
-
Keine Vorschau für diesen Raum verfügbar. Willst du direkt beitreten\?Der Raum ist gerade nicht zugänglich.
\nVersuche es später nochmal, oder bitte einen Raum-Admin um Hilfe.
@@ -1910,8 +1876,6 @@
Beim Weiterleiten des Anrufs ist ein Fehler aufgetretenWeiterleitenVerbinden
-
-
Aktiver Anruf (%1$s)Beim Suchen der Telefonnummer ist ein Fehler aufgetretenWähltastatur
@@ -2106,7 +2070,6 @@
Spaces FeedbackDieser Server ist schon in der Liste vorhandenServer oder Raumliste kann nicht gefunden werden
-
Bei %1$s anfragenZu %1$s weiterleitenSpace-Adressen
@@ -2187,7 +2150,7 @@
Alle im übergeordneten Space haben Zugriff auf den Raum - es ist nicht nötig jeden einzeln einzuladen. Du kannst dies in den Raumeinstellungen jederzeit ändern.Andere Spaces oder Räume die du kennstSpaces mit diesem Raum und dir als Mitglied
- Nur Erwähnungen & Schlüsselwörter
+ Nur Erwähnungen und SchlüsselwörterAuflegen…Sprachanruf mit %sVideoanruf mit %s
@@ -2353,7 +2316,6 @@
Sichere Nachrichtenübertragung.Besitze deine Konversationen.Um bestehende Kontakte ermitteln zu können, müsst du Kontaktinformationen (E-Mails und Telefonnummern) an Ihren Identitätsserver senden. Wir verschlüsseln deine Daten vor dem Senden, um den Datenschutz zu gewährleisten.
-
Deine Kontakte sind privat. Um in deinen Kontakten Benutzer erkennen zu können, benötigen wir deine Erlaubnis, Kontaktinformationen an deinen Identitätsserver zu senden.Dieser Server stellt keine Richtlinie bereit.Deine Identitätsserver-Richtlinie
@@ -2430,7 +2392,7 @@
Hinweis: App wird neugestartetdiese Frage überspringenNoch nicht sicher\? Du kannst %s
- Freundschaften und Familie
+ Freunde und FamilieIn Thread antwortenAus einem ThreadFilter
@@ -2450,4 +2412,10 @@
%1$d mehrWeniger anzeigen
+ %1$s und %2$s
+ %1$s, %2$s und andere
+
+ %d Server-ACL geändert
+ %d Server-ACLs geändert
+
\ No newline at end of file
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index 21d4aac1ec..44025005d3 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -38,7 +38,6 @@
TelefoninumberKutse jututuppa%1$s ja %2$s
-
Tühi jututubaEsmane laadimine:
\nImpordin kontot…
@@ -265,7 +264,6 @@
Tunnista kehtetuksKatkesta ühendusTeata kahtlasest sisust
-
võiKutsuVõta vastu
@@ -297,7 +295,6 @@
Vaid need, kellel on Matrixi kontoTulemusi ei oleJututoad
-
KogukonnadSaada logikirjedSaada kokkujooksmise logikirjed
@@ -323,7 +320,6 @@
Edasta häältKas oled kindel, et soovid algatada häälkõnet\?Kas oled kindel, et soovid algatada videokõnet\?
-
Saada faileSaada kleepseTee foto või video
@@ -339,11 +335,9 @@
See ei tundu olema e-posti aadressi moodiSelline e-posti aadress on juba kasutusel.Kas unustasid oma salasõna\?
-
See koduserver soovib olla kindel, et sa ei ole robotSinu kontoga seotud e-posti aadress peab olema sisestatud.E-posti aadressi verifitseerimine ei õnnestunud. Palun kontrolli, et sa avasid kirjas leidunud lingi
-
Palun loe läbi ja nõustu koduserveri kasutusjuhendiga:Palun sisesta korrektne URLSee ei ole toimiv Matrix\'i serveri aadress
@@ -394,10 +388,7 @@
Videokõne on pooleli…Teine osapool ei võtnud kõnet vastu.Lisateave õiguste kohta
-
-
Kõnede tegemiseks vajab ${app_name} õigusi kasutada sinu mikrofoni.
-
${app_name} vajab videokõnedeks õigusi sinu kaamera ja mikrofoni kasutamiseks.
\n
\nKõnede tegemiseks palun anna järgmisel lehel vajalikud õigused.
@@ -408,7 +399,6 @@
LiituHülgaMine lugemata sõnumite juurde
-
Isiklikud sõnumidSee võib tähendada, et keegi on suuteline pahatahtlikult sinu veebiliiklust pealtkuulama või sinu telefon ei usalda serveri kasutatavat sertifikaati.Kui serveri haldaja on sind teavitanud, et nii võib juhtuda, siis kontrolli, et sertifikaadi sõrmejälg vastab sellele, mille haldaja sulle on andnud.
@@ -455,7 +445,6 @@
Kui sa soovid oma PIN-koodi lähtestada, siis klõpsi nuppu „Unustasin PIN-koodi“.Määra põhiaadressiksEemalda põhiaadressiks määramine
-
TeemaFontide suurusPisike
@@ -513,8 +502,6 @@
Palun sisesta taastevõtiVerifitseeritud!Selge lugu
-
-
Verifitseerimispäring%s soovib verifitseerida sinu sessiooniSinu jututoad kuvatakse siin. Olemasolevate jututubade leidmiseks või uute tegemiseks klõpsi all paremal nurgas asuvat + nuppu.
@@ -578,8 +565,6 @@
Kuna sina oled selle sessiooni verifitseerinud, siis see sessioon on krüptitud sõnumite saatmiseks usaldusväärne:See isikutuvastusserver kasutab vana API\'t. ${app_name} toetab aga vaid API versiooni 2.See tegevus ei ole võimalik. Koduserveri versioon on liiga vana.
-
-
Kõik sõnumidLisa avaleheleProfiilipilt
@@ -698,15 +683,10 @@
Väldi juhuslikke kõnesidEnne kõne algatamist küsi kinnitustOsalejate loend
-
%d osaleja%d osalejat
-
-
-
-
Lahku jututoastKas oled kindel, et soovid lahkuda jututoast\?Kutsu
@@ -715,7 +695,6 @@
Taasta ligipääsMüksa väljaMaini
-
Taastevõti on salvestatud.Varukoopia on juba olemas sinu koduserverisAsenda
@@ -871,8 +850,6 @@
Lisa Matrix\'i rakendusiLuba süsteemi poolt hallatud kaamera kasutamineKäivita kohandatud vaate asemel süsteemne kaamera vaade.
-
-
Taustapiirangud on Elemendi jaoks keelatud. Seda testi tuleks läbi viia mobiilse andmeside abil (WIFI puudub).
\n%1$sElemendi jaoks on taustpiirangud lubatud.
@@ -909,7 +886,6 @@
Kui rakendus töötab taustal, siis sa ei saa saabunud sõnumite kohta teavitusi.Käivita teenus seadme käivitamiselSünkroniseerimispäring aegus
-
Viivitus sünkroonimiste vahelVersioonolm-teegi versioon
@@ -964,7 +940,6 @@
Eemalda minu konto kasutusestLeia kasutajaidHalda kasutajate otsingu seadistusi.
-
AnalüütikaSaada arendajatele analüütikatVõimaldamaks meil rakendust parandada kogub ${app_name} anonüümset teavet rakenduse kasutuse kohta.
@@ -973,7 +948,6 @@
Uuenda avalikku nimeViimati nähtud%1$s @ %2$s
-
AutentimineSisselogitud kuiKoduserver
@@ -1000,7 +974,6 @@
\nPane tähele, et antud toiming taaskäivitab rakenduse ja see võib võtta veidi aega.Vali riikEksporditavate võtmete krüptimiseks palun sisesta paroolifraas. Võtmete importimisel pead kasutama sama paroolifraasi.
-
Võtmete eksportimine õnnestusKrüptitud sõnumite taastamineHalda võtmete varundust
@@ -1016,7 +989,6 @@
VerifitseeriKinnita seda võrreldes järgnevaid andmeid oma teise sessiooni kasutajaseadetes:Kui nad omavahel ei klapi, siis teie suhtluse turvalisus võib olla ohus.
-
Vali jututubade loendServeri aadressKõik jututoad %s serveris
@@ -1025,7 +997,6 @@
%d lugemata teavitatud sõnum%d lugemata teavitatud sõnumit
-
Jätkamaks pead nõustuma kasutustingimustega.Sa oled lisanud uue sessiooni \'%s\', mis soovib saada krüptimisvõtmeid.Uus sessioon soovib krüptovõtmeid.
@@ -1066,8 +1037,6 @@
AvalehtJututoadKutsutud
-
-
%2$s müksas sind välja %1$s jututoastTunnuspiltSelleks et jätkata koduserveri %1$s kasutamist sa pead üle vaatama ja nõustuma meie kasutustingimustega.
@@ -1156,7 +1125,6 @@
Kontrollin varukoopia olekutKustuta varukoopiaKas kustutame krüptovõtmete varukoopia serverist\? Sellisel juhul sa ei saa kasutada ka taastevõtit krüptitud sõnumite ajaloo loetavaks muutmseks.
-
Turvaline varundusHoia ära, et kaotad ligipääsu krüptitud sõnumitele ja andmeteleÄra kunagi kaota krüptitud sõnumeid
@@ -1173,7 +1141,6 @@
VersioonAlgoritmAllkiri
-
Teadmata vigaSa ei kasuta ühtegi isikutuvastusserveritTundub, et sa üritad luua ühendust teise koduserveriga. Kas sa soovid välja logida\?
@@ -1448,7 +1415,6 @@
Trüki ta välja ja hoia turvalises kohasSalvesta ta mälupulgale või varunduskettaleKopeeri ta isiklikku andmehoidlasse mis asub pilves
-
Kui sa tühistad nüüd, siis sa võid peale viimasest seadmest välja logimist kaotada ligipääsu oma krüptitud sõnumitele ja andmetele.
\n
\nAga sa võid seadistustes võtta kasutusele turvalise varunduse ning hallata oma krüptovõtmeid.
@@ -1527,7 +1493,6 @@
Haldaja sai nüüd teate, et see sisu ei ole sobilik.
\n
\nKui sa ei soovi enam näha selle kasutaja sisu, siis sa võid tema sõnumite peitmiseks kasutajat eirata.
-
Eira kasutajatKõik sõnumid (lärmakas)Kõik sõnumid
@@ -1708,7 +1673,6 @@
Halda on Matrix\'i kontoga seotud e-posti aadresse ja telefoninumbreidKoodPalun kasuta rahvusvahelist vormingut (telefoninumbri alguses peaks olema „+“)
-
Kinnita oma isikusamasust verifitseerides seda sisselogimissessiooni. Sellega tagad ligipääsu krüptitud sõnumitele.Ei ole võimalik avada sellise jututoa vaadet, kus sulle on seatud suhtluskeeld.Ei leia sellist jututuba. Palun kontrolli, et ta ikka olemas on.
@@ -1791,7 +1755,6 @@
OtsevestlusLisa kaasa võtmevahetusega seotud päringute ajaluguRohkem otsingutulemusi pole
-
🔐️ Liitu minuga vestlusrakenduses ${app_name}Hei, palun suhtle minuga vestlusrakenduses ${app_name}: %sKutsu sõpru
@@ -1914,8 +1877,6 @@
SuunaÜhendaPea esmalt nõu
-
-
Kõne on pooleli (%1$s)Telefoninumbri otsimisel tekkis vigaNumbriklahvistik
@@ -2112,7 +2073,6 @@
Sisesta serveri nimi, mille sisu sa soovid uurida.Lisa uus serverSinu server
-
Vabandust, liitumisel tekkis viga: %sKogukonnakeskuse aadressidSelle kogukonnakeskuse hallatud ja nähtavad aadressid.
@@ -2315,7 +2275,6 @@
Küsitluse küsimus või teemaKoosta üks küsitlusKüsitlus
-
Saada e-posti aadressid ja telefoninumbrid %s serverisseSinu kontaktid on vaid sinu teada. Kui tahad nende hulgast leida Matrix\'i kasutajaid, siis me vajame sinu luba nende andmete saatmiseks räsitud kujul isikutuvastusserverisse.Selleks et leida tuttavaid, sa peaksid saatma oma kontaktteavet (telefoninumbreid ja/või e-posti aadresse) siin rakenduses seadistatud isikutuvastusserverile. Parema andmeturvalisuse nimel me ei saada teavet mitte loetava tekstina, vaid räsina.
@@ -2458,4 +2417,6 @@
%d serveri kasutusõiguste muudatus%d serveri kasutusõiguste muudatust
+ %1$s, %2$s ning teised kasutajad
+ %1$s ja %2$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml
index 931075ce8d..7e22ce42a8 100644
--- a/vector/src/main/res/values-fi/strings.xml
+++ b/vector/src/main/res/values-fi/strings.xml
@@ -39,7 +39,6 @@
Huonekutsu%1$s ja %2$sTyhjä huone
-
Alkusynkronointi:
\nTuodaan tiliä…Alkusynkronointi:
@@ -190,7 +189,6 @@
PoistaNimeä uudelleenIlmoita epäilyttävästä sisällöstä
-
taiKutsuKirjaudu ulos
@@ -213,7 +211,6 @@
Ainoastaan Matrix-yhteyshenkilötEi tuloksiaHuoneet
-
Lähetä lokitLähetä kaatumislokitLähetä näytönkaappauskuva
@@ -259,13 +256,10 @@
Puhelu käynnissä…Toinen puoli ei vastannut.Huomio
-
${app_name} tarvitsee käyttöluvan mikrofoniin suorittakseen puheluita.
-
${app_name} tarvitsee käyttöluvan kameraan ja mikrofoniin suorittakseen videopuheluita.
\n
\nSalli mikrofonin ja kameran käyttö seuraavilla näytöillä aloittaaksesi tämän puhelun.
-
KYLLÄEIJatka
@@ -273,7 +267,6 @@
LiityHylkääSiirry ensimmäiseen lukemattomaan viestiin.
-
Poistu huoneestaHaluatko varmasti poistua huoneesta?YKSITYISKESKUSTELUT
@@ -299,7 +292,6 @@
Sertifikaatti johon laitteesi luotti aikaisemmin on vaihtunut. Tämä on HYVIN EPÄTAVALLISTA. On suositeltavaa, että ET hyväksy tätä uutta sertifikaattia.Sertifikaatti on vaihtunut ennestään luotetusta ei-luotettuun. Palvelin on voinut uusia sertifikaattinsa. Kysy palvelimen ylläpitäjältä, mikä sormenjäljen pitäisi olla.Hyväksy sertifikaatti vain, jos palvelimen ylläpitäjä on julkaissut sormenjäljen, joka täsmää yllä olevan kanssa.
-
EtsiEtsi huoneen jäsenistäEi tuloksia
@@ -344,7 +336,6 @@
Päivitä julkinen nimiViimeksi käytetty%1$s @ %2$s
-
TunnistautuminenKirjautuneena nimelläKotipalvelin
@@ -375,7 +366,6 @@
Nämä ovat kokeellisia ominaisuuksia, jotka voivat mennä rikki. Käytä varoen.Aseta pääosoitteeksiPoista pääosoite
-
SalauksenpurkuvirheJulkinen nimiIstunnon tunnus
@@ -386,7 +376,6 @@
VieAnna salasanaVahvista salasana
-
Tuo salatun huoneen avaimetTuo huoneen avaimetTuo avaimet paikallisesta tiedostosta
@@ -527,15 +516,10 @@
Saapuvien puheluiden soittoääniVideopuhelu menossa…Käyttäjälista
-
yksi jäsen%d jäsentä
-
-
-
-
Poista huoneestayksi uusi viesti
@@ -545,8 +529,6 @@
yksi valittu%d valittu
-
-
Edistyneet ilmoitusasetuksetIlmoituksen tärkeys tapahtumakohtaisestiRatkaise ilmoituksien ongelmia
@@ -621,7 +603,6 @@
Paina lukukuittauksesta nähdäksesi tarkemman listan.Ei vaikuta kutsuihin, poistamisiin ja porttikieltoihin.Sisältää hahmokuvat ja näyttönimien vaihdot.
-
${app_name} kerää anonyymiä analytiikkaa sovelluksen parantamiseksi.Luo salalause salataksesi viedyt avaimet. Tarvitset saman salalauseen avainten tuomiseen.Salattujen viestien palautus
@@ -630,7 +611,6 @@
yksi lukematon ilmoitettu viesti%d lukematonta ilmoitettua viestiä
-
yksi huone%d huonetta
@@ -656,8 +636,6 @@
Markdown päällä/poisMatrix-sovellusten hallinnan korjaamiseenHiljainen
-
-
HahmokuvaJatkaaksesi kotipalvelimen %1$s käyttöä, sinun täytyy hyväksyä palvelun käyttöehdot.Näytä ehdot
@@ -715,7 +693,6 @@
Tallenna palautusavainJaaTallenna tiedostona
-
Teethän kopionJaa palautusavain kohteelle…Luodaan palautusavainta käyttäen salalausetta. Tässä saattaa kestää useampi sekunti.
@@ -782,8 +759,6 @@
Näppäimistön enter-näppäin lähettää viestin sen sijaan, että se lisäisi rivinvaihdonSalasana ei ole kelvollinen%1$s -> %2$s
-
-
MediaOletuksena oleva pakkauksen määräValitse
@@ -887,8 +862,6 @@
Näytä muokkaushistoriaAvaimen jakopyyntöVahvistettu!
-
-
Vahvistuspyyntö%s haluaa vahvistaa laitteesiViesti-ilmoitusten säännöt
@@ -898,7 +871,6 @@
TaustasynkronointitilaEi taustasynkronointiaEt saa ilmoituksia saapuvista viesteistä, kun sovellus on taustalla.
-
Jatkaaksesi sinun täytyy hyväksyä palvelun käyttöehdot.Et käytä mitään identiteettipalvelintaNäyttää, että yrität yhdistää toiseen kotipalvelimeen. Haluatko kirjautua ulos\?
@@ -942,7 +914,6 @@
Se on roskapostiaSe on sopimatonEi mitään
-
Optimoitu akunkestoa varten${app_name} synkronoi taustalla laitteen rajallisia resursseja (akkua) säästäen.
\nLaitteesi resurssien tilasta riippuen käyttöjärjestelmä saattaa lykätä synkronointia.
@@ -1025,7 +996,6 @@
Tämä sisältö on ilmiannettu epäsopivana.
\n
\nJos et halua nähdä enempää sisältöä tältä käyttäjältä, voit estää hänet piilottaaksesi hänen viestit.
-
Tämä ei ole kelvollinen Matrix-palvelimen osoiteJätä käyttäjä huomiottaKaikki viestit (äänekäs)
@@ -1222,7 +1192,6 @@
Varmenna %sVarmennettu %sOdotetaan käyttäjää %s…
-
Huoneessa olevat viesti eivät ole salattu osapuolten välisellä salauksella.Huoneen viestit ovat salattu osapuolten välisellä salauksella.
\n
@@ -1674,8 +1643,6 @@
Aktiivinen puhelu ·%1$d aktiivista puhelua ·
-
-
Aktiivinen puhelu (%1$s)NumeronäppäimistöEi vastausta
@@ -1926,14 +1893,14 @@
Puuttuvat oikeudetAvaruudet
- Vähintään %1$s valinta vaaditaan
- Vähintään %1$s valintaa vaaditaan
+ Vähintään yksi vaihtoehto vaaditaan
+ Vähintään %1$s vaihtoehtoa vaaditaanKysymys ei voi olla tyhjäLUO KYSELY
- LISÄÄ VALINTA
- Valinta %1$d
- Luo valinnat
+ LISÄÄ VAIHTOEHTO
+ Vaihtoehto %1$d
+ Luo vaihtoehdotKysymys tai aiheKyselyn kysymys tai aiheLuo kysely
@@ -1954,4 +1921,66 @@
Lähetä m.room.server_acl-tapahtumiaValitse kotipalvelinEi nyt
+ Palauta salaus
+ lähettää lumisadetta ❄️
+ lähettää konfettia 🎉
+ 🔐️ Liity seuraani ${app_name}-sovelluksessa
+ Hei, juttele minulle ${app_name}-sovelluksessa: %s
+ Kotipalvelimesi (%1$s) ehdottaa, että käytät palvelinta %2$s identiteettipalvelimenasi
+ Odotetaan salaushistoriaa
+ Sinulla ei ole pääsyä tähän viestiin, koska lähettäjä jätti avaimet tarkoituksella lähettämättä
+ Sinulla ei ole pääsyä tähän viestiin, koska lähettäjä ei luota istuntoosi
+ Sinulla ei ole pääsyä tähän viestiin, koska lähettäjä esti sinut
+ Ei saatavilla
+ Tapahtuma lähetetty!
+ Odotetaan tätä viestiä, tässä voi kestää jonkin aikaa
+ Sinulla ei ole pääsyä tähän viestiin
+ %1$s antoi porttikiellon
+ Äänestäjät näkevät tulokset heti äänestettyään
+
+ Lopullinen tulos yhden äänen perusteella
+ Lopullinen tulos %1$d äänen perusteella
+
+
+ Yksi ääni annettu. Äänestä nähdäksesi tulokset
+ %1$d ääntä annettu. Äänestä nähdäksesi tulokset
+
+ Ääniä ei annettu
+
+ Perustuen yhteen ääneen
+ Perustuen %1$d ääneen
+
+
+ yksi ääni
+ %1$d ääntä
+
+ Ääni annettu
+ Kartta
+ Jaa sijainti
+ Sijainti
+ Jaa sijainti
+ Suljettu kysely
+ Avoin kysely
+ Kyselyn tyyppi
+ Muokkaa kyselyä
+ Poista kysely
+ Lähetä tarra
+ Lähetä kuvia ja videoita
+ Avaa kamera
+ Näytä viestikuplat
+ Kartan lataaminen epäonnistui
+ Jaa sijainti
+ Luo kysely
+ Jaa sijainti
+ Näytä vähemmän
+ Läpisalattu, puhelinnumeroa ei vaadita. Ei mainoksia tai tiedonlouhintaa.
+ Valitse missä keskustelujasi säilytetään – sinä päätät ja olet riippumaton. Yhdistäjänä Matrix.
+ Sinä päätät.
+ Turvallista ja riippumatonta viestintää, joka on yhtä yksityistä kuin keskustelisit kasvokkain kotonasi.
+ Viestintää tiimillesi.
+ Turvallista viestintää.
+ Pidä keskustelusi hallussasi.
+ Yhdistä palvelimeen
+ Minulla on jo tili
+ Luo tili
\ No newline at end of file
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index f56efb4ec3..6bae118e59 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -39,7 +39,6 @@
Invitation au salonSalon vide%1$s et %2$s
-
Synchronisation initiale :
\nImportation du compte…Synchronisation initiale :
@@ -217,7 +216,6 @@
SupprimerRenommerSignaler le contenu
-
ouInviterSe déconnecter
@@ -368,7 +366,6 @@
Nom du serveur d’accueilTous les salons sur le serveur %sTous les salons natifs sur %s
-
Veuillez décrire l’erreur. Qu’avez-vous fait \? Quel était le comportement attendu \? Que s’est-il réellement passé \?Afin de diagnostiquer les problèmes, les journaux de ce client seront envoyés avec ce rapport d’erreur. Ce rapport d’erreur, y compris les journaux et la capture d’écran, ne seront pas visibles publiquement. Si vous préférez envoyer le texte ci-dessus uniquement, veuillez décocher :Vous semblez secouer le téléphone avec agacement. Souhaitez-vous ouvrir soumettre un rapport d’anomalie \?
@@ -377,26 +374,19 @@
L’envoi du rapport d’anomalie a échoué (%s)Ceci ne ressemble pas à une adresse e-mail valideCette adresse e-mail est déjà utilisée.
-
Ce serveur d’accueil souhaite s’assurer que vous n’êtes pas un robotL’adresse e-mail liée à votre compte doit être saisie.Impossible de vérifier l’adresse e-mail : assurez-vous d’avoir cliqué sur le lien dans l’e-mail
-
Trop de requêtes ont été envoyéesQuitterCiterLe correspondant n’a pas décroché.Information
-
-
${app_name} a besoin d’accéder à votre microphone pour passer des appels audio.
-
${app_name} a besoin d’accéder à votre appareil photo et à votre microphone pour passer des appels vidéo.
\n
\nVeuillez autoriser l’accès dans les prochaines fenêtres pour pouvoir effectuer l’appel.
-
Aller au premier message non lu
-
Voulez-vous vraiment quitter le salon \?IgnorerAfficher tous les messages de cet utilisateur
@@ -413,11 +403,9 @@
Le certificat n’est plus celui qui a été approuvé par votre téléphone. Ce comportement est INATTENDU. Il est recommandé de ne PAS ACCEPTER ce nouveau certificat.Le certificat n’est plus celui qui a été approuvé par votre téléphone. Le serveur a peut-être renouvelé son certificat. Contactez l’administrateur du serveur pour lui demander l’empreinte de son certificat.Acceptez le certificat uniquement si l’administrateur du serveur a publié une empreinte correspondant à celle ci-dessus.
-
Quand je suis invité sur un salonParamètres utilisateur%1$s @ %2$s
-
Vérifiez votre e-mail et cliquez sur le lien qu’il contient. Une fois cela fait, cliquez sur continuer.Afficher tous les messages de %s \?
\n
@@ -426,14 +414,11 @@
Uniquement les membres (à partir de l’activation de cette option)Uniquement les membres (depuis leur invitation)Ce sont des fonctionnalités expérimentales qui peuvent se comporter de façon inattendue. À utiliser avec précaution.
-
Exporter les clés vers un fichier local
-
Importer les clés à partir d’un fichier localNe jamais envoyer de messages chiffrés aux sessions non vérifiées depuis cette session.Confirmez en comparant les informations suivantes avec les paramètres utilisateur dans votre autre session :Si elles ne correspondent pas, la sécurité de votre communication est peut-être compromise.
-
Interface utilisateurLangueChoisissez une langue
@@ -508,7 +493,6 @@
Motif : %1$sBadgeSecouer avec agacement pour signaler une anomalie
-
%d membre%d membres
@@ -518,13 +502,10 @@
%d nouveaux messagesListe les membres
-
-
%d message notifié non lu%d messages notifiés non lus
-
%d salon%d salons
@@ -584,16 +565,10 @@
La conversation continue iciCe salon est la suite d’une autre conversationCliquer ici pour voir les anciens messages
-
-
-
-
%d sélectionné%d sélectionnés
-
-
Alertes systèmecontacter l’administrateur de votre serviceCe serveur d’accueil a dépassé une de ses limites de ressources donc certains utilisateurs ne pourront pas se connecter.
@@ -685,7 +660,6 @@
${app_name} n’est pas affecté par l’optimisation de la batterie.Si un utilisateur laisse un appareil débranché et immobile pour une longue durée, avec l’écran éteint, l’appareil entre en mode veille.. Cela empêche les applications d’accéder au réseau et reporte leurs tâches, synchronisations et alarmes standard.Ignorer l’optimisation
-
Aucun APK des services Google Play valide n’a été trouvé. Les notifications peuvent ne pas fonctionner correctement.Appel vidéo en cours…Sauvegarde de clé
@@ -722,7 +696,6 @@
TerminéSauvegarder la clé de récupérationEnregistrer dans un fichier
-
Veuillez en faire une copiePartager la clé de récupération avec…Génération de la clé de récupération utilisant la phrase secrète. Cette opération peut prendre plusieurs secondes.
@@ -808,7 +781,6 @@
AlgorithmeSignaturePour utiliser la sauvegarde de clés sur cette session, faites une restauration avec votre phrase secrète ou votre clé de récupération.
-
Traitement de la clé de récupération…Téléchargement des clés…Importation des clés…
@@ -817,7 +789,6 @@
Envoyer le message avec EntréeLe bouton Entrée sur le clavier logiciel enverra le message au lieu d’aller à la ligneLe mot de passe n’est pas valide
-
MédiaCompression par défautChoisir
@@ -854,8 +825,6 @@
IgnorerVérifié !Compris
-
-
Demande de vérification%s veut vérifier votre sessionErreur inconnue
@@ -952,7 +921,6 @@
AucunRévoquerDéconnecter
-
Impossible de joindre le serveur d’accueil à cette URL, veuillez la vérifierMode de synchronisation en arrière-planOptimisé pour préserver la batterie
@@ -963,7 +931,6 @@
\nCela aura un impact sur l’utilisation des données mobiles et de la batterie, une notification permanente sera affichée indiquant que ${app_name} est à l’écoute des évènements.Aucune synchronisation en arrière-planVous ne serez pas notifié des messages entrants quand l’application est en arrière-plan.
-
DécouverteGérer vos paramètres de découverte.Vous n’utilisez aucun serveur d’identité
@@ -1033,7 +1000,6 @@
Ce contenu a été signalé comme inapproprié.
\n
\nSi vous ne voulez plus voir de contenu de cet utilisateur, vous pouvez l’ignorer pour masquer ses messages.
-
IntégrationsUtilisez un gestionnaire d’intégrations pour gérer les robots, les passerelles, les widgets et les jeux d’autocollants.
\nLes gestionnaires d’intégrations reçoivent des données de configuration et peuvent modifier des widgets, envoyer des invitations de salon et définir des rangs à votre place.
@@ -1249,7 +1215,6 @@
Vérifier %s%s a été vérifiéNous attendons %s…
-
Les messages dans ce salon ne sont pas chiffrés de bout en bout.Les messages dans ce salon sont chiffrés de bout en bout.
\n
@@ -1395,7 +1360,6 @@
Imprimez-le et conservez-le en lieu sûrSauvegardez-le sur une clé USB ou un disque de sauvegardeCopiez-le sur votre stockage dans le cloud personnel
-
Chiffrement activéLes messages de ce salon sont chiffrés de bout en bout. Apprenez-en plus et vérifiez les utilisateurs sur leur profil.Chiffrement désactivé
@@ -1779,7 +1743,6 @@
Veuillez fournir une adresse de salonCette adresse est déjà utilisée%1$d de %2$d
-
AutoriserRévoquer mon autorisationVous avez donné votre autorisation pour envoyer des e-mails et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts.
@@ -1858,8 +1821,6 @@
TransférerRejoindreConsulter d’abord
-
-
Appel en cours (%1$s)Il y a eu une erreur lors de la recherche du numéro de téléphonePavé de numérotation
@@ -2060,7 +2021,7 @@
Moi et mon équipeUn espace privé pour organiser vos salonsSeulement moi
- Assurez-vous que les accès à %s sont accordés aux bonnes personnes. Vous pourrez changer ceci plus tard.
+ Assurez-vous que les bonnes personnes ont accès à %s.Avec qui travaillez-vous \?Pour rejoindre un espace existant, il vous faut une invitation.Le fichier est trop volumineux pour être envoyé.
@@ -2112,7 +2073,6 @@
Ajouter un nouveau serveurVotre serveurConsultation de %1$s
-
Désolé, une erreur est survenue en essayant de rejoindre %sAdresse de l’espaceAfficher et gérer les adresses de cet espace.
@@ -2313,7 +2273,6 @@
Question ou sujet du sondageCréer un sondageSondage
-
Envoyer des courriels et des numéros de téléphone à %sVos contacts sont personnels et privés. Pour découvrir des utilisateurs à partir de vos contacts, nous avons besoin de votre permission pour envoyer les informations des contacts à votre serveur d’identité.La session a été déconnectée !
@@ -2413,4 +2372,47 @@
Communication indépendante et sécurisée qui vous donne le même niveau d\'intimité qu\'une discussion face-à-face dans votre maison.LocalisationLe chiffrement a été mal configuré ce qui vous empêche d\'envoyer des messages. Cliquez pour ouvrir les paramètres.
+ Notification du salon
+ Utilisateurs
+ Notifier tout le salon
+
+ %1$d de plus
+ %1$d de plus
+
+ Réduire
+ Afficher les messages en bulles
+ Impossible de charger la carte
+ Carte
+ Note : l’application sera redémarrée
+ Activer les messages en fils de discussion
+ Se connecter au serveur
+ Vous cherchez à joindre un serveur existant \?
+ passer cette question
+ Pas encore sûr \? Vous pouvez %s
+ Communautés
+ Équipes
+ Famille et amis
+ Nous allons vous aider à vous connecter.
+ À qui allez-vous le plus parler \?
+ Vous êtes déjà en train de voir ce fil de discussion !
+ Voir dans le salon
+ Répondre dans le fil de discussion
+ La commande « %s » est connue mais non supportée dans les fils de discussion.
+ Depuis un fil de discussion
+ Indice : Appui long sur un message puis « %s ».
+ Les fils de discussion vous permettent de centrer vos conversations sur un sujet, et de les suivre facilement.
+ Gardez vos conversations organisées avec les fils de discussion
+ Affiche tous les fils de discussion auxquels vous avez participé
+ Mes fils de discussion
+ Affiche tous les fils de discussion du salon actuel
+ Tous les fils de discussion
+ Filtrer
+ Fils de discussion
+ Fil de discussion
+ Filtrer les fils de discussion du salon
+ %1$s, %2$s et d’autres
+ %1$s et %2$s
+ Copier le lien du fil de discussion
+ Voir dans le salon
+ Voir les fils de discussions
\ No newline at end of file
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 40121e2d1a..f189a4198f 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -39,7 +39,6 @@
Meghívó egy szobába%1$s és %2$sÜres szoba
-
Induló szinkronizáció:
\nFiók betöltése…Induló szinkronizáció:
@@ -179,7 +178,6 @@
TörlésÁtnevezésTartalom Bejelentése
-
vagyMeghívásKijelentkezés
@@ -202,7 +200,6 @@
Csak Matrix névjegyekNincs találatSzobák
-
Naplófájlok küldéseÖsszeomlásnaplók küldéseKépernyőkép küldése
@@ -230,11 +227,9 @@
Ez nem tűnik érvényes e-mail címnekEz az e-mail cím már használatban van.Elfelejtetted a jelszavad?
-
A Matrix-kiszolgáló szeretné ellenőrizni, hogy nem vagy robotMeg kell adnod a fiókodhoz tartozó e-mail-címet.Az e-mail-címed ellenőrzés sikertelen: győződj meg róla, hogy rákattintottál az e-mailben található hivatkozásra
-
Adj meg egy érvényes URL-tHibás JSONNem tartalmazott érvényes JSON-t
@@ -250,14 +245,10 @@
Hívás folyamatban…A hívott fél nem vette fel.Információ
-
-
A ${app_name}nek engedélyre van szüksége a mikrofon eléréséhez, hogy hanghívás tudjon indítani.
-
A ${app_name}nek engedélyre van szüksége a mikrofonod és kamerád eléréséhez, hogy videohívást tudj indítani.
\n
\nEngedélyezd a hozzáférést a következő felugró ablakon, hogy hívást tudj indítani.
-
IGENNEMFolytatás
@@ -265,7 +256,6 @@
CsatlakozásElutasításUgrás az olvasatlanra
-
Szoba elhagyásaBiztos el akarod hagyni a szobát?KÖZVETLEN CSEVEGÉSEK
@@ -292,7 +282,6 @@
A tanúsítvány eltér attól, amit a telefonoddal megbízhatónak jelöltél. Ez RENDKÍVÜL SZOKATLAN. Javasoljuk, hogy NE FOGADD EL ezt az új tanúsítványt.Egy korábban megbízhatónak jelölt tanúsítvány megváltozott. Lehet, hogy a szerver frissítette a tanúsítványát. Lépj kapcsolatba a szerver adminisztrátorával és egyeztesd az ujjlenyomatot.Csak akkor fogadd el a tanúsítványt, ha a szerver adminisztrátortól kapott ujjlenyomat megegyezik a fentivel.
-
KeresésSzobatagok szűréseNincs találat
@@ -337,7 +326,6 @@
Nyilvános Név frissítéseLegutóbb láttuk%1$s @ %2$s
-
AzonosításBejelentkezve mintMatrix szerver
@@ -366,7 +354,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."<
Ezek kísérleti funkciók, ezek elromolhatnak nem számított módokon. Használd elővigyázatossággal.Fő címnek állításKiszedés fő címek közül
-
Visszafejtés hibaNyilvános névMunkamenet-azonosító
@@ -377,7 +364,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."<
ExportálásÍrj be jelmondatotEllenőrizd a jelmondatot
-
E2E szoba kulcsok importálásaSzoba kulcsok importálásaKulcsok importálás helyi fájlból
@@ -389,7 +375,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."<
HitelesítésHogy ellenőrizni lehessen, hogy ez a munkamenet megbízható, kérlek használj más kommunikáció módot a tulajdonossal (pl.: személyesen vagy telefonon keresztül) és kérdezd meg hogy a kulcs amit lát a Felhasználói Beállítások alatt megegyezik-e az alábbi kulccsal:Ha nem egyeznek, akkor a kommunikáció biztonsága kompromittálva lehet. A jövőben ez a hitelesítési mód kényelmesebbé lesz téve.
-
Válassz egy szoba könyvtáratSzerver neveÖsszes szoba a %s szerveren
@@ -473,7 +458,6 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."<
%d tagság változásTagok listázása
-
%d tag%d tag
@@ -482,13 +466,10 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."<
%d új üzenet%d új üzenet
-
-
%d olvasatlan üzenet%d olvasatlan üzenet
-
%d szoba%d szoba
@@ -544,16 +525,10 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlés
A beszélgetés itt folytatódikEz a szoba egy másik beszélgetés folytatásaRégebbi üzenetek megjelenítéséhez kattints ide
-
-
-
-
%d kiválasztva%d kiválasztva
-
-
Rendszerriasztásokvedd fel a kapcsolatot a szolgáltatás adminisztrátorávalEz a Matrix szerver túllépte valamely erőforrás korlátot így néhány felhasználó nem tud majd bejelentkezni.
@@ -680,7 +655,6 @@ Helyezd biztonságba a kulcsokat, hogy ne vesszenek el.KészVisszaállítási Kulcs mentéseMentés fájlba
-
Kérlek, készíts egy másolatot!Visszaállítási Kulcs megosztása…Visszaállítási Kulcs készítése jelmondatból, ez néhány másodpercet igénybe vehet.
@@ -774,7 +748,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Üzenet küldése Enter billentyűvelAz Enter billentyű a virtuális billentyűzeten elküldi az üzenetet és nem új sort szúr beA jelszó nem érvényes
-
MédiaAlapértelmezett tömörítésVálassz
@@ -811,8 +784,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
MellőzEllenőrizve!Értem
-
-
Ellenőrzési kérés%s szeretné ellenőrizni a munkamenetedetIsmeretlen Hiba
@@ -909,7 +880,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
NincsVisszavonásBontás
-
Nem érhető el Matrix-kiszolgáló ezen a címen, ellenőrizdHáttér Szinkronizálási MódOptimalizált akkumulátor használat
@@ -920,7 +890,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
\nEz befolyásolja a rádió és az akkumulátor használatot, és folyamatosan egy értesítés fog megjelenni arról, hogy a ${app_name} figyel a neki küldött eseményekre.Nincs szinkroniziálás a háttérbenNem leszel értesítve az érkező üzenetekről, ha az alkalmazás csak a háttérben fut.
-
FelderítésFelderítési beállítások megváltoztatása.Nem használsz Azonosítási Szervert
@@ -990,7 +959,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Ez a tartalom nem idevalónak lett bejelentve.
\n
\n Ha nem akarsz ettől a felhasználótól több üzenetet látni akkor blokkolhatod, hogy az üzenetei ne jelenjenek meg számodra.
-
IntegrációkBotok, hidak, kisalkalmazások és matrica csomagok kezeléséhez használj Integrációs Menedzsert.
\n
@@ -1207,7 +1175,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Ellenőrzés: %sEllenőrizve: %sVárakozás %s felhasználóra…
-
Az üzenetek a szobában nincsenek végponttól végpontig titkosítva.A szobában az üzenetek végponttól végpontig titkosítva vannak.
\n
@@ -1353,7 +1320,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Nyomtasd ki és tárold valahol biztonságos helyenMentsd el egy USB kulcsra vagy mentő eszközreMásold fel a személyes felhő tárhelyedre
-
Titkosítás bekapcsolvaEbben a szobában az üzenetek végpontok között titkosítottak. További információkért és ellenőrzéshez nyisd meg a felhasználók profiljait!Titkosítás nincs engedélyezve
@@ -1596,7 +1562,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Szoba beállításainak módosítása sikeresNem érheted el ezt az üzenetetVárakozás erre az üzenetre, ez eltarthat egy darabig
- A végpontok közötti titkosítás miatt lehet hogy várnod kell, hogy valaki üzenet megérkezzen, mert a titkosítási kulcsok nem lettek megfelelően elküldve neked.
+ A végpontok közötti titkosítás miatt lehet hogy várnod kell, hogy valaki üzenetét el tudd olvasni, mert a titkosítási kulcsok nem lettek megfelelően elküldve neked.Nem érheted el ezt az üzenetet, mert a küldő letiltottNem érheted el ezt az üzenetet, mert a feladó nem bízik a munkamenetedbenNem érheted el ezt az üzenetet, mert a feladó szándékosan nem küldte el a kulcsokat
@@ -1674,7 +1640,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
QR kódMeghívás QR kóddalA QR kód beolvasásához szükség van kamera hozzáférésre.
-
ElutasításFogadásNincsen jogosultságod konferenciahívás indításához
@@ -1864,7 +1829,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Én és a csoporttársaimPrivát tér a szobáid csoportosításáhozCsak én
- Ellenőrizd, hogy a megfelelő személyeknek van hozzáférése ide: %s.
+ Ellenőrizd, hogy a megfelelő személyeknek van hozzáférésük ehhez: %s.Csak az olvasatlan üzenetek számát mutassa az egyszerű értesítésekben.Kivel dolgozol együtt\?Létező térbe való belépéshez meghívó szükséges.
@@ -1939,8 +1904,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
ÁtadásKapcsolódásElőször tájékozódj
-
-
Aktív hívás (%1$s)A telefonszám megkeresésekor hiba történtTárcsázó számlap
@@ -2110,7 +2073,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Add meg a felfedezni kívánt új szerver nevét.Új szerver hozzáadásaMatrix szervered
-
Bocsánat, hiba történt a csatlakozáskor ide: %sTér címTér címek megjelenítése és kezelése.
@@ -2298,7 +2260,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Matrix szerver kiválasztásaA matrix szervert nem sikerül elérni ezen az URL-en: %s. Ellenőrizd a kapcsolatodat vagy add meg a matrix szervert kézzel.Értesítések figyelése
-
A névjegyeid személyes adatok. Ahhoz, hogy a névjegyzéked alapján megtalálhass felhasználókat, szükségünk van az engedélyedre, hogy a névjegy adatokat elküldhessük az azonosítási szolgáltatásnak.Legalább %1$s válasz szükséges
@@ -2415,25 +2376,25 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Térkép betöltése sikertelenTérképFigyelem: az alkalmazás újraindul
- Üzenetszálak engedélyezése
+ Üzenetszálak bekapcsolásaSzerverhez csatlakozás
- Csatlakoznál egy már meglévő szerverhez\?
- kérdés kihagyása
- Még nem vagy biztos\? Tudhatsz ilyent: %s
+ Csatlakoznál egy meglévő szerverhez\?
+ Kihagyhatod ezt a kérdést.
+ Még nem tudod\? %sKözösségekCsoportokBarátok és családSegítünk a kapcsolatteremtésben.
- Kivel beszélgetnék leginkább\?
- Már nézed ezt az üzenetszálat!
+ Kikkel fogsz legtöbbet beszélgetni\?
+ Jelenleg ezt az üzenetszálat olvasod!Megjelenítés szobában
- Válasz az üzenetszálban
+ Válasz üzenetszálban„%s” parancs ismert, de üzenetszálban nem támogatott.
- Az üzenetszálból
- Tipp: Koppints hosszan az üzenetre és használd ezt: %s.
+ Egy üzenetszálból
+ Tipp: Koppints hosszan az üzenetre és használd a \"%s\" opciót.Az üzenetszálak segítenek a különböző témájú beszélgetések figyelemmel kísérésében.
- Beszélgetések üzenetszálakba való rendezése
- Minden üzenetszál megjelenítése ahol szerepel
+ Beszélgetések üzenetszálakba rendezése
+ Minden üzenetszál megjelenítése, ahová üzenetet küldtélÜzenetszálaimA szobában lévő összes szál mutatásaMinden üzenetszál
@@ -2456,4 +2417,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
%d szerver jogosultság változott%d szerver jogosultság változott
+ %1$s, %2$s és mások
+ %1$s és %2$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 097efda7a6..d7856089e8 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -3,7 +3,6 @@
Undangan Ruangan%1$s dan %2$sRuangan kosong
-
PengaturanOKBatal
@@ -85,9 +84,7 @@
Nama serverPilih direktori ruangTerverifikasi
-
Gandakan ke clipboard
-
Kirim tampilan layarMohon uraikan kutu tersebut. Apa yang Anda lakukan\? Apa yang Anda harapkan terjadi\? Apa yang sebenarnya terjadi\?Catat dari klien akan dikirim bersama laporan gangguan ini untuk mendalami kendala yang Anda temukan. Laporan gangguan ini, termasuk catat dan tangkapan layar, tidak akan terlihat secara umum. Jika Anda hanya ingin mengirimkan tulisan di atas, silakan hapus centang:
@@ -96,20 +93,15 @@
Kemajuan (%s%%)Nama PenggunaNama pengguna dan/atau kata sandi salah
-
Anda perlu memasukkan alamat email yang tertaut pada akun.Verifikasi alamat email gagal: pastikan tautan yang termuat di email telah diklik
-
JSON amburadulTidak berisi JSON yang sahPengajuan yang dikirimkan terlalu banyakPanggilan Video MasukPanggilan Suara MasukPanggilan Sedang Berlangsung…
-
-
${app_name} membutuhkan permisi atas akses mikrofon Anda untuk melakukan panggilan audio.
-
${app_name} membutuhkan izin untuk mengakses kamera dan mikrofon Anda untuk melakukan panggilan video.
\n
\nHarap berikan akses pada halaman berikut ini untuk melakukan panggilan.
@@ -142,19 +134,11 @@
%d perubahan keanggotaanPanggilan
-
-
Daftar AnggotaArahkan ke pesan yang belum dibaca
-
-
%d anggota
-
-
-
-
Tinggalkan ruangApa benar Anda ingin meninggalkan ruangan ini\?PERCAKAPAN LANGSUNG
@@ -189,12 +173,9 @@
%d terpilih
-
CariSaring anggota ruangTidak ada hasil
-
-
Semua pesanTambahkan ke Layar UtamaGambar Profil
@@ -263,8 +244,6 @@
BerandaRuanganTelah Diundang
-
-
Anda telah dikeluarkan dari %1$s oleh %2$sAnda telah dicekal dari %1$s oleh %2$sAlasan: %1$s
@@ -303,13 +282,11 @@
Apabila cocok, tekan tombol verifikasi berikut.
Apabila tidak, seseorang sedang menyadap perangkat ini dan mungkin perlu diblokir.
Di masa mendatang proses verifikasi ini akan dimutakhirkan.
-
Semua ruangan dalam server %sSemua ruangan bawaan %s%d pesan pemberitahuan yang belum dibaca
-
Singkapan Riwayat RuanganSiapa yang dapat membaca riwayat\?Siapapun
@@ -323,7 +300,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Ini adalah fitur uji coba dan mungkin rusak tanpa terduga. Hati-hati menggunakannya.Tentukan sebagai alamat utamaTidak tentukan sebagai alamat utama
-
TemaKesalahan dekripsiNama perangkat
@@ -335,7 +311,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.EksporMasukkan kata sandiTegaskan kata sandi
-
Mendengarkan peristiwaPemberitahuan pihak ketigaHak cipta
@@ -371,7 +346,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Nama PerangkatTerakhir terlihat%1$s @ %2$s
-
OtentikasiMasuk sebagaiHomeserver
@@ -663,7 +637,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Gagal membuat koneksi real-time.
\nSilakan minta administrator homeserver Anda untuk mengkonfigurasi server TURN agar panggilan untuk bekerja dengan andal.${app_name} Panggilan Gagal
-
URL API homeserverKirim riwayat permintaan pemberian kunciSpace
@@ -1011,7 +984,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Integrasi dinonaktifkanPengelola integrasiIzinkan integrasi
-
Ini akan menggantikan Kunci atau Frasa Anda saat ini.Buat Kunci Keamanan baru atau atur Frasa Keamanan baru untuk cadangan yang ada.Lindungi dari kehilangan akses ke pesan & data terenkripsi dengan mencadangkan kunci enkripsi di server Anda.
@@ -1032,7 +1004,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.%d detik
-
Anda tidak akan diberitahu tentang pesan masuk saat aplikasi berada di latar belakang.Tidak ada sinkronisasi latar belakang${app_name} akan disinkronkan di latar belakang secara berkala pada waktu yang tepat (dapat dikonfigurasi).
@@ -1076,7 +1047,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Memutuskan sambungan dari server identitas Anda akan membuat Anda tidak dapat ditemukan oleh pengguna lain dan Anda tidak akan dapat mengundang orang lain melalui email atau nomor telepon.Kirim email dan nomor teleponAnda telah memberikan persetujuan untuk mengirim email dan nomor telepon ke server identitas ini untuk menemukan pengguna lain dari kontak Anda.
-
Anda sedang berbagi email atau nomor telepon di server identitas %1$s. Anda harus menyambungkan kembali ke %2$s untuk berhenti membagikannya.Setujui Persyaratan Layanan server identitas (%s) agar Anda dapat ditemukan melalui email atau nomor telepon.Kami mengirimi Anda email konfirmasi ke %s, periksa email Anda dan klik tautan konfirmasi
@@ -1147,7 +1117,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Semua pesanSemua pesan (brisik)Abaikan pengguna
-
Konten ini telah dilaporkan sebagai tidak pantas.
\n
\nJika Anda tidak ingin melihat konten dari pengguna ini, Anda dapat mengabaikan pengguna itu untuk menyembunyikan pesan dari pengguna.
@@ -1295,11 +1264,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Kesalahan Tidak Diketahui%s ingin memverifikasi sesi AndaPermintaan Verifikasi
-
-
Saya mengertiTerverifikasi!
-
Tanda TanganAlgoritmaVersi
@@ -1316,7 +1282,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Jangan kehilangan pesan terenkripsiLindungi dari kehilangan akses ke pesan & data terenkripsiCadangan Aman
-
Hapus kunci enkripsi yang sudah dicadangkan dari server\? Anda akan tidak dapat menggunakan kunci pemulihan untuk membaca riwayat pesan terenkripsi.Hapus CadanganMemeriksa status cadangan
@@ -1364,7 +1329,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Sepertinya Anda telah menyiapkan cadangan kunci dari sesi lain. Apakah Anda ingin menggantinya dengan yang Anda buat\?Cadangan sudah ada di homeserver AndaKunci pemulihan telah disimpan.
-
Simpan sebagai FileBagikanSimpan Kunci Pemulihan
@@ -1543,7 +1507,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima memiliki kunci unik untuk mengakses mereka.
Pesan ini tidak terenkripsi secara ujung-ke-ujung.Pesan di ruangan ini tidak terenkripsi secara ujung-ke-ujung.
-
Menunggu untuk %s…Diverifikasi %sVerifikasi %s
@@ -1733,7 +1696,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
TingkatkanMohon sabar, ini mungkin membutuhkan waktu yang lama.Bergabung ke ruangan yang diganti
-
Ruangan Tanpa NamaBeberapa ruangan mungkin disembunyikan karena mereka privat dan Anda membutuhkan undangan.Beberapa ruangan mungkin disembunyikan karena mereka privat dan Anda membutuhkan undangan.
@@ -2086,7 +2048,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Jika Anda batalkan, Anda mungkin kehilangan pesan terenkripsi dan data Anda jika Anda kehilangan akses ke login Anda.
\n
\nAnda juga dapat mengatur Cadangan Aman dan kelola kunci Anda di Pengaturan.
-
Salin ke penyimpanan awan pribadi AndaSimpan di flashdisk atau penyimpanan cadanganCetak dan simpan di tempat yang aman
@@ -2179,8 +2140,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
%1$d panggilan aktif ·
-
-
Panggilan aktif (%1$s)Ada sebuah kesalahan saat mencari nomor teleponTombol penyetel
@@ -2275,7 +2234,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Pertanyaan atau topik pollBuat PollPoll
-
Kirim email dan nomor telepon ke %sKontak Anda privat. Untuk menemukan pengguna dari kontak Anda, kami membutuhkan izin untuk mengirim info kontak ke server identitas Anda.Sesinya telah dikeluarkan!
@@ -2321,7 +2279,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
di siniBantu kami mengidentifikasi masalah-masalah dan membuat ${app_name} lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda.
\n
-\n
\nAnda dapat membaca semua kebijakan kami %s.Bantu buat ${app_name} lebih baikAktifkan
@@ -2415,4 +2372,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
%d perubahan ACL server
+ %1$s, %2$s dan lainnya
+ %1$s dan %2$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index 849a4e92a4..492d8af526 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -39,7 +39,6 @@
Invito nella stanza%1$s e %2$sStanza vuota
-
Sincronizzazione iniziale:
\nImportazione account…Sincronizzazione iniziale:
@@ -241,7 +240,6 @@
EliminaRinominaSegnala contenuto
-
oInvitaDisconnetti
@@ -264,7 +262,6 @@
Mostra solo i contatti MatrixNessun risultatoStanze
-
Invia i registriInvia i registri di crashInvia schermata
@@ -295,11 +292,9 @@
Questo indirizzo email non sembra correttoL\'indirizzo email è già stato impostato.Hai dimenticato la password\?
-
Questo homeserver vuole assicurarsi che tu non sia un robotVa inserito l\'indirizzo email associato al tuo account.La verifica del tuo indirizzo email è fallita: assicurati di aver cliccato sul link contenuto nella mail
-
Inserisci un URL validoJSON malformatoNon conteneva un JSON valido
@@ -315,14 +310,10 @@
Chiamata in corso…Ricezione fallita da parte del destinatario.Informazione
-
-
${app_name} deve essere autorizzato ad accedere al microfono e poter così fare chiamate audio.
-
${app_name} deve essere autorizzato ad accedere a fotocamera e microfono per poter fare chiamate video.
\n
\nNel prossimo pop-up concedi le autorizzazioni per poter fare la chiamata.
-
SÌNOContinua
@@ -330,7 +321,6 @@
EntraRifiutaVai ai non letti
-
Esci dalla stanzaSei sicuro di voler uscire dalla stanza?CHAT DIRETTE
@@ -357,7 +347,6 @@
Il certificato è diverso da quello precedentemente contrassegnato sul tuo telefono come \"affidabile\". Questa cosa è MOLTO INSOLITA. Si raccomanda di NON ACCETTARE questo nuovo certificato.Il certificato del server è cambiato: quello precedente era stato contrassegnato come affidabile ma quello attuale no. Può darsi che il certificato precedente sia scaduto e sia stato semplicemente sostituito con uno nuovo. Contatta l\'amministratore del server per verificare l\'impronta digitale in uso.Contrassegna il certificato come affidabile solo se l\'mpronta digitale comunicata dall\'amministratore del server corrisponde a quella qua sopra.
-
CercaCerca tra i membri della stanzaNessun risultato
@@ -443,7 +432,6 @@
Queste sono caratteristiche sperimentali che potrebbero dare risultati inattesi. Usali con cautela.Imposta come indirizzo principaleNon usare più come indirizzo principale
-
TemaErrore di decriptazioneNome pubblico
@@ -455,7 +443,6 @@
EsportaInserisci la PassphraseConferma la Passphrase
-
Importa le chiavi di crittografia E2E della stanzaImporta le chiavi della stanzaImporta le chiavi da un file locale
@@ -467,7 +454,6 @@
ConfermaConferma confrontando la seguente con le impostazioni utente della tua altra sessione:Se non corrispondono, la sicurezza delle tue comunicazioni potrebbe essere compromessa.
-
Scegli un elenco di stanzeNome del serverTutte le stanze sull\'Home Server %s
@@ -509,7 +495,6 @@
Sicuro di voler fare una chiamata audio\?Sicuro di voler fare una videochiamata\?Elenco dei membri
-
%d utente%d utenti
@@ -519,8 +504,6 @@
%d nuovo messaggio%d nuovi messaggi
-
-
Tutti i messaggiAggiungi alla schermata inizialeAnteprima degli URL
@@ -530,7 +513,6 @@
%d messaggio notificato non letto%d messaggi notificati non letti
-
%d stanza%d stanze
@@ -608,16 +590,10 @@
La conversazione continua quiQuesta stanza contiene una conversazione cominciata altroveClicca qui per vedere i messaggi precedenti
-
-
-
-
%d selezionato%d selezionati
-
-
Avvisi di sistemacontatta l\'amministratore del servizioL\'Home Server ha superato uno dei limiti delle risorse, pertanto alcuni utenti non potranno accedere.
@@ -708,7 +684,6 @@
${app_name} non è influenzato dall\'ottimizzazione della batteria.Se si lascia un dispositivo scollegato, fermo e con lo schermo spento, dopo un certo tempo questo entra in modalità Doze. Ciò impedisce alle App di accedere alla rete e ritarda le attività, le sincronizzazioni e la ricezione dei normali allarmi.Ignora l\'ottimizzazione
-
Non è stato trovato nessun APK Google Play Services valido. Le notifiche non funzioneranno correttamente.Chiamata video in corso…Backup delle chiavi
@@ -778,7 +753,6 @@
Salva il codice di recuperoCondividiSalva come file
-
Si prega di farne una copiaCondividi il codice di recupero con…Generazione del codice di recupero basato sulla Passphrase. Questo processo può durare alcuni secondi.
@@ -826,7 +800,6 @@
Eliminazione Backup…Elimina BackupEliminare il Backup delle chiavi crittografiche dall\'Home Server\? Non potrai più usare il codice di recupero per leggere i messaggi cifrati.
-
Non perdere mai i messaggi cifratiUsa il Backup delle chiaviNuove chiavi per messaggi sicuri
@@ -840,7 +813,6 @@
VersioneAlgoritmoFirma
-
MultimediaCompressione predefinitaScegli
@@ -874,8 +846,6 @@
IgnoraVerificato!Capito
-
-
Richiesta di verifica%s vuole verificare la tua sessioneErrore sconosciuto
@@ -972,7 +942,6 @@
NessunoRevocaDisconnetti
-
Non è stato trovato alcun Home Server seguendo questo URL. Per favore, controllaloModalità sync in backgroundOttimizzato per la batteria
@@ -983,7 +952,6 @@
\nCiò avrà un certo impatto sulla quantità di dati e batteria utilizzati. Una notifica sempre accesa comunicherà che ${app_name} è attivo.Nessuna sincronizzazione in backgroundQuando l\'App è in background non ti verranno notificati i messaggi in arrivo.
-
Farsi trovareGestisci le impostazioni per farsi trovare.Non stai usando alcun server di identità
@@ -1053,7 +1021,6 @@
Questo contenuto è stato segnalato come inappropriato.
\n
\nSe non vuoi più vedere i contenuti di questo utente puoi ignorarlo. Ciò nasconderà i suoi messaggi dalla tua vista.
-
IntegrazioniUsa un gestore di integrazioni per gestire bot, bridge, widget e pacchetti di sticker.
\nI gestori di integrazioni possono ricevere dati di configurazione, modificare widget, mandare inviti alle stanze e modificare permessi a tuo nome.
@@ -1264,7 +1231,6 @@
Verifica %s%s verificatoIn attesa di %s…
-
I messaggi in questa stanza non sono cifrati E2E.I messaggi in questa stanza sono cifrati E2E.
\n
@@ -1410,7 +1376,6 @@
Stampala e conservala in un posto sicuroSalvala in una penna USB o disco di backupCopiala sul Cloud
-
Crittografia attivaI messaggi in questa stanza sono crittografati E2E. Maggiori info e verifica degli utenti nel loro profilo.Crittografia non attiva
@@ -1813,7 +1778,6 @@
Nascondi avanzateMostra avanzate%1$d di %2$d
-
Dai il consensoRevoca il mio consensoHai acconsentito ad inviare email e numeri di telefono a questo server d\'identità per poter rintracciare altri utenti tra i tuoi contatti.
@@ -1902,8 +1866,6 @@
TrasferisciConnettiPrima consulta
-
-
Chiamata attiva (%1$s)Si è verificato un errore cercando il numero di telefonoTastierino numerico
@@ -2102,7 +2064,6 @@
Inserisci il nome di un nuovo server che vuoi esplorare.Aggiungi un nuovo serverIl tuo server
-
Spiacenti, si è verificato un errore tentando di entrare: %sIndirizzo dello spazioVedi e gestisci gli indirizzi di questo spazio.
@@ -2303,7 +2264,6 @@
Domanda o argomento del sondaggioCrea sondaggioSondaggio
-
Invia email e numeri di telefono a %sI tuoi contatti sono privati. Per trovare utenti dai tuoi contatti, ci serve l\'autorizzazione per inviare le informazioni dei contatti al tuo server d\'identità.La sessione è stata disconnessa!
@@ -2448,4 +2408,6 @@
%d modifica delle ACL del server%d modifiche delle ACL del server
+ %1$s e %2$s
+ %1$s, %2$s e altri
\ No newline at end of file
diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
index 1fea4fb060..73a57823eb 100644
--- a/vector/src/main/res/values-iw/strings.xml
+++ b/vector/src/main/res/values-iw/strings.xml
@@ -82,7 +82,6 @@
ערכת נושא שחורהערכת נושא כההערכת נושא בהירה
-
חדריםאנשי קשר מטריקס בלבדשיחות
@@ -125,11 +124,9 @@
זו אינה כתובת שרת מטריקס חוקיתאנא הכנס כתובת תקינהאנא עיין וקבל את המדיניות של שרת בית זה:
-
אימות כתובת הדוא\"ל נכשל: ודא שלחצת על הקישור בדוא\"ליש להזין את כתובת הדוא\"ל המקושרת לחשבונך.שרת הבית רוצה לוודא שאתה לא רובוט
-
שכחת סיסמה\?מספר הטלפון הזה כבר קיים ומעודכן במערכת.כתובת הדוא\"ל הזו כבר קיימת ומוגדרת במערכת.
@@ -162,7 +159,6 @@
שיחת האלמנט נכשלההאם אתה בטוח שברצונך להתחיל שיחת וידאו\?האם אתה בטוח שברצונך להתחיל שיחה קולית\?
-
עבור להודעה שלא נקראהחברי רשימהדחייה
@@ -173,14 +169,10 @@
כןאפשר הרשאה לגשת לאנשי הקשר שלך.כדי לסרוק קוד QR, עליך לאפשר גישה למצלמה.
-
אלמנט זקוק להרשאה כדי לגשת למצלמה ולמיקרופון שלך כדי לבצע שיחות וידאו.
\n
\nאנא אפשר גישה בחלונות הקופצים הבאים כדי להיות מסוגל לבצע את השיחה.
-
אלמנט זקוק להרשאה כדי לגשת למיקרופון שלך כדי לבצע שיחות שמע.
-
-
מידעהצד המרוחק לא הצליח להרים.שיחת וידאו מתבצעת …
@@ -235,7 +227,6 @@
%d שניותעיכוב בין כל סינכרון
-
פסק-זמן לבקשה לסנכרוןהחל באתחוללא תקבל הודעה על הודעות נכנסות כאשר האפליקציה ברקע.
@@ -310,7 +301,6 @@
סנן משתמשים מודריםסנן חברים מהחדרחפש
-
%d נבחר%d נבחרים
@@ -410,20 +400,14 @@
תמונת פרופילהוסף למסך הביתכל ההודעות
-
האם ברצונך לעזוב את החדר\?עזוב חדר
-
-
-
-
%d חבר%d חברים%d חברים%d חברים
-
התחל אימותמושב לא מאומת מבקש מפתחות הצפנה.
\nשם מושב: %1$s
@@ -523,7 +507,6 @@
%d חדרים מועטים%d חדרים אחרים
-
הודעת התראה %d שלא נקראה%d הודעות התראה שלא נקראו
@@ -534,7 +517,6 @@
כל החדרים בשרת %sכתובת אתר של שרת ביתבחר מדריך חדרים
-
אם הם לא תואמים, אבטחת התקשורת שלך עלולה להיפגע.אשרו על ידי השוואה בין הדברים הבאים להגדרות המשתמש בפגישה האחרת שלכם:אמת
@@ -556,7 +538,6 @@
נהל גיבוי מפתחשחזור הודעות מוצפנותמפתחות יוצאו בהצלחה
-
אנא צור משפט סיסמה להצפנת המפתחות המיוצאים. יהיה עליך להזין את אותו ביטוי סיסמה כדי שתוכל לייבא את המפתחות.יצאיצא מפתחות לקובץ מקומי
@@ -567,7 +548,6 @@
שם ציבורישגיאת פענוחערכת נושא
-
ביטול ההגדרה ככתובת הראשיתהגדר ככתובת ראשיתאלה תכונות ניסיוניות שעשויות להישבר בדרכים לא צפויות. השתמש בזהירות.
@@ -690,8 +670,6 @@
סיבה: %1$sחסום על ידי %2$s מ- %1$sאתה נבעט מ- %1$s על ידי %2$s
-
-
הוזמנוחדריםבית
@@ -748,7 +726,6 @@
לעולם אל תאבד הודעות מוצפנותלהגן מפני אובדן גישה להודעות ונתונים מוצפניםגיבוי מאובטח
-
מחק גיבוי למחוק את מפתחות ההצפנה המגובים שלך מהשרת\? לא תוכל עוד להשתמש במפתח השחזור שלך כדי לקרוא את היסטוריית ההודעות המוצפנת.מחק את הגיבויבודק מצב גיבוי
@@ -794,7 +771,6 @@
נראה שכבר יש לך גיבוי מפתח הגדרה מהפעלה אחרת. האם אתה רוצה להחליף אותו לזה שאתה יוצר\?גיבוי כבר קיים בשרת הבית שלךמפתח השחזור נשמר.
-
שמירת קובץ בשםשיתוףשמור מפתח שחזור
@@ -998,7 +974,6 @@
כל ההודעותכל ההודעות (רועשות)התעלם ממשתמש זה
-
תוכן זה דווח כבלתי הולם.
\n
\nאם אינך רוצה לראות תוכן נוסף ממשתמש זה, תוכל להתעלם ממנו כדי להסתיר את ההודעות שלו.
@@ -1058,7 +1033,6 @@
אנא הזן את כתובת ה- URL של שרת הזהותלא ניתן היה להתחבר לשרת זהותהזן כתובת אתר של שרת זהות
-
תן הסכמהבטל את הסכמתינתת את הסכמתך לשלוח מיילים ומספרי טלפון לשרת זהות זה כדי לגלות משתמשים אחרים מאנשי הקשר שלך.
@@ -1157,7 +1131,6 @@
\nההודעות שלך מאובטחות במנעולים ורק לך ולמקבל יש את המפתחות הייחודיים לפתיחתם.ההודעות כאן אינן מוצפנות מקצה לקצה.הודעות בחדר זה אינן מוצפנות מקצה לקצה.
-
ממתין ל- %s…%s מאומתאמת. את %s
@@ -1219,11 +1192,8 @@
שגיאה לא ידועה%s רוצה לאמת את ההפעלה שלךבקשת אימות
-
-
הבנתימאומת!
-
חתימהאלגוריתםגירסה
@@ -1253,7 +1223,6 @@
הצג תוכן בהתראותקוד PIN הוא הדרך היחידה לפתוח את Element.אפשר ביומטריה ספציפית למכשירים, כמו טביעות אצבע וזיהוי פנים.
-
גיבוי מאובטחהוסף לחצן במלחין ההודעות כדי לפתוח מקלדת אימוג\'יהצג מקלדת אימוג\'י
@@ -1284,11 +1253,9 @@
צפה ועדכן את התפקידים הנדרשים לשינוי חלקים שונים בחדר.הרשאות חדריםחדר זה אינו ציבורי. לא תוכל להצטרף שוב ללא הזמנה.
-
(%%%s)התקדמותאנשיםמועדפים
-
ברירת מחדל מערכתקפצו לקבלת קריאההודעה ישירה
@@ -1370,7 +1337,6 @@
שרת ביתימחובר כהזדהות
-
נראה לאחרונהעדכן שם ציבורישם ציבורי
@@ -1378,7 +1344,6 @@
אלמנט אוסף ניתוח אנונימי כדי לאפשר לנו לשפר את היישום.שלח נתוני ניתוחניתוח נתונים
-
נהל את הגדרות הגילוי שלך.תַגלִיתהפוך את המשתמש שלי ללא פעיל
@@ -1568,7 +1533,6 @@
אם תבטל עכשיו, אתה עלול לאבד הודעות ונתונים מוצפנים אם תאבד את הגישה לכניסות שלך.
\n
\nאתה יכול גם להגדיר גיבוי מאובטח ולנהל את המפתחות שלך בהגדרות.
-
העתק אותו לאחסון הענן האישי שלךשמור אותו במפתח USB או בכונן גיבויהדפיסו ואחסנו במקום בטוח
@@ -1731,55 +1695,55 @@
סיימת את השיחה.%s סיים את השיחה.ענית לשיחה.
- %s שנה לשיחה.
+ %s ענה לשיחה.שלחת מידע לביצוע שיחה.%s שלח מידע לביצוע שיחה.ביצעת שיחת אודיו.
- %s ביצע שיחת אודיו.
+ %s ביצע/ה שיחה קולית.ביצעת שיחת וידאו.
- %s ביצע שיחת וידאו .
+ %s ביצע/ה שיחת וידאו.שינית את שם החדר ל: %1$s%1$s שינה את שם החדר ל: %2$s
- שינית את דמות החדר
- %1$s שינה את דמות החדר
- שינית את השם ל: %1$s
+ שינית את תמונת החדר
+ %1$s שינה את תמונת החדר
+ שינית את הנושא ל: %1$s%1$s שינה את הנושא ל: %2$s
- ביטלתם את שם התצוגה שלכם (שם קודם %1$s)
+ הסרת את שם התצוגה שלך (הוא היה %1$s)%1$sהורידו את שם התצוגה שלהם (היה קודם %2$s)שינית את שמך מ %1$s ל %2$s%1$s שינו את שמותיהם מ %2$s ל %3$sשינית את שמך ל %1$s%1$s שינה את שמו ל %2$s
- שינית את דמותך באפליקציה
- %1$s שינה את דמותו
+ שינית תמונת פרופיל
+ %1$s שינה תמונת פרופילמשכת את הזמנתו של %1$s\'s
- %1$s משך את הזמנתו של %2$s\'s
+ %1$s ביטל/ה את ההזמנה של %2$s\'sחסמת את %1$s
- %1$s חסם את%2$s
- חסמת את %1$s
- %1$s הסיר מחסימה את %2$s
+ %1$s חסם/ה את %2$s
+ הסרת את החסימה של %1$s
+ %1$s הסיר/ה מחסימה את %2$sהרחקת את %1$s
- %1$s הרחיק את%2$s
+ %1$s הרחיק/ה את %2$sדחית את ההזמנה%1$s דחה את ההזמנהעזבת את החדר
- %1$s עזב את החדר
+ %1$s עזב/ה את החדרעזבת את החדר
- %1$s עזב את החדר
+ %1$s עזב/ה את החדרהצטרפת
- %1$s הצטרף
+ %1$s הצטרף/ההצטרפת לחדר
- %1$s הצטרף לחדר
- %1$s הזמין אותך
+ %1$s הצטרף/ה לחדר
+ %1$s הזמין/ה אותךהזמנת את %1$s
- %1$s הזמין %2$s
+ %1$s הזמין/ה את %2$sיצרת את הדיון
- %1$s יצר את הדיון
+ %1$s יצר/ה את הדיוןיצרת את החדר
- %1$s יצר את החדר
+ %1$s יצר/ה את החדרהזמנתך
- %s הזמנה
- כל חברי החדר, מהנקודה בה הם הוזמנו.
+ ההזמנה של %s
+ כל חברי החדר, מהרגע שבו הוזמנו.את/ה הפכת הודעות עתידיות לגלויות בפני %1$s%1$s הפך הודעות עתידיות לגלויות בפני %2$sחסמת את %1$s. סיבה: %2$s
@@ -1830,7 +1794,6 @@
\nממתין לתגובת השרת…
חדר ריק (היה %s)חדר ריק
-
%1$s, %2$s, %3$s ו-%4$d אחר%1$s, %2$s, %3$s ו-%4$d אחרים
@@ -1894,10 +1857,10 @@
שדרגת כאן.%s שודרג כאן.שדרגת את החדר הזה.
- %s שדרג את החדר הזה.
+ %s שידרג/ה חדר זה.כל אחד.כל חברי החדר.
- כל חברי החדר, מהנקודה בה הצטרפו.
+ כל חברי החדר, מהרגע שבו הצטרפו.מפהשתף מיקוםמיקום
@@ -1924,8 +1887,8 @@
מיפוי מיקומי משתמשים בציר הזמןלאחר ההפעלה, תוכל לשלוח את המיקום שלך לכל חדראפשר שיתוף מיקום
- לפתוח בעזרת
- ${app_name} לא הצליח לגשת למיקום שלך. בבקשה נסה שוב מאוחר יותר.
+ פתח באמצעות
+ ${app_name} לא הצליח לגשת למיקום שלך. אנא נסה שוב מאוחר יותר.${app_name} לא הצליח לגשת למיקום שלךשתף מיקוםאפשר התראת דוא\"ל עבור %s
@@ -2096,4 +2059,120 @@
%1$s משך את ההזמנה של %2$s. סיבה: %3$sקיבלת את ההזמנה עבור %1$s. סיבה: %2$s%1$s קיבל את ההזמנה עבור %2$s. סיבה: %3$s
+ שיתפו את מיקומם
+ מיקום
+ משתמשים
+ הצג פחות
+ דלג על השאלה
+ יש לי כבר חשבון
+ צור חשבון
+ תקשורת מאובטחת.
+ סגור את קוֹטֵף האמוג\'י
+ פתח את קוֹטֵף האמוג\'י
+ רמת האמון אמינה
+ אזהרה רמת האמון
+ ברירת המחדל של רמת האמון
+ נבחר
+ וידאו
+ יש טיוטה שלא נשלחה
+ חלק מההודעות לא נשלחו
+ מחיקת אווטאר
+ החלף אווטאר
+ תמונה
+ ייבוא מפתחות מהקובץ
+ פתח יישומונים
+ צילום מסך
+ האימות נכשלה
+ ${app_name} כדי לבצע פעולה זו נדורש להזין את פרטי התחברות שלך.
+ יש צורך באימות מחדש
+ החלק כדי לסיים את השיחה
+ אדם לא ידוע
+ העבר אל %1$s
+ התייעצות עם %1$s
+ משתמשים
+ אירעה שגיאה בהעברת השיחה
+ הַעֲבִר
+ התחבר
+ התייעץ קודם
+ %1$s הקש לחזרה
+ שיחה פעילה (%1$s) ·
+
+ שיחה פעילה ·
+ %1$d שיחות פעילות ·
+
+
+
+ שיחה פעילה (%1$s)
+ אירעה שגיאה בחיפוש מספר הטלפון
+ לוח חיוג
+ אין מענה
+ שיחה וידאו שלא נענתה
+ שיחה קולית שלא נענתה
+ השיחה קולית נדחתה
+ שיחת וידאו נדחתה
+ שיחת הווידאו הסתיימה • %1$s
+ השיחה הקולית הסתיימה • %1$s
+ שיחת קולית פעילה
+ שיחת וידאו פעילה
+ שיחה וידאו נכנסת
+ שיחה קולית נכנסת
+ שיחה חוזרת
+ השיחה הזו הסתיימה
+ %1$s דחה את השיחה הזו
+ דחית את השיחה הזו
+ התראת חדר
+ להודיע לכל החדר
+
+ %1$d יותר
+
+
+
+
+ %1$s, %2$s ואחרים
+ %1$s ו %2$s
+
+ %d שינוי ברשימות ACL בשרתים
+
+
+
+
+ נהל חדרים
+ החלט מי יכול לראות ולהצטרף לחדר זה.
+ הקש כדי לערוך מרחבים
+ חפש שם
+ חפש באמצאות שם, כינוי או מייל
+ דוחס וידאו %d%%
+ דוחס תמונה …
+ תן משוב
+ שליחת המשוב נכשלה (%s)
+ תודה, המשוב שלך נשלח בהצלחה
+ אתה יכול לצור איתי קשר במידה ויהיו לך שאלות המשך
+ הינך משתמש בגירסת נסיון של מרחבים. המשוב שלך ישמש לעידכון גהגרסאות הבאות. פרטי המערכת ושם המשתמש ירשמו כדי שנוכל להשתמש במשוב שלך בצורה מיטבית.
+ משוב
+ סוף המשאל
+ פעולה זו תעצור את האפשרות להצביע ותציג את תוצאות המשאל.
+ סוף המשאל\?
+ אפשרות הזוכה
+ סוף המשאל
+ שאלה לא יכולה להיות ריקה
+ צור משאל
+ הוסף אפשרות
+ אפשרות %1$d
+ צור אפשריות
+ שאלה או נושא
+ שאלה או נושא המשאל
+ צור משאל
+ אתחל את הישומון כדי שהשינויים יכנסו לתוקף.
+ הצטרף בכל מקרה
+ הצטרף למרחב
+ צור מרחב
+ דלג כרגע
+ הצטרף למרחב שלי %1$s%2$s
+ הודעות קבוצה מוצפנות
+ הודעות ישירות
+ שידרוגי חדר
+ השם המוצג שלי
+ שם המשתמש שלי
+ הזמנות לחדר
+ מילות מפתח
\ No newline at end of file
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index d249aeb745..e87e5653ca 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -23,7 +23,6 @@
ルームへの招待%1$sと%2$s空のルーム
-
%1$sが今後のルーム履歴を%2$sに見えるように設定しました。ルームのメンバー全員(招待された時点から)ルームのメンバー全員(参加した時点から)
@@ -80,13 +79,12 @@
クリップボードへコピー警告お気に入り
- 知人
+ メンバールームルーム名で絞り込む招待中低優先度会話
-
不具合を報告不具合の内容と状況の説明をお願いします。何をしましたか?何が起こるべきでしたか?実際に起こった事象は何でしょうか?ここに不具合の内容を記述
@@ -141,8 +139,8 @@
メールアドレスを追加電話番号を追加通知音
- このアカウントで通知を許可
- このセッションで通知を許可
+ このアカウントでは通知を有効にする
+ このセッションでは通知を有効にする1対1のチャットでのメッセージグループチャットでのメッセージルームへ招待されたとき
@@ -173,7 +171,6 @@
最後のオンライン日時%1$s @ %2$s認証
-
ログイン中のアカウント言語を選択言語
@@ -220,11 +217,9 @@
ルームサインアウト送信
-
このホームサーバーは、あなたがロボットではないことの確認を求めていますアカウントに登録されたメールアドレスの入力が必要です。
- メールアドレスの確認に失敗しました:電子メールのリンクをクリックしたことを確認してください
-
+ メールアドレスの認証に失敗しました:電子メールのリンクをクリックしたことを確認してください不正な形式のJSON有効なJSONを含んでいませんでしたログイン要求が多すぎます
@@ -256,7 +251,6 @@
%sの全てのメッセージを表示しますか?
\n
\nこの操作はアプリを再起動するため、時間がかかる場合があります。
-
外観公開端末名ルームのエンドツーエンド暗号鍵をエクスポート
@@ -272,26 +266,21 @@
Matrixの連絡先のみ通信先が通話の受取に失敗しました。情報
-
-
${app_name}は、音声通話を実行するためにマイクへアクセスするための許可を必要としています。
-
${app_name}はビデオ通話を行うためにカメラとマイクにアクセスする許可を必要としています。
\n
\n通話をするためには、次のポップアップでアクセスを許可してください。
-
発言を通報写真を撮影動画を撮影
- 検証を開始
+ 認証を開始リクエストにuser_idがありません。リクエストにroom_idがありません。リクエストの送信に失敗しました。ウィジェットを作成できません。ウィジェットをこのルームから削除してもよろしいですか?
-
- 一致していない場合は、あなたのコミュニケーションの安全性が損なわれている可能性があります。
- このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない。
+ 一致していない場合は、コミュニケーションのセキュリティーが破られている可能性があります。
+ このセッションでは、未認証のセッションに対して暗号化されたメッセージを送信しない。認証済のセッションに対してのみ暗号化インポートローカルファイルから鍵をインポート
@@ -306,19 +295,17 @@
通知あり(音量大)通知あり(サイレント)不具合の報告
-
このユーザーにあなたと同じ権限を与えます。この変更は取り消せません。
\nよろしいですか?信用する信用しないフィンガープリント(%s):
- リモートサーバーのIDを確認できませんでした。
+ リモートサーバーのIDを認証できませんでした。誰かが不当にあなたの通信を傍受しているか、あなたの電話がリモートサーバーの証明書を信用していない可能性があります。サーバーの管理者が、これは想定されていることであると言っているのであれば、以下のフィンガープリントが、管理者によるフィンガープリントと一致していることを確認してください。証明書はあなたの電話により信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書を承認しないことを強く推奨します。証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。サーバーの管理者に連絡して、適切なフィンガープリントを確認してください。サーバーの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。
-
検索このアプリの情報をシステム設定で表示。アプリの情報
@@ -355,7 +342,6 @@
ホーム画面にショートカットを作成インラインURLプレビューコミュニティーのアバター
-
暗号鍵を要求している新しいセッション \'%s\' を追加しました。未認証のセッション \'%s\' が暗号鍵を要求しています。作成
@@ -370,15 +356,12 @@
%d個のメンバーシップの変更メンバーを表示
-
%d名のメンバー%d件の新しいメッセージ
-
-
アバタースタンプを送るダウンロード
@@ -392,10 +375,6 @@
申し訳ありません、この操作を完了するための外部アプリが見つかりません。あなたの他のセッションに暗号鍵を再要求する。鍵をこのセッションに送信できるように、メッセージを復号化できる他の端末で${app_name}を起動してください。
-
-
-
-
%d個選択済
@@ -408,7 +387,6 @@
%d件の通知された未読メッセージ
-
%d個のルーム
@@ -430,8 +408,6 @@
表示するニックネームを変更Markdown書式の入/切Matrixアプリの管理を修正するには
-
-
%1$sのホームサーバーの使用を継続するには、利用規約を確認し、同意する必要があります。エラー今すぐ確認
@@ -444,7 +420,7 @@
アカウントを停止するときに、自分の送信した全てのメッセージの履歴を消去してください(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)アカウントを停止パスワードを入力してください。
- このルームは交換されており、使用されていません。
+ このルームは置き換えられており、アクティブではありません。こちらから継続中の会話を確認このルームは別の会話の続きです以前のメッセージを見るには、ここをクリックしてください
@@ -462,7 +438,7 @@
%1$s:%2$s%d+展開
- 畳む
+ 折りたたむ承諾このホームサーバーの方針を確認し承諾してください:通話設定画面
@@ -472,13 +448,11 @@
会話から追放鍵のバックアップ鍵のバックアップを使用
-
詳細な通知設定バックグラウンド同期モードバッテリーを考慮して最適化リアルタイム性を重視して最適化バックグラウンド同期を行わない
-
入力中通知を送信文字入力中であることを他のメンバーに伝えます。開封確認メッセージを表示
@@ -595,7 +569,7 @@
%d件のアクティブなセッション
- このログインを検証
+ このログインを認証QRコードはいいいえ
@@ -606,7 +580,7 @@
削除の確認このイベントを削除してよろしいですか?ルーム名や説明の変更を削除すると、変更が取り消されますのでご注意ください。暗号化は有効です
- このルーム内でのメッセージはエンドツーエンド暗号化されます。詳細の確認や検証はユーザーのプロフィールをご確認ください。
+ このルーム内でのメッセージはエンドツーエンド暗号化されます。詳細の確認や認証はユーザーのプロフィールをご確認ください。暗号化が有効になっていません通知設定切断
@@ -683,7 +657,7 @@
招待されています%sからの招待概ね完了しました。%sの画面にも同じシールドアイコンが表示されていますか?
- 相手ユーザーの端末のコードをスキャンし、相互に安全性を検証
+ 相手ユーザーの端末のコードをスキャンし、相互に安全性を認証相手のコードをスキャンスキャンできません拒否
@@ -732,14 +706,14 @@
QRコードQRコードによる追加コードを共有
- ${app_name} で会話しましょう:%s
+ ${app_name}で話しましょう:%s友達を招待既知のユーザー無効なQRコード(無効なURI)!これを行うには設定から「インテグレーションを許可」を有効にしてください。インテグレーションが無効になっていますインテグレーションマネージャー
- インテグレーション(統合)を許可
+ インテグレーションを許可FCMトークンが正常に取得されました:
\n%1$sFirebaseトークン
@@ -812,7 +786,6 @@
ビデオ通話が行われています…有効な認証情報がないため、権限がありません${app_name} 呼び出し失敗
-
ルームディレクトリの全てのルームを表示(露骨なコンテンツのあるルームを含む)する。露骨なコンテンツのあるルームを表示ルームディレクトリ
@@ -970,7 +943,6 @@
%1$sがルームのアバターを削除しましたルームの説明を削除しましたルーム名を削除しました
-
ディスカバリー設定を管理します。ディスカバリー(発見)これにより、現在のキーまたはフレーズが置き換えられます。
@@ -985,8 +957,8 @@
/confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信チャットでエフェクトを表示ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。
- ボット、ブリッジ、ウィジェット、ステッカーパックの管理をします。
-\nインテグレーションマネージャーは、構成データを受信し、ユーザーに代わってウィジェットの変更、ルーム招待の送信、権限の設定などを行うことができます。
+ ボット、ブリッジ、ウィジェット、ステッカーパックを管理します。
+\nインテグレーションマネージャーは、構成データを受信し、ユーザーに代わってウィジェットの変更や、ルーム招待の送信、権限の設定などを行うことができます。インテグレーション(統合)アプリがバックグラウンドにある場合、着信メッセージは通知されません。${app_name}は正確な時間に定期的にバックグラウンドで同期します(構成可能)。
@@ -1101,7 +1073,6 @@
鍵のバックアップで管理鍵のバックアップを使用暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう
-
バックアップされた暗号鍵をサーバーから削除しますか?今後、現在のリカバリーキーを使って、暗号化されたメッセージの履歴を読むことができなくなります。バックアップを削除バックアップの状態を確認しています
@@ -1151,7 +1122,6 @@
別のセッションで鍵のバックアップを既に設定しているようです。上書きしますか?ホームサーバーにバックアップが存在していますリカバリーキーが保存されました。
-
リカバリーキーを保存コピーをしましたリカバリーキーはパスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください
@@ -1177,10 +1147,10 @@
\nセッション名:%1$s
\n最後のオンライン日時:%2$s
\n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
- 未認証のセッションが暗号鍵を要請しています。
+ 未認証のセッションが暗号鍵を要求しています。
\nセッション名:%1$s
\n最後のオンライン日時:%2$s
-\n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
+\n新しいセッションにログインしていない場合、この要求を無視してください。鍵の共有リクエストカスタムカメラ画面の代わりにシステムカメラを使用します。使用中のウィジェットがありません
@@ -1326,7 +1296,7 @@
プッシュ通知に関するルールあなたは既にこのルームを見ています!その他のサードパーティーの使用に関する掲示
- Matrix SDKバージョン
+ Matrix SDKのバージョンファイル\"%1$s\"からエンドツーエンド暗号鍵をインポートします。鍵のバックアップデータの取得中にエラーが発生しました信頼情報の取得中にエラーが発生しました
@@ -1344,7 +1314,7 @@
お待ち下さい…ネットワークがありません。インターネット接続を確認してください。不正な形式のイベントです。表示できません
- ルーム管理者によってモデレートされたイベント
+ ルームの管理者によってモデレートされたイベントリアクションリアクションを見るリアクションを追加
@@ -1356,18 +1326,15 @@
会話未読メッセージはありません未読はありません!
- %sがセッションの検証を要求しています
+ %sがセッションの認証を要求していますリトライ他のホームサーバーに接続しようとしているようですね。サインアウトしますか?IDサーバーを使用していません不明なエラーこのルームを含む参加済のスペースこのルームにアクセスできるスペースを決定します。スペースが選択されると、そのメンバーはルーム名を見つけて参加できます。
-
-
了解
- 完了しました!
-
+ 完了しました!メッセージの新しい鍵暗号化されたメッセージを決して失わないためにセキュアバックアップ
@@ -1418,7 +1385,6 @@
この操作を実行するための権限がありません。システム設定から権限を付与してください。IDサーバーに接続できませんでしたIDサーバーのURLを入力
-
同意する同意を撤回あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意しています。
@@ -1497,7 +1463,7 @@
コンテンツが報告されましたヘルプとサポートヘルプ
- ${app_name}のポリシー
+ ${app_name}の運営方針ここキーワードを追加自分のスレッド
@@ -1511,7 +1477,7 @@
スパムとして報告済このルームにファイルはありませんこのルームにメディアはありません
- 公開されたルームをアップグレード
+ 公開ルームをアップグレード非公開スペース公開スペース送信済
@@ -1523,8 +1489,8 @@
画像スクリーンショット接続
- 投票
- 投票
+ アンケート
+ アンケートコード役割送信
@@ -1549,9 +1515,9 @@
警告!位置情報メディア
- 投票終了
- 投票を終了しますか?
- 投票を削除
+ アンケート終了
+ アンケートを終了しますか?
+ アンケートを削除スペースを作成スペースに参加スペースを退出
@@ -1565,10 +1531,10 @@
非公開のルームをアップグレード音声メッセージを一時停止このメールアドレスをアカウントにリンク
- 投票を編集
+ アンケートを編集地図
- 投票を終了
- 投票を終了
+ アンケートを終了
+ アンケートを終了選択肢%1$d選択肢を作成位置情報
@@ -1583,7 +1549,7 @@
ファイルをアップロード連絡先を開くステッカーを送信
- 投票を作成
+ アンケートを作成位置情報を共有スペースに関する変更を行うために必要な役割を更新する権限がありませんスペースに関する変更を行うために必要な役割を選択
@@ -1598,9 +1564,9 @@
応答がありませんスレッドへのリンクをコピー有効にする
- あなたのIDサーバーのポリシー
+ あなたのIDサーバーの運営方針新しいルームを作成
- 認証コードが正しくありません。
+ 確認コードが正しくありません。IDサーバーのURLを入力してください名前、ID、メールアドレスで検索システムの設定
@@ -1621,8 +1587,8 @@
ルームの暗号化の有効化スペースのメインアドレスの変更スペースのアバターの変更
- 投票を作成
- 投票を作成
+ アンケートを作成
+ アンケートを作成暗号化が正しく設定されていないため、メッセージを送ることができません。クリックして設定を開いてください。暗号化が正しく設定されていないため、メッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。%2$dの%1$d
@@ -1636,13 +1602,13 @@
添付ファイルの取得中にエラーが発生しました。鍵のバックアップのバナーを閉じるキーワードに「%s」を含めることはできません
- %s へのメール通知を有効にする
+ %sへのメール通知を有効にするヒント:メッセージを長押しして「%s」を選択。スレッドを用いると、会話のテーマを保ったり、会話を追跡したりするのが容易になります。あなたの非公開スペースあなたの公開スペース自分のみ
- スレッドでディスカッションを整理して管理
+ スレッドで議論を整理して管理%sを待機しています…この端末でスキャン認証を送信済
@@ -1680,7 +1646,7 @@
音声メッセージ(%1$s)推奨のルームバージョンへとアップグレード音声メッセージを録音
- あなたのホームサーバーのポリシー
+ あなたのホームサーバーの運営方針一番下に移動%sが読みました%1$sと%2$sが読みました
@@ -1701,7 +1667,7 @@
イベントを送信しました!送信に失敗した全てのメッセージを削除失敗しました
- 公開されたルーム
+ 公開ルームアバターを削除アバターを変更かけ直す
@@ -1732,23 +1698,22 @@
%d人のユーザーが読みましたスペースへのアクセス
- このサーバーはポリシーを提供していません。
+ このサーバーは運営方針を提供していません。数秒かかるかもしれません。少々お待ちください。利用可能な言語を読み込んでいます…ユーザーを招待ユーザーを招待しています…パスワードを選択してください。メンバーを追加
- ログインを検証
- メッセージ…
+ ログインを認証
+ メッセージを送る…このファイルは大きすぎてアップロードできません。この情報の送信に同意しますか?連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。
-
メールアドレスと電話番号を%sに送信
- このIDサーバーはポリシーを提供していません
- IDサーバーのポリシーを隠す
- IDサーバーのポリシーを表示
+ このIDサーバーは運営方針を提供していません
+ IDサーバーの運営方針を隠す
+ IDサーバーの運営方針を表示アカウントの新しいパスワードを設定…シェイクを検出しました!電話を振って、しきい値を試してください
@@ -1776,12 +1741,12 @@
続行するには%sを入力してください有効なリカバリーキーではありませんリカバリーキーを入力してください
- 投票の種類
- 実施中の投票
+ アンケートの種類
+ 投票の際に結果を公開投票する投票した人には、投票の際に即座に結果が表示されます
- 終了した投票
- 結果は投票を終了した後でのみ明らかにされます
+ アンケートの終了後に結果を公開
+ 結果はアンケートを終了した後でのみ明らかにされます以下で開く暗号化のアップグレードが利用可能ですSSSSキーをリカバリーキーから生成しています
@@ -1797,7 +1762,7 @@
%1$d個の投票
- 投票を締め切り、投票の最終結果を表示します。
+ アンケートを締め切り、最終結果を表示します。招待者のみ参加可能。個人やチームに最適スペースを作成連絡先をスペースに招待
@@ -1824,7 +1789,7 @@
音声メッセージを録音しています録音を削除音声メッセージがアクティブの間は返信や編集はできません
- この投票を削除してよろしいですか?一度削除すると復元することはできません。
+ このアンケートを削除してよろしいですか?一度削除すると復元することはできません。共有データの取り扱いに失敗しました回転とクロップルームを探索
@@ -1850,7 +1815,7 @@
\nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
%1$sにより%2$sに質問あるいはトピック
- 投票の質問あるいはトピック
+ アンケートの質問あるいはトピック少々お待ちください。少し時間がかかるかもしれません。ルームのバージョン 👓不安定
@@ -1873,7 +1838,7 @@
一般転送信頼済
- 検証済
+ 認証済未送信のメッセージを削除カスタムイベントを送信ルームの状態を探索
@@ -1886,14 +1851,13 @@
自分の会話は、自分のもの。%1$sがこのルームを「招待者のみ参加可能」に設定しました。選択したメッセージをネタバレとして送信
-
%1$sはこのルームを「リンクを知っている人が参加可能」に設定しました。初めに設定画面でIDサーバーの利用規約を承認してください。初めにIDサーバーを設定してください。%1$d個の投票があります。結果を見るには投票してください
- 未検証の端末で暗号化
+ 未認証の端末で暗号化メッセージを紙吹雪と共に送るメッセージを降雪と共に送る紙吹雪🎉を送る
@@ -1910,7 +1874,7 @@
\nアップグレードは通常、ルームがサーバー上で処理される仕方にだけ影響します。
一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。%sして、このルームを皆に紹介しましょう。
- このコードを皆と共有し、スキャンして追加してもらい、会話を始めましょう。
+ このコードを共有し、スキャンして追加してもらい、会話を始めましょう。正当な参加者が%sにアクセスできることを確認してください。参加者を追加
@@ -1924,7 +1888,7 @@
スペースは、ルームや連絡先をグループ化する新しい方法です。招待されています新しいスペースを、あなたが管理するスペースに追加。
- 注意:アプリケーションは再起動します
+ 注意:アプリケーションが再起動しますホームサーバーの管理者にお問い合わせくださいあなたが参加している全てのルームがホームに表示されます。親のスペースを自動的に更新
@@ -1968,7 +1932,7 @@
メッセージを送信できませんでしたウィジェットを開く%1$sに転送
- 通話は終了しました
+ 通話が終了しました自分自身にダイレクトメッセージを送信することはできません!電話番号(任意)電話番号を確認
@@ -1990,7 +1954,7 @@
パスワードはまだ変更されていません。
\n
\n変更作業を中止しますか?
- %1$sに認証メールを送信しました。
+ %1$sに確認メールを送信しました。メールボックスを確認してくださいサインインに戻る元の大きさのままメディアファイルを送信
@@ -2004,18 +1968,18 @@
認証の要求%sがキャンセルしました既読
- 検証
+ 認証メールアドレスが正しくないようです国際電話番号の形式を使用してください。国際電話番号は「+」から始まる必要がありますコードを%1$sに送信しました。以下に入力して認証してください。このメールアドレスはどのアカウントにも登録されていません
- パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションから鍵をエクスポートしておいてください。
- パスワードの再設定を確認するために認証メールを送信します。
- このメールアドレスはどのアカウントにも属していません。
- このアプリではこのホームサーバーにアカウントを作成できません。
+ パスワードを変更すると、全てのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションからルームの鍵をエクスポートしておいてください。
+ パスワードの再設定を確認するために確認メールを送信します。
+ このメールアドレスはどのアカウントにも登録されていません。
+ このアプリでは、このホームサーバーにアカウントを作成できません。
\n
-\nウェブクライエントを使用してアカウント登録しますか?
+\nウェブクライアントを使用してアカウントを作成しますか?申し訳ありませんが、このサーバーはアカウントの新規登録を受け入れていません。このアプリではこのホームサーバーにサインインできません。このホームサーバーは次のサインインの方法に対応しています:%1$s
\n
@@ -2073,11 +2037,11 @@
セキュリティーキーを保存位置情報を共有しました%sでリアクションしました
- 検証終了
- 次のいずれかのセキュリティが破られている可能性があります。
+ 認証が完了しました
+ 次のいずれかのセキュリティーが破られている可能性があります。
\n
\n - あなたのホームサーバー
-\n - 検証している相手のホームサーバー
+\n - 認証している相手が接続しているホームサーバー
\n - あなたか相手のインターネット接続
\n - あなたか相手の端末セキュアではない
@@ -2127,7 +2091,7 @@
ユーザーを招待できませんでした。招待したいユーザーを確認して、もう一度試してください。%sの利用規約を開くユーザーによる同意は与えられていません。
- 代わりに、他のIDサーバーのURLを入力できます
+ または、他のIDサーバーのURLを入力できますサーバー上の暗号鍵をバックアップして、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう。いまキャンセルすると、ログインできなくなった際に、暗号化されたメッセージとデータを失ってしまう可能性があります。
\n
@@ -2166,24 +2130,23 @@
ナビゲーションのメニューを開く承諾しました%sが承諾しました
- %sが検証済み
- %sを検証する
- 絵文字を比較して検証
- 絵文字を比較して検証
+ %sが認証済
+ %sを認証する
+ 絵文字を比較して認証
+ 絵文字を比較して認証対面でない場合は、代わりに絵文字を比較してくださいあなたのホームサーバーで許容されている添付ファイルの最大サイズは%sです。新しいパスワードを確認するには下記のリンクを開いてください。リンクにアクセスしてから、以下をクリックしてください。PINコードを再設定するには「PINコードを忘れた」をタップしてログアウトし、その後再設定してください。
- いま検証できる%d個の端末を表示
+ いま認証できる%d個の端末を表示この操作を実行するには ${app_name}に認証情報を入力する必要があります。あなただけが知っている秘密のパスワードを入力してください。バックアップ用にセキュリティーキーを生成します。
- 暗号化されたメッセージにアクセスするには、ログインを検証し、本人確認を行う必要があります。
- 暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを検証し、本人確認を行う必要があります。
+ 暗号化されたメッセージにアクセスするには、ログインを認証し、本人確認を行う必要があります。
+ 暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを認証し、本人確認を行う必要があります。詳しく知る
- セキュリティーを高めるために、使い捨てコードが一致しているのを確認して、%sを検証しましょう。
-
+ セキュリティーを高めるために、使い捨てコードが一致しているのを確認して、%sを認証しましょう。暗号化の設定が正しくありません。暗号化を復元暗号化を有効な状態に取り戻すために、管理者に連絡してください。
@@ -2200,12 +2163,12 @@
完了!アカウントパスワードと違うものにしてください。続行するには%sを入力してください。
- 検証を中止しました
- 今中止すると、%1$s(%2$s)を検証しません。検証は相手のユーザープロフィール画面からもう一度開始できます。
+ 認証を中止しました
+ 今中止すると、%1$s(%2$s)を認証しません。認証は相手のユーザープロフィール画面からもう一度開始できます。中止すると、新しい端末では暗号化されたメッセージが読めず、他のユーザーに信頼されません中止すると、この端末では暗号化されたメッセージが読めず、他のユーザーに信頼されません自分ではない
- 新しいセッションを検証して、暗号化されたメッセージにアクセスできるようにしましょう。
+ 新しいセッションを認証して、暗号化されたメッセージにアクセスできるようにしましょう。新しいログイン。あなたですか?${app_name} Androidルームの管理者によって削除されています。理由:%1$s
@@ -2217,9 +2180,9 @@
\n
\n予期しないトラブルを起こす可能性があるので注意してください。%1$s(%2$s)が新しいセッションでサインインしました:
- このセッションは%1$s(%2$s)によって検証されているので、メッセージのセキュリティは信頼できます。
- 既存のセッションでこのセッションを検証して、暗号化されたメッセージへアクセスできるようにしましょう。
- あなたはこのセッションを検証しているので、メッセージのセキュリティは信頼できます。
+ このセッションは%1$s(%2$s)によって認証されているので、メッセージのセキュリティは信頼できます。
+ 既存のセッションでこのセッションを認証して、暗号化されたメッセージへアクセスできるようにしましょう。
+ あなたはこのセッションを認証しているので、メッセージのセキュリティは信頼できます。利用可能な暗号情報がありません既定のバージョン非公開のルームとダイレクトメッセージにおけるエンドツーエンド暗号化は、あなたのサーバーの管理者により既定として無効にされています。
@@ -2228,16 +2191,16 @@
%1$sの権限レベルを変更しました。誰と使いますか?どんなスペースを作りますか?
- あなたとチームメイトの非公開のスペース
+ 自分と仲間の非公開のスペースルームを整理するためのプライベートスペースここが会話のスタート地点です。ここが%sのスタート地点です。あと少しです!確認を待機しています…あと少しです!もう一方のデバイスは同じマークを表示していますか?%sを待機しています…
- このユーザーがこのセッションを検証するまで、送受信されるメッセージには警告マークが付きます。手動で検証することも可能です。
+ このユーザーがこのセッションを認証するまで、送受信されるメッセージには警告マークが付きます。手動で認証することも可能です。セッションの取得に失敗しました
- チームメイトは誰ですか?
+ 誰がチームの仲間ですか?%sを探索できるようになります私のスペース %1$s %2$s に参加してくださいスキップ
@@ -2265,5 +2228,118 @@
再認証が必要です全てリセット連絡先
- 検証がキャンセルされました。再び検証を開始することができます。
+ 認証をキャンセルしました。あらためて開始してください。
+ 押し続けて録音し、離すと送信
+ PINコードを設定してください
+
+ %d個のサーバーアクセス制御リストの変更
+
+ 置き換えられたルームに参加
+ このルームが発見できません。存在することを確認してください。
+ 指紋や顔画像など、端末に固有の生体認証を有効にする。
+ 絵文字で認証
+ テキストで認証
+ すべてのセッションを認証し、アカウントとメッセージが安全であることを確認してください
+ ログインしている場所を確認
+ 復旧用の手段を全て無くしてしまいましたか?全てリセットする
+ クロス署名に対応した他のMatrixのクライアントでも使用できます。
+ どのような議論を%sで行いたいですか?
+ クロス署名の設定に失敗しました
+ 履歴とメッセージが消去され、信頼済の端末、信頼済のユーザーが取り消されます
+ 全てをリセットすると
+ 最新の${app_name}を他の端末で、${app_name} ウェブ版、${app_name} デスクトップ版、${app_name} iOS、${app_name} Android、あるいはクロス署名に対応した他のMatrixのクライアントでご使用ください
+ スライドして通話を終了
+ 電話番号を検索する際にエラーが発生しました
+ 着信を拒否しました
+ それぞれにルームを作りましょう。後から追加することもできます(既にあるルームも追加できます)。
+ このスペースを特定できるような特徴を記入してください。これはいつでも変更できます。
+ 目立つように特徴を記入してください。これはいつでも変更できます。
+ 未読のメッセージ数のみを通知に表示。
+ 2分間${app_name}を使用しないと、PINコードが要求されます。
+ 🔐️ ${app_name}で話しましょう
+ 個人情報保護の観点から、${app_name}はハッシュ化されたメールアドレスと電話番号の送信のみをサポートしています。
+ アプリの名前を変更しました!アプリは最新版で、アカウントにはログイン済です。
+ ステートイベントを送信
+ ステートイベント
+ カスタムのステートイベントを送信
+ ステートイベントを送信しました!
+ 続行するには名前を付けてください。
+ どんな作業に取り組みますか?
+
+ あと%1$d件
+
+ Matrix上の連絡先を検索
+ 通話の転送中にエラーが発生しました
+ 2分後にPINコードを要求
+ ルーム名やメッセージの内容などの詳細を表示。
+ エラーが多すぎます。ログアウトしました
+ 警告!もう一度誤ったコードを入力すると、ログアウトします!
+
+ コードが誤っています。残りの試行回数は%d回です
+
+ プッシュ通知を有効にするには、設定を確認してください
+ リンク %1$s は別のサイトに移動します:%2$s
+\n
+\n続行してよろしいですか?
+ このリンクを再確認してください
+ ログインを認証してください:%1$s
+ 機密ストレージのアクセスに失敗しました
+ この設定を有効にすると、全てのアクティビティーにFLAG_SECUREを追加します。変更を有効にするにはアプリケーションの再起動が必要です。
+ このアカウントは無効化されています。
+ 個人用のクラウドストレージにコピー
+ 印刷して安全な場所に保管
+ %2$sと%1$sが設定されました。
+\n
+\n安全な場所で保管してください!それらは、アクティブなセッションを全て失ってしまった際、暗号化されたメッセージや安全な情報のロックを解除するために必要となります。
+ 作成したアイデンティティーキーを公開しています
+ アプリケーションのスクリーンショットを防ぐ
+ 続行するには%1$sか%2$sを使用してください。
+ エラーのためメッセージが送信されませんでした
+ %sにいない人を探していますか?
+ 直接${app_name}で招待を受け取るには、設定画面から%sしてください。
+ PINコードでしか${app_name}のロックを解除することはできません。
+ ${app_name}を開く際には、毎回PINコードの入力が必要です。
+ あなたがブロックされているルームを開くことはできません。
+ PINコードの認証に失敗しました。新しいコードを入力してください。
+ 端末の連絡先がありません
+ 暗号化の履歴を待機しています
+ 送信者があなたのセッションを信頼していないため、このメッセージにアクセスすることができません
+ 送信者によりブロックされているため、このメッセージにアクセスすることができません
+ このメッセージを待機しています。時間がかかる可能性があります
+ ルームの設定の変更に成功しました
+ 確認のため、セキュリティーフレーズを再入力してください。
+ ホームサーバー(%1$s)は、IDサーバーに%2$sを設定するように提案しています
+ IDサーバー %s から切断しますか?
+ ダイレクトメッセージを作成できませんでした。招待したユーザーを確認し、もう一度やり直してください。
+ セキュリティーフレーズ
+ 自分と仲間
+ メッセージの種類がありません
+ 絵文字の一覧を閉じる
+ 絵文字の一覧を開く
+ 認証に失敗しました
+ 復旧を設定しています。
+ このセッションは、他のセッションと認証を共有することができません。
+\n認証は端末に保存され、新しいバージョンのアプリで共有されます。
+ %1$s、%2$s他
+ %1$sと%2$s
+ 自分と相手を認証して、チャットを安全に保ちましょう
+ あなたしか知らないセキュリティーフレーズを入力してください。サーバーで機密情報を保護するために使用します。
+ 監査結果をエクスポート
+ ストレージから機密情報を発見できません
+ 操作を実行できません。ホームサーバーは最新のバージョンではありません。
+ ビデオ通話が拒否されました
+ 音声通話が拒否されました
+ %1$sは通話を拒否しました
+ このデバイスを認証可能な他の端末が全くない場合にのみ、続行してください。
+ このセッションを信頼済として認証すると、暗号化されたメッセージにアクセスすることができます。このアカウントにサインインしなかった場合は、あなたのアカウントのセキュリティーが破られている可能性があります:
+ アカウントのセキュリティーが破られている可能性があります
+ 選択したスペースに追加
+ 最新の${app_name}は他のデバイスでも使用できます:
+ 音声通話が終了しました・%1$s
+ ビデオ通話が終了しました・%1$s
+ メールアドレスで招待したり、連絡先を検索したりできます…
+ 鍵のバックアップの機密情報をSSSSに保存しています
+ あなたしか知らないセキュリティーフレーズを入力してください。サーバーで機密情報を保護するために使用します。
+ 詳細を非表示
+ 他の参加者はいません。%sに招待しましょう。
\ No newline at end of file
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index 408fc207f5..fe98d6b6a1 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -30,7 +30,6 @@
Zaproszenie do pokoju%1$s i %2$sPusty pokój
-
** Nie można odszyfrować: %s **%s wykonał(a) rozmowę wideo.%s wykonał(a) połączenie głosowe.
@@ -46,14 +45,14 @@
Wstępna synchronizacja:
\nImportowanie kryptografiiWstępna synchronizacja:
-\nImportowanie pokoi
+\nImportowanie pokojówWstępna synchronizacja:
\nImportowanie Twoich konwersacji
-\nJeśli dołączyłeś(aś) do wielu pokoi, może to zająć dłuższą chwilę
+\nJeśli dołączyłeś(aś) do wielu pokojów, może to zająć dłuższą chwilęWstępna synchronizacja:
-\nImportowanie zaproszonych pokoi
+\nImportowanie zaproszonych pokojów
Wstępna synchronizacja:
-\nImportowanie opuszczonych pokoi
+\nImportowanie opuszczonych pokojów
Wstępna synchronizacja:
\nImportowanie grupWstępna synchronizacja:
@@ -74,7 +73,6 @@
UsuńZmień nazwęZgłoś treść
-
iZaprośWyloguj się
@@ -97,7 +95,6 @@
Tylko kontakty MatrixaBrak wynikówPokoje
-
Wyślij dziennikiWyślij dzienniki awariiWyślij zrzut ekranu
@@ -141,7 +138,6 @@
Przychodzące połączenie głosoweW trakcie połączenia…Informacja
-
${app_name} wymaga uprawnienia, aby przeprowadzić połączenie audio.TAKNIE
@@ -169,7 +165,6 @@
IgnorujOdcisk palca (%s):Nie można zweryfikować tożsamości serwera.
-
SzukajFiltruj członków pokojuBrak wyników
@@ -209,7 +204,6 @@
Zaaktualizuj nazwę publicznąOstatnio widziany(-a)%1$s @ %2$s
-
UwierzytelnianieZalogowany jakoInterfejs użytkownika
@@ -240,7 +234,6 @@
LaboratoriumZnajdują się tu eksperymentalne funkcje, których należy używać z ostrożnością.Ustaw jako główny adres
-
MotywNazwa publicznaID sesji
@@ -283,8 +276,6 @@
Czy jesteś pewien, że chcesz rozpocząć wideorozmowę?Zrób zdjęcieNagraj film
-
-
Uszkodzony JSON%d zmiana członkostwa
@@ -293,15 +284,10 @@
Rozmówca nie połączył się.
-
-
${app_name} wymaga dostępu do kamery i mikrofonu, aby przeprowadzać rozmowy wideo.
\n
\nPrzyznaj dostęp w następnym oknie.
-
Lista uczestników
-
-
1 członekkilku członków
@@ -323,8 +309,6 @@
Certyfikat różni się od tego zaufanego w twoim telefonie. Serwer mógł odnowić certyfikat. Skontaktuj się z administratorem serwera w celu weryfikacji odcisku palca.Certyfikat zmienił stan z zaufanego na niezaufany. jest to NIEZWYKLE RZADKIE. Rekomendowane jest NIE AKCEPTOWANIE nowego certyfikatu.Akceptuj certyfikat tylko wtedy gdy administrator opublikował odcisk palca pasujący do tego powyżej.
-
-
Wszystkie wiadomościDodaj do ekranu domowegoPokaż informacje o aplikacji w ustawieniach systemu.
@@ -363,7 +347,6 @@
Wysyłaj dane analityczne${app_name} zbiera anonimowe informacje które pozwolą ulepszyć aplikację.Eksportuj klucze E2E pokoju
-
Importuj klucze E2E pokojuImportuj klucze pokojuImportuj klucze z lokalnego pliku
@@ -372,14 +355,13 @@
Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych sesji w tym pokoju z tej sesji.Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej:Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany.
-
Wyślij naklejkęNiestety, nie znaleziono zewnętrznej aplikacji, która ukończy to działanie.1 pokój%d pokoje
- %d pokoi
-
+ %d pokojów
+ %d pokojów1 aktywny widżet
@@ -399,7 +381,6 @@
%d nieprzeczytanych wiadomości powiadomienia
-
%1$s w %2$sCzy na pewno chcesz usunąć widżet z tego pokoju?Nie można utworzyć widżetu.
@@ -436,16 +417,10 @@
BłądOpuszcza pokójWłącza/Wyłącza markdown
-
-
Kliknij tutaj, aby zobaczyć starsze wiadomościPrzepraszamy, wystąpił błądAlerty systemoweJeżeli to możliwe, proszę napisz opis w języku angielskim.
-
-
-
-
1 zaznaczone%d zaznaczone
@@ -603,7 +578,6 @@
Jeżeli nie pamiętasz swoich danych odzystkiwania, możesz %s.Zgubiłeś (-łaś) swój klucz odzyskiwania\? Możesz ustawić nowy w ustawieniach.Kopia zapasowa posiada poprawną sygnaturę z niezweryfikowanej sesji %s
-
Jesteś na bieżąco!UnieważnijRozłącz
@@ -652,7 +626,6 @@
Niepowodzenie przy pobieraniu wersji kluczy (%s).Usuwanie kopii zapasowej…Sprawdzanie stanu kopii zapasowej
-
ReakcjeBrak sieci. Sprawdź swoje połączenie z Internetem.Proszę czekać…
@@ -682,7 +655,6 @@
IGNORUJ UŻYTKOWNIKATreść zgłoszonaZgłoszone jako spam
-
Proszę napisz swoją sugestię poniżej.Opisz swoją sugestię tutajUtwórz nową rozmowę bezpośrednią
@@ -712,7 +684,6 @@
Opuść pokójWłącz szyfrowanie end-to-end…Brak
-
Nie udało się połączyć z serwerem o podanym adresie URL, upewnij się, że wpisano go poprawnieNiektóre rodzaje wiadomości będą ciche (wygenerują powiadomienie bez dźwięku).Weryfikacja Usług Google
@@ -761,9 +732,8 @@
\nWpłynie to na użycie baterii i sieci, na panelu powiadomień pozostanie wyświetlone stałe powiadomiene o nasłuchiwaniu zdarzeń.Brak synchronizacji w tleNie będziesz otrzymywać powiadomień o przychodzących wiadomościach gdy aplikacja będzie działać w tle.
-
Użyj menedżera integracji aby zarządzać botami, mostami, widżetami i pakietami naklejek.
-\nMenedżerzy integracji odbierają dane konfiguracji, modyfikują widżety, wysyłają zaproszenia do pokoi i ustawiają poziomy uprawnień na Twe żądanie.
+\nMenedżerzy integracji odbierają dane konfiguracji, modyfikują widżety, wysyłają zaproszenia do pokojów i ustawiają poziomy uprawnień w Twoim imieniu.
Pokaż podgląd linków wewnątrz czatu jeśli twój serwer wspiera tę funkcję.Formatuj wiadomości używając składni Markdown zanim zostaną wysłane. Pozwala to na zaawansowane formatowanie takie jak używanie asterysków do wyświetlania tekstu w kursywie.Nie wpływa to na zaproszenia, wyrzucenia oraz bany.
@@ -771,7 +741,6 @@
Przycisk enter na klawiaturze programowej wyśle wiadomość zamiast wprowadzania łamanania liniiZnajdźZarządzaj ustawieniami wyszukiwania.
-
MediaDomyślne źródło mediówOdzyskiwanie zaszyforwanych wiadomości
@@ -832,7 +801,6 @@
Zrobiłem kopięZapisz Klucz OdzyskiwaniaZapisz jako plik
-
Kopia zapasowa istnieje już na Twoim serwerze domowymWygląda na to, iż kopia zapasowa kluczy została skonfigurowana za pomocą innej sesji. Czy chcesz zastąpić ją tą, którą tworzysz\?Generowanie Klucza Odzyskiwania używając hasła, proces może zająć kilka sekund.
@@ -861,7 +829,6 @@
Kopiowanie %d kluczy…
-
Niektóre powiadomienia są wyłączone w osobistej konfiguracji.Usługi Google Play są aktualne.Rozumiem
@@ -930,7 +897,6 @@
Zawartość została zgłoszona jako niewłaściwa.
\n
\nJeżeli nie chcesz widzieć treści od tego użytkownika, możesz go zablokować aby ukryć jego wiadomości.
-
Opuść pokój%1$s nie dokona(-ła) zmianWysyła wiadomość jako spoiler
@@ -1109,7 +1075,6 @@
Zweryfikuj %sZweryfikowano %sOczekiwanie na %s…
-
Wiadomości w tym pokoju są zaszyfrowane end-to-end.
\n
\nTwoje wiadomości są zabezpieczone zamkami i jedynie Ty oraz Twój odbiorca posiadacie klucze, aby je odblokować.
@@ -1222,7 +1187,7 @@
Szyfrowanie miniatury…Nie możesz czegoś odnaleźć\?Utwórz nowy pokój
- Otwórz katalog pokoi
+ Otwórz katalog pokojówNazwa lub ID (#przykład:matrix.org)Serwer toższamościOdłącz od serwera tożsamości
@@ -1345,7 +1310,6 @@
Jeżeli teraz przerwiesz, możesz utracić zaszyfrowane wiadomości oraz dane jeżeli utracisz dostęp do zalogowanych sesji.
\n
\nMożesz także ustawić Secure Backup i zarządzać swoimi kluczami w Ustawieniach.
-
Wydrukuj go i schowaj w bezpiecznym miejscuTwój %2$s i %1$s są teraz ustawione.
\n
@@ -1457,7 +1421,6 @@
Dodaj obraz zNiepoprawny kod weryfikacyjny.Kod
-
Udziel zgodyWycofaj moją zgodęUdzieliłeś zgody na wysłanie adresów e-mail oraz numerów telefonów do tego serwera tożsamości w celu odkrycia innych użytkowników z Twoich kontaktów.
@@ -1508,7 +1471,7 @@
%d zablokowanych użytkowników%d zablokowanych użytkowników
- Opublikować ten pokój dla wszystkich w katalogu pokoi %1$s\?
+ Opublikować ten pokój dla wszystkich w katalogu pokojów %1$s\?Cofnij publikację tego adresuOpublikuj ten adresDodaj adres lokalny
@@ -1526,7 +1489,7 @@
To jest główny adresOpublikowane adresy mogą zostać wykorzystane przez kogokolwiek na dowolnym serwerze do dołączenia do Twojego pokoju. Żeby opublikować adres musi być on najpierw ustawiony jako adres lokalny.Opublikowane adresy
- Obejrzyj i zarządzaj adresami tego pokoju oraz jego widocznością w katalogu pokoi.
+ Obejrzyj i zarządzaj adresami tego pokoju oraz jego widocznością w katalogu pokojów.Adres pokojuDostęp do pokojuZmiany dotyczące tego kto może czytać historię zostaną zastosowane wyłącznie do przyszłych wiadomości w tym pokoju. Widoczności już istniejącej historii pozostanie niezmieniona.
@@ -1557,7 +1520,7 @@
Anuluj zaproszenieZaprzestanie ignorowania użytkownika spowoduje ponowne wyświetlanie wszystkich jego wiadomości.Przestań ignorować użytkownika
- Zignorowanie tego użytkownika usunie wszystkie jego wiadomości z pokoi, które współdzielicie.
+ Zignorowanie tego użytkownika usunie wszystkie jego wiadomości z pokojów, które współdzielicie.
\n
\nOperacja ta może zostać cofnięta w dowolnej chwili poprzez ustawienia ogólne.Ignoruj użytkownika
@@ -1895,7 +1858,7 @@
Adres URL serwera domowegoWyślij historię żądań udostępnienia kluczaPrzestrzenie
- Katalog pokoi
+ Katalog pokojówSugerowane pokojeNowa wartośćPrzełącz
@@ -1984,7 +1947,7 @@
Niektóre pokoje mogą być ukryte, gdyż są prywatne i wymagają od Ciebie zaproszenia.Niektóre pokoje mogą być ukryte, gdyż są prywatne i wymagają od Ciebie zaproszenia.
\nNie masz uprawnień aby dodawać pokoje.
- W tej Przestrzeni nie ma żadnych pokoi
+ W tej Przestrzeni nie ma żadnych pokojówProszę skontaktować się z administratorem Twojego serwera domowego aby uzyskać więcej informacjiWygląda na to, że Twój serwer domowy jeszcze nie obsługuje PrzestrzeniLubisz eksperymentować\?
@@ -2006,7 +1969,7 @@
Dodaj istniejące pokoje i przestrzenieWybierz aby opuścićOpuść wybrane pokoje i przestrzenie…
- Nie opuszczaj żadnych pokoi i przestrzeni
+ Nie opuszczaj żadnych pokojów i przestrzeniOpuść wszystkie pokoje i przestrzenieJesteś jedynym administratorem tej przestrzeni. Opuszczenie jej oznacza brak kontroli nad nią.Nie będziesz w stanie ponownie dołączyć, do momentu kiedy nie zostaniesz ponownie zaproszony.
@@ -2051,7 +2014,7 @@
PublicznaPrywatna przestrzeń dla Ciebie i Twoich znajomychJa i moi znajomi
- Prywatna przestrzeń do organizacji Twoich pokoi
+ Prywatna przestrzeń do organizacji Twoich pokojówTylko jaUpewnij się, że odpowiednie osoby mają dostęp do %s. Możesz zmienić to później.Z kim pracujesz\?
@@ -2132,7 +2095,7 @@
Opuścić obecną konferencję i przejść do innej\?Wystąpił błąd podczas próby dołączenia do konferencjiTen serwer znajduje się już na liście
- Nie można odnaleźć tego serwera lub jego listy pokoi
+ Nie można odnaleźć tego serwera lub jego listy pokojówWprowadź nazwę nowego serwera, który chcesz odkrywać.Dodaj nowy serwerTwój serwer
@@ -2149,7 +2112,7 @@
wyproszenie użytkownika zaskutkuje usunięciem go z tej przestrzeni.
\n
\nAby uniemożliwić mu ponowne dołączenie, należy go zbanować.
- Pokaż wszystkie pokoje w katalogu pokoi (również te zawierające treści dla dorosłych).
+ Pokaż wszystkie pokoje w katalogu pokojów (również te zawierające treści dla dorosłych).Pokaż pokoje z treścią dla dorosłych%1$s zmienił(a) alternatywne adresy dla tego pokoju.Dodano %1$s i usunięto %2$s jako adresy tego pokoju.
@@ -2190,7 +2153,6 @@
Ustawienia pokojuAnkietaPlik jest zbyt duży, aby go przesłać.
-
Wyślij maile i numery telefonów do %sTwoje kontakty są prywatne. Aby odnaleźć użytkowników z Twoich kontaktów, potrzebujemy zgody do wysłania informacji o nich na Twój serwer tożsamości.Bezpieczna kopia
@@ -2273,7 +2235,6 @@
Aktualizuj pokój prywatnyAktualizuj pokój publicznyDołącz do pokoju zastępczego
-
Dodaj kilka szczegółów, aby ułatwić ludziom identyfikację. Możesz je zmienić w dowolnym momencie.Dodaj kilka szczegółów, aby się wyróżnić. Możesz je zmienić w dowolnym momencie.Twoja przestrzeń prywatna
@@ -2336,7 +2297,6 @@
%1$d aktywnych połączeń ·%1$d aktywnych połączeń ·
-
Zgadzasz się na wysłanie tych informacji\?Ustawienia systemuWersje
@@ -2371,7 +2331,6 @@
NiedostępnyDostępnyPokaż znaczniki odczytania
-
Odkrywanie (%s)Dokończ konfigurację odkrywania.Usuń wszystkie nieudane wiadomości
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index b603364d4e..cd237407d2 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -39,7 +39,6 @@
Convite de Sala%1$s e %2$sSala vazia
-
Seu convite%1$s criou a salaVocê criou a sala
@@ -244,7 +243,6 @@
DeletarRenomearReportar Conteúdo
-
ouConvidarFazer signout
@@ -267,7 +265,6 @@
Contatos de Matrix somenteNenhum resultadoSalas
-
Enviar logsEnviar crash logsEnviar screenshot
@@ -295,11 +292,9 @@
Isto não parece com um endereço de email válidoEste endereço de email já está definido.Esqueceu senha\?
-
Este servidorcasa gostaria de assegurar que você não é um robôO endereço de email linkado a sua conta deve ser entrado.Falha para verificar endereço de email: assegure-se que clicou no link no email
-
Por favor entre um URL válidoJSON malformadoNão continha JSON válido
@@ -315,14 +310,10 @@
Chamada Em Progresso…O lado remoto falhou para atender.Informação
-
-
${app_name} precisa de permissão para acessar seu microfone para performar chamadas de áudio.
-
${app_name} precisa de permissão para acessar sua câmera e seu microfone para performar chamadas de vídeo.
\n
\nPor favor permita acesso no próximo pop-up para ser capaz de fazer a chamada.
-
SIMNÃOContinuar
@@ -330,7 +321,6 @@
Juntar-seRejeitarPular para não-lida(s)
-
Sair de salaVocê tem certeza que você quer sair da sala\?Mensagens Diretas
@@ -357,7 +347,6 @@
O certificado tem mudado de um que era confiado por seu telefone. Isto é ALTAMENTE INCOMUM. É recomendado que você NÃO ACEITE este novo certificado.O certificado tem mudado de um previamente confiado para um que não é confiado. O servidor pode ter renovado seu certificado. Contacte o/a administrador(a) de servidor para a impressão digital esperada.Somente aceite o certificado se o/a administrador(a) de servidor tem publicado uma impressão digital que corresponde com a acima.
-
PesquisarFiltrar membros de salaNenhum resultado
@@ -402,7 +391,6 @@
Atualizar Nome PúblicoVisto por último%1$s @ %2$s
-
AutenticaçãoFeito login comoServidorcasa
@@ -433,7 +421,6 @@
Estes são recursos experimentais que podem quebrar de maneiras inesperadas. Use com cuidado.Definir como endereço principalDes-definir como endereço principal
-
Erro de decriptaçãoNome públicoID de sessão
@@ -444,7 +431,6 @@
ExportarEntrar frasepasseConfirmar frasepasse
-
Importar chaves de sala E2EImportar chaves de salaImportar as chaves de um arquivo local
@@ -456,7 +442,6 @@
VerificarConfirme ao comparar o seguinte com as Configurações de Usuária(o) em sua outra sessão:Se não correspondem, a segurança de sua comunicação pode estar comprometida.
-
Selecionar um diretório de salasNome de servidorTodas as salas em servidor %s
@@ -532,7 +517,6 @@
Você foi expulsa(o) de %1$s por %2$sVocê foi banida(o) de %1$s por %2$sRazão: %1$s
-
%d membro%d membros
@@ -541,8 +525,6 @@
%d nova mensagem%d novas mensagens
-
-
%d mudança de filiação%d mudanças de filiação
@@ -552,7 +534,6 @@
%d mensagem notificada não-lida%d mensagens notificadas não-lidas
-
%d sala%d salas
@@ -576,10 +557,6 @@
Desculpe, nenhum aplicativo externo tem sido encontrado para completar esta ação.Re-requisitar chaves de encriptação de suas outras sessões.Por favor lance ${app_name} num outro dispositivo que possa decriptar a mensagem para que ele possa enviar as chaves para esta sessão.
-
-
-
-
%d selecionada%d selecionadas
@@ -603,8 +580,6 @@
Muda seu apelido de exibiçãoAtivar/Desativar markdownPara consertar gerenciamento de Apps Matrix
-
-
Para continuar usando o servidorcasa %1$s você deve revisar e aceitar os termos e condições.Revisar agoraDesativar Conta
@@ -690,7 +665,6 @@
Convites, remoções e bans são desafetados.Mostrar eventos de contaInclui mudanças de avatar e nome de exibição.
-
Restrições de background estão desabilitadas para ${app_name}. Este teste devia ser rodado usando dados móveis (sem Wi-Fi).
\n%1$sRestrições de background estão habilitadas para ${app_name}.
@@ -757,7 +731,6 @@
CopiarSucessoNotificações
-
Chamada ${app_name} FalhouFalha para estabelecer conexão em tempo real.
\nPor favor peça ao/à administrador(a) de seu servidorcasa para configurar um servidor TURN a fim que chamadas funcionem confiavelmente.
@@ -805,7 +778,6 @@
\nIsto vai impactar uso de rádio e bateria, vai ter uma notificação permanente exibida declarando que ${app_name} está à escuta por eventos.Sem sinc em backgroundVocê não vai ser notificada(o) sobre mensagens entrantes quando o app está em background.
-
IntegraçõesUse um gerenciador de integrações para gerenciar bots, bridges, widgets e pacotes de stickers.
\nGerenciadores de integrações recebem dados de configuração, e podem modificar widgets, enviar convites de sala e definir níveis de poder em seu nome.
@@ -921,7 +893,6 @@
Salvar Chave de RecuperaçãoCompartilharSalvar como Arquivo
-
A chave de recuperação tem sido salva.Um backup já existe em seu servidorcasaParece que você já tem configurado backup de chave de uma outra sessão. Você quer substituí-lo pelo que você está criando\?
@@ -975,7 +946,6 @@
Checando estado de backupDeletar BackupDeletar suas chaves de encriptação, das quais foi feito backup, do servidor\? Você não vai ser mais capaz de usar sua chave de recuperação para ler histórico de mensagens encriptadas.
-
Backup SeguroSalvaguardar-se contra perda de acesso a mensagens & dados encriptadosNunca perca mensagens encriptadas
@@ -991,11 +961,8 @@
VersãoAlgoritmo
-
Verificada(o)!Entendido
-
-
Requisição de Verificação%s quer verificar sua sessãoErro Desconhecido
@@ -1159,7 +1126,6 @@
Este conteúdo foi reportado como inapropriado.
\n
\nSe você não quer ver mais nada de conteúdo desta(e) usuária(o), você pode ignorá-la(o) para esconder mensagens dela(e).
-
Ignorar usuária(o)Todas as mensagens (barulhento)Todas as mensagens
@@ -1363,7 +1329,6 @@
Verificar %sVerificou %sEsperando por %s…
-
Mensagens nesta sala não são encriptadas ponta-a-ponta.Mensagens nesta sala são encriptadas ponta-a-ponta.
\n
@@ -1449,7 +1414,7 @@
Quase lá! %s está mostrando um tick (✓)\?SimNão
- Conectividade ao servidor tem sido perdida
+ A conexão com o servidor foi perdidaModo avião está ligadoFerramentas DevDados de Conta
@@ -1521,7 +1486,6 @@
Mensagem…Usar ArquivoChecando Chave de backup
-
Se você cancelar agora, você pode perder mensagens & dados encriptados se você perder acesso a seus logins.
\n
\nVocê também pode configurar Backup Seguro & gerenciar suas chaves em Configurações.
@@ -1823,7 +1787,6 @@
Você poderia habilitar isto se a sala vai somente ser usada para colaborar com times internos em seu servidorcasa. Isto não poder ser mudado mais tarde.Bloquear qualquer pessoa que não é parte de %s de jamais se juntar a esta sala%1$d de %2$d
-
Dar consentimentoRevogar meu consentimentoVocê tem dado seu consentimento para enviar emails e números de telefone para este servidor de identidade para descobrir outras(os) usuárias(os) de seus contatos.
@@ -1913,8 +1876,6 @@
TransferirConectarConsultar primeiro
-
-
Chamada ativa (%1$s)Houve um erro ao procurar o número de telefonePad de disco
@@ -2112,7 +2073,6 @@
Enviar vídeo com o tamanho originalEnviar vídeos com o tamanho original
-
Desculpe, um erro ocorreu enquanto tentando se juntar: %sEndereço de espaçoVer e gerenciar endereços deste espaço.
@@ -2147,7 +2107,7 @@
%d chamadas de vídeo perdidas
- Chamadad de áudio perdida
+ Chamada de áudio perdida%d chamadas de áudio perdidasPor favor note que fazer upgrade vai fazer uma nova versão da sala. Todas as mensagens atuais vão permanecer nesta sala arquivada.
@@ -2313,7 +2273,6 @@
Sondar pergunta ou tópicoCriar SondagemSondagem
-
Enviar emails e números de telefone para %sSeus contatos são privados. Para descobrir usuárias(os) de seus contatos, você precisa de permissão para enviar info de contato a seu servidor de identidade.O signout desta sessão tem sido feito!
@@ -2458,4 +2417,6 @@
%d mudança de ACLs de servidor%d mudanças de ACLs de servidor
+ %1$s, %2$s e outras(os)
+ %1$s e %2$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index d9d9c7b1cd..8d97ff7266 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -39,7 +39,6 @@
Pozvanie do miestnosti%1$s a %2$sPrázdna miestnosť
-
%s aktualizoval/a túto miestnosť.Úvodná synchronizácia:
\nPrebieha import účtu…
@@ -197,7 +196,6 @@
VymazaťPremenovaťNahlásiť obsah
-
aleboPozvaťOdhlásiť sa
@@ -220,7 +218,6 @@
Len Matrix kontaktyŽiadne výsledkyMiestnosti
-
Odoslať záznamyOdoslať záznamy o zlyhaníOdoslať snímku obrazovky
@@ -248,11 +245,9 @@
Zdá sa, že toto nie je platná emailová adresaTáto emailová adresa sa už používa.Zabudli ste heslo?
-
Tento domovský server by sa rád uistil, že nieste robotMusíte zadať emailovú adresu prepojenú s vašim účtom.Nepodarilo sa overiť emailovú adresu: Uistite sa, že ste správne klikli na odkaz v emailovej správe
-
Zadajte platnú adresu URLChybné údaje vo formáte JSONNeplatné údaje vo formáte JSON
@@ -269,14 +264,10 @@
Prebiehajúci hovor…Vzdialenej strane sa nepodarilo prijať hovor.Informácia
-
-
Aby ste mohli uskutočňovať audio hovory, ${app_name} potrebuje prístup k mikrofónu vašeho zariadenia.
-
${app_name} potrebuje povolenie na prístup k vašej kamere a mikrofónu na uskutočňovanie videohovorov.
\n
\nPovoľte prístup v ďalších vyskakovacích oknách, aby ste mohli uskutočniť hovor.
-
ÁNONIEPokračovať
@@ -284,7 +275,6 @@
VstúpiťOdmietnuťPreskočiť na neprečítanú správu
-
Opustiť miestnosťSte si istí, že chcete opustiť miestnosť?PRIAME KONVERZÁCIE
@@ -311,7 +301,6 @@
Certifikát sa zmenil na iný, ktorému tento telefón dôveroval. Toto je VEĽMI NEZVYČAJNÉ. Odporúča sa, aby ste tento nový certifikát NEPRIJALI.Certifikát sa zmenil z predtým dôveryhodného na nedôveryhodný. Server mohol obnoviť svoj certifikát. Obráťte sa na správcu servera, aby vám poskytol očakávaný odtlačok.Dôverujte certifikátu len v prípade, že správca servera zverejnil odtlačok zhodný s odtlačkom zobrazeným vyššie.
-
HľadaťFiltrovať členov v miestnostiŽiadne výsledky
@@ -364,7 +353,6 @@
Aktualizovať verejné menoNaposledy videné%1$s @ %2$s
-
OvereniePrihlásený akoDomovský server
@@ -402,7 +390,6 @@
Tieto funkcie sú experimentálne a môžu sa nečakane pokaziť. Pri používaní buďte opatrní.Nastaviť ako hlavnú adresuZrušiť nastavenie ako hlavnej adresy
-
VzhľadChyba dešifrovaniaVerejné meno
@@ -414,7 +401,6 @@
ExportovaťZadajte prístupovú frázuPotvrďte prístupovú frázu
-
Importovať šifrovacie kľúče miestnostiImportovať kľúče miestnostiImportovať kľúče z lokálneho súboru
@@ -426,7 +412,6 @@
OveriťAk chcete overiť, či táto relácia je skutočne dôveryhodná, kontaktujte jeho vlastníka iným spôsobom (napr. osobne alebo cez telefón) a opýtajte sa ho, či kľúč, ktorý má zobrazený v Nastaveniach, sa zhoduje s kľúčom zobrazeným nižšie:Ak sa kľúče zhodujú, stlačte tlačidlo Overiť nižšie. Ak sa nezhodujú, niekto ďalší odpočúva toto zariadenie a mali by ste ho pridať na čiernu listinu.
-
Vyberte adresár miestnostíNázov serveraVšetky miestnosti na serveri %s
@@ -488,7 +473,6 @@
%d zmien členstvaZobraziť členov
-
1 člen%d členovia
@@ -499,14 +483,11 @@
%d nové správy%d nových správ
-
-
1 neprečítaná správa%d neprečítané správy%d neprečítaných správ
-
1 miestnosť%d miestnosti
@@ -546,10 +527,6 @@
Nebola nájdená žiadna vhodná aplikácia na dokončenie tejto akcie.Prosím, vložte svoje heslo.Ak je to možné, prosím popis napíšte v angličtine.
-
-
-
-
1 vybratý%d vybratí
@@ -569,8 +546,6 @@
Mení vaše zobrazované meno / prezývkuZapnutie/vypnutie formátovanie textu markdownUžitočné na opravu spravovania Matrix aplikácií
-
-
Táto miestnosť bola nahradená inou a nie je viac aktívna.Konverzácia pokračuje tuTáto miestnosť je pokračovaním predchádzajúcej konverzácii
@@ -662,7 +637,6 @@
Pozvania, odstránenia a zákazy nie sú ovplyvnené.Zobrazovať udalosti účtuZahŕňa zmeny zobrazovaného mena a obrázka v profile.
-
HesloSpustiť predvolený fotoaparát v systéme namiesto zobrazenia vlastnej vstavanej obrazovky.Príkaz \"%s\" vyžaduje viac argumentov, alebo nie sú všetky zadané správne.
@@ -684,7 +658,7 @@
IgnorovaťSte si istí, že sa chcete odhlásiť\?Označiť ako prečítané
- Prihlásiť sa použitím jediného prihlásenia
+ Prihlásiť sa použitím jednotného prihlásenia SSOPokročilé nastavenia oznámeníDôležitosť oznámení pre udalostiVlastné nastavenia.
@@ -735,7 +709,6 @@
ZrušiťOdpojiť saOdmietnuť
-
Toto nie je platná adresa Matrix serveruDomovský server je nedostupný na tejto URL adrese, preverte to prosímRelácie
@@ -799,7 +772,6 @@
\nBude to mať vplyv na používanie rádia a batérie, bude sa zobrazovať trvalé oznámenie, že ${app_name} počúva udalosti.Žiadna synchronizácia na pozadíNebudete dostávať oznámenia o prichádzajúcich správach, keď aplikácia pracuje na pozadí.
-
IntegráciePoužite správcu integrácií na nastavenie botov, premostení, widgetov a balíčkov s nálepkami.
\nSprávcovia integrácie dostávajú konfiguračné údaje a môžu vo vašom mene upravovať widgety, posielať pozvánky do miestnosti a nastavovať úrovne oprávnení.
@@ -897,7 +869,6 @@
Uložiť kľúč obnoveniaZdieľaťUložiť ako súbor
-
Kľúč obnovenia bol uložený.Záloha už existuje na vašom domovskom serveriZdá sa, že ste si už zálohovanie kľúčov nastavili z inej relácie. Chcete ho nahradiť zálohou, ktorú vytvárate teraz\?
@@ -953,7 +924,6 @@
Zisťovanie stavu zálohovaniaVymazať zálohuVymazať vaše zálohované šifrovacie kľúče z domovského servera\? Na čítanie histórie zašifrovaných správ už nebudete môcť použiť kľúč na obnovenie.
-
Bezpečné zálohovanieZabezpečte sa proti strate šifrovaných správ a údajovNikdy neprídete o šifrované správy
@@ -971,11 +941,8 @@
VerziaAlgoritmusPodpis
-
Overené!Rozumiem
-
-
Žiadosť o overenie%s chce overiť vašu reláciuNeznáma chyba
@@ -1381,7 +1348,6 @@
Nastavenia miestnostiVerzia miestnostiNová hodnota
-
ČíselníkZavolať späťVyčistiť históriu
@@ -1680,13 +1646,12 @@
Otvoriť ponuku vytvorenia miestnostiZdá sa, že serveru trvá príliš dlho, kým odpovie, čo môže byť spôsobené buď zlým pripojením, alebo chybou servera. Skúste to o chvíľu znova.Súhlasíte so zaslaním týchto informácií\?
-
Preskočiť na potvrdenie o prečítaníVlastné (%1$d) v %2$sPredvolené v %1$sOveriť porovnaním emotikonov
- Namiesto toho overte porovnaním emoji
- Ak nie ste osobne, porovnajte namiesto toho emoji
+ Namiesto toho overte porovnaním emotikonov
+ Ak nie ste osobne, porovnajte namiesto toho emotikonySkenovať pomocou tohto zariadeniaNaskenujte kód pomocou iného zariadenia alebo prepnite a skenujte pomocou tohto zariadeniaNaskenujte kód pomocou zariadenia druhého používateľa, aby ste sa navzájom bezpečne overili
@@ -1783,7 +1748,6 @@
Žiadna odpoveďPodržali ste hovorAutomaticky aktualizovať nadradený priestor
-
Niektoré miestnosti môžu byť skryté, pretože sú súkromné a potrebujete pozvánku.Tento priestor nemá žiadne miestnostiPre viac informácií sa obráťte na správcu domovského servera
@@ -1906,7 +1870,6 @@
Zmienky a kľúčové slováIba zmienky a kľúčové slováMedzinárodné telefónne čísla musia začínať znakom \"+\"
-
Ak chcete zistiť existujúce kontakty, potrebujete odoslať kontaktné informácie (e-maily a telefónne čísla) na server totožností. Pred odoslaním vaše údaje zahašujeme kvôli ochrane osobných údajov.Odoslať e-maily a telefónne čísla na %sDali ste súhlas na odosielanie e-mailov a telefónnych čísel na tento server totožností na objavenie ďalších používateľov z vašich kontaktov.
@@ -1965,7 +1928,6 @@
\n
\nZastaviť proces zmeny hesla\?Zabudli ste alebo ste stratili všetky možnosti obnovy\? Obnovte všetko
-
Použite prístupovú frázu na obnovenie alebo kľúčPoužite najnovšiu aplikáciu ${app_name} na svojich ostatných zariadeniach:Použite najnovšiu aplikáciu ${app_name} na svojich ostatných zariadeniach, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} pre Android alebo iného klienta Matrix podporujúceho krížové podpisovanie
@@ -1994,7 +1956,6 @@
%1$d aktívne hovory ·%1$d aktívnych hovorov ·
-
Prebiehajúci hovor (%1$s)Pri vyhľadávaní telefónneho čísla došlo k chybeŽiadna odpoveď
@@ -2056,7 +2017,7 @@
Vyberte si prosím heslo.Vyberte si prosím používateľské meno.Nepodarilo sa nastaviť krížové podpisovanie
- Interaktívne overte pomocou emoji
+ Interaktívne overte pomocou emotikonovManuálne overte pomocou textuOverte nové prihlásenie s prístupom k vášmu účtu: %1$sOverte všetky vaše relácie, aby ste si boli istý, že sú vaše správy a účet bezpečné
@@ -2154,7 +2115,7 @@
Krížové podpisovanie nie je povolenéVaša nová relácia je teraz overená. Má prístup k vašim šifrovaným správam a ostatní používatelia ju uvidia ako dôveryhodnú.Porovnajte kód s kódom zobrazeným na obrazovke druhého používateľa.
- Porovnajte jedinečné emoji a uistite sa, že sú zobrazené v rovnakom poradí.
+ Porovnajte jedinečné emotikony a uistite sa, že sú zobrazené v rovnakom poradí.Aby ste si boli istý, urobte to osobne alebo použite iný dôveryhodný spôsob komunikácie.Ak chcete byť v bezpečí, overte %s pomocou jednorazového kódu.Po zapnutí šifrovania pre miestnosť ho už nemožno vypnúť. Správy odoslané v zašifrovanej miestnosti nemôže vidieť server, iba účastníci miestnosti. Zapnutie šifrovania môže zabrániť správnemu fungovaniu mnohých botov a premostení.
@@ -2164,7 +2125,6 @@
Táto relácia nemôže zdieľať toto overenie s vašimi ostatnými reláciami.
\nOverenie bude uložené lokálne a zdieľané v budúcej verzii aplikácie.Akcie správcu
-
Zobrazujú sa len prvé výsledky, zadajte ďalšie písmená…Zatraste telefónom a otestujte prah detekciePrahová hodnota detekcie
@@ -2184,7 +2144,6 @@
Zadajte kľúčové slová pre vyhľadanie reakcie.Odobrať z nízkej priorityPridať k nízkej priorite
-
Na pokračovanie použite %1$s alebo %2$s.Ak chcete pokračovať, zadajte prístupovú frázu pre zálohovanie kľúčov.Generovanie kľúča SSSS z prístupovej frázy %s
@@ -2365,8 +2324,8 @@
Kto má prístup\?Zmeny týkajúce sa toho, kto môže čítať históriu, sa budú vzťahovať len na budúce správy v tejto miestnosti. Viditeľnosť existujúcej histórie zostane nezmenená.Tento server neposkytuje žiadne zásady a pravidlá.
- Pridať tlačidlo do editora správ na otvorenie klávesnice emodži
- Zobraziť klávesnicu emodži
+ Pridať tlačidlo do editora správ na otvorenie klávesnice emotikonov
+ Zobraziť klávesnicu emotikonovPoužite príkaz /confetti alebo pošlite správu obsahujúcu ❄️ alebo 🎉Zobraziť efekty konverzácieRelácia bola odhlásená!
@@ -2464,7 +2423,7 @@
Udalosť vymazaná používateľom, dôvod: %1$sDôvod úpravy${app_name} sa stretol s problémom pri vykresľovaní obsahu udalosti s id \'%1$s\'
- Reagoval/a s: %s
+ Reagoval/a: %s${app_name} môže padať častejšie, keď sa vyskytne neočakávaná chybaZadajte adresu servera, ktorý chcete použiťVlastné hlásenie…
@@ -2476,4 +2435,21 @@
%d zmeny ACL servera%d zmien ACL servera
+ %1$s, %2$s a ďalší
+ %1$s a %2$s
+ Pokračovať pomocou jednotného prihlásenia SSO
+ jednotné prihlásenie SSO
+ Zatvoriť výzvu na zálohovanie kľúčov
+ Výťazná odpoveď
+ Nezaškrtnuté
+ Zaškrtnuté
+ Rozpísaná správa
+ Otvoriť navigačnú ponuku
+ Videli
+
+ %1$d ďalšia
+ %1$d ďalšie
+ %1$d ďalších
+
+ Zadajte URL adresu servera Modular Element alebo adresu servera, ktorý si želáte použiť
\ No newline at end of file
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index c363b8e4e9..48fc274eb7 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -39,7 +39,6 @@
%1$s hoqi emrin e tij në ekran (%2$s)%1$s hoqi temën e dhomës%1$s dërgoi një ftesë për %2$s që të marrë pjesë në dhomë
-
%s e përmirësoi këtë dhomë.Njëkohësimi fillestar:
\nPo importohet llogaria…
@@ -315,10 +314,6 @@
Hidheni tejAnëtarë listeHidhu te të palexuarit
-
-
-
-
Dilni nga dhomaJeni i sigurt se doni të dilni nga dhoma?FJALOSJE TË DREJTPËRDREJTA
@@ -501,8 +496,6 @@
KreuDhomaI ftuar
-
-
Jeni përzënë prej %1$s nga %2$sJeni dëbuar prej %1$s nga %2$sArsye: %1$s
@@ -520,7 +513,6 @@
%1$s: %2$s%d+Po përgjohet për akte
-
Të parapëlqyerJu lutemi, përshkruajeni të metën. Ç’po bënit? Ç’prisnit të ndodhte? Ç’ndodhi në fakt?Duket se po përplasni telefonin nga inati. Do të donit të hapej skena për njoftim të metash?
@@ -528,17 +520,12 @@
Dështoi dërgimi i njoftimit të të metës (%s)DilniDërgoni mesazh zanor
-
Verifikimi i adresës email dështoi: sigurohuni se keni klikuar lidhjen te email-i
-
Ju lutemi, niseni ${app_name}-in në një tjetër pajisje që mund të shfshehtëzojë mesazhin, që kështu të mund të dërgojë kyçet te ky sesion.
-
%1$s & %2$s & të tjerë po shtypin…version olmFikso dhomat me njoftime të humburaVetëm anëtarët (që nga çasti i përzgjedhjes së kësaj mundësie)
-
-
Përdor kamerë të brendshmeShtuat një sesion të ri \'%s\', i cili po kërkon kyçe fshehtëzimi.Sesioni juaj i paverifikuar \'%s\' po kërkon kyçe fshehtëzimi.
@@ -551,7 +538,6 @@
Ju lutemi, %s për ta zmadhuar këtë kufi.Ju lutemi, %s që të vazhdoni përdorimin e këtij shërbimi.ose
-
Në qoftë e mundur, ju lutemi, përshkrimin shkruajeni në anglisht.Hëpërhë, s’keni të aktivizuar ndonjë pako ngjitësash.
\n
@@ -565,16 +551,11 @@
Po bëhet lidhja e thirrjes…Ana e largët dështoi të përgjigjet.
-
-
Për të kryer thirrje audio, ${app_name}-i lyp leje të përdorë mikrofonin tuaj.
-
Për të kryer thirrje video, ${app_name}-i lyp leje të përdorë kamerën dhe mikrofonin tuaj.
\n
\nJu lutemi, lejoni përdorimin, që nga flluskat pasuese, që të jetë në gjendje të bëjë thirrjen.
-
JO
-
%d anëtar%d anëtarë
@@ -585,14 +566,9 @@
%d mesazh i ri%d mesazhe të rinj
-
-
-
-
Ju lutemi, kontrolloni email-in tuaj dhe klikoni mbi lidhjen që përmban. Pasi të jetë bërë kjo, klikoni që të vazhdohet.Këto janë veçori eksperimentale që mund të ngecin në rrugë të papritura. Përdorini me kujdes.Ju lutemi, krijoni një frazëkalim për fshehtëzimin e kyçeve të eksportuar. Që të jeni në gjendje t’i importoni kyçet, do t’ju duhet të jepni të njëjtin frazëkalim.
-
Mos dërgo kurrë prej këtij sesioni mesazhe të fshehtëzuar te sesione të paverifikuar.Ripohojeni duke krahasuar sa vijon me Rregullimet e Përdoruesit te sesioni juaj tjetër:Nëse s’përputhen, siguria e komunikimeve tuaja mund të jetë komprometuar.
@@ -639,7 +615,6 @@
%d mesazh i njoftuar i palexuar%d mesazhe të njoftuar të palexuar
-
Dërgoni një ngjitësDërgoni një ngjitësThirrje
@@ -700,7 +675,6 @@
${app_name}-i nuk preket nga Optimizime Baterie.Nëse një përdorues e lë një pajisje jo në prizë dhe të palëvizshme për një periudhë, me ekranin të fikur, pajisja kalon nën mënyrën Dremitje. Kjo u parandalon aplikimeve të hyjnë në rrjet dhe shtyn për më vonë punët e tyre, njëkohësimet dhe alarmet standarde.Shpërfille Optimizimin
-
S’u gjet APK për Google Play Services. Njoftimet mund të mos punojnë saktë.Thirrje Video Në Kryerje e Sipër…Kopjeruajtje Kyçesh
@@ -737,7 +711,6 @@
U bëRuani Kyç RimarrjeshRuaje si Skedë
-
Ju lutemi, bëni një kopjeJepjani kyçin e rimarrjeve…Po prodhohet Kyç Rimarrjesh duke përdorur frazëkalim, ky proces mund të hajë disa sekonda.
@@ -831,8 +804,6 @@
Po shkarkohen kyçe…Po importohen kyçe…Që të përdorni Kopjeruajtje Kyçesh në këtë sesion, rikthejeni tani përmes frazëkalimit tuaj ose kyçi rimarrjesh.
-
-
MediaNgjeshje parazgjedhjeZgjidhni
@@ -869,8 +840,6 @@
ShpërfilleU verifikua!E mora vesh
-
-
Kërkesë Verifikimi%s dëshiron të verifikojë sesionin tuajGabim i Panjohur
@@ -965,7 +934,6 @@
AsnjëShfuqizojeShkëputu
-
S’kapet dot shërbyes Home te kjo URL, ju lutemi, kontrollojeniMënyrë Njëkohësimi Në PrapaskenëE optimizuar për baterinë
@@ -976,7 +944,6 @@
\nKjo do të ketë ndikim mbi përdorimin e baterisë dhe të transmetimit, do të shfaqet një njoftim i pandërprerë që pohon se ${app_name}-i po përgjon për akte.Pa njëkohësim në prapraskenëS’do të njoftoheni për mesazhe ardhës, kur aplikacioni gjendet në prapaskenë.
-
Administroni rregullimet tuaja për zbulime.S’po përdorni ndonjë shërbyes identiteteshDuket se po rrekeni të lidheni me një tjetër shërbyes Home. Doni të bëhet dalja\?
@@ -1045,7 +1012,6 @@
Kjo lëndë është raportuar si e papërshtatshme.
\n
\nNëse s’doni të shihni më lëndë nga ky përdorues, mund ta shpërfillni, që të fshihen mesazhet e tij.
-
IntegrimePërdorni një përgjegjës integrimesh që të administroni robotë, ura, widget-e dhe paketa ngjitësish.
\nPërgjegjësit e integrimeve marrin të dhëna formësimi dhe mund të ndryshojnë widget-e, të dërgojnë ftesa për në dhoma dhe të caktojnë shkallë pushteti në emrin tuaj.
@@ -1324,7 +1290,6 @@
%s u pranuaSkanojeni kodin me pajisjen e përdoruesit tjetër, për të verifikuar në mënyrë të sigurt njëri-tjetrinNëse s’jeni vetë atje, krahasoni emoji-n
-
${app_name} nuk trajton akte të llojit \'%1$s\'${app_name} ndeshi një problem kur vizatohej lëndë e aktit me ID \'%1$s\'Ky sesion s’është në gjendje të ndajë këtë verifikim me sesionet tuaj të tjerë.
@@ -1407,7 +1372,6 @@
Shtypeni dhe ruajeni diku në një vend të parrezikRuajeni në një diskth USB ose pajisje kopjeruajtjeshKopjojeni te depozita juaj personale në re
-
Fshehtëzimi u aktivizuaMesazhet në këtë dhomë janë të fshehtëzuara skaj-më-skaj. Mësoni më tepër & verifikoni përdorues te profilet e tyre.Fshehtëzim jo i aktivizuar
@@ -1811,7 +1775,6 @@
Fshihi të mëtejshmetShfaq të mëtejshme%1$d nga %2$d
-
Jepe praniminShfuqizoje pranimin timKeni dhënë pranimin tuaj për të dërguar email-e dhe numra telefonash te ky shërbyes identitetesh që të zbulojë përdorues të tjerë prej kontakteve tuaj.
@@ -1863,8 +1826,6 @@
ShpërnguleLidheKonsultohu së pari
-
-
Thirrje aktive (%1$s)Pati një gabim gjatë kërkimit të numrit të telefonitPjesa e numrave
@@ -2028,7 +1989,7 @@
Unë dhe anëtarët e ekipit timNjë hapësirë private për të sistemuar dhomat tuajaUnë vetëm
- Siguroni që personat e duhur të kenë hyrje te %s. Këtë mund të ndryshoni më vonë.
+ Siguroni që personat e duhur të kenë hyrje te %s.Me cilët po punoni\?Që të hyni në një hapësirë ekzistuese, ju duhet një ftesë.Këtë mund ta ndryshoni më vonë
@@ -2102,7 +2063,6 @@
Jepni emrin e e një shërbyesi të ri që doni të eksploroni.Shtoni shërbyes të riShërbyesi juaj
-
Na ndjeni, ndodhi një gabim teksa provohej të hyhej: %sAdresë hapësireAdresa hapësire
@@ -2302,7 +2262,6 @@
Krijoni PyetësorA pranoni të dërgohen këto hollësi\?Për të zbuluar kontakte ekzistuese, duhet të dërgoni hollësi kontakti (email-e dhe numra telefonash) te shërbyesi juaj i identiteteve. Para dërgimit, i fshehtëzojmë të dhënat tuaja, për privatësi.
-
Dërgo email-e dhe numra telefonash te %sKontaktet tuaja janë private. Për të zbuluar përdorues prej kontakteve tuaja, na duhet leja juaj për të dërguar hollësi kontakti te shërbyesi juaj i identiteteve.Është bërë dalja nga sesioni!
@@ -2435,4 +2394,18 @@
Kopjoje lidhjen te rrjedhaShiheni në dhomëShihni Rrjedha
+ %1$s dhe %2$s
+ %1$s, %2$s dhe të tjerë
+
+ %1$d më tepër
+ %1$d më tepër
+
+ Njofto krejt dhomën
+ Përdorues
+
+ %d ndryshim ACL-sh shërbyesi
+ %d ndryshimë ACL-sh shërbyesi
+
+ Njoftim dhome
+ Shfaq më pak
\ No newline at end of file
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 38f7a60928..69c25ebba3 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -88,7 +88,6 @@
TelefonnummerRumsinbjudan%1$s och %2$s
-
Tomt rumInledande synk:
\nImporterar konto…
@@ -264,7 +263,6 @@
ÅterkallaKoppla ifrånRapportera innehåll
-
ellerBjud inGodkänn
@@ -296,7 +294,6 @@
Bara Matrix-kontakterInga resultatRum
-
GemenskaperSkicka loggarSkicka kraschloggar
@@ -322,7 +319,6 @@
Skicka röstÄr du säker på att du vill starta ett röstsamtal\?Är du säker på att du vill skapa ett videosamtal\?
-
Skicka filerSkicka dekalTa foto eller video
@@ -339,11 +335,9 @@
Det här ser inte ut som en giltig e-postadressDen här e-postadressen är redan definierad.Glömt lösenordet\?
-
Denna hemserver skulle vilja verifiera att du inte är en robotDu måste skriva in e-postadressen länkad till ditt konto.Misslyckades att verifiera e-postadressen: se till att du klickade på länken i e-brevet
-
Vänligen granska och acceptera villkoren för denna hemserver:Vänligen skriv in en giltig URLDet här är inte en giltig Matrixserveradress
@@ -374,14 +368,10 @@
Videosamtal pågår…Den andra parten svarade inte.Information
-
-
${app_name} behöver tillstånd att komma åt din mikrofon för hålla röstsamtal.
-
${app_name} behöver tillstånd att komma åt din kamera och mikrofon för att kunna utföra videosamtal.
\n
\nVänligen ge tillstånd i nästa popup för att kunna utföra samtalet.
-
JANEJFortsätt
@@ -390,16 +380,10 @@
AvslåLista medlemmarHoppa till oläst
-
-
%d medlem%d medlemmar
-
-
-
-
Lämna rumÄr du säker på att du vill lämna rummet\?DIREKTCHATT
@@ -409,8 +393,6 @@
KickaIgnoreraFiltrera rumsmedlemmar
-
-
Fäst rum med missade aviseringarFäst rum med olästa meddelandenAlla rum på %s-servern
@@ -420,7 +402,6 @@
%d rumRum
-
Detta kommer att göra ditt konto permanent oanvändbart. Du kommer inte kunna logga in, och ingen kommer kunna registrera sig med samma användar-ID. Detta kommer att få ditt konto att lämna alla rum det är med i, och det kommer att ta bort din kontoinformation från din identitetsserver. Den här handlingen går inte att ångra.
\n
\nAtt inaktivera ditt konto får oss normalt inte att glömma meddelanden du har skickat. Om du skulle vilja att vi glömmer dina meddelanden, markera rutan nedan.
@@ -456,7 +437,6 @@
Meddelanden som innehåller mitt visningsnamnAnvändarinställningarInnehåller byten av avatar eller visningsnamn.
-
LösenordByt lösenordNuvarande lösenord
@@ -473,7 +453,6 @@
Din återställningsnyckel är ett skyddsnät - du kan använda den för att återfå åtkomst till dina krypterade meddelanden om du skulle glömma din lösenfras.
\nLagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp)Lagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp)
-
Byt nätverkAlla gemenskaperAllmänt
@@ -483,7 +462,6 @@
Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera användare i deras profiler.AvignoreraKunde inte verifiera den externa serverns identitet.
-
Alla meddelandenLägg till e-postadressLägg till telefonnummer
@@ -541,7 +519,6 @@
Inaktivera mitt kontoUpptäckbarhetHantera dina upptäckbarhetsinställningar.
-
Inloggad somHemserverIdentitetsserver
@@ -714,7 +691,6 @@
Använd den här sessionen för att verifiera din nya och ge den tillgång till krypterade meddelanden.Om du avbryter så kommer du inte kunna läsa krypterade meddelanden på den här enheten, och andra användare kommer inte att lita på denOm du avbryter så kommer du inte kunna läsa krypterade meddelanden på din nya enhet, och andra användare kommer inte att lita på den
-
När jag bjuds in till ett rumSamtalsinbjudningarMeddelanden skickade av en bott
@@ -755,15 +731,12 @@
Integrationer är avstängdaAktivera \'Tillåt integrationer\' I inställningarna för att göra detta.Exportera nycklarna till en lokal fil
-
Importera nycklarna från en lokal fil
-
Välj en rumskatalog%d oläst aviserat meddelande%d olästa aviserade meddelanden
-
%1$s: %2$d meddelande%1$s: %2$d meddelanden
@@ -783,7 +756,6 @@
Krypterat meddelandekontakta din tjänstadministratörSpara som fil
-
Radera dina säkerhetskopierade krypteringsnycklar från servern\? Du kommer inte längre kunna använda din återställningsnyckel för att läsa krypterad meddelandehistorik.Skydda dig mot att tappa åtkomst till krypterade meddelanden och dataNya säkra meddelandenycklar
@@ -1013,7 +985,6 @@
Sätt upp säker säkerhetskopieringAlla nycklar säkerhetskopieradeAlgoritm
-
%s vill verifiera din sessionVisa borttagna meddelandenVisa en platshållare för borttagna meddelanden
@@ -1029,7 +1000,6 @@
%1$s, %2$s och %3$d annan har läst%1$s, %2$s och %3$d andra har läst
-
Du ignorerar inga användareAnnanOm du har skapat ett konto på en hemserver så kan du använda ditt Matrix-ID (t.ex. @användare:domän.com) och lösenord nedan.
@@ -1124,7 +1094,6 @@
Du kommer inte att bli aviserad om inkommande meddelanden när appen är i bakgrunden.Starta vid bootTimeout för synkbegäran
-
Fördröjning mellan varje synkroniseringLokala kontakterKontaktbehörighet
@@ -1172,7 +1141,6 @@
Rummets interna IDSätt som huvudadressAvsätt som huvudadress
-
AvkrypteringsfelPublikt namnNycklar framgångsrikt importerade
@@ -1253,7 +1221,6 @@
SkapaHemBjöd in
-
Du har blivit utsparkad från %1$s av %2$sDu har blivit bannad från %1$s av %2$sOrsak: %1$s
@@ -1319,10 +1286,8 @@
Säkerhetskopierar %d nycklar…Signatur
-
Verifierad!Jag förstår
-
VerifieringsbegäranOkänt felDet verkar som att du försöker ansluta till en annan hemserver. Vill du logga ut\?
@@ -1525,7 +1490,6 @@
Verifiera %sVerifierade %sVäntar på %s…
-
SäkerhetAdminhandlingarLämnar rummet…
@@ -1830,7 +1794,6 @@
Dölj avanceratVisa avancerat%1$d av %2$d
-
Ge samtyckeÅterkalla mitt samtyckeDu har gett samtycke att skicka e-postadresser och telefonnummer till den här identitetsservern för att upptäcka andra användare baserat på dina kontakter.
@@ -1914,8 +1877,6 @@
FlyttaAnslutRådfråga först
-
-
Aktivt samtal (%1$s)Ett fel inträffade när telefonnumret slogs uppKnappsats
@@ -2003,7 +1964,7 @@
Jag och mina teamkamraterAtt privat utrymme för att organisera dina rumBara jag
- Det till att rätt personer har åtkomst till %s. Du kan ändra detta senare.
+ Det till att rätt personer har åtkomst till %s.Vem jobbar du med\?För att gå med i ett existerande utrymme så behöver du en inbjudan.Detta kan ändras senare
@@ -2112,7 +2073,6 @@
Ange namnet för en ny server du vill utforska.Lägg till en ny serverDin server
-
För att utföra detta, vänligen ge kameraåtkomst från systeminställningarna.Vissa behörigheter saknas för att utföra detta, vänligen ge behörighet från systeminställningarna.Observera att uppgradering kommer att göra en ny version av rummet. Alla nuvarande meddelanden kommer att vara kvar i det här arkiverade rummet.
@@ -2313,7 +2273,6 @@
Omröstningens fråga eller ämneSkapa omröstningOmröstning
-
Skicka e-postadresser och telefonnummer till %sDina kontakter är privata. För att upptäcka användare från dina kontakter så behöver vi ditt tillstånd att skicka kontaktinfo till din identitetsserver.Sessionen har loggats ut!
@@ -2446,4 +2405,18 @@
Kopiera länk till trådVisa i rumVisa trådar
+ Rumsaviseringar
+ Användare
+ Avisera hela rummet
+
+ %1$d till
+ %1$d till
+
+ Visa mindre
+ %1$s, %2$s och fler
+ %1$s och %2$s
+
+ %d server-ACL-ändring
+ %d server-ACL-ändringar
+
\ No newline at end of file
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index 066a40fdf4..2f34fc66fb 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -39,7 +39,6 @@
Помилка MatrixАдреса електронної поштиНомер телефону
-
%s оновлює цю кімнату.Початкове налаштування:
\nІмпортування даних облікового запису
@@ -164,7 +163,6 @@
ВидалитиПерейменуватиПоскаржитись на вміст
-
абоЗапрошенняВийти з облікового запису
@@ -187,7 +185,6 @@
Лише Matrix-контактиНемає результатівКімнати
-
Надіслати журналиНадіслати журнали помилокНадіслати знімок екрана
@@ -235,7 +232,6 @@
ІнформаціяДля здійснення аудіодзвінків потрібен доступ до мікрофону.Для здійснення відеодзвінків потрібен доступ до камери та мікрофону.\n\nБудь ласка, надайте його у наступних виринаючих вікнах, щоб мати змогу їх здійснити.
-
ТАКНІПродовжити
@@ -269,7 +265,6 @@
Сертифікат почав відрізнятися від того, якому довіряв ваш телефон. Це ДУЖЕ НЕЗВИЧНО. Наполегливо радимо НЕ ПРИЙМАТИ цей новий сертифікат.Сертифікат змінився з довіреного на недовірений. Сервер міг оновити свій сертифікат. Зв\'яжіться з адміністратором сервера, щоб отримати дійсний відбиток.Приймайте сертифікат лише у випадку збігу відбитку вище з відбитком, оприлюдненим адміністратором сервера.
-
ПошукФільтр переліку користувачівТут порожньо
@@ -370,7 +365,6 @@
ЕкспортВведіть парольну фразуПідтвердіть парольну фразу
-
Імпортувати E2E ключі кімнатиІмпортувати ключі кімнатиІмпортувати ключі з локального файлу
@@ -382,7 +376,6 @@
ЗвіритиПідтвердьте, порівнявши вказане за допомогою налаштувань користувача в іншому сеансі:Якщо вони відрізняються, безпека вашого зв\'язку може бути під загрозою.
-
Вибір каталогу кімнатІм\'я сервераВсі кімнати на сервері %s
@@ -439,7 +432,6 @@
У вас поки що не має наліпок.
\n
\nДодати зараз\?
-
%d учасник%d учасники
@@ -466,18 +458,12 @@
ПомилкаСистемні сповіщенняЯкщо можливо, будь ласка, напишіть опис англійською.
-
-
-
-
%d вибрано%d вибрано%d вибрано%d вибрано
-
-
Попередній перегляд посиланьПопередній перегляд медіа перед надсиланням${app_name} збирає анонімну аналітику, щоб ми могли вдосконалювати цей додаток.
@@ -490,7 +476,6 @@
%d непрочитаних сповіщень
-
%d кімната%d кімнати
@@ -522,8 +507,6 @@
ДомівкаКімнатиЗапрошено
-
-
%2$s вилучає вас із %1$s%2$s блокує вас у %1$sПричина: %1$s
@@ -753,7 +736,6 @@
Не вдалося встановити зв’язок у режимі реального часу.
\nПопросіть адміністратора вашого домашнього сервера налаштувати сервер TURN для надійної роботи викликів.${app_name} не вдалося здійснити виклик
-
Більше немає результатівВідкликати публікуванняДодати
@@ -784,7 +766,6 @@
Схоже у вас вже є резервна копія ключа налаштування з іншого сеансу. Хочете замінити його тим, який ви створюєте\?Резервна копія вже існує на вашому homeserverКлюч відновлення збережено.
-
Зберегти як файлПоділитисяЗберегти ключ відновлення
@@ -932,7 +913,6 @@
Інтеграцію вимкненоКерування інтеграцієюДозволити інтеграції
-
Це замінить ваш поточний ключ або фразу.Створіть новий ключ безпеки або встановіть нову фразу безпеки для наявної резервної копії.Захистіться від втрати доступу до зашифрованих повідомлень і даних створенням резервної копії ключів шифрування на своєму сервері.
@@ -950,7 +930,6 @@
%d секунд%d секунд
-
Ви не отримуватимете сповіщення про вхідні повідомлення, коли програма перебуває у фоновому режимі.Немає фонової синхронізації${app_name} періодично синхронізуватиметься у фоновому режимі в певний час (налаштовується).
@@ -1273,9 +1252,7 @@
Ви утримали виклик%s утримали викликУтримати
-
Активний виклик (%1$s)
-
Змінити мережуЗмінитиPush-сповіщення вимкнено
@@ -1372,7 +1349,6 @@
Зазначте адресу сервера ідентифікаціїНеможливо під\'єднатись до сервера ідентифікаціїЗазначте адресу сервера ідентифікації
-
ПовторитиВід\'єднання від вашого сервера ідентифікації означатиме, що ви не будете виявними для інших користувачів та не зможете запрошувати інших через електронну пошту або номер телефону.Ви наразі не використовуєте жодного сервера ідентифікації. Для того, щоб виявляти інших та бути виявним для знайомих вам наявних контактів, налаштуйте такий сервер нижче.
@@ -1786,8 +1762,6 @@
%s хоче звірити ваш сеансЗапит перевіркиЗапит перевірки
-
-
ЗрозумілоРезервні копії всіх ключів створеноРезервне копіювання ключів. Це може тривати кілька хвилин…
@@ -1823,14 +1797,12 @@
Перегляд реакційТут буде показано ваші кімнати. Натисніть + унизу праворуч, щоб знайти наявні або створити власні.Схоже, ви намагаєтесь під\'єднатися до іншого домашнього сервера. Бажаєте вийти\?
-
Резервне копіювання %d ключа…Резервне копіювання %d ключів…Резервне копіювання %d ключів…Резервне копіювання %d ключів…
-
Додати наявну кімнату до просторуСтворити простірЛише я
@@ -1844,7 +1816,6 @@
Сталася помилка пошуку номера телефонаВи відхилили цей викликВаша книга контактів порожня
-
Закрити нагадування про резервне копіювання ключівСхоже, що відповідь сервера надто тривала, це може бути спричинено або поганим з’єднанням, або помилкою сервера. Повторіть спробу через деякий час.Повторіть спробу, коли погодитесь з умовами свого домашнього сервера.
@@ -2285,7 +2256,6 @@
\n
\nБажаєте зайти через вебклієнт\?Щоб знайти наявні контакти, надішліть дані контактів (е-пошти й номери телефонів) серверу ідентифікації. Ми хешуємо ваші дані перед надсиланням для приватності.
-
Ваші контакти приватні. Щоб дізнаватись про користувачів, відповідних вашим контактам, дозвольте нам надсилати дані ваших контактів серверу ідентифікації.Надіслати електронні адреси та номери телефонів %sСеанс завершено!
@@ -2337,7 +2307,6 @@
Кімната — версії %s, яку домашній сервер позначив нестабільною.Поліпшення кімнати — серйозна операція. Її зазвичай радять, коли кімната нестабільна через вади, брак функціоналу чи вразливості безпеки.
\nЗазвичай це впливає лише на деталі опрацювання кімнати сервером.
-
Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення.Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення.
\nУ вас нема дозволу додавати кімнати.
@@ -2382,14 +2351,12 @@
Якщо скасуєте це й загубите пристрій, то втратите зашифровані повідомлення й дані.
\n
\nВвімкнути захищене резервне копіювання й керувати своїми ключами можна в налаштуваннях.
-
Скасування залишить %1$s (%2$s) без звірки. У їхньому користувацькому профілі можна почати заново.Звірте цим сеансом свій новий. Це надасть йому доступ до зашифрованих повідомлень.Надіслані цьому сеансу й цим сеансом повідомлення позначатимуться застереженнями, поки цей користувач йому не довірить. Або ви можете власноруч звірити сеанс.Якщо ви увімкнете шифрування для кімнати, його неможливо буде вимкнути. Надіслані у зашифровану кімнату повідомлення будуть прочитними тільки для учасників кімнати, натомість для сервера вони будуть непрочитними. Увімкнення шифрування може унеможливити роботу ботів та мостів.Не вдалося поширити звірку цього сеансу з вашими іншими.
\nЗвірка збережеться локально, її поширить майбутня версія застосунку.
-
Можете ввімкнути це, якщо в кімнаті співпрацюватимуть лише внутрішні команди на вашому домашньому сервері. Цього більше не можна буде змінити.Цей сеанс — користувача %1$s, а ви надаєте облікові дані користувача %2$s. Це не підтримується в ${app_name}.
\nБудь ласка, спершу очистіть дані, а тоді ввійдіть в інший обліковий запис.
@@ -2542,4 +2509,6 @@
І ще %1$dЗгорнути
+ %1$s, %2$s та інші
+ %1$s і %2$s
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 060b739305..564a4ccf8a 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -39,7 +39,6 @@
聊天室邀請%1$s 和 %2$s空聊天室
-
初始化同步:
\n正在匯入帳號……初始化同步:
@@ -243,7 +242,6 @@
刪除重新命名回報內容
-
或邀請登出
@@ -266,7 +264,6 @@
僅 Matrix 聯絡人沒有結果聊天室
-
社群傳送記錄傳送當機紀錄
@@ -300,11 +297,9 @@
這看起不像是有效的電子郵件地址此電子郵件位址已經被定義。忘記密碼?
-
這個家伺服器想要確定您不是機器人必須輸入和您帳號關聯的電子郵件地址。電子郵件地址驗證失敗: 請確保您已點擊郵件中的連結
-
請輸入有效的網址異常的 JSON沒有包含有效的 JSON
@@ -321,14 +316,10 @@
通話進行中……遠端未能接聽。資訊
-
-
${app_name} 需要權限來存取麥克風,來撥打語音通話。
-
${app_name} 需要權限來存取相機及麥克風來撥打視訊通話。
\n
\n為了可以正常使用通話功能,請在下個彈跳視窗中允許存取。
-
是否繼續
@@ -337,8 +328,6 @@
拒絕列出成員跳到未讀
-
-
%d 個成員
@@ -385,12 +374,9 @@
請在另一個可以解密訊息的裝置上啟動 ${app_name},以便它將金鑰發送到此工作階段。憑證已從以前受信任的更改為不受信任的憑證。伺服器可能已續訂其憑證。請與伺服器管理員聯繫以尋找所需的指紋。僅當伺服器管理員發佈的指紋與上面的指紋匹配時才接受此憑證。
-
搜尋過濾聊天室成員沒有結果
-
-
所有訊息新增到主畫面個人檔案圖片
@@ -449,7 +435,6 @@
更新公開名稱上次上線%1$s @ %2$s
-
授權登入爲家伺服器
@@ -488,7 +473,6 @@
這些是可能以非預期的方式壞掉的實驗性功能。請小心服用。設定為主要地址取消設定為主要地址
-
主題解密錯誤公開名稱
@@ -500,7 +484,6 @@
匯出輸入通關密語確認通關密語
-
匯入聊天室端到端加密密鑰匯入聊天室金鑰從本機檔案匯入金鑰
@@ -512,7 +495,6 @@
驗證透過將以下內容與您的其他工作階段中的使用者設定來確認:如果不符合的話,您的通訊安全可能正受到威脅。
-
選擇一個聊天室目錄伺服器名稱在 %s 伺服器上的所有聊天室
@@ -520,7 +502,6 @@
%d 條未讀的已通知訊息
-
%d 個聊天室
@@ -594,15 +575,9 @@
對話在此繼續這個聊天示是其他對話的延續點選這裡以檢視更舊的訊息
-
-
-
-
%d 已選取
-
-
系統警告聯絡您的服務管理員此家伺服器已經超過其中一項資源限制,所以有一些使用者將會無法登入。
@@ -694,7 +669,6 @@
${app_name} 不會被電池最佳化影響。如果使用者不為裝置充電,並讓其靜置一段時間,且將螢幕關閉,裝置將會進入 Doze 模式。這可能會導致應用程式無法存取網路,並延遲它們的工作、同步與標準警報。忽略最佳化
-
找不到有效的 Google Play 服務 APK。通知可能無法正常運作。視訊通話進行中……金鑰備份
@@ -731,7 +705,6 @@
完成儲存復原金鑰儲存為檔案
-
請複製分享復原金鑰與……正在使用通關密語生成復原金鑰,這個過程可能需要數秒鐘。
@@ -814,7 +787,6 @@
演算法簽章要在此工作階段上使用金鑰備份,現在就使用您的通關密語或復原金鑰復原。
-
正在計算復原金鑰……正在下載金鑰……正在匯出金鑰……
@@ -823,7 +795,6 @@
使用 Enter 傳送訊息軟體鍵盤的 Enter 按鈕將會傳送訊息而非換行密碼無效
-
媒體預設壓縮選擇
@@ -858,8 +829,6 @@
忽略已驗證!知道了
-
-
驗證請求%s 想要驗證您的工作階段未知錯誤
@@ -956,7 +925,6 @@
無撤銷斷線
-
無法在此 URL 找到家伺服器,請檢查背景同步模式為電池最佳化
@@ -967,7 +935,6 @@
\n這會影響到網路與電池的使用,並會顯示指出 ${app_name} 正在監聽某事件的永久通知。無背景同步當應用程式在背景時,您將不會收到訊息通知。
-
探索管理您的探索設定。您未使用任何身份識別伺服器
@@ -1036,7 +1003,6 @@
此內容已被回報為不合適。
\n
\n如果您不想要看到從此使用者而來的更多內容,您可以忽略他們以隱藏他們的訊息。
-
整合使用整合管理員以管理機器人、橋接、小工具與貼紙包。
\n整合管理員可以代表您接收設定資料,調整小工具、傳送聊天室邀請並設定權力等級。
@@ -1251,7 +1217,6 @@
驗證 %s已驗證 %s正在等待驗證 %s……
-
此聊天室中的訊息未端到端加密。此聊天室中的訊息有端到端加密。
\n
@@ -1394,7 +1359,6 @@
列印並將其存放在安全的地方將其儲存在 USB 隨身碟或備份磁碟上將其複製到您的私人雲端儲存空間
-
加密已啟用在此聊天室中的訊息已端到端加密。取得更多資訊並在使用者的個人檔案中驗證他們。加密未啟用
@@ -1789,7 +1753,6 @@
隱藏進階顯示進階%2$d 中的 %1$d
-
給予同意撤銷我的同意您已同意傳送電子郵件與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。
@@ -1880,8 +1843,6 @@
轉移連線先諮詢
-
-
通話中 (%1$s)查詢電話號碼時發生錯誤撥號鍵盤
@@ -2075,7 +2036,6 @@
輸入您想要探索的新伺服器名稱。加入新的伺服器您的伺服器
-
抱歉,試圖加入時發生錯誤:%s空間地址檢視與管理此空間的地址。
@@ -2272,7 +2232,6 @@
投票問題或主題建立投票投票
-
向 %s 傳送電子郵件與電話號碼您的通訊錄是私人的。要從您的通訊錄中探索使用者,我們需要您的權限來傳送聯絡人資訊到您的身份識別伺服器。已登出工作階段!
@@ -2411,4 +2370,6 @@
%d 伺服器 ACL 變更
+ %1$s 與 %2$s
+ %1$s、%2$s 與其他人
\ No newline at end of file
diff --git a/vector/src/main/res/values/donottranslate.xml b/vector/src/main/res/values/donottranslate.xml
index 968d01e717..9b4e8318e7 100755
--- a/vector/src/main/res/values/donottranslate.xml
+++ b/vector/src/main/res/values/donottranslate.xml
@@ -1,15 +1,7 @@
- Debug screen
-
…
- +
- :
-
- ********
-
- #Not implemented yet in ${app_name}
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index dc52d2cf5d..3f142cc738 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
-
+%s\'s invitationYour invitation%1$s created the room
@@ -597,7 +597,7 @@
Continue
- List members
+ MembersJump to unread
@@ -2781,7 +2781,7 @@
Explore roomsAdd rooms
- Leave Space
+ LeaveAre you sure you want to leave %s?You are the only person here. If you leave, no one will be able to join in the future, including you.You won\'t be able to rejoin unless you are re-invited.
@@ -2924,9 +2924,17 @@
Share locationLocation
- Share location
+
+ Share locationMap
- Share location
+
+ Share location
+ Share my current location
+ Share my current location
+ Share live location
+ Share live location
+ Share this location
+ Share this location${app_name} could not access your location${app_name} could not access your location. Please try again later.Open with
diff --git a/vector/src/main/res/values/strings_login_v2.xml b/vector/src/main/res/values/strings_login_v2.xml
index 5d1e14d73e..c84455a665 100644
--- a/vector/src/main/res/values/strings_login_v2.xml
+++ b/vector/src/main/res/values/strings_login_v2.xml
@@ -19,7 +19,6 @@
Enter an email associated to your Matrix accountChoose a new passwordPlease choose an identifier
- Your identifier will be used to connect to your Matrix accountOnce your account is created, your identifier cannot be modified. However you will be able to change your display name.If you\'re not sure, select this optionElement Matrix Server and others
diff --git a/vector/src/main/res/xml/riotx_provider_paths.xml b/vector/src/main/res/xml/riotx_provider_paths.xml
deleted file mode 100644
index 7d3fcb2203..0000000000
--- a/vector/src/main/res/xml/riotx_provider_paths.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/vector/src/main/res/xml/vector_settings_keyword_view.xml b/vector/src/main/res/xml/vector_settings_keyword_view.xml
deleted file mode 100644
index ed6ed8e32a..0000000000
--- a/vector/src/main/res/xml/vector_settings_keyword_view.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
\ No newline at end of file
diff --git a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
index e273c0b3c9..6b97b715db 100644
--- a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
@@ -22,6 +22,7 @@ import org.amshove.kluent.shouldBeTrue
import org.junit.Test
import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
+import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
class LocationDataTest {
@@ -64,13 +65,24 @@ class LocationDataTest {
@Test
fun selfLocationTest() {
- val contentWithNullAsset = MessageLocationContent(body = "", geoUri = "", locationAsset = null)
+ val contentWithNullAsset = MessageLocationContent(body = "", geoUri = "")
contentWithNullAsset.isSelfLocation().shouldBeTrue()
- val contentWithNullAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = null))
+ val contentWithNullAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = null))
contentWithNullAssetType.isSelfLocation().shouldBeTrue()
- val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", locationAsset = LocationAsset(type = LocationAssetType.SELF))
+ val contentWithSelfAssetType = MessageLocationContent(body = "", geoUri = "", unstableLocationAsset = LocationAsset(type = LocationAssetType.SELF))
contentWithSelfAssetType.isSelfLocation().shouldBeTrue()
}
+
+ @Test
+ fun unstablePrefixTest() {
+ val geoUri = "geo :12.34,56.78;13.56"
+
+ val contentWithUnstablePrefixes = MessageLocationContent(body = "", geoUri = "", unstableLocationInfo = LocationInfo(geoUri = geoUri))
+ contentWithUnstablePrefixes.getBestLocationInfo()?.geoUri.shouldBeEqualTo(geoUri)
+
+ val contentWithStablePrefixes = MessageLocationContent(body = "", geoUri = "", locationInfo = LocationInfo(geoUri = geoUri))
+ contentWithStablePrefixes.getBestLocationInfo()?.geoUri.shouldBeEqualTo(geoUri)
+ }
}
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
index 62a38146fc..085e1a8049 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
@@ -20,8 +20,8 @@ import android.net.Uri
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.test.MvRxTestRule
-import im.vector.app.features.DefaultVectorOverrides
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeAnalyticsTracker
@@ -29,20 +29,27 @@ import im.vector.app.test.fakes.FakeAuthenticationService
import im.vector.app.test.fakes.FakeContext
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
import im.vector.app.test.fakes.FakeHomeServerHistoryService
+import im.vector.app.test.fakes.FakeRegistrationWizard
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.fakes.FakeStringProvider
import im.vector.app.test.fakes.FakeUri
import im.vector.app.test.fakes.FakeUriFilenameResolver
import im.vector.app.test.fakes.FakeVectorFeatures
+import im.vector.app.test.fakes.FakeVectorOverrides
import im.vector.app.test.test
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
private const val A_DISPLAY_NAME = "a display name"
private const val A_PICTURE_FILENAME = "a-picture.png"
private val AN_ERROR = RuntimeException("an error!")
+private val AN_UNSUPPORTED_PERSONALISATION_STATE = PersonalizationState(
+ supportsChangingDisplayName = false,
+ supportsChangingProfilePicture = false
+)
class OnboardingViewModelTest {
@@ -55,6 +62,7 @@ class OnboardingViewModelTest {
private val fakeSession = FakeSession()
private val fakeUriFilenameResolver = FakeUriFilenameResolver()
private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession)
+ private val fakeAuthenticationService = FakeAuthenticationService()
lateinit var viewModel: OnboardingViewModel
@@ -75,21 +83,84 @@ class OnboardingViewModelTest {
}
@Test
- fun `when handling display name update then updates upstream user display name`() = runBlockingTest {
+ fun `given supports changing display name when handling PersonalizeProfile then emits contents choose display name`() = runBlockingTest {
+ val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = true, supportsChangingProfilePicture = false))
+ viewModel = createViewModel(initialState)
+ val test = viewModel.test(this)
+
+ viewModel.handle(OnboardingAction.PersonalizeProfile)
+
+ test
+ .assertEvents(OnboardingViewEvents.OnChooseDisplayName)
+ .finish()
+ }
+
+ @Test
+ fun `given only supports changing profile picture when handling PersonalizeProfile then emits contents choose profile picture`() = runBlockingTest {
+ val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = false, supportsChangingProfilePicture = true))
+ viewModel = createViewModel(initialState)
+ val test = viewModel.test(this)
+
+ viewModel.handle(OnboardingAction.PersonalizeProfile)
+
+ test
+ .assertEvents(OnboardingViewEvents.OnChooseProfilePicture)
+ .finish()
+ }
+
+ @Test
+ fun `given homeserver does not support personalisation when registering account then updates state and emits account created event`() = runBlockingTest {
+ fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(HomeServerCapabilities(canChangeDisplayName = false, canChangeAvatar = false))
+ givenSuccessfullyCreatesAccount()
+ val test = viewModel.test(this)
+
+ viewModel.handle(OnboardingAction.RegisterDummy)
+
+ test
+ .assertStates(
+ initialState,
+ initialState.copy(asyncRegistration = Loading()),
+ initialState.copy(
+ asyncLoginAction = Success(Unit),
+ asyncRegistration = Loading(),
+ personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE
+ ),
+ initialState.copy(
+ asyncLoginAction = Success(Unit),
+ asyncRegistration = Uninitialized,
+ personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE
+ )
+ )
+ .assertEvents(OnboardingViewEvents.OnAccountCreated)
+ .finish()
+ }
+
+ @Test
+ fun `given changing profile picture is supported when updating display name then updates upstream user display name and moves to choose profile picture`() = runBlockingTest {
+ val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = true))
+ viewModel = createViewModel(personalisedInitialState)
val test = viewModel.test(this)
viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
test
- .assertStates(
- initialState,
- initialState.copy(asyncDisplayName = Loading()),
- initialState.copy(
- asyncDisplayName = Success(Unit),
- personalizationState = initialState.personalizationState.copy(displayName = A_DISPLAY_NAME)
- )
- )
- .assertEvents(OnboardingViewEvents.OnDisplayNameUpdated)
+ .assertStates(expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState))
+ .assertEvents(OnboardingViewEvents.OnChooseProfilePicture)
+ .finish()
+ fakeSession.fakeProfileService.verifyUpdatedName(fakeSession.myUserId, A_DISPLAY_NAME)
+ }
+
+ @Test
+ fun `given changing profile picture is not supported when updating display name then updates upstream user display name and completes personalization`() = runBlockingTest {
+ val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = false))
+ viewModel = createViewModel(personalisedInitialState)
+ val test = viewModel.test(this)
+
+ viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
+
+ test
+ .assertStates(expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState))
+ .assertEvents(OnboardingViewEvents.OnPersonalizationComplete)
.finish()
fakeSession.fakeProfileService.verifyUpdatedName(fakeSession.myUserId, A_DISPLAY_NAME)
}
@@ -184,7 +255,7 @@ class OnboardingViewModelTest {
return OnboardingViewModel(
state,
fakeContext.instance,
- FakeAuthenticationService(),
+ fakeAuthenticationService,
fakeActiveSessionHolder.instance,
FakeHomeServerConnectionConfigFactory().instance,
ReAuthHelper(),
@@ -193,7 +264,7 @@ class OnboardingViewModelTest {
FakeVectorFeatures(),
FakeAnalyticsTracker(),
fakeUriFilenameResolver.instance,
- DefaultVectorOverrides()
+ FakeVectorOverrides()
)
}
@@ -214,4 +285,23 @@ class OnboardingViewModelTest {
state.copy(asyncProfilePicture = Loading()),
state.copy(asyncProfilePicture = Fail(cause))
)
+
+ private fun givenSuccessfullyCreatesAccount() {
+ fakeActiveSessionHolder.expectSetsActiveSession(fakeSession)
+ val registrationWizard = FakeRegistrationWizard().also { it.givenSuccessfulDummy(fakeSession) }
+ fakeAuthenticationService.givenRegistrationWizard(registrationWizard)
+ fakeAuthenticationService.expectReset()
+ fakeSession.expectStartsSyncing()
+ }
+
+ private fun expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState: OnboardingViewState): List {
+ return listOf(
+ personalisedInitialState,
+ personalisedInitialState.copy(asyncDisplayName = Loading()),
+ personalisedInitialState.copy(
+ asyncDisplayName = Success(Unit),
+ personalizationState = personalisedInitialState.personalizationState.copy(displayName = A_DISPLAY_NAME)
+ )
+ )
+ }
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
index 4b2264752a..d0825a0043 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
@@ -18,7 +18,9 @@ package im.vector.app.test.fakes
import im.vector.app.core.di.ActiveSessionHolder
import io.mockk.every
+import io.mockk.justRun
import io.mockk.mockk
+import org.matrix.android.sdk.api.session.Session
class FakeActiveSessionHolder(
private val fakeSession: FakeSession = FakeSession()
@@ -26,4 +28,8 @@ class FakeActiveSessionHolder(
val instance = mockk {
every { getActiveSession() } returns fakeSession
}
+
+ fun expectSetsActiveSession(session: Session) {
+ justRun { instance.setActiveSession(session) }
+ }
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
index 9cdd7c9136..e1a605c7df 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
@@ -16,7 +16,18 @@
package im.vector.app.test.fakes
+import io.mockk.coJustRun
+import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.AuthenticationService
+import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
-class FakeAuthenticationService : AuthenticationService by mockk()
+class FakeAuthenticationService : AuthenticationService by mockk() {
+ fun givenRegistrationWizard(registrationWizard: RegistrationWizard) {
+ every { getRegistrationWizard() } returns registrationWizard
+ }
+
+ fun expectReset() {
+ coJustRun { reset() }
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerCapabilitiesService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerCapabilitiesService.kt
new file mode 100644
index 0000000000..006789f62b
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeServerCapabilitiesService.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fakes
+
+import io.mockk.every
+import io.mockk.mockk
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
+
+class FakeHomeServerCapabilitiesService : HomeServerCapabilitiesService by mockk() {
+
+ fun givenCapabilities(homeServerCapabilities: HomeServerCapabilities) {
+ every { getHomeServerCapabilities() } returns homeServerCapabilities
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt
new file mode 100644
index 0000000000..6ae394eea1
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fakes
+
+import io.mockk.coEvery
+import io.mockk.mockk
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult
+import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
+import org.matrix.android.sdk.api.session.Session
+
+class FakeRegistrationWizard : RegistrationWizard by mockk() {
+
+ fun givenSuccessfulDummy(session: Session) {
+ coEvery { dummy() } returns RegistrationResult.Success(session)
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
index 952a75cbeb..1fff67e982 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
@@ -17,10 +17,13 @@
package im.vector.app.test.fakes
import android.net.Uri
+import im.vector.app.core.extensions.configureAndStart
+import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.extensions.vectorStore
import im.vector.app.features.session.VectorSessionStore
import im.vector.app.test.testCoroutineDispatchers
import io.mockk.coEvery
+import io.mockk.coJustRun
import io.mockk.mockk
import io.mockk.mockkStatic
import org.matrix.android.sdk.api.session.Session
@@ -28,6 +31,7 @@ import org.matrix.android.sdk.api.session.Session
class FakeSession(
val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
val fakeProfileService: FakeProfileService = FakeProfileService(),
+ val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(),
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService()
) : Session by mockk(relaxed = true) {
@@ -42,6 +46,7 @@ class FakeSession(
override val coroutineDispatchers = testCoroutineDispatchers
override suspend fun setDisplayName(userId: String, newDisplayName: String) = fakeProfileService.setDisplayName(userId, newDisplayName)
override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) = fakeProfileService.updateAvatar(userId, newAvatarUri, fileName)
+ override fun getHomeServerCapabilities() = fakeHomeServerCapabilitiesService.getHomeServerCapabilities()
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
coEvery {
@@ -50,4 +55,11 @@ class FakeSession(
vectorSessionStore
}
}
+
+ fun expectStartsSyncing() {
+ coJustRun {
+ this@FakeSession.configureAndStart(any(), startSyncing = true)
+ this@FakeSession.startSyncing(any())
+ }
+ }
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorOverrides.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorOverrides.kt
new file mode 100644
index 0000000000..b4dfbe1d8c
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorOverrides.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fakes
+
+import im.vector.app.features.DefaultVectorOverrides
+import im.vector.app.features.VectorOverrides
+
+class FakeVectorOverrides : VectorOverrides by DefaultVectorOverrides()