diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml
index 2dd968951f..0e51d5155e 100644
--- a/.github/ISSUE_TEMPLATE/enhancement.yml
+++ b/.github/ISSUE_TEMPLATE/enhancement.yml
@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
- Thank you for taking the time to propose a new feature or make a suggestion.
+ Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/vector-im/element-meta/discussions/new?category=ideas).
- type: textarea
id: usecase
attributes:
diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml
index b28dbbde69..b41188a920 100644
--- a/.github/ISSUE_TEMPLATE/release.yml
+++ b/.github/ISSUE_TEMPLATE/release.yml
@@ -20,7 +20,6 @@ body:
- [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores.
- [ ] While Weblate is locked, and after the PR from Weblate has been merged, handle all the TODOs in the main `strings.xml` file
- [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect.
-
- [ ] Ensure all [the required PRs](https://github.com/vector-im/element-android/pulls?q=is%3Aopen+is%3Apr+label%3AZ-NextRelease) have been merged
### Do the release
@@ -32,7 +31,6 @@ body:
- [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()`
- [ ] Create an account on matrix.org and do some smoke tests that the sanity test does not cover like: 1-1 call, 1-1 video call, Jitsi call for instance
- [ ] Run towncrier: `towncrier build --version v1.2.3 --draft` (remove `--draft` do write the file CHANGES.md)
- - [ ] Check that the folder `changelog.d` is empty. It can happen that some remaining files stay here
- [ ] Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things
- [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs
- [ ] (optional) Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes.
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
index 5698a696b6..30b6600c94 100644
--- a/.github/workflows/danger.yml
+++ b/.github/workflows/danger.yml
@@ -11,7 +11,7 @@ jobs:
- run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger
- uses: danger/danger-js@11.1.3
+ uses: danger/danger-js@11.1.4
with:
args: "--dangerfile tools/danger/dangerfile.js"
env:
diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index bf948064ed..aac4fffa4e 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -52,7 +52,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
- uses: michaelkaye/setup-matrix-synapse@v1.0.3
+ uses: michaelkaye/setup-matrix-synapse@v1.0.4
with:
uploadLogs: true
httpPort: 8080
@@ -94,7 +94,7 @@ jobs:
needs:
- should-i-run
- ui-tests
- if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success'))
+ if: always() && (needs.should-i-run.result == 'success' ) && (needs.ui-tests.result != 'success')
# No concurrency required, runs every time on a schedule.
steps:
- uses: michaelkaye/matrix-hookshot-action@v1.0.0
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 1692e2e281..9d9e8e76e8 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -66,7 +66,7 @@ jobs:
yarn add danger-plugin-lint-report --dev
- name: Danger lint
if: always()
- uses: danger/danger-js@11.1.3
+ uses: danger/danger-js@11.1.4
with:
args: "--dangerfile tools/danger/dangerfile-lint.js"
env:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 1816fe3a78..bb16d8abe8 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -50,7 +50,7 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: 3.8
- - uses: michaelkaye/setup-matrix-synapse@v1.0.3
+ - uses: michaelkaye/setup-matrix-synapse@v1.0.4
with:
uploadLogs: true
httpPort: 8080
diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml
index 6a22bf5223..4dadc25ab4 100644
--- a/.github/workflows/triage-incoming.yml
+++ b/.github/workflows/triage-incoming.yml
@@ -10,7 +10,7 @@ jobs:
# Skip in forks
if: github.repository == 'vector-im/element-android'
steps:
- - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
+ - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d
with:
project: Issue triage
column: Incoming
diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml
index 174e3c54c0..34e36a55da 100644
--- a/.github/workflows/triage-labelled.yml
+++ b/.github/workflows/triage-labelled.yml
@@ -48,7 +48,13 @@ jobs:
# Skip in forks
if: >
github.repository == 'vector-im/element-android' &&
- contains(github.event.issue.labels.*.name, 'X-Needs-Design')
+ contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
+ (contains(github.event.issue.labels.*.name, 'S-Critical') &&
+ (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
+ (contains(github.event.issue.labels.*.name, 'S-Major') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent')) ||
+ contains(github.event.issue.labels.*.name, 'A11y'))
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml
index e762102226..07e73fe805 100644
--- a/.github/workflows/triage-priority-bugs.yml
+++ b/.github/workflows/triage-priority-bugs.yml
@@ -24,7 +24,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'A11y') &&
contains(github.event.issue.labels.*.name, 'O-Frequent'))
steps:
- - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
+ - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d
with:
project: Android App Team
column: Important Issues & Topics (P1)
@@ -50,7 +50,7 @@ jobs:
contains(github.event.issue.labels.*.name, 'A11y') &&
contains(github.event.issue.labels.*.name, 'O-Frequent')))
steps:
- - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
+ - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d
with:
project: Crypto Team
column: Ready
diff --git a/.github/workflows/triage-unlabelled.yml b/.github/workflows/triage-unlabelled.yml
index 06df286d09..98d6579958 100644
--- a/.github/workflows/triage-unlabelled.yml
+++ b/.github/workflows/triage-unlabelled.yml
@@ -28,7 +28,7 @@ jobs:
echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV
fi
- name: Move issue
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
+ uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d
if: ${{ env.ALREADY_IN_BOARD == 'true' }}
with:
project: Issue triage
diff --git a/CHANGES.md b/CHANGES.md
index d1e4834988..7e2df7716b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,64 @@
+Changes in Element v1.5.4 (2022-10-19)
+======================================
+
+Features ✨
+----------
+ - Add WYSIWYG editor, under a lab flag. ([#7288](https://github.com/vector-im/element-android/issues/7288))
+ - New Device management, can be enabled in the labs settings.
+ - Voice broadcast can be enabled in the labs settings (recording is possible only on Android 10 and up).
+
+Bugfixes 🐛
+----------
+ - Fix wrong mic button direction to cancel on RTL languages ([#5968](https://github.com/vector-im/element-android/issues/5968))
+ - Handle properly when getUser returns null - prefer using getUserOrDefault ([#7372](https://github.com/vector-im/element-android/issues/7372))
+ - [Device Management] Long session names not handled well ([#7310](https://github.com/vector-im/element-android/issues/7310))
+ - Fix editing formatted messages with plain text editor ([#7359](https://github.com/vector-im/element-android/issues/7359))
+
+In development 🚧
+----------------
+ - [Device Management] Save "matrix_client_information" events on login/registration ([#7257](https://github.com/vector-im/element-android/issues/7257))
+ - [Device management] Add lab flag for the feature ([#7336](https://github.com/vector-im/element-android/issues/7336))
+ - [Device management] Add lab flag for matrix client info account data event ([#7344](https://github.com/vector-im/element-android/issues/7344))
+ - [Device Management] Redirect to the new screen everywhere when lab flag is on ([#7374](https://github.com/vector-im/element-android/issues/7374))
+ - [Device Management] Show correct device type icons ([#7277](https://github.com/vector-im/element-android/issues/7277))
+ - [Device Management] Render extended device info ([#7294](https://github.com/vector-im/element-android/issues/7294))
+ - [Device management] Improve the parsing for OS of Desktop/Web sessions ([#7321](https://github.com/vector-im/element-android/issues/7321))
+ - [Device management] Hide the IP address and last activity date on current session ([#7324](https://github.com/vector-im/element-android/issues/7324))
+ - [Device management] Update the unknown verification status icon ([#7327](https://github.com/vector-im/element-android/issues/7327))
+ - [Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget ([#7273](https://github.com/vector-im/element-android/issues/7273))
+ - [Voice Broadcast] Aggregate state events in the timeline ([#7283](https://github.com/vector-im/element-android/issues/7283))
+ - [Voice Broadcast] Record and send non aggregated voice messages to the room ([#7363](https://github.com/vector-im/element-android/issues/7363))
+ - [Voice Broadcast] Start listening to a voice broadcast ([#7387](https://github.com/vector-im/element-android/issues/7387))
+ - [Voice Broadcast] Enable the feature (behind a lab flag and only for Android 10 and up) ([#7393](https://github.com/vector-im/element-android/issues/7393))
+ - [Voice Broadcast] Add additional data in events ([#7397](https://github.com/vector-im/element-android/issues/7397))
+ - Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API ([#7217](https://github.com/vector-im/element-android/issues/7217))
+ - Adds pusher toggle setting to device manager v2 ([#7261](https://github.com/vector-im/element-android/issues/7261))
+ - Implement QR Code Login UI ([#7338](https://github.com/vector-im/element-android/issues/7338))
+ - Implements client-side of local notification settings event ([#7300](https://github.com/vector-im/element-android/issues/7300))
+ - Links "Enable Notifications for this session" setting to enabled value in pusher ([#7281](https://github.com/vector-im/element-android/issues/7281))
+
+SDK API changes ⚠️
+------------------
+ - Stop using `original_event` field from `/relations` endpoint ([#7282](https://github.com/vector-im/element-android/issues/7282))
+ - Add `formattedText` or similar optional parameters in several methods:
+ * RelationService:
+ * editTextMessage
+ * editReply
+ * replyToMessage
+ * SendService:
+ * sendQuotedTextMessage
+ This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible. ([#7288](https://github.com/vector-im/element-android/issues/7288))
+ - Add support for `m.login.token` auth during QR code based sign in ([#7358](https://github.com/vector-im/element-android/issues/7358))
+ - Allow getting the formatted or plain text body of a message for the fun `TimelineEvent.getTextEditableContent()`. ([#7359](https://github.com/vector-im/element-android/issues/7359))
+
+Other changes
+-------------
+ - Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment. ([#7285](https://github.com/vector-im/element-android/issues/7285))
+ - Dependency to arrow has been removed. Please use `org.matrix.android.sdk.api.util.Optional` instead. ([#7335](https://github.com/vector-im/element-android/issues/7335))
+ - Update WYSIWYG editor designs. ([#7354](https://github.com/vector-im/element-android/issues/7354))
+ - Update WYSIWYG library to v0.2.1. ([#7384](https://github.com/vector-im/element-android/issues/7384))
+
+
Changes in Element v1.5.2 (2022-10-05)
======================================
diff --git a/build.gradle b/build.gradle
index 486da53e98..6ccb83e703 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,9 +28,9 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.14'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
- classpath "com.likethesalad.android:stem-plugin:2.2.2"
- classpath 'org.owasp:dependency-check-gradle:7.2.1'
- classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10"
+ classpath "com.likethesalad.android:stem-plugin:2.2.3"
+ classpath 'org.owasp:dependency-check-gradle:7.3.0'
+ classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
classpath libs.squareup.paparazziPlugin
@@ -45,7 +45,7 @@ plugins {
// Detekt
id "io.gitlab.arturbosch.detekt" version "1.21.0"
// Ksp
- id "com.google.devtools.ksp" version "1.7.20-1.0.6"
+ id "com.google.devtools.ksp" version "1.7.20-1.0.7"
// Dependency Analysis
id 'com.autonomousapps.dependency-analysis' version "1.13.1"
@@ -342,17 +342,21 @@ subprojects { project ->
if (it instanceof com.android.build.gradle.LibraryExtension) {
libraryVariants.all { variant ->
def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin")
- variant.addJavaSourceFoldersToModel(outputFolder)
- android.sourceSets.getAt(variant.name).java {
- srcDir(outputFolder)
+ if (outputFolder.exists()) {
+ variant.addJavaSourceFoldersToModel(outputFolder)
+ android.sourceSets.getAt(variant.name).java {
+ srcDir(outputFolder)
+ }
}
}
} else if (it instanceof com.android.build.gradle.AppExtension) {
applicationVariants.all { variant ->
def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin")
- variant.addJavaSourceFoldersToModel(outputFolder)
- android.sourceSets.getAt(variant.name).java {
- srcDir(outputFolder)
+ if (outputFolder.exists()) {
+ variant.addJavaSourceFoldersToModel(outputFolder)
+ android.sourceSets.getAt(variant.name).java {
+ srcDir(outputFolder)
+ }
}
}
}
diff --git a/changelog.d/7217.wip b/changelog.d/7217.wip
deleted file mode 100644
index a8cc2a3ef3..0000000000
--- a/changelog.d/7217.wip
+++ /dev/null
@@ -1 +0,0 @@
-Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API
diff --git a/changelog.d/7257.wip b/changelog.d/7257.wip
deleted file mode 100644
index c6f9aefbd8..0000000000
--- a/changelog.d/7257.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Device Management] Save "matrix_client_information" events on login/registration
diff --git a/changelog.d/7261.wip b/changelog.d/7261.wip
deleted file mode 100644
index f7063fcc1b..0000000000
--- a/changelog.d/7261.wip
+++ /dev/null
@@ -1 +0,0 @@
-Adds pusher toggle setting to device manager v2
diff --git a/changelog.d/7273.wip b/changelog.d/7273.wip
deleted file mode 100644
index c480a79a43..0000000000
--- a/changelog.d/7273.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget
diff --git a/changelog.d/7277.wip b/changelog.d/7277.wip
deleted file mode 100644
index 168d10b809..0000000000
--- a/changelog.d/7277.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Device Management] Show correct device type icons
diff --git a/changelog.d/7281.wip b/changelog.d/7281.wip
deleted file mode 100644
index c457ffbdb9..0000000000
--- a/changelog.d/7281.wip
+++ /dev/null
@@ -1 +0,0 @@
-Links "Enable Notifications for this session" setting to enabled value in pusher
diff --git a/changelog.d/7283.wip b/changelog.d/7283.wip
deleted file mode 100644
index f7cbd323f1..0000000000
--- a/changelog.d/7283.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Voice Broadcast] Aggregate state events in the timeline
diff --git a/changelog.d/7285.misc b/changelog.d/7285.misc
deleted file mode 100644
index ce94383146..0000000000
--- a/changelog.d/7285.misc
+++ /dev/null
@@ -1 +0,0 @@
-Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment.
diff --git a/changelog.d/7288.feature b/changelog.d/7288.feature
deleted file mode 100644
index be00e26179..0000000000
--- a/changelog.d/7288.feature
+++ /dev/null
@@ -1 +0,0 @@
-Add WYSIWYG editor.
diff --git a/changelog.d/7288.sdk b/changelog.d/7288.sdk
deleted file mode 100644
index 9c4a33ad22..0000000000
--- a/changelog.d/7288.sdk
+++ /dev/null
@@ -1,10 +0,0 @@
-Add `formattedText` or similar optional parameters in several methods:
-
-* RelationService:
- * editTextMessage
- * editReply
- * replyToMessage
-* SendService:
- * sendQuotedTextMessage
-
-This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible.
diff --git a/changelog.d/7294.wip b/changelog.d/7294.wip
deleted file mode 100644
index f163f6b680..0000000000
--- a/changelog.d/7294.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Device Management] Render extended device info
diff --git a/changelog.d/7300.wip b/changelog.d/7300.wip
deleted file mode 100644
index 0a1777e651..0000000000
--- a/changelog.d/7300.wip
+++ /dev/null
@@ -1 +0,0 @@
-Implements client-side of local notification settings event
diff --git a/changelog.d/7310.bugfix b/changelog.d/7310.bugfix
deleted file mode 100644
index 3570b2d3ad..0000000000
--- a/changelog.d/7310.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-[Device Management] Long session names not handled well
diff --git a/changelog.d/7321.wip b/changelog.d/7321.wip
deleted file mode 100644
index 2a539503b7..0000000000
--- a/changelog.d/7321.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Device management] Improve the parsing for OS of Desktop/Web sessions
diff --git a/changelog.d/7324.wip b/changelog.d/7324.wip
deleted file mode 100644
index 6602ef3c85..0000000000
--- a/changelog.d/7324.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Device management] Hide the IP address and last activity date on current session
diff --git a/changelog.d/7335.misc b/changelog.d/7335.misc
deleted file mode 100644
index 3b14aa1339..0000000000
--- a/changelog.d/7335.misc
+++ /dev/null
@@ -1 +0,0 @@
-Dependency to arrow has been removed. Please use `org.matrix.android.sdk.api.util.Optional` instead.
diff --git a/changelog.d/7336.feature b/changelog.d/7336.feature
deleted file mode 100644
index fb2d165b57..0000000000
--- a/changelog.d/7336.feature
+++ /dev/null
@@ -1 +0,0 @@
-[Device management] Add lab flag for the feature
diff --git a/changelog.d/7369.feature b/changelog.d/7369.feature
new file mode 100644
index 0000000000..240fac3516
--- /dev/null
+++ b/changelog.d/7369.feature
@@ -0,0 +1 @@
+Add logic for sign in with QR code
diff --git a/changelog.d/7419.wip b/changelog.d/7419.wip
new file mode 100644
index 0000000000..06f69dfa7f
--- /dev/null
+++ b/changelog.d/7419.wip
@@ -0,0 +1 @@
+[Voice Broadcast] Live listening support
diff --git a/changelog.d/7421.wip b/changelog.d/7421.wip
new file mode 100644
index 0000000000..4a399eee04
--- /dev/null
+++ b/changelog.d/7421.wip
@@ -0,0 +1 @@
+[Voice Broadcast] Improve rendering in the timeline
diff --git a/changelog.d/7428.bugfix b/changelog.d/7428.bugfix
new file mode 100644
index 0000000000..8f014af31b
--- /dev/null
+++ b/changelog.d/7428.bugfix
@@ -0,0 +1 @@
+Fix crash by disabling Flipper on Android API 22 and below - only affects debug version of the application.
diff --git a/dependencies.gradle b/dependencies.gradle
index 3b39f72965..dfb69ff698 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,23 +1,23 @@
ext.versions = [
'minSdk' : 21,
- 'compileSdk' : 32,
- 'targetSdk' : 32,
+ 'compileSdk' : 33,
+ 'targetSdk' : 33,
'sourceCompat' : JavaVersion.VERSION_11,
'targetCompat' : JavaVersion.VERSION_11,
]
-def gradle = "7.2.2"
+def gradle = "7.3.1"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.7.20"
def kotlinCoroutines = "1.6.4"
def dagger = "2.44"
-def appDistribution = "16.0.0-beta04"
+def appDistribution = "16.0.0-beta05"
def retrofit = "2.9.0"
def markwon = "4.6.2"
def moshi = "1.14.0"
def lifecycle = "2.5.1"
def flowBinding = "1.2.0"
-def flipper = "0.169.0"
+def flipper = "0.171.1"
def epoxy = "5.0.0"
def mavericks = "3.0.1"
def glide = "4.14.2"
@@ -27,7 +27,7 @@ def jjwt = "0.11.5"
// the whole commit which set version 0.16.0-SNAPSHOT
def vanniktechEmoji = "0.16.0-SNAPSHOT"
def sentry = "6.4.3"
-def fragment = "1.5.3"
+def fragment = "1.5.4"
// Testing
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
def espresso = "3.4.0"
@@ -47,12 +47,12 @@ ext.libs = [
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
],
androidx : [
- 'activity' : "androidx.activity:activity:1.5.1",
+ 'activity' : "androidx.activity:activity-ktx:1.6.0",
'appCompat' : "androidx.appcompat:appcompat:1.5.1",
'biometric' : "androidx.biometric:biometric:1.1.0",
- 'core' : "androidx.core:core-ktx:1.8.0",
+ 'core' : "androidx.core:core-ktx:1.9.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
- 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.4",
+ 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.5",
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4",
@@ -79,11 +79,11 @@ ext.libs = [
'transition' : "androidx.transition:transition:1.2.0",
],
google : [
- 'material' : "com.google.android.material:material:1.6.1",
+ 'material' : "com.google.android.material:material:1.7.0",
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber
- 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.12.56"
+ 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.12.57"
],
dagger : [
'dagger' : "com.google.dagger:dagger:$dagger",
@@ -98,7 +98,7 @@ ext.libs = [
],
element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0",
- 'wysiwyg' : "io.element.android:wysiwyg:0.1.0"
+ 'wysiwyg' : "io.element.android:wysiwyg:0.2.1"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",
@@ -166,7 +166,7 @@ ext.libs = [
'sentryAndroid' : "io.sentry:sentry-android:$sentry"
],
tests : [
- 'kluent' : "org.amshove.kluent:kluent-android:1.68",
+ 'kluent' : "org.amshove.kluent:kluent-android:1.72",
'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1",
'junit' : "junit:junit:4.13.2",
]
diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle
index 109aee1c2c..68de2c1581 100644
--- a/dependencies_groups.gradle
+++ b/dependencies_groups.gradle
@@ -147,6 +147,7 @@ ext.groups = [
'io.netty',
'io.noties.markwon',
'io.opencensus',
+ 'io.perfmark',
'io.reactivex.rxjava2',
'io.realm',
'io.sentry',
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105000.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105000.txt
new file mode 100644
index 0000000000..032fa68e48
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: Odložené přímé zprávy jsou ve výchozím nastavení povoleny.
+Úplný seznam změn: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105020.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105020.txt
new file mode 100644
index 0000000000..b2b7ca7675
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: Nové rozvržení aplikace je povoleno ve výchozím nastavení!
+Úplný seznam změn: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/de-DE/changelogs/40105000.txt b/fastlane/metadata/android/de-DE/changelogs/40105000.txt
new file mode 100644
index 0000000000..cd3ec93387
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Die wichtigste Änderung in dieser Version: Verzögerte Direktnachrichten standardmäßig aktiviert!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.2.0
diff --git a/fastlane/metadata/android/de-DE/changelogs/40105020.txt b/fastlane/metadata/android/de-DE/changelogs/40105020.txt
new file mode 100644
index 0000000000..ac08e662db
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Die wichtigste Änderung in dieser Version: Neues App-Layout standardmäßig aktiviert!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.2.0
diff --git a/fastlane/metadata/android/en-US/changelogs/40105040.txt b/fastlane/metadata/android/en-US/changelogs/40105040.txt
new file mode 100644
index 0000000000..1073dc57e0
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40105040.txt
@@ -0,0 +1,2 @@
+Main changes in this version: New features under the labs settings: Rich text composer, new device management, voice broadcast. Still under active development!
+Full changelog: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/et/changelogs/40105000.txt b/fastlane/metadata/android/et/changelogs/40105000.txt
new file mode 100644
index 0000000000..2a031d88ac
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: ajastatud otsesõnumite saatmine on nüüd vaikimisi kasutusel.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/et/changelogs/40105020.txt b/fastlane/metadata/android/et/changelogs/40105020.txt
new file mode 100644
index 0000000000..7a4a6ca253
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: rakenduse uus kujundus on nüüd vaikimisi kasutusel.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/fa/changelogs/40105000.txt b/fastlane/metadata/android/fa/changelogs/40105000.txt
new file mode 100644
index 0000000000..605efd76f1
--- /dev/null
+++ b/fastlane/metadata/android/fa/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+تغییرات عمده در این نگارش: پیامهای مستقیم تعویقی به کار افتاده به صورت پیشگزیده!
+گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/fa/changelogs/40105020.txt b/fastlane/metadata/android/fa/changelogs/40105020.txt
new file mode 100644
index 0000000000..f6e193ede5
--- /dev/null
+++ b/fastlane/metadata/android/fa/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+تغییرات عمده در این نگارش: چینش کارهٔ جدید به کار افتاده به صورت پیشگزیده!
+گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105000.txt b/fastlane/metadata/android/fr-FR/changelogs/40105000.txt
new file mode 100644
index 0000000000..707cc20fd3
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : Création des conversations privées différée activée par défaut.
+Intégralité des changements : https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105020.txt b/fastlane/metadata/android/fr-FR/changelogs/40105020.txt
new file mode 100644
index 0000000000..fec750141c
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : Nouvelle disposition de l’application activée par défaut !
+Intégralité des changements : https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/id/changelogs/40105000.txt b/fastlane/metadata/android/id/changelogs/40105000.txt
new file mode 100644
index 0000000000..08e3e78300
--- /dev/null
+++ b/fastlane/metadata/android/id/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Perubahan utama dalam versi ini: Pesan langsung yang ditangguhkan diaktifkan secara bawaan.
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/id/changelogs/40105020.txt b/fastlane/metadata/android/id/changelogs/40105020.txt
new file mode 100644
index 0000000000..736d2c48a6
--- /dev/null
+++ b/fastlane/metadata/android/id/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Perubahan utama dalam versi ini: Tata letak aplikasi baru diaktifkan secara bawaan!
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt
index 20d805c582..79baa1f9ac 100644
--- a/fastlane/metadata/android/id/full_description.txt
+++ b/fastlane/metadata/android/id/full_description.txt
@@ -1,4 +1,4 @@
-Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung-ke-ujung untuk menyediakan konferensi video, pembagian berkas, dan panggilan suara yang aman.
+Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi perpesanan ini menggunakan enkripsi ujung ke ujung untuk menyediakan konferensi video, pembagian berkas, dan panggilan suara yang aman.
Fitur Element termasuk:
- Alat komunikasi daring yang canggih
@@ -11,7 +11,7 @@ Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas ya
Element benar-benar berbeda dari aplikasi perpesanan dan aplikasi kolaborasi lainnya. Element beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi yang terdesentralisasi.
Perpesanan dengan privasi dan enkripsi
-Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data, dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung-ke-ujung, dan verifikasi perangkat menggunakan penandatanganan silang.
+Element melindungi Anda dari iklan yang tidak diinginkan, penambangan data, dan taman berdinding. Element juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu dengan enkripsi ujung ke ujung, dan verifikasi perangkat menggunakan penandatanganan silang.
Element memberikan Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan siapa saja secara aman di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan aplikasi seperti Slack.
@@ -30,7 +30,7 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda:
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain, atau bahkan menggunakan aplikasi perpesanan yang berbeda.
Sangat aman
-Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang di dalam obrolan dapat mendekripsikan pesan), dan verifikasi perangkat menggunakan penandatanganan silang.
+Enkripsi ujung ke ujung yang nyata (hanya mereka yang di dalam obrolan dapat mendekripsikan pesan), dan verifikasi perangkat menggunakan penandatanganan silang.
Komunikasi dan integrasi lengkap
Perpesanan, panggilan suara dan video, pembagian berkas, pembagian layar dan banyak integrasi bot dan widget. Buat ruangan dan komunitas, tetap terhubung, dan selesaikan hal-hal penting.
diff --git a/fastlane/metadata/android/it-IT/changelogs/40105000.txt b/fastlane/metadata/android/it-IT/changelogs/40105000.txt
new file mode 100644
index 0000000000..9132fea7b9
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: messaggi diretti differiti attivati in modo predefinito.
+Cronologia completa: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/it-IT/changelogs/40105020.txt b/fastlane/metadata/android/it-IT/changelogs/40105020.txt
new file mode 100644
index 0000000000..accee9e36d
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: nuova interfaccia dell'app attivata in modo predefinito!
+Cronologia completa: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105000.txt b/fastlane/metadata/android/pt-BR/changelogs/40105000.txt
new file mode 100644
index 0000000000..d8f3a953a3
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Principais mudanças nesta versão: DM diferida habilitada por default.
+Changelog completo: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40105020.txt b/fastlane/metadata/android/pt-BR/changelogs/40105020.txt
new file mode 100644
index 0000000000..d7cca674fa
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Principais mudanças nesta versão: Novo layout de app habilitado por default!
+Changelog completo: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sk/changelogs/40105000.txt b/fastlane/metadata/android/sk/changelogs/40105000.txt
new file mode 100644
index 0000000000..9f48366456
--- /dev/null
+++ b/fastlane/metadata/android/sk/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Hlavné zmeny v tejto verzii: Oneskorené priame správy sú zapnuté ako predvolené.
+Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sk/changelogs/40105020.txt b/fastlane/metadata/android/sk/changelogs/40105020.txt
new file mode 100644
index 0000000000..ceec087a30
--- /dev/null
+++ b/fastlane/metadata/android/sk/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Hlavné zmeny v tejto verzii: Nové rozvrhnutie aplikácie je zapnuté ako predvolené!
+Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104300.txt b/fastlane/metadata/android/sv-SE/changelogs/40104300.txt
new file mode 100644
index 0000000000..dcf7fd896c
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104300.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Aktiverar förbättrad inloggnings- och registreringsprocedur.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104310.txt b/fastlane/metadata/android/sv-SE/changelogs/40104310.txt
new file mode 100644
index 0000000000..dcf7fd896c
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104310.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Aktiverar förbättrad inloggnings- och registreringsprocedur.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104320.txt b/fastlane/metadata/android/sv-SE/changelogs/40104320.txt
new file mode 100644
index 0000000000..d8db452b51
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104320.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104340.txt b/fastlane/metadata/android/sv-SE/changelogs/40104340.txt
new file mode 100644
index 0000000000..d8db452b51
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104340.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104360.txt b/fastlane/metadata/android/sv-SE/changelogs/40104360.txt
new file mode 100644
index 0000000000..1f2c984748
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40104360.txt
@@ -0,0 +1,3 @@
+Ny applayout kan aktiveras in experimentinställningarna. Ge det ett försök!
+Fixa problem med missade aviseringar, och long inkrementell synk.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105000.txt b/fastlane/metadata/android/sv-SE/changelogs/40105000.txt
new file mode 100644
index 0000000000..f425ec24e8
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Fördröjda DM:er aktiverad som förval.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105020.txt b/fastlane/metadata/android/sv-SE/changelogs/40105020.txt
new file mode 100644
index 0000000000..66af38ec60
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: Ny applayout aktiv som förval.
+Full ändringslogg: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/uk/changelogs/40105000.txt b/fastlane/metadata/android/uk/changelogs/40105000.txt
new file mode 100644
index 0000000000..ab8964b99a
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: відкладені особисті повідомлення увімкнено типово.
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.2.0
diff --git a/fastlane/metadata/android/uk/changelogs/40105020.txt b/fastlane/metadata/android/uk/changelogs/40105020.txt
new file mode 100644
index 0000000000..90a62209dc
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: новий вигляд застосунку типово увімкнено!
+Журнал усіх змін: https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105000.txt b/fastlane/metadata/android/zh-TW/changelogs/40105000.txt
new file mode 100644
index 0000000000..7ab6a7a7bf
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40105000.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:預設啟用延遲直接訊息。
+完整的變更紀錄:https://github.com/vector-im/element-android/releases
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105020.txt b/fastlane/metadata/android/zh-TW/changelogs/40105020.txt
new file mode 100644
index 0000000000..d83fd08a53
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40105020.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:預設啟用新的應用程式佈局!
+完整的變更紀錄:https://github.com/vector-im/element-android/releases
diff --git a/library/attachment-viewer/build.gradle b/library/attachment-viewer/build.gradle
index 8bbafd3387..fc9495b113 100644
--- a/library/attachment-viewer/build.gradle
+++ b/library/attachment-viewer/build.gradle
@@ -18,6 +18,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
+ namespace "im.vector.lib.attachmentviewer"
compileSdk versions.compileSdk
diff --git a/library/attachment-viewer/src/main/AndroidManifest.xml b/library/attachment-viewer/src/main/AndroidManifest.xml
index 8970b47178..8072ee00db 100644
--- a/library/attachment-viewer/src/main/AndroidManifest.xml
+++ b/library/attachment-viewer/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt
index 98398760d1..21d96afb77 100644
--- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt
+++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt
@@ -316,10 +316,6 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
}
return false
}
-
- override fun onDoubleTap(e: MotionEvent?): Boolean {
- return super.onDoubleTap(e)
- }
})
override fun onEvent(event: AttachmentEvents) {
diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt
index 7a83ee28d4..2f840cebee 100644
--- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt
+++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/SwipeToDismissHandler.kt
@@ -96,31 +96,27 @@ class SwipeToDismissHandler(
.setDuration(ANIMATION_DURATION)
.setInterpolator(AccelerateInterpolator())
.setUpdateListener { onSwipeViewMove(swipeView.translationY, translationLimit) }
- .setAnimatorListener(onAnimationEnd = {
+ .setAnimatorEndListener {
if (translationTo != 0f) {
onDismiss()
}
// remove the update listener, otherwise it will be saved on the next animation execution:
swipeView.animate().setUpdateListener(null)
- })
+ }
.start()
}
}
-internal fun ViewPropertyAnimator.setAnimatorListener(
- onAnimationEnd: ((Animator?) -> Unit)? = null,
- onAnimationStart: ((Animator?) -> Unit)? = null
-) = this.setListener(
+private fun ViewPropertyAnimator.setAnimatorEndListener(
+ onAnimationEnd: () -> Unit,
+) = setListener(
object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- onAnimationEnd?.invoke(animation)
+ override fun onAnimationEnd(animation: Animator) {
+ onAnimationEnd()
}
+ }
+)
- override fun onAnimationStart(animation: Animator?) {
- onAnimationStart?.invoke(animation)
- }
- })
-
-internal val View?.hitRect: Rect
- get() = Rect().also { this?.getHitRect(it) }
+private val View.hitRect: Rect
+ get() = Rect().also { getHitRect(it) }
diff --git a/library/core-utils/build.gradle b/library/core-utils/build.gradle
index 0f7789a2a8..b985127ec6 100644
--- a/library/core-utils/build.gradle
+++ b/library/core-utils/build.gradle
@@ -20,6 +20,8 @@ plugins {
}
android {
+ namespace "im.vector.lib.core.utils"
+
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
diff --git a/library/core-utils/src/main/AndroidManifest.xml b/library/core-utils/src/main/AndroidManifest.xml
index 20a9414519..8072ee00db 100644
--- a/library/core-utils/src/main/AndroidManifest.xml
+++ b/library/core-utils/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/compat/Compat.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/compat/Compat.kt
new file mode 100644
index 0000000000..8b0ad7767b
--- /dev/null
+++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/compat/Compat.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.lib.core.utils.compat
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcelable
+import java.io.Serializable
+
+inline fun Intent.getParcelableExtraCompat(key: String): T? = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableExtra(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T?
+}
+
+inline fun Intent.getParcelableArrayListExtraCompat(key: String): ArrayList? = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableArrayListExtra(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getParcelableArrayListExtra(key)
+}
+
+inline fun Bundle.getParcelableCompat(key: String): T? = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelable(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getParcelable(key) as? T?
+}
+
+inline fun Bundle.getSerializableCompat(key: String): T? = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getSerializable(key) as? T?
+}
+
+inline fun Intent.getSerializableExtraCompat(key: String): T? = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java)
+ else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T?
+}
+
+fun PackageManager.queryIntentActivitiesCompat(data: Intent, flags: Int): List {
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> queryIntentActivities(
+ data,
+ PackageManager.ResolveInfoFlags.of(flags.toLong())
+ )
+ else -> @Suppress("DEPRECATION") queryIntentActivities(data, flags)
+ }
+}
+
+fun PackageManager.resolveActivityCompat(data: Intent, flags: Int): ResolveInfo? {
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> resolveActivity(
+ data,
+ PackageManager.ResolveInfoFlags.of(flags.toLong())
+ )
+ else -> @Suppress("DEPRECATION") resolveActivity(data, flags)
+ }
+}
diff --git a/library/external/dialpad/build.gradle b/library/external/dialpad/build.gradle
index fade8ddf30..e6f249f535 100644
--- a/library/external/dialpad/build.gradle
+++ b/library/external/dialpad/build.gradle
@@ -2,6 +2,8 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
+ namespace "com.android.dialer.dialpadview"
+
compileSdk versions.compileSdk
defaultConfig {
diff --git a/library/external/dialpad/src/main/AndroidManifest.xml b/library/external/dialpad/src/main/AndroidManifest.xml
index 1d412d0ae5..8072ee00db 100644
--- a/library/external/dialpad/src/main/AndroidManifest.xml
+++ b/library/external/dialpad/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java b/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java
index 5c6ce46257..09079235af 100644
--- a/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java
+++ b/library/external/dialpad/src/main/java/com/android/dialer/dialpadview/DialpadView.java
@@ -103,6 +103,7 @@ public class DialpadView extends LinearLayout {
@Override
protected void onFinishInflate() {
+ super.onFinishInflate();
setupKeypad();
mDigits = (EditText) findViewById(R.id.digits);
mDelete = (ImageButton) findViewById(R.id.deleteButton);
@@ -201,14 +202,6 @@ public class DialpadView extends LinearLayout {
zero.setLongHoverContentDescription(resources.getText(R.string.description_image_button_plus));
}
- private Drawable getDrawableCompat(Context context, int id) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- return context.getDrawable(id);
- } else {
- return context.getResources().getDrawable(id);
- }
- }
-
public void setShowVoicemailButton(boolean show) {
View view = findViewById(R.id.dialpad_key_voicemail);
if (view != null) {
diff --git a/library/external/jsonviewer/build.gradle b/library/external/jsonviewer/build.gradle
index 50bb635e8e..a5d297b860 100644
--- a/library/external/jsonviewer/build.gradle
+++ b/library/external/jsonviewer/build.gradle
@@ -18,6 +18,8 @@ buildscript {
}
android {
+ namespace "org.billcarsonfr.jsonviewer"
+
compileSdk versions.compileSdk
defaultConfig {
diff --git a/library/external/jsonviewer/src/main/AndroidManifest.xml b/library/external/jsonviewer/src/main/AndroidManifest.xml
index 73322c2fdb..cc947c5679 100644
--- a/library/external/jsonviewer/src/main/AndroidManifest.xml
+++ b/library/external/jsonviewer/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
+
diff --git a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt
index 0ebf539d4d..696655a19f 100644
--- a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt
+++ b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt
@@ -23,6 +23,7 @@ import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.airbnb.mvrx.Mavericks
+import im.vector.lib.core.utils.compat.getParcelableCompat
class JSonViewerDialog : DialogFragment() {
@@ -36,16 +37,17 @@ class JSonViewerDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val args: JSonViewerFragmentArgs = arguments?.getParcelable(Mavericks.KEY_ARG) ?: return
+ val args: JSonViewerFragmentArgs = arguments?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
if (savedInstanceState == null) {
childFragmentManager.beginTransaction()
.replace(
- R.id.fragmentContainer, JSonViewerFragment.newInstance(
- args.jsonString,
- args.defaultOpenDepth,
- true,
- args.styleProvider
- )
+ R.id.fragmentContainer,
+ JSonViewerFragment.newInstance(
+ args.jsonString,
+ args.defaultOpenDepth,
+ true,
+ args.styleProvider
+ )
)
.commitNow()
}
diff --git a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt
index 719ce29045..f7c7f4d7bc 100644
--- a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt
+++ b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt
@@ -28,6 +28,7 @@ import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksView
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize
@Parcelize
@@ -53,7 +54,7 @@ class JSonViewerFragment : Fragment(), MavericksView {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- val args: JSonViewerFragmentArgs? = arguments?.getParcelable(Mavericks.KEY_ARG)
+ val args: JSonViewerFragmentArgs? = arguments?.getParcelableCompat(Mavericks.KEY_ARG)
val inflate =
if (args?.wrap == true) {
inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false)
diff --git a/library/multipicker/build.gradle b/library/multipicker/build.gradle
index 2de99d5c20..c77a86a764 100644
--- a/library/multipicker/build.gradle
+++ b/library/multipicker/build.gradle
@@ -19,6 +19,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
+ namespace "im.vector.lib.multipicker"
+
compileSdk versions.compileSdk
defaultConfig {
@@ -35,9 +37,19 @@ android {
}
}
+ compileOptions {
+ sourceCompatibility versions.sourceCompat
+ targetCompatibility versions.targetCompat
+ }
+
+ kotlinOptions {
+ jvmTarget = "11"
+ }
}
dependencies {
+ implementation project(":library:core-utils")
+
api libs.androidx.activity
implementation libs.androidx.exifinterface
implementation libs.androidx.core
diff --git a/library/multipicker/src/main/AndroidManifest.xml b/library/multipicker/src/main/AndroidManifest.xml
index c02a22d1d9..2b4ef0e884 100644
--- a/library/multipicker/src/main/AndroidManifest.xml
+++ b/library/multipicker/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-
+() {
* Call this function from onActivityResult(int, int, Intent).
* Returns selected contact or empty list if user did not select any contacts.
*/
+ @SuppressLint("Recycle")
override fun getSelectedFiles(context: Context, data: Intent?): List {
val contactList = mutableListOf()
diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt
index 8960f3228b..1cfcba505f 100644
--- a/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt
+++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/Picker.kt
@@ -22,6 +22,9 @@ import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import androidx.activity.result.ActivityResultLauncher
+import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
+import im.vector.lib.core.utils.compat.queryIntentActivitiesCompat
/**
* Abstract class to provide all types of Pickers.
@@ -45,13 +48,13 @@ abstract class Picker {
val uriList = mutableListOf()
if (data.action == Intent.ACTION_SEND) {
- (data.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { uriList.add(it) }
+ data.getParcelableExtraCompat(Intent.EXTRA_STREAM)?.let { uriList.add(it) }
} else if (data.action == Intent.ACTION_SEND_MULTIPLE) {
- val extraUriList: List? = data.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
+ val extraUriList: List? = data.getParcelableArrayListExtraCompat(Intent.EXTRA_STREAM)
extraUriList?.let { uriList.addAll(it) }
}
- val resInfoList: List = context.packageManager.queryIntentActivities(data, PackageManager.MATCH_DEFAULT_ONLY)
+ val resInfoList: List = context.packageManager.queryIntentActivitiesCompat(data, PackageManager.MATCH_DEFAULT_ONLY)
uriList.forEach {
for (resolveInfo in resInfoList) {
val packageName: String = resolveInfo.activityInfo.packageName
@@ -91,6 +94,7 @@ abstract class Picker {
} else if (dataUri != null) {
selectedUriList.add(dataUri)
} else {
+ @Suppress("DEPRECATION")
data?.extras?.get(Intent.EXTRA_STREAM)?.let {
(it as? List<*>)?.filterIsInstance()?.let { uriList ->
selectedUriList.addAll(uriList)
diff --git a/library/ui-strings/build.gradle b/library/ui-strings/build.gradle
index 6a31f24c9b..b6e6de5c22 100644
--- a/library/ui-strings/build.gradle
+++ b/library/ui-strings/build.gradle
@@ -5,6 +5,8 @@ plugins {
}
android {
+ namespace "im.vector.lib.strings"
+
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
diff --git a/library/ui-strings/src/main/AndroidManifest.xml b/library/ui-strings/src/main/AndroidManifest.xml
index deff03ee0a..8072ee00db 100644
--- a/library/ui-strings/src/main/AndroidManifest.xml
+++ b/library/ui-strings/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/library/ui-strings/src/main/res/values-az/strings.xml b/library/ui-strings/src/main/res/values-az/strings.xml
index 84f2772950..53100db285 100644
--- a/library/ui-strings/src/main/res/values-az/strings.xml
+++ b/library/ui-strings/src/main/res/values-az/strings.xml
@@ -1,6 +1,5 @@
-
%s-nin dəvəti%1$s dəvət etdi %2$s%1$s sizi dəvət etdi
@@ -27,37 +26,22 @@
bütün otaq üzvləri.hər kəs.%s bu otağı təkmilləşdirdi.
-
-
(avatar da dəyişdirilib)%1$s otaq adını sildi%1$s otaq mövzusunu sildi%1$s otağa qoşulmaq üçün %2$s dəvətnamə göndərdi%1$s otağa qoşulmaq üçün %2$s dəvətini ləğv etdi%1$s %2$s üçün dəvəti qəbul etdi
-
** Şifrəni aça bilmir: %s **Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib.
-
Mesaj göndərmək olmur
-
-
Matris xətası
-
-
Şifrəli mesaj
-
Elektron poçt ünvanıTelefon nömrəsi
-
Otağa dəvət
-
%1$s və %2$s
-
-
-
Boş otaq
-
İlkin sinxronizasiya:
\nHesab idxal olunur…İlkin sinxronizasiya:
@@ -72,9 +56,7 @@
\nTərk olunmuş otaqların idxalıİlkin sinxronizasiya:
\nHesab məlumatlarının idxalı
-
Mesaj göndərilir…
-
%1$s-nin dəvəti. Səbəb: %2$s%1$s dəvət olunmuş %2$s. Səbəb: %3$s%1$s sizi dəvət etdi. Səbəb: %2$s
@@ -86,4 +68,5 @@
%1$s blokladı %2$s. Səbəb: %3$s%1$s %2$s üçün dəvəti qəbul etdi. Səbəb: %3$s%1$s %2$s dəvətini geri götürdü. Səbəb: %3$s
-
+ Otağ yaratdınız
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml
index 25c490807e..fa363cee8c 100644
--- a/library/ui-strings/src/main/res/values-ca/strings.xml
+++ b/library/ui-strings/src/main/res/values-ca/strings.xml
@@ -891,7 +891,7 @@
Motiu de l\'expulsióExpulsa usuariContinua amb SSO
- Per a la teva pròpia privadesa, ${app_name} només admet l\'enviament del \"hash\" de correus electrònics i números de telèfon.
+ Per a la teva pròpia privadesa, ${app_name} només admet l\'enviament del \'hash\' d\'adreces de correu electrònic i números de telèfon.Només admès en sales xifradesEl xifrat que utilitza aquesta sala no és compatibleL\'aplicació no ha pogut crear un compte en aquest servidor.
@@ -978,7 +978,7 @@
Error SSL.La trucada d\'${app_name} ha fallatRestableix la contrasenya a %1$s
- Aquest correu electrònic no està associat amb cap compte.
+ Aquesta adreça de correu electrònic no està associada a cap compte.Ho sentim, aquest servidor no accepta comptes nous.S\'ha produït un error en carregar la pàgina: %1$s (%2$d)Introdueix l\'adreça del servidor que vulguis utilitzar
@@ -1002,10 +1002,10 @@
Dóna consentimentRevoca el meu consentimentEnvia correus i números de telèfon
- T\'hem enviat un correu de confirmació a %s, primer revisa el correu i fes clic a l\'enllaç de confirmació
- T\'hem enviat un correu de confirmació a %s, revisa\'l i fes clic a l\'enllaç de confirmació
+ Hem enviat un correu electrònic a %s, primer revisa el correu i fes clic a l\'enllaç de confirmació
+ Hem enviat un correu electrònic a %s, revisa\'l i fes clic a l\'enllaç de confirmacióLes opcions de descobriment apareixeran quan hagis afegit un número de telèfon.
- Les opcions de descobriment apareixeran quan hagis afegit un correu.
+ Les opcions de descobriment apareixeran quan hagis afegit una adreça de correu electrònic.Desconnecta el servidor d\'identitatConfigura el servidor d\'identitatCanvia el servidor d\'identitat
@@ -1100,7 +1100,7 @@
Revisa i gestiona les adreces d\'aquesta sala i la seva visibilitat al directori de sales.Adreces de la salaAccés a la sala
- Gestiona els correus i els números de telèfon vinculats amb el teu compte de Matrix
+ Gestiona les adreces de correu electrònic i els números de telèfon vinculats amb el teu compte de MatrixCorreus i números de telèfonActiva \'Permet integracions\' a la configuració per poder fer això.Les integracions estan desactivades
@@ -1154,7 +1154,7 @@
Assegura\'t de que has clicat a l\'enllaç del correu que t\'hem enviat.Elimina %s\?Números de telèfon
- No s\'ha afegit cap correu electrònic al teu compte
+ No s\'ha afegit cap adreça de correu electrònic al teu compteNo s\'ha afegit cap número de telèfon al teu compteFiltra usuaris vetatsTema
@@ -1200,7 +1200,7 @@
Url:session_name:push_key:
- app_id:
+ ID d\'aplicació:Revisa la configuració per activar les notificacionsEstàs veient la notificació! Clica\'m!Vetat per %1$s
@@ -1249,7 +1249,7 @@
ConfiguracióIgnora usuariGira i retalla
- Estableix un correu per a la recuperació del compte. Posterior i opcionalment, pots permetre que els usuaris que coneixes et puguin trobar a partir del correu electrònic.
+ Estableix una adreça de correu electrònic per recuperar el teu compte. Posterior i opcionalment, pots permetre que els usuaris que coneixes et puguin trobar a partir d\'aquesta adreça.SegüentNúmero de telèfon (opcional)Número de telèfon
@@ -1262,7 +1262,7 @@
No s\'ha pogut trobar un servidor local vàlid. Comprova l\'identificadorAccepta els termes de servei del servidor d\'identitat (%s) per poder ser trobat mitjançant l\'adreça de correu electrònic o el número de telèfon.Introdueix l\'URL d\'un servidor d\'identitat
- Has donat el teu consentiment per poder enviar correus electrònics i números de telèfon a aquest servidor d\'identitat per trobar altres usuaris dels teus contactes.
+ Has donat el teu consentiment per poder enviar adreces de correu electrònic i números de telèfon a aquest servidor d\'identitat per trobar altres usuaris dels teus contactes.Números de telèfon perquè et trobinSi et desconnectes del servidor d\'identitat no podràs ser trobat per altres usuaris ni convidar-los mitjançant el correu electrònic o el número de telèfon.Correus electrònics perquè et puguin trobar
@@ -1611,7 +1611,7 @@
Fes clic a l\'enllaç per confirmar la nova contrasenya. Quan hagis anat a l\'enllaç que conté, fes clic a sota.S\'ha enviat un correu de verificació a %1$s.Revisa la teva safata d\'entrada
- Aquest correu no està vinculat amb cap compte
+ Aquesta adreça de correu electrònic no està vinculada a cap compteContinuaAtenció!Nova contrasenya
@@ -2120,8 +2120,8 @@
Assegura\'t que les persones adequades tinguin accés a %s. Pots convidar-ne més després.Assegura\'t que les persones adequades tinguin accés a %s.Envia multimèdia a mida real
- Per descobrir contactes existents, s\'ha d\'enviar informació de contacte (correus i números de telèfon) al servidor d\'identitat utilitzat. Es fa un \'hash\' de les dades abans d\'enviar-les per privacitat.
- Envia correus i números de telèfon a %s
+ Per descobrir contactes existents, s\'ha d\'enviar informació de contacte (adreces de correu electrònic i números de telèfon) al servidor d\'identitat utilitzat. Es fa un \'hash\' de les dades abans d\'enviar-les per privacitat.
+ Envia adreces de correu electrònic i números de telèfon a %sEls teus contactes són privats. Per descobrir els usuaris dels teus contactes, necessitem permís per enviar informació dels contactes al servidor d\'identitat que estiguis utilitzant.Estàs utilitzant una versió beta dels espais. Els teus comentaris ajudaran a les properes versions. S\'anotaran la teva plataforma i nom d\'usuari per poder utilitzar els teus comentaris tant bé com puguem.Qualsevol a un espai amb aquesta sala podrà trobar-la i unir-s\'hi. Només els administradors d\'aquesta sala poden afegir-la a un espai.
@@ -2150,7 +2150,7 @@
Comparteix ubicacióReinicia l\'aplicació per aplicar els canvis.Activa format matemàtic amb LaTeX
- Enllaça aquest correu amb el teu compte
+ Enllaça aquesta adreça de correu electrònic al teu compte(%1$s)%1$s (%2$s)No s\'ha pogut reproduir %1$s
@@ -2438,7 +2438,7 @@
AltresMencions i paraules clauNotificacions per defecte
- Per rebre notificacions per correu, has d\'associar un correu electrònic amb el teu compte de Matrix
+ Per rebre notificacions per correu, has d\'associar una adreça de correu electrònic al teu compte de MatrixS\'ha tancat la sessió!La sala ha estat abandonada!Cap
@@ -2616,7 +2616,6 @@
Ordena perMostra recentsMostra filtres
- Mostra totes les sessions (V2, WIP)Crea salaInicia xatVerificada · Última activitat %1$s
@@ -2710,4 +2709,40 @@
Sessió actualElement simplificat amb pestanyes opcionalsActiva la nova visualització
+ Les sessions inactives son sessions que no has utilitzat durant un temps, però continuen rebent claus de xifrat.
+\n
+\nL\'eliminació de sessions inactives millora la seguretat i el rendiment, i et pot ajudar a identificar sessions noves sospitoses.
+ Tanca aquesta sessió
+ Obre la pantalla d\'eines per a desenvolupadors
+ Els usuaris dels xats directes i sales al les quals t\'hagis unit poden veure la llista completa de les teves sessions.
+\n
+\nAixò els pot proporcionar més confiança de que realment parlen amb tu però, poden veure el nom de sessió que introdueixis.
+ Les sessions verificades son sessions en què has iniciat sessió amb les teves credencials i s\'han verificat utilitzant una frase de seguretat o mitjançant la verificació creuada.
+\n
+\nAixò vol dir que contenen claus de xifrat dels teus missatges anteriors i confirmen als altres usuaris amb qui parles, que aquestes sessions son realment teves.
+ Les sessions no verificades son sessions en què has iniciat sessió amb les teves credencials però s\'hi ha fet una verificació creuada.
+\n
+\nAssegura\'t que reconeixes aquestes sessions especialment, ja que podrien representar un ús no autoritzat del teu compte.
+ Canvi de nom de sessions
+ Sessions verificades
+ Sessions no verificades
+ Sessions inactives
+ Tingues en compte que els noms de sessió son visibles per les persones amb qui parlis.
+ Els noms de sessió personalitzats et permeten identificar els teus dispositius més fàcilment.
+ Nom de la sessió
+ Canvia el nom de la sessió
+ No verificada · Sessió actual
+ L\'autenticitat d\'aquest missatge xifrat no ha pogut ser garantida en aquest dispositiu.
+ Afegeix (╯°□°)╯︵ ┻━┻ abans d\'un missatge de text
+ ⚠ Hi ha dispositius no verificats en aquesta sala, no podran desxifrat els missatges que enviïs.
+ No enviïs mai missatges xifrats a sessions no verificades d\'aquesta sala.
+ D\'acord
+ Transmissió de veu
+ Inicia una transmissió de veu
+ Sol·licita que no es desi cap dada personalitzada del teclat en funció del que escrius a les converses (per exemple l\'historial d\'escriptura o el diccionari). Tingues en compte que alguns teclats poden no respectar aquesta configuració.
+ Teclat incògnit
+ 🔒 Has activat el xifrat a només en sessions verificades a totes les sales, a Configuració > Seguretat.
+ Estat de verificació desconegut
+ Escaneja codi QR
+ ID de sessió:
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml
index 1983036271..3410858988 100644
--- a/library/ui-strings/src/main/res/values-cs/strings.xml
+++ b/library/ui-strings/src/main/res/values-cs/strings.xml
@@ -648,8 +648,8 @@
Import klíčů místnostíImport klíčů z místního souboruImport
- Šifruj pouze do ověřených relací
- Nikdy neposílejte šifrované zprávy do neověřených relací z této relace.
+ Šifrovat pouze do ověřených relací
+ Nikdy neposílat šifrované zprávy do neověřených relací z této relace.Není ověřenoPotvrzenoneznámá ip
@@ -962,10 +962,10 @@
Push pravidlaŽádná push pravidla nejsou definovánaŽádné push brány nejsou registrovány
- app_id:
- push_key:
- app_display_name:
- session_name:
+ ID aplikace:
+ Klíč push:
+ Zobrazovaný název aplikace:
+ Název relace:Url:Formát:Hlas a video
@@ -1011,7 +1011,7 @@
Volby pro nalezení se ukážou, jakmile doplníte telefonní číslo.Odpojení od serveru identit bude znamenat, že Vás jiní uživatelé nebudou moci najít a Vy nebudete moci pozvat druhé pomocí emailu nebo telefonního čísla.Telefonní čísla pro nalezení
- Poslali jsme Vám potvrzovací email na %s, podívejte se do emailu a klikněte na protvrzovací odkaz
+ Poslali jsme vám email na %s, podívejte se do něj a klikněte na protvrzovací odkazZadejte URL serveru pro identityNemohl jsem se spojit se serverem pro identityProsím, zadejte url serveru pro identity
@@ -1079,12 +1079,12 @@
Neignorujete žádné uživateleVíce možností po dlouhém stisku na místnost%1$s učinil místnost veřejnou pro kohokoli znalého odkazu.
- %1$s nastavil místnost jen pro pozvané.
+ %1$s nastavil místnost jako pouze pro pozvané.Nepřečtené zprávyJe to Vaše konverzace. Vlastněte ji.Chatujte s lidmi přímo nebo ve skupináchUdržujte konverzace soukromé pomocí šifrování
- Rozšiřte a upravte si svůj zážitek
+ Rozšiřte a upravte si možnostiMůžeme začítVybrat serverJako email, účty mají jeden domov, ačkoli můžete mluvit s kýmkoli
@@ -1136,7 +1136,7 @@
\n
\nZastavit proces změny hesla\?Nastavit emailovou adresu
- Nastavte emailovou adresu pro obnově svého účtu. Později můžete volitelně dovolit lidem, které znáte, aby Vás podle emailu nalezli.
+ Nastavte e-mailovou adresu pro obnovení účtu. Později můžete volitelně povolit svým známým, aby vás podle této adresy nalezli.EmailEmail (volitelné)Dále
@@ -1218,7 +1218,7 @@
${app_name} může spadnout častěji, když se objeví neočekávané chybyPředsune ¯\\_(ツ)_/¯ do textové zprávyZapnout šifrování
- Jakmile zapnuto, šifrování nelze vypnout.
+ Po zapnutí šifrování ho není možné vypnout.Vaše emailová doména není autorizována registrovat se na tomto serveruNedůvěryhodné přihlášeníShodují se
@@ -1297,15 +1297,15 @@
Vaše nová relace je nyní ověřena. Má přístup k Vašim zašifrovaným zprávám a ostatní uživatelé ji uvidi jako důvěryhodnou.Křížové podepisováníKřížové podpisování je zapnuto.
-\nPrivátní klíče v zařízení.
+\nSoukromé klíče v zařízení.
Křížové podpisování je zapnuto
\nKlíče jsou důvěryhodné.
-\nPrivátní klíče nejsou známy
+\nSoukromé klíče nejsou známy
Křížové podpisování je zapnuto.
\nKlíče nejsou důvěryhodnéKřížové podpisování není zapnutoAktivní relace
- Ukázat všechny relace
+ Zobrazit všechny relaceSpráva relacíOdhlásit se z této relaceŽádná kryptografická informace není k dispozici
@@ -1440,7 +1440,7 @@
Zpráva smazánaUkázat odstraněné zprávyZobrazit zástupný symbol za odstraněné zprávy
- Poslali jsme Vám potvrzovací email na %s, podívejte se nejdříve do emailu a klikněte na protvrzovací odkaz
+ Poslali jsme vám email na %s, podívejte se nejdříve do emailu a klikněte na protvrzovací odkazOvěřovací kód není správný.MÉDIAV této místnosti nejsou žádná média
@@ -2019,20 +2019,20 @@
Dejte prostoru jméno a pokračujte.Doplňte nějaké podrobnosti, aby zaujal. Můžete je kdykoli změnit.Vytvořit prostor
- Pouze na pozvání, nejlepší pro Vás nebo týmy
- Privátní
+ Pouze na pozvání, nejlepší pro vás nebo týmy
+ SoukromýOtevřený pro všechny, nejlepší pro komunityVeřejný
- Privátní prostor pro Vás a Vaše kolegy
+ Soukromý prostor pro vás a vaše kolegyJá a kolegové
- Privátní prostor k organizaci Vašich místností
+ Soukromý prostor pro uspořádání vašich místnostíJen jáUjistěte se, že ti správní lidé mají přístup do %s.S kým pracujete\?Ke vstupu do existujícího prostoru potřebujete pozvání.Můžete změnit pozdějiJaký typ prostoru chcete vytvořit\?
- Váš privátní prostor
+ Váš soukromý prostorVáš veřejný prostorPřidat prostorOpustit místnost s daným id (nebo nynější místnost pokud prázdné)
@@ -2044,7 +2044,7 @@
Kdokoliv může místnost najít a připojit se do níVeřejnýPouze pozvaní mohou místnost najít a vstoupit do ní
- Privátní
+ SoukromýNeznámé nastavení přístupu (%s)Každý může na místnost zaklepat, členové pak mohou přijmout či odmítnoutDovolit hostům vstoupit
@@ -2165,7 +2165,7 @@
Prostory, které mají přístupUmožněte členům prostoru ho najít a zpřístupnit.Členové prostoru %s mohou vyhledávat, prohlížet a připojovat se.
- Soukromý (pouze pro pozvané)
+ Soukromý (jen pro pozvané)Chcete-li odesílat hlasové zprávy, povolte oprávnění mikrofonu.Aktualizace místnostiZprávy od bota
@@ -2650,7 +2650,6 @@
\nTento domovský server nemusí být nakonfigurován pro zobrazování map.
Otevřít nastaveníVšechny konverzace
- Zobrazit všechny relace (V2, WIP)V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.Ostatní relaceRelace
@@ -2764,4 +2763,100 @@
Povolit odložené přímé zprávyZjednodušený Element s volitelnými kartamiPovolit nový vzhled
+ Ostatní uživatelé v přímých zprávách a místnostech, ke kterým se připojíte, si mohou prohlédnout úplný seznam vašich relací.
+\n
+\nTo jim poskytuje jistotu, že s vámi skutečně mluví, ale také to znamená, že mohou vidět název relace, který zde zadáte.
+ Přejmenování relací
+ Ověřené relace se přihlásily pomocí vašich přihlašovacích údajů a poté byly ověřeny buď pomocí vaší zabezpečené přístupové fráze, nebo křížovým ověřením.
+\n
+\nTo znamená, že uchovávají šifrovací klíče pro vaše předchozí zprávy a potvrzují ostatním uživatelům, se kterými komunikujete, že tyto relace jste skutečně vy.
+ Ověřené relace
+ Neověřené relace jsou relace, které se přihlásily pomocí vašich přihlašovacích údajů, ale nebyly křížově ověřeny.
+\n
+\nMěli byste si být obzvláště jisti, že tyto relace rozpoznáte, protože by mohly představovat neoprávněné použití vašeho účtu.
+ Neověřené relace
+ Neaktivní relace jsou relace, které jste po určitou dobu nepoužili, ale nadále dostávají šifrovací klíče.
+\n
+\nOdstranění neaktivních relací zvyšuje zabezpečení a výkon a usnadňuje identifikaci podezřelé nové relace.
+ Neaktivní relace
+ Uvědomte si, že jména relací jsou viditelná i pro osoby, se kterými komunikujete.
+ Vlastní názvy relací vám pomohou snadněji rozpoznat vaše zařízení.
+ Název relace
+ Přejmenovat relaci
+ Odhlásit se z této relace
+ Neověřeno · Vaše aktuální relace
+ Zahájit hlasové vysílání
+ Pravost této šifrované zprávy nelze v tomto zařízení zaručit.
+ Požadujte, aby klávesnice neaktualizovala žádné personalizované údaje, jako je historie psaní a slovník, na základě toho, co jste napsali v konverzacích. Upozorňujeme, že některé klávesnice nemusí toto nastavení respektovat.
+ Inkognito klávesnice
+ Přidá znaky (╯°□°)╯︵ ┻━┻ před zprávy ve formátu obyčejného textu
+ Hlasové vysílání
+ Otevřít nástroje pro vývojáře
+ 🔒 V nastavení zabezpečení jste povolili šifrování pouze do ověřených relací pro všechny místnosti.
+ ⚠ V této místnosti jsou neověřená zařízení, která nebudou schopna dešifrovat odeslané zprávy.
+ Nikdy neodesílat šifrované zprávy do neověřených relací v této místnosti.
+ Rozumím
+ Použít podtržení
+ Použít přeškrtnutí
+ Použít tučný text
+ Použít kurzívu
+ Zaznamenávat název, verzi a url pro snadnější rozpoznání relací ve správci relací.
+ Povolit záznamenávání informací o klientu
+ Získejte lepší přehled a kontrolu nad všemi relacemi.
+ Použít nový správce relací
+ Operační systém
+ Model
+ Prohlížeč
+ URL
+ Verze
+ Název
+ Aplikace
+ Přijímat push oznámení v této relaci.
+ Push oznámení
+ Ověřením aktuální relace zjistíte stav ověření této relace.
+ Neznámý stav ověření
+ Zapnuto:
+ ID relace:
+ Něco se pokazilo. Zkontrolujte prosím síťové připojení a zkuste to znovu.
+ Udělit oprávnění
+ ${app_name} potřebuje oprávnění k zobrazování oznámení.
+\nUdělte prosím toto oprávnění.
+ ${app_name} potřebuje oprávnění k zobrazování oznámení. Oznámení mohou zobrazovat vaše zprávy, pozvánky atd.
+\n
+\nPro zobrazování oznámení povolte přístup na dalších vyskakovacích oknech.
+ Vyzkoušejte rozšířený textový editor (textový režim již brzy)
+ Povolit rozšířený textový editor
+ Ujistěte se, že znáte původ tohoto kódu. Propojením zařízení poskytnete někomu plný přístup ke svému účtu.
+ Potvrdit
+ Zkuste to znovu
+ Neshoduje se\?
+ Probíhá přihlašování
+ Připojování k zařízení
+ Naskenujte QR kód
+ Přihlašování na mobilním zařízením\?
+ Zobrazit QR kód na tomto zařízení
+ Vyberte možnost \"Naskenovat QR kód\"
+ Začněte na přihlašovací obrazovce
+ Vyberte možnost \"Přihlásit se pomocí QR kódu\"
+ Začněte na přihlašovací obrazovce
+ Vyberte možnost \"Zobrazit QR kód na tomto zařízení\"
+ Přejděte do Nastavení -> Zabezpečení a soukromí -> Zobrazit všechny relace
+ Otevřete ${app_name} na vašem druhém zařízení
+ Žádost byla na druhém zařízení zamítnuta.
+ Propojení nebylo dokončeno v požadovaném čase.
+ Propojení s tímto zařízením není podporováno.
+ Neúspěšné připojení
+ Zkontrolujte vaše přihlášené zařízení, měl by se zobrazit níže uvedený kód. Zkontrolujte, zda níže uvedený kód odpovídá danému zařízení:
+ Zabezpečené připojení navázáno
+ Pomocí odhlášeného zařízení naskenujte níže uvedený QR kód.
+ Pomocí přihlášeného zařízení naskenujte níže uvedený QR kód:
+ Přihlásit se pomocí QR kódu
+ Pomocí fotoaparátu na tomto zařízení naskenujte QR kód zobrazený na druhém zařízení:
+ Naskenovat QR kód
+ 3
+ 2
+ 1
+ Pomocí tohoto zařízení se můžete přihlásit do mobilního nebo webového zařízení pomocí QR kódu. Můžete to provést dvěma způsoby:
+ Přihlásit se pomocí QR kódu
+ Naskenovat QR kód
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml
index 27f46160bc..fe849a1ddf 100644
--- a/library/ui-strings/src/main/res/values-de/strings.xml
+++ b/library/ui-strings/src/main/res/values-de/strings.xml
@@ -40,9 +40,9 @@
%1$s und %2$sLeerer Raum%s hat diesen Raum aufgewertet.
- Sende eine Nachricht…
+ Sende eine Nachricht …Erste Synchronisation:
-\nImportiere Benutzerkonto…
+\nImportiere Benutzerkonto …
Erste Synchronisation:
\nImportiere KryptoschlüsselErste Synchronisation:
@@ -300,11 +300,11 @@
GroßMittelKlein
- Verbindungsaufbau…
+ Verbindungsaufbau …Anruf beendetEingehender VideoanrufEingehender Sprachanruf
- Anruf aktiv…
+ Anruf aktiv …Die Gegenseite hat den Anruf nicht angenommen.Information${app_name} benötigt die Berechtigung, auf dein Mikrofon zugreifen zu können, um (Sprach-)Anrufe tätigen zu können.
@@ -329,9 +329,9 @@
ErwähnenDu wirst diese Änderung nicht rückgängig machen können, da die Person dieselbe Berechtigungsstufe wie du erhalten wird.
\nBist du sicher\?
- %s schreibt…
- %1$s und %2$s schreiben…
- %1$s, %2$s und andere schreiben…
+ %s tippt …
+ %1$s und %2$s tippen …
+ %1$s, %2$s und andere tippen …Du bist nicht berechtigt, in diesen Raum zu schreiben.VertrauenNicht vertrauen
@@ -431,7 +431,7 @@
Schlüssel aus lokaler Datei importierenImportierenNur zu verifizierten Sitzungen verschlüsseln
- Von dieser Sitzung aus keine verschlüsselten Nachrichten an nicht-verifizierte Sitzungen senden.
+ Niemals verschlüsselte Nachrichten von dieser Sitzung zu unverifizierten Sitzungen senden.Nicht verifiziertVerifiziertBestätigen
@@ -495,7 +495,7 @@
AusLautVerschlüsselte Nachricht
- Lädt…
+ Lädt …Sicher, dass du einen Sprachanruf starten möchtest\?Sicher, dass du einen Videoanruf starten möchtest\?Die Verbannung einer Person entfernt sie aus diesem Raum und hindert sie am erneuten Beitritt.
@@ -628,7 +628,7 @@
AkzeptierenBitte lese und akzeptiere die Richtlinien dieses Homeservers:Tests ausführen
- Läuft… (%1$d von %2$d)
+ Läuft … (%1$d von %2$d)Einer oder mehrere Tests sind fehlgeschlagen. Versuche vorgeschlagene Lösung(en).Einer oder mehrere Tests sind fehlgeschlagen. Bitte sende einen Fehlerbericht, damit dies untersucht werden kann.Systemeinstellungen.
@@ -646,7 +646,7 @@
Benachrichtigungen sind für diese Sitzung nicht aktiviert.
\nBitte überprüfe die Einstellungen für ${app_name}.Aktiviere
- ${app_name} benutzt Google-Play-Dienste um Push-Nachrichten zu übermitteln, doch scheinen sie nicht korrekt konfiguriert zu sein:
+ ${app_name} benutzt Google-Play-Dienste, um Push-Nachrichten zu übermitteln, allerdings scheint dies nicht korrekt konfiguriert zu sein:
\n%1$sRepariere Play-DiensteFirebase-Token
@@ -679,7 +679,7 @@
Wenn ein Benutzer ein abgestecktes Gerät mit ausgeschaltetem Bildschirm eine Weile nicht bewegt, wechselt es in den Bereitschaftsmodus. Dies hindert Apps daran, auf das Netzwerk zuzugreifen und verzögert die Ausführung von Aufgaben, Synchronisierungen und Standard-Alarmen.Ignoriere OptimierungenKeine validen Google-Play-Dienste gefunden. Benachrichtigungen könnten nicht richtig funktionieren.
- Videogespräch aktiv…
+ Videogespräch aktiv …SchlüsselsicherungSchlüsselsicherung verwendenÜberspringen
@@ -692,7 +692,7 @@
Laute Benachrichtigungen einstellenAnrufbenachrichtigung einstellenStumme Benachrichtigungen einstellen
- LED-Farbe, Vibration, Ton usw. wählen
+ Wähle LED-Farbe, Vibration, Ton …StummBitte eine Passphrase eingebenPassphrase ist zu schwach
@@ -703,16 +703,16 @@
Wiederherstellungsschlüssel speichernSichere als DateiBitte mache eine Kopie
- Wiederherstellungsschlüssel teilen mit…
+ Wiederherstellungsschlüssel teilen mit …WiederherstellungsschlüsselUnerwarteter FehlerBist du sicher\?Wiederherstellungsschlüssel eingeben
- Stelle Backup wieder her:
+ Stelle Sicherung wieder her:Historie entschlüsselnVon Sicherung wiederherstellen
- Sicherung löschen
- Lösche Sicherung…
+ Lösche Sicherung
+ Lösche Sicherung …Lösche SicherungPräferenz der Benachrichtigungen nach Ereignis[%1$s]
@@ -722,13 +722,13 @@
[%1$s]
\nDieser Fehler ist außerhalb von ${app_name} passiert. Es gibt kein Google-Konto auf dem Gerät. Bitte füge ein Google-Konto hinzu.Verwaltung der Kryptoschlüssel
- Schlüssel-Sicherung verwalten
+ Schlüsselsicherung verwaltenNachrichten in verschlüsselten Räumen sind mit Ende-zu-Ende-Verschlüsselung gesichert. Nur du und der Empfänger haben die Schlüssel um diese Nachrichten zu lesen.
\n
\nSichere deine Schlüssel, um sie nicht zu verlieren.Wiederherstellungsschlüssel aus Passphrase generieren. Dies kann mehrere Sekunden brauchen.Du verlierst möglicherweise den Zugang zu deinen Nachrichten, wenn du dich abmeldest oder das Gerät verlierst.
- Rufe Backup-Version ab…
+ Rufe Sicherungsversion ab …Nutze deine Wiederherstellungs-Passphrase, um deinen verschlüsselten Nachrichtenverlauf lesen zu könnennutze deinen WiederherstellungsschlüsselWenn du deine Wiederherstellungspassphrase nicht weist, kannst du %s.
@@ -762,11 +762,11 @@
Schlüssel-Sicherung wird durchgeführt. Wenn du dich jetzt abmeldest, gehen deine verschlüsselten Nachrichten verloren.Schlüsselsicherung sollte bei allen Sitzungen aktiviert sein, um den Verlust verschlüsselter Nachrichten zu verhindern.Ich möchte meine verschlüsselten Nachrichten nicht
- Sichere Schlüssel…
- Sicher\?
+ Sichere Schlüssel …
+ Bist du sicher\?SicherungAlle verschlüsselten Nachrichten gehen verloren, wenn Du dich abmeldest ohne die Schlüssel gesichert zu haben.
- Wirklich abmelden\?
+ Bist du sicher, dass du dich abmelden möchtest\?Wiederherstellung verschlüsselter NachrichtenBitte gib einen Benutzernamen ein.Richte Schlüsselsicherung ein
@@ -783,25 +783,25 @@
Deine Schlüssel wurden gesichert.Dein Wiederherstellungsschlüssel ist ein Sicherungsnetz - du kannst es benutzen um den Zugriff auf deine verschlüsselten Nachrichten wiederherzustellen, falls du deine Passphrase vergisst.
\nVerwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe)
- Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort auf, wie z.B. einem Passwortmanager (oder Tresor) auf
+ Bewahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie einem Passwortmanager (oder Safe) aufIch habe eine Kopie angefertigtTeilenVerliere nie wieder verschlüsselte NachrichtenBenutze SchlüsselsicherungNeue sichere Schlüssel für NachrichtenVerwalte Schlüsselsicherung
- Sichere Schlüssel…
+ Sichere deine Schlüssel. Dies könnte einige Minuten dauern …Alle Schlüssel sind gesichert
- Sichere %d Schlüssel…
- Sichere %d Schlüssel…
+ Sichere einen Schlüssel …
+ Sichere %d Schlüssel …VersionAlgorithmusSignatur
- Berechne Wiederherstellungsschlüssel…
- Lade Schlüssel herunter…
- Importiere Schlüssel…
+ Berechne Wiederherstellungsschlüssel …
+ Lade Schlüssel herunter …
+ Importiere Schlüssel …IgnorierenMit Single-Sign-On anmeldenNachricht mit Eingabetaste senden
@@ -870,7 +870,7 @@
Kein Netzwerk. Bitte überprüfe deine Internetverbindung.ÄndernNetzwerk wechseln
- Bitte warten…
+ Bitte warten …Für diesen Raum kann keine Vorschau angezeigt werdenRäumeDirektnachrichten
@@ -902,9 +902,9 @@
Beschreibe hier deine AnmerkungVersteckte Ereignisse in der Zeitleiste anzeigenDirektnachrichten
- Warten…
- Miniaturbild wird verschlüsselt…
- Datei wird verschlüsselt…
+ Warten …
+ Vorschaubild wird verschlüsselt …
+ Verschlüssle Datei …(bearbeitet)NachrichtenbearbeitungKeine Änderungen gefunden
@@ -912,7 +912,7 @@
Sende eine neue DirektnachrichtDas Raumverzeichnis anzeigenLink in die Zwischenablage kopiert
- Raum erstellen…
+ Erstelle Raum …Bearbeitungsverlauf anzeigenE2E-Schlüssel aus der Datei \"%1$s\" importieren.Vielen Dank, der Vorschlag wurde erfolgreich gesendet
@@ -947,7 +947,7 @@
Identitäts-Server konfigurierenIdentitäts-Server ändernAuffindbare E-Mail-Adressen
- Erkennungsoptionen werden angezeigt, sobald du eine E-Mail hinzugefügt hast.
+ Entdeckungsoptionen werden angezeigt, sobald du eine E-Mail-Adresse hinzugefügt hast.Gib eine Identitäts-Server-Adresse einKonnte keine Verbindung zum Homeserver herstellenDies ist keine Adresse eines Matrixservers
@@ -997,13 +997,13 @@
Bitte erneut versuchen, nachdem du die Nutzungsbedingungen deines Heim-Servers akzeptiert hast.Bei Benutzung könnten Cookies gesetzt werden und es könnten Daten mit %s geteilt werden:Bei Benutzung könnten Daten mit %s geteilt werden:
- Optionen zum Finden werden erscheinen, sobald du eine Telefonnummer hinzugefügt hast.
- Wir haben dir eine Bestätigungsmail an %s gesendet. Prüfe dein Postfach und klicke auf den Bestätigungslink
+ Entdeckungsoptionen werden angezeigt, sobald du eine Telefonnummer hinzugefügt hast.
+ Wir haben eine E-Mail an %s gesendet. Prüfe deine E-Mails und klicke auf den BestätigungslinkEs sieht aus, als würde der Server zu viel Zeit benötigen, um zu antworten. Der Grund kann eine schlechte Verbindung oder ein Fehler mit dem Server sein. Bitte versuche es später erneut.Anhang sendenNavigationsmenü öffnenRaumerstellungsmenü öffnen
- Schließe das Raumerstellungsmenü…
+ Schließe das Raumerstellungsmenü …Erstelle eine neue DirektnachrichtErstelle einen neuen RaumSchließe Key-Backup-Einblendung
@@ -1022,7 +1022,7 @@
StickerEs ist SpamEs ist unangebracht
- Benutzerdefinierte Meldung…
+ Benutzerdefinierte Meldung …Diesen Inhalt meldenMeldegrundMELDEN
@@ -1067,7 +1067,7 @@
Du teilst deine E-Mail-Adressen oder Telefonnummern momentan auf dem Identitäts-Server %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören.Stimme den Nutzungsbedingungen des Identitäts-Servers (%s) zu, um per E-Mail-Adresse oder Telefonnummer auffindbar zu sein zu können.Zu teilende Daten nicht verarbeitbar
- Erweitere und individualisiere dein Benutzererlebnis
+ Erweitere und personalisiere deine ErfahrungMit %1$s verbindenMit Element Matrix Services verbindenMit einem anderen Server verbinden
@@ -1082,14 +1082,14 @@
Die Anwendung kann kein neues Benutzerkonto auf diesem Server erstellen.
\n
\nMöchtest du dich mit einer Web-Anwendung anmelden\?
- Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft.
+ Diese E-Mail-Adresse ist mit keinem Konto verknüpft.Passwort auf %1$s zurücksetzenE-MailNeues PasswortAchtung!Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssel zurücksetzen. Dein verschlüsselter Verlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt.Fortfahren
- Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft
+ Diese E-Mail-Adresse ist mit keinem Konto verknüpftPrüfe deinen PosteingangEine Bestätigungsmail wurde an %1$s versendet.Klicke auf den Link um dein neues Passwort zu bestätigen. Sobald du dem enthaltenen Link gefolgt bist, klicke unten.
@@ -1133,7 +1133,7 @@
WeiterDu wurdest von allen Sitzungen abgemeldet und erhältst keine Push-Benachrichtigungen mehr. Um Benachrichtigungen wieder zu aktivieren, melde dich auf jedem Gerät erneut an.Warnung
- Lege eine E-Mail-Adresse fest, um dein Konto wiederherzustellen. Später kannst du optional zulassen, dass Personen dich anhand dieser E-Mail-Adresse entdecken.
+ Lege eine E-Mail-Adresse fest, um dein Konto wiederherzustellen. Später kannst du optional zulassen, dass Personen dich anhand dieser E-Mail-Adresse entdecken können.WeiterLege Telefonnummer festLege eine Telefonnummer fest, damit Personen dich anhand dieser entdecken können.
@@ -1149,8 +1149,8 @@
Bitte löse das CaptchaVeralteter Homeserver
- Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunde…
- Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunden…
+ Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunde …
+ Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunden …Gesehen vonDu bist abgemeldet
@@ -1181,7 +1181,7 @@
\nBitte zuerst die Daten löschen und dann erneut anmelden.matrix.to-Link fehlerhaftDie Beschreibung ist zu kurz
- Initiale Synchronisierung…
+ Initiale Synchronisierung …Erweiterte EinstellungenEntwicklermodusDer Entwicklermodus aktiviert versteckte Funktionen und kann die Anwendung weniger stabil machen. Nur für Entwickler!
@@ -1192,7 +1192,7 @@
EinstellungenAktuelle SitzungAndere Sitzungen
- Zeigt nur die ersten Ergebnisse, gib mehr Buchstaben ein…
+ Zeigt nur die ersten Ergebnisse, gib weitere Zeichen ein …Ausfallsicher${app_name} kann häufiger abstürzen, wenn ein unerwarteter Fehler auftrittStellt einer Klartextnachricht ¯\\_(ツ)_/¯ voran
@@ -1213,9 +1213,9 @@
Bild.AudioDatei
- Warten…
+ Warte …%s brach ab
- Du hast abgebrochen
+ Du brachst ab%s hat akzeptiertDu hast akzeptiertVerifizierung gesendet
@@ -1228,7 +1228,7 @@
Verifizieren via Emoji-Vergleich%s verifizieren%s verifiziert
- Warte auf %s…
+ Warte auf %s …Nachrichten in diesem Raum sind nicht Ende-zu-Ende-verschlüsselt.Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt.
\n
@@ -1244,7 +1244,7 @@
Hochgeladene DateienRaum verlassen
- Verlasse den Raum…
+ Verlasse den Raum …AdministratorenModeratorenBenutzerdefiniert
@@ -1262,7 +1262,7 @@
Sendet das angegebene Emote in RegenbogenfarbenZeitleisteNachrichteneditor
- Ende-zu-Ende-Verschlüsselung aktivieren…
+ Aktiviere Ende-zu-Ende-Verschlüsselung …Verschlüsselung aktivieren\?Nach der Aktivierung kann die Verschlüsselung für den Raum nicht deaktiviert werden. Nachrichten können nicht vom Server gesehen werden, nur von den Teilnehmenden des Raums. Möglicherweise funktionieren danach einige Bots und Bridges nicht mehr ordnungsgemäß.Verschlüsselung aktivieren
@@ -1284,7 +1284,7 @@
Aktive SitzungenAlle Sitzungen anzeigenSitzungen verwalten
- Diese Sitzung abmelden
+ Von dieser Sitzung abmeldenKeine kryptografischen Informationen verfügbarDiese Sitzung ist für sichere Kommunikation vertrauenswürdig, da du sie überprüft hast:Verifiziere diese Sitzung, um sie als vertrauenswürdig zu markieren, und gewähren ihr Zugriff auf verschlüsselte Nachrichten. Wenn du dich nicht bei dieser Sitzung angemeldet hast, ist dein Konto möglicherweise gefährdet:
@@ -1316,7 +1316,7 @@
Nutze eine WiederherstellungsmethodeWenn du auf keine existierende Sitzung zugreifen kannstKann keine Geheimnisse im Speicher finden
- Entfernen…
+ Entferne …Möchtest du diesen Anhang an %1$s senden\?Sende Bild in Originalgröße
@@ -1385,13 +1385,13 @@
Verschlüsselung aktiviertNachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr und verifiziere Benutzer in deren Profil.Die Verschlüsselung in diesem Raum wird nicht unterstützt
- Warte auf %s…
+ Warte auf %s …Fehlerbehebung%s hat den Raum erstellt und konfiguriert.Fast geschafft! Zeigt das andere Gerät ein Häkchen an\?
- Fast geschafft! Warte auf Bestätigung…
+ Fast geschafft! Warte auf Bestätigung …Verschlüsselte Direktnachrichten
- Nachricht…
+ Nachricht …Verifiziere dich und andere, um eure Unterhaltungen zu schützenGib zum Fortfahren deinen %s einDatei benutzen
@@ -1411,7 +1411,7 @@
Schlüsselbackup-WiederherstellungsschlüsselBildschirmfotos der Anwendung verhindernDas Aktivieren dieser Einstellung setzt FLAG_SECURE in allen Aktivitäten. Starte die Anwendung neu, damit die Änderung wirksam wird.
- Neues Benutzerpasswort festlegen…
+ Lege ein neues Kontopasswort fest …Nutze die neueste Version von ${app_name} auf deinen anderen Geräten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} für Android oder eine andere Matrix-Anwendung, die Quersignierung unterstützt${app_name} Web
\n${app_name} Desktop
@@ -1445,7 +1445,7 @@
%1$s: %2$s %3$sMitglieder hinzufügenEINLADEN
- Benutzer werden eingeladen…
+ Lade Benutzer ein …Personen einladenEinladung gesendet an %1$sEinladungen gesendet an %1$s und %2$s
@@ -1495,11 +1495,11 @@
Grund für den BannBann des Benutzers aufhebenDas Aufheben des Bannes wird dem Benutzer erlauben dem Raum wieder beizutreten.
- Sicheres Backup
- Backup einrichten
- Backup zurücksetzen
+ Verschlüsselte Sicherung
+ Sicherung einrichten
+ Sicherung zurücksetzenAuf diesem Gerät einrichten
- Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden.
+ Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst.Generiere einen neuen Sicherheitsschlüssel oder setze eine neue Sicherheitspassphrase für dein existierendes Backup.Dieses wird deinen aktuellen Schlüssel oder deine aktuelle Phrase ersetzen.Integrationen sind deaktiviert
@@ -1512,14 +1512,14 @@
ANSICHTAktive WidgetsDer Sicherheitsschlüssel ist gespeichert worden.
- Backup
+ Verschlüsselte SicherungAbsicherung gegen den Verlust verschlüsselter Nachrichten
- Richte Backup ein
+ Sicherung einrichtenNachricht entferntGelöschte Nachrichten zeigenZeigt einen Platzhalter für gelöschte Nachrichten anDedizierten Tab für ungelesene Nachrichten zur Hauptansicht hinzufügen
- Wir haben dir eine Bestätigungsmail an %s gesendet. Bitte prüfe deine E-Mails und klicke auf den Bestätigungslink
+ Wir haben eine E-Mail an %s gesendet. Bitte prüfe deine E-Mails und klicke auf den BestätigungslinkDer Verifizierungscode ist nicht korrekt.MEDIENEs gibt in diesem Raum keine Medien
@@ -1534,7 +1534,7 @@
Gib die Adresse des Servers ein, den du benutzen möchtestEinloggen mit Matrix-IDEinloggen mit Matrix-ID
- Wenn du einen Account auf einem Homeserver eingerichtet hast, benutze deine Matrix-ID (z.B. @benutzer:domain.com) und Passwort.
+ Falls du ein Konto auf einem Heim-Server eingerichtet hast, verwende nachstehend deine Matrix-ID (z. B. @benutzer:domain.com) und dein Passwort.Matrix-IDWenn du dein Passwort nicht weißt, gehe zurück um es zurücksetzen zu lassen.Dies ist keine gültige Benutzerkennung. Erwartetes Format: \'@benutzer:homeserver.org\'
@@ -1547,20 +1547,20 @@
Gib eine Sicherheitsphrase ein, die nur du kennst. Diese wird benutzt um deine Daten auf dem Server geheim zu halten.Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten und Daten verlieren.
\n
-\nDu kannst auch ein Backup einrichten und deine Schlüssel in den Einstellungen verwalten.
+\nDu kannst auch eine Sicherung einrichten und deine Schlüssel in den Einstellungen verwalten.Du hast den Raum erstellt und konfiguriert.Dieser Account ist deaktiviert worden.Konnte Mediendatei nicht speichernAktuelle SpracheAndere verfügbare Sprachen
- Lade verfügbare Sprachen…
+ Lade verfügbare Sprachen …Öffne AGBs von %sVerbindung zu Identitäts-Server %s trennen\?Dieser Identitäts-Server ist veraltet. ${app_name} unterstützt nur API V2.Diese Operation ist nicht möglich. Der Homeserver ist veraltet.Bitte konfiguriere zuerst einen Identitäts-Server.Bitte akzeptiere zuerst die AGB des Identitäts-Servers in den Einstellungen.
- Deiner Privatsphäre wegen unterstützt ${app_name} nur das Senden gehashter E-Mail-Adressen und Telefonnummern.
+ Deiner Privatsphäre wegen unterstützt ${app_name} nur das Senden von E-Mail-Adressen und Telefonnummern als Streuwert (Hash).Die Assoziierung ist fehlgeschlagen.Für diese Kennung gibt es aktuell keine Zuordnung.Dein Heim-Server (%1$s) schlägt %2$s als Identitäts-Server vor
@@ -1575,14 +1575,14 @@
Aktiviere MikrophonStoppe KameraStarte Kamera
- Backup
- Verlust verschlüsselter Nachrichten und Daten verhindern, indem die Schlüssel für die Entschlüsselung am Server gesichert werden.
+ Verschlüsselte Sicherung
+ Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst.Sicherheitsschlüssel benutzen
- Generiere einen Sicherheitsschlüssel, welcher z.B. in einem Passwortmanager oder in einem Tresor sicher aufbewahrt werden sollte.
+ Generiere einen Sicherheitsschlüssel, den du in einem Passwort-Manager oder Tresor sicher aufbewahren solltest.Eine Sicherheitsphrase benutzenGib eine geheime Phrase ein, die nur du kennst und generiere einen Schlüssel als Backup.Speichere deinen Sicherheitsschlüssel
- Bewahre deinen Sicherheitsschlüssel irgendwo sicher auf, wie z.B. in einem Passwortmanager oder in einem Tresor.
+ Bewahre deinen Sicherheitsschlüssel in einem Passwort-Manager oder Tresor sicher auf.Sicherheitsphrase setzenGib eine Sicherheitsphrase ein, welche nur du kennst und deine Daten auf dem Server geheim halten soll.Sicherheitsphrase
@@ -1602,7 +1602,7 @@
VERSTANDENMEHR ERFAHRENSpeichere Wiederherstellungsschlüssel in
- Ermittle deine Kontakte…
+ Ermittle deine Kontakte …Deine Kontaktliste ist leerKontaktlisteEinladung zurücknehmen
@@ -1654,12 +1654,12 @@
Diese Telefonnummer ist bereits registriert.Deinem Konto wurde keine Telefonnummer hinzugefügtE-Mail-Adressen
- Deinem Konto wurde keine E-Mail hinzugefügt
+ Deinem Konto wurde keine E-Mail-Adresse hinzugefügtTelefonnummern%s entfernen\?Stelle sicher, dass du auf den Link in der E-Mail geklickt hast, die wir dir gesendet haben.E-Mail und Telefon
- Verwalte E-Mails und Telefonnummern, die mit deinem Matrix-Konto verknüpft sind
+ Verwalte E-Mail-Adressen und Telefonnummern, die mit deinem Matrix-Konto verknüpft sindCodeVerwende das internationale Format (Telefonnummer muss mit \'+\' beginnen)Bestätige deine Identität, indem du dieses Login verifizierst, um Zugriff auf verschlüsselte Nachrichten zu erhalten.
@@ -1777,7 +1777,7 @@
Teile diesen Code, damit andere ihn einlesen und mit dir schreiben können.Meinen Code teilenMein Code
- Scanne einen QR-Code
+ QR-Code einlesenDas ist kein korrekter QR-Code von Matrix🔐️ Komm mit zu ${app_name}Hey, schreibe mit mir auf ${app_name}: %s
@@ -1790,7 +1790,7 @@
Das ist der Anfang dieser Konversation.Das ist der Anfang von %s.Du hast nicht die nötigen Berechtigungen, um die Verschlüsselung in diesem Raum zu aktivieren.
- Erstelle Raum…
+ Erstelle Raum …Manche Zeichen sind nicht zulässigBitte gib eine Raumadresse anDiese Adresse ist bereits vergeben
@@ -1810,7 +1810,7 @@
Dieser Raum hat keine lokalen AdressenFüge Adressen für diesen Raum hinzu, damit andere Nutzer ihn auf %1$s finden könnenLokale Adresse
- Neue öffentliche Adresse (z.B. #alias:server)
+ Neue öffentliche Adresse (z. B. #alias:server)Noch keine weiteren öffentlichen Adressen vorhanden.Noch keine weiteren öffentlichen Adressen vorhanden, füge unten eine hinzu.Die Adresse \"%1$s\" löschen\?
@@ -1926,9 +1926,9 @@
Raum-VersionNeuer WertErste Synchronisation:
-\nLade Daten herunter…
+\nLade Daten herunter …Erste Synchronisation:
-\nWarte auf Serverantwort…
+\nWarte auf Antwort vom Server …GesendetRaumverzeichnisWechseln
@@ -1988,13 +1988,13 @@
Offen für Jeden. Am Besten für CommunitiesEin privater Space für meine Teamkameraden und michMeine Teamkameraden und ich
- Ein privater Space um deine Räume zu organisieren
+ Ein privater Space zum Organisieren deiner RäumeUm einem bereits existierenden Space beizutreten, benötigst du eine Einladung.Dein privater SpaceDein öffentlicher SpaceBetrete einen Space mit der angegebenen IDBeschreibung
- Erzeuge Space …
+ Erstelle Space …Ohne ThemaAllgemeinEinen Space erstellen
@@ -2028,7 +2028,7 @@
Die Datei ist zu groß.Video wird komprimiert (%d%%)
- Bild wird komprimiert…
+ Komprimiere Bild …Als Standard festsetzen und nicht mehr fragenJedes Mal fragenGib den Namen eines neuen Servers ein, den du erkunden möchtest.
@@ -2129,7 +2129,7 @@
Verpasster Sprachanruf%d verpasste Sprachanrufe
- Heim-Server API URL
+ Heim-Server-API-AdresseUm Sprachnachrichten zu senden, erlaube bitte Zugriff aufs Mikrofon.Um fortzufahren, erlaube bitte in den Systemeinstellungen Zugriff auf die Kamera.Für diese Aktion fehlen einige Berechtigungen, bitte erlaube diese in den Systemeinstellungen.
@@ -2140,7 +2140,7 @@
Andere Spaces oder Räume die du kennstSpaces mit diesem Raum und dir als MitgliedNur Erwähnungen und Schlüsselwörter
- Auflegen…
+ Auflegen …Sprachanruf mit %sVideoanruf mit %sAlle beigetretenen Räume werden auf der Startseite angezeigt.
@@ -2163,7 +2163,7 @@
Nicht erreichtDie angerufene Person ist beschäftigt.Person beschäftigt
- Klingeln…
+ Klingeln …SpacesVerpasster VideoanrufVerpasster Sprachanruf
@@ -2184,7 +2184,7 @@
Willst du %s wirklich verlassen\?Mit Benutzername oder E-Mail einladenZum ausgewählten Space hinzufügen
- Erstelle Space…
+ Erstelle Space …Hilfreiche Informationen zur Fehlersuche anzeigenDebug-Info anzeigenDas schaut nicht nach einer gültigen E-Mail-Adresse aus
@@ -2193,7 +2193,7 @@
ZugriffWer hat Zugriff\?Benachrichtigungen per Email für %s aktivieren
- Um Benachrichtigungen per E-Mail zu empfangen, musst du einen E-Mail-Adresse hinzufügen
+ Um Benachrichtigungen per E-Mail zu empfangen, musst du eine E-Mail-Adresse hinzufügenEmailbenachrichtigungenSpace upgradenNamen vom Space ändern
@@ -2239,7 +2239,7 @@
Frage oder ThemaAbstimmungsthema oder FrageAbstimmung erstellen
- Abstimmung
+ UmfrageAuffindungseinstellungen öffnenSitzung abgemeldet!Raum verlassen!
@@ -2247,7 +2247,7 @@
Es konnte kein Heim-Server mit der Adresse %s gefunden werden. Bitte überprüfe die Adresse oder wähle den Heim-Server manuell.Untergeordneten Space hinzufügen.Bist du dir wirklich sicher, dass du diese Informationen senden willst\?
- E-Mail-Adressen und Telefonnummern an %s senden
+ E-Mail-Adressen und Telefonnummern an %s übermittelnNicht jetztAuf Benachrichtigungen wartenExterne Bibliotheken
@@ -2294,13 +2294,13 @@
Das System sendet automatisch Protokolle, wenn ein Fehler bei der Entschlüsselung auftrittEntschlüsselungsfehler automatisch melden.Auffindbarkeit (%s)
- Per E-Mail einladen, finde deine Kontakte und mehr…
+ Lade per E-Mail ein, finde deine Kontakte und mehr …Schließe die Konfiguration des Auffindbarkeitsdienstes ab.Du verwendest derzeit keinen Identitäts-Server. Um Team-Mitglieder einzuladen und für sie auffindbar zu sein, konfiguriere zunächst einen.Ich habe bereits ein KontoSichere Kommunikation.Besitze deine Unterhaltungen.
- Um bestehende Kontakte ermitteln zu können, musst du Kontaktinformationen (E-Mail-Adressen und Telefonnummern) an deinen Identitäts-Server übermitteln. Wir verschlüsseln deine Daten vor der Übermittlung, um den Datenschutz gewährleisten zu können.
+ Um bestehende Kontakte ermitteln zu können, musst du Kontaktinformationen (E-Mail-Adressen und Telefonnummern) an deinen Identitäts-Server übermitteln. Deine Daten werden als Streuwert (Hash) übermittelt, um den Datenschutz zu gewährleisten.Deine Kontakte sind privat. Um unter deinen Kontakten Matrix-Nutzer finden zu können, benötigen wir deine Erlaubnis, Kontaktinformationen an deinen Identitäts-Server zu übermitteln.Dieser Server stellt keine Richtlinie bereit.Richtlinie deines Identitäts-Servers
@@ -2399,13 +2399,13 @@
%d Server-ACLs geändertBeenden
- Live-Standort aktiviert
+ Echtzeit-Standort aktiviertStandort teilenDiesen Standort teilenMeinen Standort teilenMeinen Standort teilen
- Live-Standort teilen
- Live-Standort teilen
+ Echtzeit-Standort freigeben
+ Echtzeit-Standort teilenThreads nähern sich der Beta 🎉DeaktivierenBETA
@@ -2415,14 +2415,14 @@
Threads BetaBildschirm teilenProbiere es aus
- Live bis %1$s
+ Echtzeit bis %1$sWähle Deine Benachrichtigungsmethode
- Vorläufige Implementierung: Standorte bleiben im Nachrichtenverlauf von Räumen erhalten
+ Vorläufige Implementierung: Standorte verbleiben im RaumverlaufProfil-Tag:h
- Standortfreigabe aktivieren
+ Aktiviere StandortfreigabeBitte beachte: Dies ist eine experimentelle Funktion, die eine temporäre Implementierung nutzt. Das bedeutet, dass du deinen Standortverlauf nicht löschen kannst und erfahrene Nutzer ihn sehen können, selbst wenn du deinen Live-Standort nicht mehr mit diesem Raum teilst.
- Live-Standortfreigabe
+ Echtzeit-StandortfreigabeAktuelles Gateway: %sGatewayKann den Endpunkt nicht finden.
@@ -2443,17 +2443,17 @@
Bildschirmfreigabe ist in Arbeit${app_name} BildschirmfreigabeAktualisiert vor %1$s
- Aktiviere Live-Standortfreigabe
+ Aktiviere Echtzeit-StandortfreigabeStandortfreigabe ist in Arbeit
- ${app_name} Live-Standort
+ ${app_name} Echtzeit-Standort%1$s übrig
- Live-Standort anzeigen
- Live-Standort beendet
- Live-Standort laden…
+ Echtzeit-Standort anzeigen
+ Echtzeit-Standort beendet
+ Lade Echtzeit-Standort …8 Stunden1 Stunde15 Minuten
- Teile Deinen Live-Standort für
+ Gebe deinen Echtzeit-Standort frei fürAuf aktuellen Standort zoomenPin des ausgewählten Ortes auf der Karte(%1$s)
@@ -2507,7 +2507,7 @@
Die biometrische Authentifizierung konnte nicht aktiviert werden.Die biometrische Authentifizierung wurde deaktiviert, weil kürzlich eine neue biometrische Authentifizierungsmethode hinzugefügt wurde. Du kannst sie in den Einstellungen wieder aktivieren.Der Heim-Server akzeptiert keine Benutzernamen, die nur aus Ziffern bestehen.
- teilten ihren Live-Standort
+ Teilte den eigenen Echtzeit-StandortSchritt überspringenSpeichern und fortfahrenÖffne die Einstellungen jederzeit um dein Profil zu aktualisieren
@@ -2545,7 +2545,7 @@
Kann Link nicht öffnen: Communities wurden durch Spaces ersetztMSC3061: Raumschlüssel für vorherige Nachrichten teilenBeim Einladen in einen Raum mit sichtbarem Verlauf wird der verschlüsselte Verlauf sichtbar sein.
- Live-Standort
+ Echtzeit-Standort%d Nachricht gelöscht%d Nachrichten gelöscht
@@ -2553,10 +2553,10 @@
Keine Element-Call-BerechtigungsabfragenBestätige automatisch Element-Call-Widgets und erlaube Kamera- und MikrofonzugriffLos
- ändern
- oder
- Der Ort, an dem deine Gespräche stattfinden
- Das zukünftige Zuhause für deine Gespräche
+ Bearbeiten
+ Oder
+ Der zukünftige Ort deiner Gespräche
+ Der zukünftige Ort deiner GesprächeSystemstandard nutzenAutomatisch festlegenSchriftgröße wählen
@@ -2568,7 +2568,7 @@
Nutzername / E-Mail-Adresse / TelefonnummerErstelle dein KontoServer-URL
- Wie lautet die Adresse deines Servers\? Das wird eine Art Zuhause für deine Daten
+ Wie lautet die Adresse deines Servers\? Dies ist eine Art Zuhause für all deine DatenWie lautet die Adresse deines Servers\?Muss 8 oder mehr Zeichen umfassenWähle deinen Server
@@ -2586,7 +2586,6 @@
UngelesenePersonenSchreibe deine erste Nachricht, um %s zur Unterhaltung einzuladen
- Alle Sitzungen anzeigen (V2, in Arbeit)Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.Andere SitzungenSitzungen
@@ -2601,9 +2600,9 @@
Du wirst deinen verschlüsselten Nachrichtenverlauf nicht abrufen können. Um neu zu beginnen, setze deine Sicherung und Verifizierungsschlüssel zurück.Verifizierung dieses Gerätes nicht möglichAktualisiere deine Daten …
- Standort teilen
- Du musst die Berechtigung erhalten, um deinen Live-Standort mit diesem Raum zu teilen.
- Dir fehlt die Berechtigung, deinen Live-Standort teilen zu dürfen
+ Standort freigeben
+ Du benötigst die entsprechenden Berechtigungen, um deinen Echtzeit-Standort in diesem Raum freizugeben.
+ Dir fehlt die Berechtigung, deinen Echtzeit-Standort freigeben zu dürfenPasswort zurückgesetztCode erneut schickenEin Code wurde an %s gesendet
@@ -2666,7 +2665,7 @@
SitzungsdetailsFilter zurücksetzenKeine inaktiven Sitzungen gefunden.
- Keine nicht verifizierten Sitzungen gefunden.
+ Keine unverifizierten Sitzungen gefunden.Keine verifizierten Sitzungen gefunden.Erwäge, dich aus alten (ein Tag oder mehr), nicht mehr verwendeten Sitzungen abzumelden.
@@ -2707,6 +2706,102 @@
Verifiziere deine aktuelle Sitzung für besonders sichere Kommunikation.Deine aktuelle Sitzung ist für sichere Kommunikation bereit.Details anzeigen
- Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzungen oder melde dich von ihr ab.
- Für die bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder nicht mehr benutzt.
+ Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzung oder melde sie ab.
+ Für bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder benutzt.
+ Andere Nutzer in Direktnachrichten und Räumen, in denen du dich befindest, können eine vollständige Liste deiner Sitzungen einsehen.
+\n
+\nDies gibt ihnen die Sicherheit, dass sie auch wirklich mit dir kommunizieren. Allerdings bedeutet es auch, dass sie die Sitzungsnamen sehen können, die du hier angibst.
+ Verifizierte Sitzungen wurden mit deinen Daten angemeldet und anschließend mit deiner Sicherheitspassphrase oder durch Quersignierung verifiziert.
+\n
+\nDies bedeutet, dass sie die Verschlüsselungs-Schlüssel für deine bisherigen Nachrichten besitzen und anderen Nutzern bestätigen können, dass diese Sitzungen tatsächlich von dir stammen.
+ Sitzungen umbenennen
+ Verifizierte Sitzungen
+ Nicht verifizierte Sitzungen sind jene, die angemeldet, aber nicht quer signiert sind.
+\n
+\nDu solltest besonders sicherstellen, dass du diese Sitzungen erkennst, da sie auf eine nicht autorisierte Nutzung deines Kontos hindeuten könnten.
+ Nicht verifizierte Sitzungen
+ Inaktive Sitzungen sind jene, die du einige Zeit nicht mehr verwendet hast, die aber weiterhin Verschlüsselungs-Schlüssel erhalten.
+\n
+\nDas Löschen von inaktiven Sitzungen verbessert Sicherheit und Leistung, und hilft dir zu erkennen, ob eine neue Sitzung verdächtig ist.
+ Inaktive Sitzungen
+ Sei dir bitte bewusst, dass Sitzungsnamen auch für Personen, mit denen du kommunizierst, sichtbar sind.
+ Individuelle Sitzungsnamen können dir helfen, deine Geräte einfacher zu erkennen.
+ Sitzungsname
+ Sitzung umbenennen
+ Von dieser Sitzung abmelden
+ Nicht verifiziert · Deine aktuelle Sitzung
+ Beginne eine Sprachübertragung
+ Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden.
+ Tastatur auffordern, keine personalisierten Daten, z. B. den Tippverlauf und Wörterbücher, basierend auf deinen Konversationen zu aktualisieren. Beachte, dass einige Tastaturen diese Einstellung ignorieren werden.
+ Inkognito-Tastatur
+ Stellt (╯°□°)╯︵ ┻━┻ einer Klartextnachricht voran
+ Sprachübertragung
+ Öffne die Entwicklungswerkzeuge
+ 🔒 Du hast in den Sicherheitseinstellungen aktiviert, dass Verschlüsselung in allen Räumen ausschließlich für verifizierte Sitzungen erlaubt ist.
+ ⚠ Es befinden sich nicht verifizierte Geräte in diesem Raum. Sie werden deine Nachrichten nicht entschlüsseln können.
+ Niemals verschlüsselte Nachrichten zu unverifizierten Sitzungen in diesem Raum senden.
+ Verstanden
+ Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus)
+ Aktiviere Rich-Text-Editor
+ Browser
+ Durchgestrichen formatieren
+ Kursiv formatieren
+ Fett formatieren
+ Unterstrichen formatieren
+ ${app_name} benötigt die Berechtigung zur Anzeige von Benachrichtigungen.
+\nBitte gewähre diese Berechtigung.
+ Bezeichnung, Version und URL der Anwendung registrieren, damit diese Sitzung in der Sitzungsverwaltung besser erkennbar ist.
+ Anwendungsinformationen erfassen
+ URL
+ Bessere Übersicht und Kontrolle über all deine Sitzungen.
+ Aktiviere neue Sitzungsverwaltung
+ Betriebssystem
+ Modell
+ Version
+ Name
+ Anwendung
+ Erhalte Push-Benachrichtigungen in dieser Sitzung.
+ Push-Benachrichtigungen
+ Verifiziere deine aktuelle Sitzung, um den Verifizierungsstatus dieser Sitzung anzuzeigen.
+ Unbekannter Verifizierungsstatus
+ Sitzungs-ID:
+ Aktiviert:
+ Etwas ist schiefgelaufen. Bitte überprüfe deine Internetverbindung und versuche es erneut.
+ Berechtigung geben
+ ${app_name} braucht die Berechtigung, um Benachrichtigungen anzuzeigen. Benachrichtigungen können deine Nachrichten, Einladungen etc. anzeigen.
+\n
+\nBitte erlaube den Zugriff im nächsten Dialog, damit Benachrichtigungen angezeigt werden können.
+ Bitte vergewissere dich, dass du den Ursprung dieses Codes kennst. Durch Verbindung neuer Geräte gewährst du vollen Zugriff auf dein Konto.
+ Bestätigen
+ Erneut versuchen
+ Keine Übereinstimmung\?
+ Du wirst angemeldet
+ Verbinde mit Gerät
+ QR-Code einlesen
+ Mobiles Gerät anmelden\?
+ QR-Code auf diesem Gerät anzeigen
+ Wähle „QR-Code einlesen“
+ Beginne auf dem Anmeldebildschirm
+ Wähle „Mit QR-Code anmelden“
+ Beginne auf dem Anmeldebildschirm
+ Wähle \'QR-Code auf diesem Gerät anzeigen\'
+ Gehe zu Einstellungen -> Sicherheit und Privatsphäre -> Alle Sitzungen anzeigen
+ Öffne ${app_name} auf deinem anderen Gerät
+ Die Anfrage wurde auf dem anderen Gerät abgelehnt.
+ Die Verbindung konnte nicht in der erforderlichen Zeit hergestellt werden.
+ Verbindung mit diesem Gerät nicht unterstützt.
+ Verbindung fehlgeschlagen
+ Überprüfe dein angemeldetes Gerät. Der unten gezeigte Code sollte angezeigt werden. Bestätige, dass beide Codes übereinstimmen:
+ Sichere Verbindung hergestellt
+ Lese den unten angezeigten QR-Code mit deinem nicht angemeldeten Gerät ein.
+ Benutze dein angemeldetes Gerät um den unten angezeigten QR-Code einzulesen:
+ Mit QR-Code anmelden
+ Benutze die Kamera auf diesem Gerät um den vom anderen Gerät angezeigten QR-Code zu scannen:
+ QR-Code scannen
+ 3
+ 2
+ 1
+ Du kannst dieses Gerät benutzen um ein anderes Gerät per QR-Code anzumelden. Dafür gibt es zwei Wege:
+ Mit QR-Code anmelden
+ QR-Code scannen
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml
index bc4299c1bd..f73c4952c6 100644
--- a/library/ui-strings/src/main/res/values-es/strings.xml
+++ b/library/ui-strings/src/main/res/values-es/strings.xml
@@ -2548,7 +2548,6 @@
EscritorioWebMóvil
- Mostrar todas las sesiones (V2, WIP)Auto aprovar widgets de Element Call y dar permisos de cámara y micrófono%d mensaje borrado
diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml
index dbdbbdbb00..7ead21394c 100644
--- a/library/ui-strings/src/main/res/values-et/strings.xml
+++ b/library/ui-strings/src/main/res/values-et/strings.xml
@@ -1158,10 +1158,10 @@
Tõuketeavituste reeglidTõuketeavituste reegleid pole kirjeldatudTõuketeavituste võrguväravaid pole registreeritud
- app_id:
- push_key:
- app_display_name:
- session_name:
+ Rakenduse ID:
+ Tõuketeenuse võti:
+ Rakenduse kuvatav nimi:
+ Sessiooni nimi:URL:Vorming:Heli ja video
@@ -1197,8 +1197,8 @@
Isikutuvastusserveri kasutamise lõpetamine tähendab, et sa ei ole leitav teiste kasutajate poolt ega sulle ei saa telefoninumbri või e-posti aadressi alusel kutset saata. Küll aga saab kutset saata Matrix\'i kasutajatunnuse alusel.Leitavad telefoninumbridLisa avaekraanile eraldi sakk lugemata teavituste jaoks.
- Saatsime kinnituskirja e-posti aadressile %s. Palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki
- Saatsime kinnituskirja e-posti aadressile %s. Esmalt palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki
+ Saatsime kirja e-posti aadressile %s. Palun vaata oma kirju ja klõpsi selles kirjas leiduvat linki
+ Saatsime kirja e-posti aadressile %s. Esmalt palun vaata oma kirju ja klõpsi selles kirjas leiduvat linkiSelleks, et sind võiks leida e-posti aadressi või telefoninumbri alusel, nõustu isikutuvastusserveri (%s) kasutustingimustega.Lülita sisse detailsete logide salvestamine.Sa parasjagu ei eira ühtegi kasutajat
@@ -1336,7 +1336,7 @@
Kas katkestame ühenduse isikutuvastusserveriga %s\?Palun esmalt seadista isikutuvastusserver.Palun esmalt nõustu seadistustes isikutuvastusserveri kasutustingimustega.
- Sinu privaatsuse nimel saadab ${app_name} e-posti aadressid ja telefoninumbrid serverisse vaid räsituna.
+ Sinu privaatsuse nimel saadab ${app_name} e-posti aadressid ja telefoninumbrid serverisse vaid räsidena.Seoste loomine ei õnnestunud.Selle tunnusega ei ole hetkel ühtegi seost.Sinu koduserver (%1$s) soovitab kasutada sinu isikutuvastusserverina %2$s teenust
@@ -1778,7 +1778,7 @@
See ei ole korralik Matrix\'i QR-koodNõustuTühista minu nõusolek
- Selleks, et leida Matrixikasutajaid oma kontaktide hulgast, oled sa andnud nõusoleku saata e-posti aadresse ja telefoninumbreid sellele isikutuvastusserverile.
+ Selleks, et leida Matrixikasutajaid oma kontaktide hulgast, oled sa andnud nõusoleku e-posti aadresside ja telefoninumbrite saatmiseks sellele isikutuvastusserverile.Saada e-posti aadresse ja telefoninumbreidSoovitusedTuttavad kasutajad
@@ -2211,7 +2211,7 @@
Ligipääs siia kogukondaKes pääsevad ligi siia jututuppa\?Saada teavitusi %s e-posti aadressile
- Kui soovi e-posti teel saada teavitusi, siis palun seosta oma e-posti aadress oma Matrix\'i kasutajakontoga
+ Kui soovid e-posti teel saada teavitusi, siis palun seosta oma e-posti aadress oma Matrix\'i kasutajakontogaE-posti teel saadetavad teavitusedUuenda kogukondaMuuda kogukonna nime
@@ -2591,7 +2591,6 @@
\nSee koduserver ei pruugi olla seadistatud kuvama kaarte.
Ava seadistusedKõik vestlused
- Näita kõiki sessioone (V2, WIP)Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.Muud sessioonidSessioonid
@@ -2691,7 +2690,7 @@
FiltreeriViimati kasutusel %1$sSeade
- Sessioonid
+ SessioonPraegune sessioonParima turvalisuse ja töökindluse nimel verifitseeri see sessioon või logi ta võrgust välja.Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon.
@@ -2701,4 +2700,100 @@
Võta kasutusele viivitusega otsevestlusedLihtsustatud Element valikuliste kaartidegaVõta kasutusele rakenduse uus välimus
+ 🔒 Sa oled rakenduse turvaseadistustes määranud, et krüptimine kehtib vaid verifitseeritud sessioonidele.
+ ⚠ Selles jututoas on verifitseerimata seadmeid, mis ei saa sinu saadetud sõnumeid dekrüptida.
+ Teised osapooled nii otsesõnumites kui jututubades näevad sinu kõikide sessioonide loendit.
+\n
+\nSee tähendab, et nad võivad uskuda, et tegemist on tõesti sinuga. Samal ajal näevad ka siin sisestatud sessiooninime.
+ Sessioonide nimede muutmine
+ Verifitseeritud sessioonid on sellised, kuhu sa oled oma kasutajanime ja salasõnaga sisse loginud ning mille puhul oled risttunnustamise läbi teinud või paroolifraasi abil ta turvaliseks märkinud.
+\n
+\nSee tähendab, et nendes sessioonides on olemas sinu varasemate sõnumite krüptovõtmed ja teistele osapooltele on nad tuvastatavad nii, et tegemist on tõesti sinuga.
+ Verifitseeritud sessioonid
+ Verifitseerimata sessioonid on sellised, kuhu sa oled oma kasutajanime ja salasõnaga sisse loginud, kuid mille puhul on risttunnustamine tegemata.
+\n
+\nPalun kontrolli, et need on sulle teadaolevad sessioonid. Vastasel korral võib olla tegemist sinu kasutajakonto lubamatu kasutamisega.
+ Verifitseerimata sessioonid
+ Sessioonid, mida sa pole mõnda aega kasutanud, saavad jätkuvalt pärida sinu krüptovõtmeid.
+\n
+\nSelliste sessioonide eemaldamine parandab jõudlust ja turvalisust ning sul on lihtsam märgata, kui loendisse tekib midagi kahtlast.
+ Mitteaktiivsed sessioonid
+ Palun arvesta, et sessioonide nimed on näha ka kõikidele osapooltele, kellega sa suhtled.
+ Sinu enda kirjutatud sessiooninimede alusel on sul oma seadmeid lihtsam ära tunda.
+ Sessiooni nimi
+ Muuda sessiooni nime
+ Logi sellest sessioonist välja
+ Verifitseerimata · Sinu praegune sessioon
+ Alusta ringhäälingukõnet
+ Selle krüptitud sõnumi autentsus pole selles seadmes tagatud.
+ Väldi sisetuste ja muude isikustatud andmete edastamist väljaspoole seda rakendust. Palun arvesta, et kõik klaviatuurirakendused ei pruugi seda seadistust tunnistada.
+ Privaatne klahvistik
+ Lisab vormindamata sõnumi ette (╯°□°)╯︵ ┻━┻
+ Ringhäälingukõne
+ Ava arendaja töövahendite vaade
+ Ära iialgi saada selles jututoas krüptitud sõnumeid verifitseerimata sessioonidesse.
+ Selge lugu
+ Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim)
+ Võta kasutusele vormindatud teksti pruukiv tekstitoimeti
+ Vaata seadet, kus sa oled Matrix\'i võtku loginud - seal peaks nüüd kuvatama QR-koodi. Kinnita, et allpool toodud QR-kood on sama kui tolles seadmes kuvatav kood:
+ Sa võid seda seadet kasutada nutiseadme või veebirakenduse sisselogimiseks QR-koodi alusel. Sa saad seda teha kahel moel:
+ Kasuta allajoonitud kirja
+ Kasuta läbijoonitud kirja
+ Kasuta kaldkirja
+ Kasuta paksu kirja
+ Palun vaata, et sa kindlasti tead, kust see QR-kood kuvatakse. Sellisel viisil seadmete sidumisel sa annad oma kasutajakontole täiemahulise ligipääsu.
+ Kinnita
+ Proovi uuesti
+ Ei klapi\?
+ Logime sind võrku
+ Loon ühendust seadmega
+ Loe QR-koodi
+ Kas logid sisse nutiseadmest\?
+ Näita selles seadmes QR-koodi
+ Vali „Loe QR-koodi“
+ Alusta sisselogimisvaatest
+ Vali „Logi võrku QR-koodi abil“
+ Alusta sisselogimisvaatest
+ Vali „Näita selles seadmes QR-koodi“
+ Ava Seadistused -> Turvalisus ja privaatsus -> Näita kõiki sessioone
+ Ava ${app_name} oma teises seades
+ Teine seade lükkas päringu tagasi.
+ Sidumine ei lõppenud etteantud aja jooksul.
+ Sidumine selle seadmega ei ole toetatud.
+ Seoste loomine ei õnnestunud
+ Turvaline ühendus on olemas
+ Loe QR-koodi seadmega, kus sa oled Matrix\'i võrgust välja loginud.
+ Järgneva QR-koodi skaneerimiseks kasuta seadet, kus sa oled Matrix\'i võrku loginud:
+ Logi sisse QR-koodi abil
+ Kasuta selle seadme kaamerat ja logi sisse teises seadmes kuvatud QR-koodi alusel:
+ Loe QR-koodi
+ 3
+ 2
+ 1
+ Sessioonide paremaks tuvastamiseks saad nüüd sessioonihalduris salvestada klientrakenduse nime, versiooni ja aadressi.
+ Luba klientrakenduse teabe salvestamine
+ Sellega saad parema ülevaate oma sessioonidest ja võimaluse neid mugavasti hallata.
+ Kasuta uut sessioonihaldurit
+ Logi sisse QR-koodi abil
+ Operatsioonisüsteem
+ Mudel
+ Brauser
+ URL
+ Versioon
+ Nimi
+ Rakendus
+ Luba selles sessioonis tõuketeavitused.
+ Tõuketeavitused
+ Selle sessiooni olekut ei saa tuvastada enne kui oled ta verifitseerinud.
+ Verifitseerimise olek on määratlemata
+ Loe QR-koodi
+ Kasutusel:
+ Sessiooni tunnus:
+ Midagi läks nüüd sassi. Palun kontrolli oma seadme võrguühendust ja proovi uuesti.
+ Anna õigused
+ ${app_name} vajab teavituste näitamiseks õigusi.
+\nPalun luba vastavad õigused.
+ ${app_name} vajab teavituste näitamiseks õigusi. Teavituste sisuks võivad olla sulle saadetud sõnumid, kutsed ja muud olulist.
+\n
+\nJärgmistes vaadetes palun anna sellele rakendusele teavituste kuvamiseks vajalikud õigused.
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml
index 9012bc2ebe..dbf3658887 100644
--- a/library/ui-strings/src/main/res/values-fa/strings.xml
+++ b/library/ui-strings/src/main/res/values-fa/strings.xml
@@ -155,7 +155,7 @@
به میهمانان اجازهٔ پیوستن به گروه دادید.میمهانان را از پیوستن به گروه بازداشتید.رمزنگاری سرتاسری را روشن کردید.
- رمزنگاری سرتاسری را روشن کردید (الگوریتم ناشناخته %1$s).
+ رمزنگاری سرتاسری را روشن کردید (الگوریتم تشخیصدادهنشده %1$s).مهمانها را از پیوستن به اتاق بازداشتید.%1$s مهمانها را از پیوستن به اتاق بازداشت.به مهمانها اجازه دادید به اینجا بپیوندند.
@@ -659,7 +659,7 @@
مدیریت تنظیمات کشفتان.نمایش همهٔ پیامها از %s؟رایانامهها و شماره تلفنها
- مدیریت رایانامهها و شماره تلفنهای پیوسته به حساب ماتریکستان
+ مدیریت نشانیهای رایانامه و شماره تلفنهای پیوسته به حساب ماتریکستان۳ روز۱ هفته۱ ماه
@@ -822,7 +822,7 @@
پیکربندی کارساز هویتتغییر کارساز هویتنشانیهای رایانامهٔ قابلکشف
- گزینههای کشف به محض افزودن یک رایانامه، ظاهر خواهند شد.
+ گزینههای کشف به محض افزودن یک نشانی رایانامه، ظاهر خواهند شد.گزینههای کشف به محض افزودن یک شماره تلفن، ظاهر خواهند شد.شماره تلفنهای قابلکشفبه کار انداختن گزارشهای پرگو.
@@ -1234,7 +1234,7 @@
مطمئن شوید لینک فعالسازیای را که به ایمیل شما ارسال شده، باز کردهاید.برداشتن %s؟شماره تلفنها
- هیچ رایانامهای به حسابتان افزوده نشده
+ هیچ نشانی رایانامهای به حسابتان افزوده نشدهنشانیهای رایانامههیچ شماره تلفنی به حسابتان افزوده نشدهنتیجهای در پی نداشت
@@ -1397,7 +1397,7 @@
ثبت نام در %1$sشماره تلفن نامعتبر است. لطفا آن را بررسی کنیدما یک کد فعالسازی به %1$s ارسال کردیم. لطفا آن را وارد کنید.
- برای بازیابی حساب خود یک ایمیل تنظیم کنید. بعداً، در صورتی که تمایل داشتید میتوانید به افراد اجازه دهید با ایمیل، شما را پیدا کنند.
+ برای بازیابی حسابتان، نشانی رایانامهای تنظیم کنید. بعداً میتوانید بگذارید افرادی که میشناسید، با این نشانی بیابندتان.یک شماره تلفن تنظیم کنید تا در صورتی که تمایل دارید، به افراد اجازه پیدا کردن شما را با استفاده از آن بدهد.گذرواژه شما هنوز تغییر نکردهاست.
\n
@@ -1407,12 +1407,12 @@
برای تایید گذرواژه جدید لینک ارسالی را باز کرده، سپس روی متن زیر کلیک کنید.یک ایمیل تائید به %1$s ارسال شد.صندوق ایمیل خود را بررسی کنید
- این ایمیل به هیچ حسابی متصل نشدهاست
+ این نشانی رایانامه به هیچ حسابی پیوند داده نشدهبا تغییر گذرواژه ، کلیدهای رمزگذاری سرتاسر در تمام نشستهای شما تغییر کرده و تاریخچه گفتگوی رمزشدهی شما غیرقابل خواندن میشود. قبل از تغییر گذرواژه، کلید امنیتی یا کلید پشتیبان را وارد کرده و یا کلیدهای اتاق خود را از نشست دیگری استخراج کنید.بعدیبرای تأیید تنظیم گذرواژهی جدید ، یک ایمیل تأیید به آدرس ایمیل شما ارسال خواهد شد.بازنشانی گذرواژه در %1$s
- این ایمیل به هیچ حسابی مرتبط نیست.
+ این نشانی رایانامه به هیچ حسابی مرتبط نیست.برنامه قادر به ایجاد حساب در این سرور نیست.
\n
\nآیا می خواهید با استفاده از مرورگر حساب کاربری بسازید؟
@@ -1427,7 +1427,7 @@
درست مانند ایمیل، حسابهای کاربری یک خانه دارند؛ اگرچه می توانید با هر کسی که دوست دارید، صحبت کنیدکارسازی برگزینیدشروع کنید
- تجارب خود را گسترش داده و شخصیسازی کنید
+ تجربههایتان را گسترش داده و شخصیسازی کنیدگفتگوهای خصوصی خود را با رمزگذاری محافظت کنیدبا افراد به صورت مستقیم و یا در قالب گروه گپ بزنیدصاحب گفتگوهای خود باشید.
@@ -1484,18 +1484,18 @@
لطفاً نشانی کارساز هویت را وارد کنیدنتوانست به کارساز هویت وصل شودیک نشانی کارساز هویت وارد کنید
- ما یک ایمیل تأیید به %s ارسال کردیم، ابتدا ایمیل خود را بررسی کرده و روی لینک تأیید کلیک کنید
- ما یک ایمیل تأیید به %s ارسال کردیم، ایمیل خود را بررسی کرده و روی لینک تأیید کلیک کنید
+ رایانامهای به %s فرستادیم. لطفاً نخست رایانامهتان را بررسی کرده و روی پیوند تأیید کلیک کنید
+ رایانامهای به %s فرستادیم. رایانامهتان را بررسی کرده و روی پیوند تأیید کلیک کنیدقطع ارتباط با سرور هویتسنجی به این معنی است که توسط کاربران دیگر قابل شناسایی نخواهید بود و نمی توانید دیگران را از طریق ایمیل یا تلفن دعوت کنید.در حال استفاده از کارساز هویتی نیستید. برای کشق و قابل کشف بودن به دست آشنایان موجودی که میشناسید، یک کارساز هویت در زیر پیکربندی کنید.دارید برای کشف و قابل کشف بودن به دست آشنایان موجودی که میشناسید از %1$s استفاده میکنید.ثبت ژتونفرمت:آدرس:
- نام نشست:
- نام برنامه:
- کلید push:
- شناسه برنامه:
+ نام نمایشی نشست:
+ نام نمایشی کاره:
+ کلید ارسال:
+ شناسهٔ کاره:هیچ push gatewayای ثبت نشده استهیچ قانونی برای push تعریف نشده استشما در حال مشاهده این اتاق هستید!
@@ -1680,7 +1680,7 @@
عبارت رمزی که فقط خودتان میدانید را وارد کرده و کلیدی برای پشتیبان تولید کنید.به هنوتم جایگزین، میتوانید نشامی هر کارساز هویت دیگری را وارد کنیدکارساز خانگیتان (%1$s) پیشنهاد استفاده از %2$s برای کارساز هویتتان را میدهد
- برای محرمانگیتان، المنت تنها از فرستادن شماره تلفن و رایانامههای کاربری در هم ریخته پشتیبانی میکند.
+ برای محرمانگیتان، ${app_name} تنها از فرستادن شمارههای تلفن و نشانیهای رایانامهٔ کاربری در هم ریخته پشتیبانی میکند.این عملیات ممکن نیست. کارساز خانگی منقضی شده است.نتوانستیم کاربران را دعوت کنیم. لطفاً کاربرانی که میخواهید دعوت کنید را بررسی کرده و دوباره تلاش کنید.نتوانستیم پیامتان را ایجاد کنیم. لطفاً کاربرانی که میخواهید دعوت کنید را بررسی کرده و دوباره تلاش کنید.
@@ -1747,7 +1747,7 @@
دستگاهی را که میتوانید با استفاده از آنها خود را تایید کنید نشان بده%d دستگاهی را که میتوانید با استفاده از آنها خود را تایید کنید نشان بده
- رضاییتان را برای فرستادن رایانامهها و شمارهتلفنها به این کارساز هویت به منظور کشف دیگر کاربران از آشنایانتان، دادهاید.
+ رضاییتان را برای فرستادن نشانیهای رایانامه و شمارههای تلفن به این کارساز هویت به منظور کشف دیگر کاربران از آشنایانتان، دادهاید.در حال حاضر این اتاق قابل دسترسی نیست.
\nبعدا دوباره تلاش کنید، یا از مدیر اتاق بخواهید بررسی کند که آیا دسترسی دارید.انتشار این نشانی
@@ -2141,7 +2141,7 @@
اشارهها و کلیدواژگانآگاهیهای پیشگزیده%s در تنظیمات برای دریافت مستقیم دعوتها در المنت.
- این رایانامه را به حسابتان پیوند دهید
+ پیوند این نشانی رایانامه به حسابتاناین دعوت به این فضا به %s فرستاده شده که با حسابتان در ارتباط نیستاین دعوت به این اتاق به %s فرستاده شده که با حسابتان در ارتباط نیستاین اتاق را از %1$s به %2$s ارتقا خواهید داد.
@@ -2211,7 +2211,7 @@
دسترسی فضاچه کسی میتواند دسترسی داشته باشد؟به کار انداختن آگاهیهای رایانامهای برای %s
- برای دریافت رایانامه با آگاهی، لطفاً رایانامهای را به حساب ماتریکستان وصل کنید
+ برای دریافت رایانامه با آگاهی، لطفاً نشانی رایانامهای را به حساب ماتریکستان وصل کنیدآگاهی رایانامهایارتقای فضاتغییر نام فضا
@@ -2258,13 +2258,13 @@
پرسش یا موضوع نظرسنجیایجاد نظرسنجینظرسنجی
- فرستادن رایانامهّا و شمارههای تلفن به %s
+ فرستادن نشانیهای رایانامه و شمارههای تلفن به %sآشنایانتان خصوصی هستند. برای کشف کاربران از آشنایانتان، نیاز به اجازهتان برای فرستادن اطّلاعات آشنا به کارساز هویتتان داریم.نشست خارج شده است!اتاق ترک شده است!با فرستادن این اطّلاعات موافقید؟اکنون نه
- برای کشف آشنایان موجود، لازم است اطلاعات آشنایان (رایانامهها و شماره تلفنها) را به کارساز هویتتان بفرستید. برای محرمانگیتان، دادههایتان را پیش از فرستادن، در هم میریزیم.
+ برای کشف آشنایان موجود، لازم است اطلاعات آشنایان (نشانیهای رایانامه و شمارههای تلفن) را به کارساز هویتتان بفرستید. برای محرمانگیتان، دادههایتان را پیش از فرستادن، در هم میریزیم.با همرسانی دادهّای استفادهٔ ناشناس، در تشخیص مشکلها و بهبود المنت یاریمان کنید. برای درک چگونگی استفادهٔ مردم از چندین افزاره، شناسهای کاتورهای بین افزارههایتان همرسانی خواهیم کرد.
\n
\nمیتوانید از %s قوانینمان را بخوانید.
@@ -2600,7 +2600,6 @@
\nشاید این کارساز خانگی برای نمایش نقشهها پیکربندی نشده باشد.
گشودن تنظیماتتمامی گپها
- نمایش تمامی نشستها (ن۲، دحت)برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.دیگر نشستهانشستها
@@ -2705,4 +2704,26 @@
غیرفعّال برای ۱ روز یا بیشترغیرفعّال برای %1$d روز یا بیشتر
+ تأیید نشده · نشست کنونیتان
+ اعتبار این پیام رمز شده نمیتواند روی این افزاره تأیید شود.
+ در این اتاق، هرگز پیامهای رمز شده به نشستهای تأیید نشده فرستاده نشود.
+ تغییر نام نشستها
+ نشستهای تأییدشده
+ نشستهای تأییدنشده
+ نشستهای غیرفعّال
+ لطفاً آگاه باشید که نامهای نشست برای افرادی که با آنها در تماسید نیز نشان داده میشود.
+ نامهای نشست شخصی به نظم دادن آسانتر افزارههایتان کمک میکند.
+ نام نشست
+ تغییر نام نشست
+ خروج از این نشست
+ صفحهکلید ناشناس
+ گشودن صفحهٔ ابزارهای توسعهدهنده
+ به کار انداختن پیامهای مستقیم تعویقی
+ گرفتم
+ آغاز یک پخش همگانی صوتی
+ (╯°□°)╯︵ ┻━┻ را به ابتدای پیام متنی خام میافزاید
+ پخش همگانی صدا
+ اعطای دسترسی
+ ویرایشگر متن غنی را بیازمایید (حالت متن خام به زودی)
+ به کار انداختن ویرایشگر متن غنی
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml
index c7100e3a1e..b77173519d 100644
--- a/library/ui-strings/src/main/res/values-fr/strings.xml
+++ b/library/ui-strings/src/main/res/values-fr/strings.xml
@@ -869,10 +869,10 @@
Règles de notificationAucune règle de notification définieAucune passerelle de notification enregistrée
- app_id :
- push_key :
- app_display_name :
- session_name :
+ App ID :
+ Clé Push :
+ Nom d’affichage de l’application :
+ Nom d’affichage de la session :URL :Format :Voix et vidéo
@@ -934,11 +934,11 @@
Vous utilisez actuellement %1$s pour découvrir et être découvrable par les contacts existants que vous connaissez.Vous n’utilisez actuellement aucun serveur d’identité. Pour découvrir et être découvrable par les contacts existants que vous connaissez, configurez-en un ci-dessous.Adresses électronique découvrables
- Les options de découverte apparaîtront quand vous aurez ajouté un courriel.
+ Les options de découverte apparaîtront quand vous aurez ajouté une adresse courriel.Les options de découverte apparaîtront quand vous aurez ajouté un numéro de téléphone.La déconnexion du serveur d’identité signifie que vous ne pourrez plus être découvrable par les autres utilisateurs et que vous ne pourrez plus inviter d’autres personnes par courriel ou par téléphone.Numéros de téléphone découvrables
- Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation
+ Nous vous avons envoyé un courriel à %s, consultez vos courriels et cliquez sur le lien de confirmationRenseignez l’URL d’un serveur d’identitéImpossible de se connecter au serveur d’identitéVeuillez renseigner l’URL du serveur d’identité
@@ -1066,7 +1066,7 @@
L’application ne peut pas créer de compte sur ce serveur d’accueil.
\n
\nVoulez-vous vous inscrire en utilisant un client web \?
- Ce courriel n’est associé à aucun compte.
+ Cette adresse de courriel n’est associée à aucun compte.Réinitialiser le mot de passe sur %1$sUn courriel de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe.Suivant
@@ -1075,7 +1075,7 @@
Attention !Le changement de mot de passe réinitialisera toutes les clés de chiffrement sur toutes vos sessions, rendant l’historique des discussions chiffrées illisible. Configurez la sauvegarde de clés ou exportez vos clés de salon depuis une autre session avant de réinitialiser votre mot de passe.Poursuivre
- Ce courriel n’est lié à aucun compte
+ Ce adresse de courriel n’est liée à aucun compteVérifiez votre boîte de réceptionUn courriel de vérification a été envoyé à %1$s.Touchez le lien pour confirmer votre nouveau mot de passe. Après avoir suivi le lien qu’il contient, cliquez ci-dessous.
@@ -1089,7 +1089,7 @@
\n
\nArrêter le processus de changement \?
Définir l’adresse électronique
- Définir une adresse électronique pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec votre adresse électronique.
+ Définir une adresse de courriel pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec cette adresse.CourrielCourriel (facultatif)Suivant
@@ -1432,7 +1432,7 @@
Message suppriméAfficher les messages supprimésAfficher un remplaçant pour les messages supprimés
- Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation
+ Nous vous avons envoyé un courriel à %s, consultez vos courriels et cliquez sur le lien de confirmationLe code de vérification n’est pas correct.MÉDIAIl n’y a aucun média dans ce salon
@@ -1455,7 +1455,7 @@
Cette opération n’est pas possible. Le serveur d’accueil est obsolète.Veuillez d’abord configurer un serveur d’identité.Veuillez d’abord accepter les termes du serveur d’identité dans les paramètres.
- Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses électronique et des numéros de téléphone hachés.
+ Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses de courriel et des numéros de téléphone hachés.L’association a échoué.Il n’y a actuellement aucune association avec cet identifiant.Votre serveur d’accueil (%1$s) propose d’utiliser %2$s comme serveur d’identité
@@ -1669,7 +1669,7 @@
Assurez-vous d\'avoir cliqué sur le lien envoyé par courriel.Supprimer %s \?Numéros de téléphone
- Aucune adresse électronique n’a été ajoutée à votre compte
+ Aucune adresse de courriel n’a été ajoutée à votre compteAdresses électroniquesAucun numéro de téléphone n’a été ajouté à votre compteFiltrer les utilisateurs exclus
@@ -1734,7 +1734,7 @@
%1$d de %2$dAutoriserRévoquer mon autorisation
- Vous avez donné votre autorisation pour envoyer des courriels et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts.
+ Vous avez donné votre autorisation pour envoyer des adresses de courriel et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts.Envoyer des courriels et des numéros de téléphoneSuggestionsUtilisateurs connus
@@ -2142,7 +2142,7 @@
Mentions et mots-clésNotifications par défaut%s dans les paramètres pour recevoir les invitations directement dans ${app_name}.
- Lier ce courriel à votre compte
+ Lier cette adresse de courriel à votre compteCette invitation à cette espace a été envoyée à %s qui n’est pas associé à votre compteCette invitation à ce salon a été envoyée à %s qui n’est pas associé à votre compteTous les salons dans lesquels vous vous trouvez seront affichés sur l’Accueil.
@@ -2258,7 +2258,7 @@
Question ou sujet du sondageCréer un sondageSondage
- Envoyer des courriels et des numéros de téléphone à %s
+ Envoyer des adresses de courriel et des numéros de téléphone à %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 !Le salon a été quitté !
@@ -2600,7 +2600,6 @@
\nCe serveur d’accueil n’a peut-être pas été configuré pour afficher les cartes.
Ouvrir les paramètresToutes les conversations
- Afficher toutes les sessions (V2, en cours)Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.Autres sessionsSessions
@@ -2710,4 +2709,67 @@
Activer les conversations privées différéesUn Element simplifié avec des onglets optionnelsActiver la nouvelle présentation
+ Les autres utilisateurs en conversations privées ou salons qui peuvent vous parler sont capables de voir la liste complète de vos sessions.
+\n
+\nCela leur fournit une preuve de confiance que c’est bien avec vous qu\'ils communiquent, mais cela veut également dire qu’ils peuvent voir le nom de la session que vous saisissez ici.
+ Renommer les sessions
+ Les sessions vérifiées sont celles qui sont identifiées avec votre mot de passe puis vérifiée, soit à l’aide de votre phrase de sécurité, ou bien par la vérification croisée.
+\n
+\nCela signifie qu’elles possèdent les clés de chiffrement de vos messages passés, et certifient aux autres utilisateurs avec qui vous communiquez que ces sessions viennent vraiment de vous.
+ Sessions vérifiées
+ Les sessions non vérifiées sont celles qui sont identifiées avec votre mot de passe sans avoir fait de vérification croisée.
+\n
+\nVous devriez tout particulièrement vous assurer de reconnaître ces sessions, car elles pourraient être la preuve d’un usage non autorisé de votre compte.
+ Sessions non vérifiées
+ Les sessions inactives sont celles que vous n’avez pas utilisées depuis un certain temps, mais qui continue de recevoir les clés de chiffrements.
+\n
+\nLa suppression des sessions inactives améliore la sécurité et la performance, et vous aide à identifier si une nouvelle session est douteuse.
+ Sessions inactives
+ Soyez conscient que les noms de sessions sont également visibles pour les personnes avec lesquelles vous communiquez.
+ Les noms de sessions personnalisés peuvent vous aider à reconnaître vos appareils plus facilement.
+ Nom de la session
+ Renommer la session
+ Se déconnecter de cette session
+ Non vérifiée · Votre session actuelle
+ Démarrer une diffusion audio
+ L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil.
+ Demande au clavier de ne pas mettre à jour les données personnalisées, comme l’historique de la frappe et le dictionnaire composé de ce que vous avez tapé dans vos conversations. Il est possible que certains claviers ne respectent pas ce paramètre.
+ Clavier incognito
+ Préfixe le message par (╯°□°)╯︵ ┻━┻
+ Diffusion audio
+ Ouvrir l’écran des outils développeurs
+ 🔒 Vous avez activé le chiffrement vers les sessions vérifiées uniquement, pour tous les salons, dans les paramètres Sécurité.
+ ⚠ Il y a des appareils non vérifiés dans ce salon, ils ne pourront pas déchiffrer vos messages envoyés.
+ Ne jamais envoyer de messages chiffrés aux sessions non vérifiées dans ce salon.
+ Compris
+ Souligner le texte
+ Barrer le texte
+ Mettre en italique
+ Mettre en gras
+ Enregistre le nom du client, sa version, et son URL pour retrouvez vos sessions plus facilement dans le gestionnaire de sessions.
+ Activer l’enregistrement des informations du client
+ Ayez une meilleur visibilité et plus de contrôle sur toutes vos sessions.
+ Activer le nouveau gestionnaire de session
+ Système d’exploitation
+ Modèle
+ Navigateur
+ URL
+ Version
+ Nom
+ Application
+ Recevoir les notifications push sur cette session.
+ Notifications push
+ Vérifiez votre session actuelle pour découvrir le statut de vérification de cette session.
+ Status de vérification inconnu
+ Activer :
+ Identifiant de session :
+ Quelque chose s’est mal passé. Vérifiez votre connexion réseau et réessayez.
+ Accorder la permission
+ ${app_name} a besoin d’une permission pour afficher les notifications.
+\nVeuillez accorder la permission.
+ ${app_name} a besoin de la permission pour afficher les notifications. Les notifications peuvent afficher vos messages, vos invitations, etc.
+\n
+\nVeuillez autoriser l’accès sur la prochaine fenêtre pour pouvoir voir des notifications.
+ Essayer l’éditeur de texte formaté (le mode texte brut arrive bientôt)
+ Activer l’éditeur de texte formaté
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml
index cac0a2eb5d..59792a9218 100644
--- a/library/ui-strings/src/main/res/values-hu/strings.xml
+++ b/library/ui-strings/src/main/res/values-hu/strings.xml
@@ -569,7 +569,7 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlés
Alapszintű diagnosztika nem talált hibát. Ha még mindig nem kapsz értesítéseket, kérlek küldj egy hiba jegyet amivel segítheted a hibakeresésünket.Egy vagy több teszt is sikertelen volt, próbáld ki a javasolt javítást, javításokat.Egy vagy több teszt sikertelenül végződött, kérlek küldj egy hibabejelentést ami segít nekünk a problémát kivizsgálni.
- Rendszer beállítások.
+ Rendszerbeállítások.Az értesítések engedélyezve vannak a rendszerbeállításokban.Az értesítések tiltva vannak a rendszerbeállításokban.
Kérlek ellenőrizd a rendszerbeállításokat.
@@ -582,7 +582,7 @@ Kérlek ellenőrizd a fiókbeállításokat.
Munkamenet beállítások.Az értesítések engedélyezve vannak ezen az munkameneten.Az értesítések tiltva vannak ezen a munkameneten. Kérlek ellenőrizd a ${app_name} beállításokat.
- Engedélyez
+ EngedélyezésPlay Szolgáltatások ellenőrzéseGoogle Play Services APK elérhető és a legújabb verziójú."${app_name} a Google Play Services-t használja a „push” értesítések fogadásához, de úgy tűnik az nincs megfelelően beállítva:
@@ -824,18 +824,18 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Már nézed ezt a szobát!ÁltalánosBeállítások
- Biztonság & Adatvédelem
+ Biztonság és adatvédelem„Push” szabályok„Push” szabályok nincsenek„Push” átjárók nincsenek regisztrálva
- app_id:
- push_key:
- app_display_name:
- session_name:
+ Alk azon:
+ Push kulcs:
+ Alk. képernyő név:
+ Munkamenet képernyő név:Url:Formátum:
- Hang & Videó
- Segítség & Névjegy
+ Hang és videó
+ Súgó és névjegyToken regisztrálásaJavaslat tételA javaslatodat kérlek ír le alulra.
@@ -897,7 +897,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Amint hozzáadtál egy telefonszámot megjelenik a felderítési beállítási lehetőség.Az azonosítási szerverről való lecsatlakozással nem leszel mások által megtalálható és másokat sem tudsz meghívni e-mail címmel vagy telefonszámmal.Felderíthető telefonszámok
- Megerősítő levelet küldtünk ide: %s, ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra
+ E-mailt küldtünk ide: %s, ellenőrizd és kattints a megerősítő hivatkozásraAdd meg az azonosítási szerver URL-jétAz azonosítási szerverhez nem lehet csatlakozniKérlek add meg az azonosítási szerver url-jét
@@ -1391,7 +1391,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Felhasználókat nem tudtuk meghívni. Ellenőrizd azokat a felhasználókat akiket meg szeretnél hívni és próbáld újra.Üzenet eltávolítvaHelykitöltő mutatása a törölt szövegek helyett
- Megerősítő levelet küldtünk ide: %s, először ellenőrizd az e-mailedet és kattints a megerősítő hivatkozásra
+ E-mailt küldtünk ide: %s, először ellenőrizd és kattints a megerősítő hivatkozásraMÉDIAFÁJLOK%1$s itt: %2$s
@@ -2611,7 +2611,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
AsztaliWebMobil
- Minden munkamenet megjelenítése (V2, WIP)A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.Más munkamenetekMunkamenetek
@@ -2710,4 +2709,100 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
Késleltetett közvetlen üzenetek engedélyezéseEgyszerűsített Element opcionálisan lapokkalÚj kinézet engedélyezése
+ Más felhasználók akikkel közvetlenül vagy szobában beszélgetsz látják a teljes listát a munkameneteidről.
+\n
+\nEzzel ők biztosak lehetnek abban, hogy ténylegesen veled beszélgetnek. Ez azt is jelenti, hogy látják a munkamenet nevét amit itt megadsz.
+ Ellenőrzött munkamenetbe a neveddel és jelszavaddal léptek be és ellenőrizve lett vagy a biztonsági jelmondattal vagy másik munkamenetből.
+\n
+\nEz azt jelenti, hogy tartalmazzák a titkosítási kulcsokat az régi üzenetekhez, és biztosítja a többieket a kommunikációban, hogy ezt a munkamenetet tényleg te használod.
+ Aláhúzott
+ Áthúzott
+ Dőlt
+ Félkövér
+ Kliens neve, verziója és url felvétele a munkamenet könnyebb azonosításához a munkamenet kezelőben.
+ Kliens információ felvételének engedélyezése
+ Jobb áttekintés és felügyelet a munkamenetek felett.
+ Új munkamenet kezelő engedélyezése
+ Munkamenet átnevezése
+ Hitelesített munkamenetek
+ Az ellenőrizetlen munkamenetek azok amikre a felhasználói neveddel és jelszavaddal léptek be de nem lett ellenőrizve.
+\n
+\nMindenképpen győződj meg arról, hogy felismered ezeket a munkameneteket mert lehet, hogy illetéktelenül használják a fiókodat.
+ Ellenőrizetlen munkamenetek
+ Az inaktív munkamenetek azok amiket egy ideje nem használtál, de továbbra is megkapják a titkosítási kulcsokat.
+\n
+\nA nem aktív munkamenetek törlésével növelhető a biztonság és a sebesség valamint könnyebb lesz felismerni a gyanús munkameneteket.
+ Nem aktív munkamenetek
+ Fontos, hogy a munkamenet neve a kommunikációban résztvevők számára látható.
+ Az egyedi munkamenet név segíthet az eszköz könnyebb felismerésében.
+ Munkamenet neve
+ Munkamenet átnevezése
+ Operációs rendszer
+ Modell
+ Böngésző
+ URL
+ Verzió
+ Név
+ Alkalmazás
+ Push értesítések fogadása ebben a munkamenetben.
+ Push értesítések
+ Kijelentkezés ebből a munkamenetből
+ Ellenőrizetlen · A jelenlegi munkameneted
+ Ellenőrizd a jelenlegi munkamenetedet, hogy ismert állapotba kerüljön.
+ Ismeretlen ellenőrzési státusz
+ Hang közvetítés indítása
+ A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni.
+ Utasítja a billentyűzetet, hogy ne mentsen személyre szabott adatokat, mint előzmények vagy szótár abból amit a beszélgetésekben írsz. Vedd figyelembe, hogy nem minden billentyűzet veszi ezt figyelembe.
+ Inkognitó billentyűzet
+ (╯°□°)╯︵ ┻━┻ -t tesz a szöveg elejére
+ Hang közvetítés
+ Engedélyezve:
+ Munkamenet azon.:
+ Valami nem sikerült. Kérlek ellenőrizd a hálózati kapcsolatot és próbáld újra.
+ A fejlesztői eszközök képernyő megnyitása
+ 🔒 Bekapcsoltad a Biztonsági beállításoknál, hogy csak ellenőrzött munkamenetek számára legyen titkosítva az üzenet bármely szobában.
+ ⚠ Ellenőrizetlen eszközök vannak a szobában, ezek nem fogják tudni visszafejteni az általad küldött üzeneteket.
+ Sose küldj titkosított üzenetet ellenőrizetlen munkamenetbe ebből a munkamenetből ebben a szobában.
+ Engedély megadása
+ ${app_name} alkalmazásnak értesítések megjelenítéséhez engedélyre van szüksége.
+\nKérjük, adj rá engedélyt.
+ ${app_name} alkalmazásnak szüksége van engedélyre az értesítések megjelenítéséhez. Az értesítés megjelenítheti az üzenetet, meghívót, stb.
+\n
+\nA következő felugró ablakban adj rá engedélyt, hogy az értesítések megjelenhessenek.
+ Próbálja ki az új szövegbevitelt (hamarosan érkezik a sima szöveges üzemmód)
+ Vizuális szerkesztő engedélyezése
+ Értem
+ Nem egyezik\?
+ Bejelentkeztetés
+ Mobil eszközzel jelentkezel be\?
+ Kezd a bejelentkező képernyőn
+ Kezd a bejelentkező képernyőn
+ Nézd meg a már bejelentkezett eszközödet, az alábbi kódot kell megjelenítenie. Erősítsd meg, hogy az alábbi kód megegyezik a másik eszközön láthatóval:
+ Használd a már belépett eszközt az alábbi QR kód beolvasásához:
+ Ezzel az eszközzel, QR kód segítségével, bejelentkezhetsz mobil és webes munkamenetbe. Két lehetőséged is van:
+ Győződj meg a kód eredetéről. Az eszközök összekötésével esetleg valakinek teljes hozzáférést adhatsz a fiókodhoz.
+ Megerősítés
+ Próbáld újra
+ Csatlakozás az eszközhöz
+ QR kód beolvasása
+ QR kód megjelenítése ezen az eszközön
+ Válaszd ezt: „QR kód beolvasása”
+ Válaszd ezt: „Belépés QR kóddal”
+ Válaszd ezt: „QR kód megjelenítése ezen az eszközön”
+ Menj a Beállítások -> Biztonság és Adatvédelem -> Minden munkamenet megjelenítése menübe
+ Nyisd meg a(z) ${app_name} alkalmazást a másik eszközön
+ A kérést elutasították a másik eszközön.
+ Az összekötés az elvárt időn belül nem fejeződött be.
+ Összekötés ezzel az eszközzel nem támogatott.
+ Kapcsolat sikertelen
+ Biztonságos kapcsolat beállítva
+ A kijelentkezett eszközzel olvasd be a QR kódot alább.
+ Belépés QR kóddal
+ Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására:
+ QR kód beolvasása
+ 3
+ 2
+ 1
+ Belépés QR kóddal
+ QR kód beolvasása
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml
index 7b103a9131..6d20563d55 100644
--- a/library/ui-strings/src/main/res/values-in/strings.xml
+++ b/library/ui-strings/src/main/res/values-in/strings.xml
@@ -95,7 +95,7 @@
Nama pengguna dan/atau kata sandi salahVerifikasi alamat email gagal: pastikan tautan yang termuat di email telah diklikJSON amburadul
- Tidak berisi JSON yang sah
+ Tidak berisi JSON yang absahPengajuan yang dikirimkan terlalu banyakPanggilan Video MasukPanggilan Suara Masuk
@@ -183,8 +183,8 @@
Tampilkan info aplikasi dalam pengaturan sistem.Info aplikasiSuara pemberitahuan
- Perbolehkan pemberitahuan untuk akun ini
- Perbolehkan pemberitahuan untuk perangkat ini
+ Aktifkan pemberitahuan untuk akun ini
+ Aktifkan pemberitahuan untuk sesi iniPesan yang berisikan nama layarkuPesan berisikan nama layarkuPesan percakapan empat mata
@@ -216,7 +216,7 @@
Tidak ada user_id dalam permohonan.Ruang %s tidak terlihat.Ada parameter penting yang hilang.
- Tambahkan apps Matrix
+ Tambahkan aplikasi MatrixGunakan kamera bawaanAnda menambahkan perangkat baru \'%s\', yang sedang meminta kunci enkripsi.Perangkat Anda yang belum terverifikasi \'%s\' sedang meminta kunci enkripsi.
@@ -234,7 +234,7 @@
Keluarkan pengguna dengan id berikutUbah nama panggilan layar AndaMati/Nyalakan markdown
- Untuk memperbaiki kepengurusan Apps Matrix
+ Untuk memperbaiki kepengurusan Aplikasi MatrixMatiBerisikPesan terenkripsi
@@ -394,7 +394,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
\nMohon periksa pengaturan akun anda.
PerbolehkanPengaturan Perangkat.
- Pemberitahuan diperbolehkan untuk perangkat ini.
+ Pemberitahuan diperbolehkan untuk sesi ini.Notifikasi tidak diaktifkan pada sesi ini.
\nMohon periksa pengaturan ${app_name}.Perbolehkan
@@ -414,7 +414,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
\n%1$s
Mulai ketika menyalakan perangkatLayanan akan dimulai ketika perangkat dinyalakan kembali.
- Layanan tidak akan mulai ketika perangkat dinyalakan kembali, Anda tidak akan menerima pemberitahuan hingga Anda membuka ${app_name}.
+ Layanan tidak akan mulai ketika perangkat dinyalakan kembali, Anda tidak akan menerima pemberitahuan sampai Anda membuka ${app_name}.Perbolehkan memulai ketika perangkat dinyalakanPeriksa halangan di balik layarLarangan latar belakang dinonaktifkan untuk ${app_name}. Percobaan ini sebaiknya dijalankan menggunakan jaringan data ponsel (bukan WiFi).
@@ -442,7 +442,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Urgensi pemberitahuan lewat kejadianPengaturan Sesukanya.Perhatikan bahwa sebagian jenis pesan tersetel diam (mengeluarkan pemberitahuan tanpa suara).
- Sebagian pemberitahuan dimatikan dalam aturan Anda.
+ Sebagian pemberitahuan dimatikan dalam pengaturan Anda.[%1$s]
\nError ini di luar kendali ${app_name} dan menurut Google, error ini muncul ketika terlalu banyak aplikasi terdaftar dengan FCM pada perangkat tersebut. Error ini tidak seharusnya mempengaruhi pengguna biasa.[%1$s]
@@ -450,7 +450,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.[%1$s]
\nError ini di luar kendali ${app_name}. Tidak terdapat akun Google pada perangkat. Mohon buka pengelola akun dan tambahkan akun Google.Tambah Akun
- Apabila perangkat tidak sedang diisi atau dipergunakan dengan layar dimatikan, perangkat masuk mode Doze. Ini akan menghalangi aplikasi mengakses jaringan dan menunda tugas, sinkronisasi, dan alarm standar.
+ Apabila perangkat tidak sedang diisi atau dipergunakan dengan layar dimatikan, perangkat masuk mode tidur. Ini akan menghalangi aplikasi mengakses jaringan dan menunda tugas, sinkronisasi, dan alarm standar.Abaikan OptimisasiKelola Pemberitahuan BerisikKelola Pemberitahuan Panggilan
@@ -459,7 +459,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Pengelolaan Kunci KriptografiPratinjau tautan dalam obrolan apabila homeserver mendukung fitur ini.Kirim pemberitahuan mengetik
- Beritahu pengguna lain bahwa Anda sedang mengetik.
+ Beri tahu pengguna lain bahwa Anda sedang mengetik.Format markdownFormat pesan menggunakan sintaks markdown sebelum dikirim. Ini mengizinkan format lanjutan seperti menggunakan tanda bintang untuk menunjukkan teks miring.Tunjukkan tanda telah dibaca
@@ -565,7 +565,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Mengubah alamat utama untuk ruangan iniMengubah avatar ruanganMengubah widget
- Beritahu semuanya
+ Beri tahu semuanyaMenghapus pesan yang dikirim dari yang lainUbah pengaturanPeran bawaan
@@ -610,11 +610,11 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Meminta untuk konfirmasi sebelum memulai panggilanCegah panggilan tidak disengaja
- Tidak sah, tidak ada kredensial otentikasi yang valid
+ Tidak sah, tidak ada kredensial otentikasi yang absahKesalahan SSL.Kesalahan SSL: identitas peer belum diverifikasi.Tidak dapat mencapai homeserver pada URL ini, silakan periksa
- Ini bukan alamat server Matrix yang valid
+ Ini bukan alamat server Matrix yang absahNomor telepon ini sudah ditentukan.Masuk dengan single sign-onGunakan sebagai bawaan dan jangan tanya lagi
@@ -676,10 +676,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
BatalkanTidak AdaBawaan Sistem
- Anda mengaktifkan enkripsi ujung-ke-ujung. (algoritma tidak dikenali %1$s).
- %1$s mengaktifkan enkripsi ujung-ke-ujung. (algoritma tidak dikenali %2$s).
- Anda mengaktifkan enkripsi ujung-ke-ujung.
- %1$s mengaktifkan enkripsi ujung-ke-ujung.
+ Anda mengaktifkan enkripsi ujung ke ujung. (algoritma tidak dikenali %1$s).
+ %1$s mengaktifkan enkripsi ujung ke ujung. (algoritma tidak dikenali %2$s).
+ Anda mengaktifkan enkripsi ujung ke ujung.
+ %1$s mengaktifkan enkripsi ujung ke ujung.Anda telah mencegah para tamu untuk bergabung ke ruangan.%1$s telah mencegah para tamu untuk bergabung ke ruangan.Anda telah mencegah para tamu untuk bergabung ke ruangan.
@@ -816,7 +816,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
🎉 Semua server dilarang untuk berpartisipasi! Ruangan ini tidak lagi dapat digunakan.Tidak ada berubahan.Nomor telepon
- Tidak ada email yang ditambahkan ke akun Anda
+ Tidak ada alamat email yang ditambahkan ke akun AndaSurel• Server yang cocok dengan literal IP sekarang dilarang.• Server yang cocok dengan %s sekarang dilarang.
@@ -962,16 +962,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Pengaturan akunAnda dapat mengelola notifikasi di %1$s.Harap dicatat bahwa pemberitahuan sebutan & kata kunci tidak tersedia dalam ruangan terenkripsi di ponsel.
- Beritahu saya untuk
+ Beri tahu saya untukPutar suara ranaPilihSumber media bawaanPilihKompresi bawaanMedia
- Kelola email dan nomor telepon yang ditautkan ke akun Matrix Anda
+ Kelola alamat email dan nomor telepon yang ditautkan ke akun Matrix AndaEmail dan nomor telepon
- Sandi tidak valid
+ Kata sandi tidak absahSandiAktifkan \'Izinkan integrasi\' di Pengaturan untuk melakukan ini.Integrasi dinonaktifkan
@@ -997,7 +997,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
%d detik
- Anda tidak akan diberitahu tentang pesan masuk saat aplikasi berada di latar belakang.
+ Anda tidak akan diberi tahu tentang pesan masuk saat aplikasi berada di latar belakang.Tidak ada sinkronisasi latar belakang${app_name} akan disinkronkan di latar belakang secara berkala pada waktu yang tepat (dapat dikonfigurasi).
\nIni akan memengaruhi penggunaan radio dan baterai, dan ada juga pemberitahuan yang ditampilkan permanen menyatakan bahwa ${app_name} sedang mendengarkan peristiwa.
@@ -1021,7 +1021,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Kata kunci tidak boleh diawali dengan \'.\'Tambahkan kata kunci baruKata kunci Anda
- Beritahu saya untuk
+ Beri tahu saya untukLainnyaSebutan dan Kata KunciNotifikasi Bawaan
@@ -1035,16 +1035,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Panggilan video dengan %sPanggilan berdering…Space
- Kami mengirimi Anda email konfirmasi ke %s, mohon periksa email Anda dan klik tautan konfirmasi
- Opsi penemuan akan muncul setelah Anda menambahkan email.
+ Kami mengirim Anda email konfirmasi ke %s, mohon periksa email Anda dan klik tautan konfirmasi
+ Opsi penemuan akan muncul setelah Anda menambahkan sebuah alamat email.Memutuskan sambungan dari server identitas Anda akan membuat Anda tidak dapat ditemukan oleh pengguna lain dan Anda tidak akan dapat mengundang orang lain melalui email atau nomor telepon.Kirim email dan nomor telepon
- Anda telah memberikan persetujuan untuk mengirim email dan nomor telepon ke server identitas ini untuk menemukan pengguna lain dari kontak Anda.
+ Anda telah memberikan persetujuan untuk mengirim alamat email dan nomor telepon ke server identitas ini untuk menemukan pengguna lain dari kontak Anda.Anda sedang berbagi email atau nomor telepon di server identitas %1$s. Anda harus menyambungkan kembali ke %2$s untuk berhenti membagikannya.Setujui Persyaratan Layanan server identitas (%s) agar Anda dapat ditemukan melalui email atau nomor telepon.
- Kami mengirimi Anda email konfirmasi ke %s, periksa email Anda dan klik tautan konfirmasi
+ Kami mengirim Anda sebuah email ke %s, periksa email Anda dan klik tautan konfirmasiSetel ulang sandi di %1$s
- Email ini tidak terkait dengan akun apa pun.
+ Alamat email ini tidak terkait dengan akun apa pun.Aplikasi tidak dapat membuat akun di homeserver ini.
\n
\nApakah Anda ingin mendaftar menggunakan klien web\?
@@ -1080,7 +1080,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Sama seperti email, akun memiliki satu tempat, tetapi Anda dapat berkomunikasi dengan siapa sajaPilih serverMulai
- Luaskan & sesuaikan pengalaman Anda
+ Tingkatkan & sesuaikan pengalaman AndaIni adalah percakapan Anda. Miliki percakapan Anda.Jaga percakapan tetap pribadi dengan enkripsiChat dengan orang-orang secara langsung atau dalam grup
@@ -1148,9 +1148,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
%1$s: %2$s %3$sTambahkan tab terdedikasi untuk notifikasi yang belum dibaca di layar utama.File ini terlalu besar untuk diupload.
- Cadangan mempunyai tanda tangan yang tidak valid dari sesi %s yang belum diverifikasi
- Cadangan mempunyai tanda tangan yang tidak valid dari sesi %s yang terverifikasi
- Cadangan mempunyai tanda tangan yang valid dari sesi %s yang belum diverifikasi
+ Cadangan mempunyai tanda tangan yang tidak absah dari sesi %s yang belum diverifikasi
+ Cadangan mempunyai tanda tangan yang tidak absah dari sesi %s yang terverifikasi
+ Cadangan mempunyai tanda tangan yang absah dari sesi %s yang belum diverifikasiMengirim gambar mini (%1$s / %2$s)%d pengguna telah membaca
@@ -1240,10 +1240,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Suara & VideoFormat:Url:
- session_name:
- app_display_name:
- push_key:
- app_id:
+ Nama Tampilan Sesi:
+ Nama Tampilan Aplikasi:
+ Kunci Dorongan:
+ ID Aplikasi:Tidak ada gateway dorong terdaftarTidak ada aturan push yang ditentukanAturan Push
@@ -1278,8 +1278,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Hapus CadanganMemeriksa status cadanganMenghapus cadangan…
- Cadangan mempunyai tanda tangan yang valid dari sesi %s yang terverifikasi.
- Cadangan mempunyai tanda tangan yang valid dari sesi ini.
+ Cadangan mempunyai tanda tangan yang absah dari sesi %s yang terverifikasi.
+ Cadangan mempunyai tanda tangan yang absah dari sesi ini.Cadangan mempunyai tanda tangan dari sesi tidak dikenal dengan ID %s.Kunci Anda tidak dicadangkan dari sesi ini.Cadangan Kunci belum aktif di sesi ini.
@@ -1342,7 +1342,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Ekspor kunci secara manual(Lanjutan)Mulai menggunakan Cadangan Kunci
- Pesan di ruangan terenkripsi diamankan dengan enkripsi ujung-ke-ujung. Hanya Anda dan penerima memiliki kunci untuk membaca pesan-pesan ini.
+ Pesan di ruangan terenkripsi diamankan dengan enkripsi ujung ke ujung. Hanya Anda dan penerima memiliki kunci untuk membaca pesan ini.
\n
\nCadangkan kunci Anda dengan aman untuk menghindari kehilangan kunci Anda.Jangan pernah kehilangan pesan terenkripsi
@@ -1351,7 +1351,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Mohon masukkan frasa sandiFrasa sandi tidak cocokBuat frasa sandi
- Tidak menemukan APK Layanan Google Play yang valid. Notifikasi mungkin tidak berkerja dengan seharusnya.
+ Tidak menemukan APK Layanan Google Play yang absah. Notifikasi mungkin tidak bekerja dengan seharusnya.Keamanan & PrivasiPreferensiUmum
@@ -1435,7 +1435,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Kelola SesiTampilkan Semua SesiSesi Aktif
- Admin server Anda telah menonaktifkan enkripsi ujung-ke-ujung secara bawaan di ruangan & Pesan Langsung privat.
+ Admin server Anda telah menonaktifkan enkripsi ujung ke ujung secara bawaan di ruangan & Pesan Langsung privat.Tanda Tangan Silang dinonaktifkanTanda Tangan Silang diaktifkan.
\nKunci dipercaya.
@@ -1446,7 +1446,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
\nKunci Privat di perangkat.
Tanda Tangan SilangSesi baru Anda telah diverifikasi. Ini memiliki akses ke pesan terenkripsi Anda, dan pengguna lain akan melihatnya sebagai tepercaya.
- Pesan dengan pengguna ini dienkripsi ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga.
+ Pesan dengan pengguna ini dienkripsi ujung ke ujung dan tidak dapat dibaca oleh pihak ketiga.Bandingkan kode dengan yang ditampilkan di layar pengguna lain.Bandingkan emoji yang unik, dan pastikan mereka muncul di urutan yang sama.Supaya aman, lakukan secara langsung atau gunakan cara lain untuk berkomunikasi.
@@ -1454,8 +1454,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Aktifkan enkripsiKetika diaktifkan, enkripsi tidak dapat dinonaktifkan. Pesan yang dikirim di ruangan terenkripsi tidak dapat dilihat oleh servernya, hanya anggota ruangan. Mengaktifkan enkripsi mungkin mencegah banyaknya bot dan jembatan bekerja dengan seharusnya.Aktifkan enkripsi\?
- Anda tidak memiliki izin untuk mengaktifkan enkripsi ujung-ke-ujung di ruangan ini.
- Aktifkan enkripsi ujung-ke-ujung…
+ Anda tidak memiliki izin untuk mengaktifkan enkripsi ujung ke ujung di ruangan ini.
+ Aktifkan enkripsi ujung ke ujung…Editor pesanLinimasaMengirim emote yang dicantum berwarna pelangi
@@ -1490,14 +1490,14 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Lebih banyakPelajari lebih lanjutKeamanan
- Pesan di ruangan ini dienkripsi ujung-ke-ujung.
+ Pesan di ruangan ini dienkripsi secara ujung ke ujung.
\n
\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima memiliki kunci unik untuk mengakses mereka.
- Pesan di ruangan ini dienkripsi ujung-ke-ujung.
+ Pesan di ruangan ini dienkripsi secara ujung ke ujung.
\n
\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima memiliki kunci unik untuk mengakses mereka.
- Pesan ini tidak terenkripsi secara ujung-ke-ujung.
- Pesan di ruangan ini tidak terenkripsi secara ujung-ke-ujung.
+ Pesan ini tidak terenkripsi secara ujung ke ujung.
+ Pesan di ruangan ini tidak terenkripsi secara ujung ke ujung.Menunggu untuk %s…Diverifikasi %sVerifikasi %s
@@ -1587,8 +1587,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
\n• Administrator server Anda telah menghilangkan akses Anda untuk keamanan.Anda telah keluarDilihat oleh
- Tidak dapat menemukan homeserver yang valid. Mohon cek pengenal Anda
- Ini bukan pengenal pengguna yang valid. Format yang diharapkan: \'@pengguna:homeserver.org\'
+ Tidak dapat menemukan homeserver yang absah. Mohon cek pengenal Anda
+ Ini bukan pengenal pengguna yang absah. Format yang diharapkan: \'@pengguna:homeserver.org\'Jika Anda tidak tahu kata sandi Anda, kembali untuk mengatur ulang.ID MatrixJika Anda membuat akun di sebuah homeserver, gunakan ID Matrix Anda (mis. @pengguna:domain.com) dan kata sandi dibawah.
@@ -1616,7 +1616,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Kata sandiNama penggunaNama pengguna atau email
- Nomor telepon kelihatannya tidak valid. Mohon dicek lagi
+ Nomor telepon kelihatannya tidak absah. Mohon dicek lagiNomor telepon internasional harus mulai dengan \'+\'Mohon menggunakan format internasional (nomor telepon harus mulai dengan \'+\')Lanjut
@@ -1632,7 +1632,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
LanjutEmail (opsional)Email
- Atur sebuah email untuk memulihkan akun Anda. Nantinya, Anda dapat mengizinkan orang yang Anda tahu untuk menemukan Anda dari email secara opsional.
+ Atur sebuah alamat email untuk memulihkan akun Anda. Nantinya, Anda dapat mengizinkan orang yang Anda tahu untuk menemukan Anda dari email ini secara opsional.Atur alamat emailKata sandi Anda belum diubah.
\n
@@ -1646,9 +1646,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Ketuk tautan untuk mengkonfirmasi kata sandi baru Anda. Setelah Anda mengikuti petunjuk yang ada di tautan, klik bawahnya.Email verifikasi terkirim ke %1$s.Cek kotak masuk Anda
- Email ini tidak tertaut dengan akun apa pun
+ Alamat email ini tidak tertaut dengan akun apa punLanjut
- Mengubah kata sandi Anda akan mengatur ulang kunci enkripsi ujung-ke-ujung pada semua sesi Anda, yang akan membuat riwayat obrolan terenkripsi tidak dapat dibaca. Atur Cadangan Kunci atau ekspor kunci ruangan Anda dari sesi lain sebelum mengatur ulang kata sandi Anda.
+ Mengubah kata sandi Anda akan mengatur ulang kunci enkripsi ujung ke ujung pada semua sesi Anda, yang akan membuat riwayat obrolan terenkripsi tidak dapat dibaca. Atur Cadangan Kunci atau ekspor kunci ruangan Anda dari sesi lain sebelum mengatur ulang kata sandi Anda.Peringatan!Kata sandi baruEmail
@@ -1760,7 +1760,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
PublikSpace privat untuk Anda & tim AndaSaya dan tim saya
- Space yang privat untuk mengorganisir ruangan Anda
+ Space yang privat untuk mengelola ruangan AndaSaya sajaPastikan orang yang tepat memiliki akses ke %s.Dengan siapa Anda bekerja\?
@@ -1803,9 +1803,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Alat PengembangRuangan publikLihat laporan dibaca
- Jangan beritahu
- Beritahu tanpa suara
- Beritahu dengan suara
+ Jangan beri tahu
+ Beri tahu tanpa suara
+ Beri tahu dengan suaraPesan tidak terkirim karena kesalahanTidak dicentangDicentang
@@ -1814,7 +1814,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Ruangannya belum dibuat. Batalkan pembuatan ruangan\?Tautannya cacatKode QR tidak dipindai!
- Kode QR tidak valid (URI tidak valid)!
+ Kode QR tidak absah (URI tidak absah)!Tidak dapat membuat pesan langsung dengan Anda sendiri!Bagikan melalui teksTidak dapat mencari ruangan ini. Pastikan ruangannya sudah ada.
@@ -1875,7 +1875,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Anda tidak dapat mengakses pesan ini karena pengirim telah sengaja tidak mengirim kuncinyaAnda tidak dapat mengakses pesan ini karena sesi Anda tidak dipercayai oleh pengirimAnda tidak dapat mengakses pesan ini karena Anda telah diblokir oleh pengirim
- Karena enkripsi ujung-ke-ujung, Anda mungkin harus menunggu untuk pesan dari seseorang untuk datang karena kunci enkripsinya tidak dikirim secara benar ke Anda.
+ Karena adanya enkripsi ujung ke ujung, Anda mungkin harus menunggu untuk pesan dari seseorang untuk datang karena kunci enkripsinya tidak dikirim secara benar ke Anda.Menunggu untuk pesan ini, mungkin membutuhkan beberapa waktuAnda tidak dapat mengakses pesan iniAtur avatar
@@ -1910,7 +1910,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Izin pengguna belum diberikan.Tidak ada asosiasi saat ini dengan pengenal ini.Asosiasi telah gagal.
- Untuk pricvasi Anda, ${app_name} hanya mendukung pengiriman email pengguna yang telah di-hash dan nomor telepon.
+ Demi privasi Anda, ${app_name} hanya mendukung pengiriman email pengguna dan nomor telepon yang telah di-hash.Mohon terima ketentuan server identitas ini di pengaturan.Mohon konfigurasi server identitas.Server identitas ini telah usang. ${app_name} hanya mendukung API V2.
@@ -1946,7 +1946,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Beri nama untuk melanjutkan.Gagal untuk memvalidasi PIN, mohon ketuk yang baru.%s di Pengaturan untuk menerima undangan secara langsung di ${app_name}.
- Tautkan email ini ke akun Anda
+ Tautkan alamat email ini ke akun AndaUndangan space ini telah dikirim ke %s yang tidak diasosiasikan dengan akun AndaUndangan ruangan ini telah dikirim ke %s yang tidak diasosiasikan dengan akun AndaPeriksa ulang tautan ini
@@ -2001,7 +2001,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Memeriksa kunci cadangan (%s)Memeriksa kunci cadanganMohon masukkan sebuah kunci pemulihan
- Bukan kunci pemulihan yang valid
+ Bukan kunci pemulihan yang absahGunakan FileMasukkan %s Anda untuk melanjutkanVerifikasi diri Anda dan lainnya untuk tetap membuat pesan Anda aman
@@ -2027,8 +2027,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Anda membuat dan mengatur ruangan ini.%s membuat dan mengatur ruangan ini.Enkripsi tidak diaktifkan
- Pesan di obrolan ini dienkripsi secara ujung-ke-ujung.
- Pesan di ruangan ini dienkripsi secara ujung-ke-ujung. Pelajari lebih lanjut & verifikasi pengguna di profil mereka.
+ Pesan di obrolan ini dienkripsi secara ujung ke ujung.
+ Pesan di ruangan ini dienkripsi secara ujung ke ujung. Pelajari lebih lanjut & verifikasi pengguna di profil mereka.Enkripsi diaktifkanJika Anda batalkan, Anda mungkin kehilangan pesan terenkripsi dan data Anda jika Anda kehilangan akses ke login Anda.
\n
@@ -2165,7 +2165,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.Membuat space…Tampilkan info yang berguna untuk membantu debugging aplikasiTampilkan info debug di layar
- Tidak terlihat sebagai alamat email yang valid
+ Tidak terlihat sebagai alamat email yang absahBuka Pengaturan PenemuanCari dengan nama, ID atau emailBuat Space Baru
@@ -2173,7 +2173,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Akses spaceSiapa yang dapat akses\?Aktifkan notifikasi email untuk %s
- Untuk menerima email dengan notifikasi, mohon tautkan sebuah email ke akun Matrix Anda
+ Untuk menerima email dengan notifikasi, mohon tautkan sebuah alamat email ke akun Matrix AndaNotifikasi emailTingkatkan space iniUbah nama space
@@ -2219,12 +2219,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Pertanyaan atau topik pollBuat PollPoll
- Kirim email dan nomor telepon ke %s
+ Kirim alamat 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!Ruangannya telah ditinggalkan!Apakah Anda setuju untuk mengirimkan info ini\?
- Untuk menemukan kontak yang sudah ada, Anda harus mengirim info kontak (email dan nomor telepon) ke server identitas Anda. Kami meng-hash data Anda sebelum mengirim untuk privasi.
+ Untuk menemukan kontak yang sudah ada, Anda harus mengirim info kontak (email dan nomor telepon) ke server identitas Anda. Kami hash data Anda sebelum mengirim demi privasi.NantiApakah Anda yakin untuk menghapus poll ini\? Anda tidak akan dapat memulihkannya setelah dihapus.Hapus poll
@@ -2298,17 +2298,17 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Tidak ada suaraEnkripsi dikonfigurasi dengan salahPulihkan Enkripsi
- Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang valid.
+ Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang absah.Enkripsi telah dikonfigurasi dengan salah.Membagikan lokasinyaBuat akunPerpesanan untuk tim Anda.
- Terenkripsi secara ujung-ke-ujung dan tidak memerlukan nomor telepon. Tidak ada iklan atau penambangan data.
+ Terenkripsi secara ujung ke ujung dan tidak memerlukan nomor telepon. Tanpa iklan atau penambangan data.Anda pilih di mana percakapan Anda disimpan, memberikan Anda kendali dan kebebasan. Terhubung via Matrix.Komunikasi aman dan independen yang memberikan tingkat privasi yang sama seperti percakapan wajah-ke-wajah di dalam rumah Anda sendiri.LokasiEnkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Klik untuk membuka pengaturan.
- Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang valid.
+ Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang absah.Belum yakin\? %sTampilkan gelembung pesanGagal untuk memuat peta
@@ -2344,7 +2344,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Tampilkan UtasanNotifikasi ruanganPengguna
- Beritahu seluruh ruangan
+ Beri tahu seluruh ruangan%1$d lagi
@@ -2429,7 +2429,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Tampilkan info profil (avatar dan nama tampilan) terkini untuk semua pesan.Tampilkan info pengguna terkiniSibuk
- Cadangan memiliki tandatangan yang valid dari pengguna ini.
+ Cadangan memiliki tandatangan yang absah dari pengguna ini.Langsung sampai %1$sDiperbarui %1$s yang laluImplementasi sementara: lokasi tetap di riwayat ruangan
@@ -2480,7 +2480,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Ketika mengundang ke ruangan terenkripsi yang juga membagikan riwayat, riwayat terenkripsi akan dapat dilihat.MSC3061: Pembagian kunci ruangan untuk pesan lamaKirim pesan pertama Anda untuk mengundang %s ke obrolan
- Pesan di obrolan ini akan dienkripsi secara ujung-ke-ujung.
+ Pesan di obrolan ini akan dienkripsi secara ujung ke ujung.MulaiIkuti petunjuk yang terkirim ke %sIkuti petunjuk yang terkirim ke %s
@@ -2552,7 +2552,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
%1$s dan %2$sEmail belum diverifikasi, periksa kotak masuk AndaSemua Obrolan
- Tampilkan Semua Sesi (V2, Dalam Pengembangan)Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.Sesi lainnyaSesi
@@ -2658,4 +2657,100 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Aktifkan pesan langsung tangguhanSebuah Element yang sederhana dengan fitur tab opsionalAktifkan tata letak baru
+ Pengguna lain dalam pesan langsung dan ruangan yang Anda bergabung dapat melihat daftar sesi Anda yang lengkap.
+\n
+\nIni memberikan mereka kepastian bahwa mereka berbicara dengan Anda, tetapi ini juga berarti bahwa mereka dapat melihat nama sesi yang Anda masukkan di sini.
+ Mengubah nama sesi
+ Sesi yang terverifikasi telah masuk dengan kredensial Anda dan juga telah diverifikasi, menggunakan frasa sandi atau memverifikasi secara silang.
+\n
+\nIni berarti mereka memegang kunci enkripsi ke pesan Anda sebelumnya, dan mengonfirmasi pengguna lain yang Anda berkomunikasi bahwa sesi ini memang Anda.
+ Sesi tidak aktif
+ Sesi belum diverifikasi
+ Sesi terverifikasi
+ Sesi yang belum diverifikasi adalah sesi yang telah masuk dengan kredensial Anda tetapi belum diverifikasi secara silang.
+\n
+\nAnda seharusnya yakin bahwa Anda mengenal sesi ini karena mereka bisa saja berarti seseorang menggunakan akun Anda secara tidak sah.
+ Sesi yang tidak aktif adalah sesi yang Anda tidak gunakan dalam beberapa waktu, tetapi mereka masih mendapatkan kunci enkripsi.
+\n
+\nMenghapus sesi yang sudah tidak aktif meningkatkan keamanan dan performa, dan membuatnya lebih mudah untuk mengenal jika sebuah sesi baru mencurigakan.
+ Harap diketahui bahwa nama sesi juga terlihat ke orang-orang yang Anda berkomunikasi.
+ Nama sesi khusus dapat membantu Anda mengenal perangkat Anda dengan lebih mudah.
+ Nama sesi
+ Ubah nama sesi
+ Keluar dari sesi ini
+ Belum diverifikasi · Sesi Anda saat ini
+ Mulai sebuah siaran suara
+ Keaslian pesan terenkripsi ini tidak dapat dijamin pada perangkat ini.
+ Minta papan ketik untuk tidak memperbarui data yang dipersonalisasi seperti riwayat pengetikan dan kamus berdasarkan apa yang Anda ketik dalam percakapan. Dicatat bahwa beberapa papan ketik mungkin tidak menghormati pengaturan ini.
+ Papan ketik samaran
+ Menambahkan (╯°□°)╯︵ ┻━┻ ke pesan teks biasa
+ Siaran Suara
+ Buka layar alat pengembang
+ 🔒 Anda telah mengaktifkan enkripsi ke sesi yang terverifikasi hanya untuk semua ruangan di Pengaturan Keamanan.
+ ⚠ Ada perangkat yang belum diverifikasi di ruangan ini, mereka tidak akan mendekripsikan pesan yang Anda kirim.
+ Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi di ruangan ini.
+ Saya mengerti
+ Terapkan format garis bawah
+ Terapkan format coret
+ Terapkan format miring
+ Terapkan format tebal
+ Rekam nama klien, versi, dan URL untuk lebih mudah mengenal sesi di pengelola sesi.
+ Aktifkan perekaman info klien
+ Miliki keterlihatan dan kendali yang lebih baik pada semua sesi Anda.
+ Aktifkan pengelola sesi baru
+ Sistem operasi
+ Model
+ Peramban
+ URL
+ Versi
+ Nama
+ Aplikasi
+ Terima notifikasi dorongan di sesi ini.
+ Notifikasi dorongan
+ Verifikasi sesi Anda saat ini untuk menampilkan status verifikasi sesi ini.
+ Status verifikasi tidak diketahui
+ Diaktifkan:
+ ID Sesi:
+ Ada sesuatu yang salah. Mohon periksa koneksi jaringan Anda dan coba lagi.
+ Berikan Izin
+ ${app_name} membutuhkan izin untuk menampilkan notifikasi.
+\nMohon berikan izin itu.
+ ${app_name} membutuhkan izin untuk menampilkan notifikasi. Notifikasi dapat menampilkan pesan Anda, undangan Anda, dll.
+\n
+\nMohon perbolehkan akses di munculan berikutnya untuk dapat melihat notifikasi.
+ Coba editor teks kaya (mode teks biasa akan datang)
+ Aktifkan editor teks kaya
+ Pastikan Anda tahu asal kode ini. Dengan menautkan perangkat, Anda akan memberikan seseorang akses penuh ke akun Anda.
+ Konfirmasi
+ Coba lagi
+ Tidak cocok\?
+ Memasukkan Anda
+ Menghubungkan ke perangkat
+ Pindai kode QR
+ Ingin masuk di perangkat ponsel\?
+ Tampilkan kode QR di perangkat ini
+ Pilih \'Pindai dengan kode QR\'
+ Mulai dari layar masuk
+ Pilih \'Masuk dengan kode QR\'
+ Mulai dari layar masuk
+ Pilih \'Tampilkan kode QR di perangkat ini\'
+ Pergi ke Pengaturan → Keamanan & Privasi → Tampilkan Semua Sesi
+ Buka ${app_name} di perangkat Anda yang lain
+ Permintaan ditolak di perangkat lain.
+ Penautan tidak selesai dalam waktu yang dibutuhkan.
+ Penautan dengan perangkat ini tidak didukung.
+ Koneksi tidak berhasil
+ Periksa perangkat yang masuk, kode di bawah seharusnya ditampilkan. Konfirmasi bahwa kode di bawah cocok dengan perangkat itu:
+ Koneksi aman dibuat
+ Pindai kode QR di bawah dengan perangkat Anda yang telah keluar dari akun.
+ Gunakan perangkat yang sudah masuk untuk memindai kode QR di bawah:
+ Masuk dengan kode QR
+ Gunakan kamera pada perangkat ini untuk memindai kode QR yang ditampilkan pada perangkat Anda yang lain:
+ Pindai kode QR
+ 3
+ 2
+ 1
+ Anda dapat menggunakan perangkat ini untuk masuk ke perangkat ponsel atau web dengan sebuah kode QR. Ada dua cara untuk melalukan ini:
+ Masuk dengan Kode QR
+ Pindai kode QR
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml
index 69191e1741..ceb4d614de 100644
--- a/library/ui-strings/src/main/res/values-is/strings.xml
+++ b/library/ui-strings/src/main/res/values-is/strings.xml
@@ -152,7 +152,7 @@
ÚtgáfaÚtgáfa olmSkilmálar og kvaðir
- Athugasemdir frá þriðja aðila
+ Tilkynningar frá utanaðkomandi aðilumHöfundarrétturMeðferð persónuupplýsingaHreinsa skyndiminni
@@ -1218,7 +1218,7 @@
Öll skilaboð (hávært)Tilkynnt sem óviðeigandiTilkynnt sem ruslpóstur
- Efni tilkynnt
+ Efni kærtKÆRAÁstæður fyrir kæru á þessu efniKæra þetta efni
@@ -1990,7 +1990,7 @@
Séð afSleppa þessu skrefiVista og halda áfram
- Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu.
+ Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínuNú ertu tilbúin(n)!Hefjumst handaÞú getur breytt þessu hvenær sem er
@@ -2214,4 +2214,53 @@
Kanna spjallrásirBúa til spjallrásHefja spjall
+ Auðkennisþjónninn sem þú valdir er ekki með neina þjónustuskilmála. Ekki halda áfram nema þú treystir eiganda netþjónsins
+ Mistókst að skrá FCM-teikn á heimaþjóninn:
+\n%1$s
+ Það tókst að skrá FCM-teikn á heimaþjóninn.
+ Ein eða fleiri prófanir mistókust, prófaðu tillögur að lagfæringum.
+ Gakktu úr skugga um að þú hafir smellt á tengilinn í tölvupóstinum sem við sendum þér.
+ Þú hefur ekki heimild til að uppfæra þau hlutverk sem krafist er til að breyta ýmsum þáttum svæðisins
+ Þú hefur ekki heimild til að uppfæra þau hlutverk sem krafist er til að breyta ýmsum þáttum spjallrásarinnar
+ Veldu þau hlutverk sem krafist er til að breyta ýmsum þáttum svæðisins
+ Veldu þau hlutverk sem krafist er til að breyta ýmsum þáttum spjallrásarinnar
+ Dulritun er rangt stillt þannig að þú getur ekki sent skilaboð. Smelltu til að opna stillingar.
+ Dulritun er rangt stillt þannig að þú getur ekki sent skilaboð. Hafðu samband við einhvern stjórnanda til að koma dulritun í lag.
+ Afbönnun á þessum notanda mun gera viðkomandi kleift að taka þátt aftur í svæðinu.
+ Afbönnun á þessum notanda mun gera viðkomandi kleift að taka þátt aftur í spjallrásinni.
+ Bann á notanda mun henda honum út af þessu svæði og koma í veg fyrir að viðkomandi komi aftur.
+ Notandinn verður fjarlægður af þessu svæði.
+\n
+\nTil koma í veg fyrir að viðkomandi komi aftur, ætti frekar að banna hann.
+ Notandinn verður fjarlægður af þessari spjallrás.
+\n
+\nTil koma í veg fyrir að viðkomandi komi aftur, ætti frekar að banna hann.
+ Ertu viss um að þú viljir hætta við boðið til þessa notanda\?
+ Afhunsun á þessum notanda mun sýna öll skilaboð frá viðkomandi aftur.
+ Að hunsa þennan notanda mun fjarlægja skilaboð frá viðkomandi í þeim spjallrásum sem þið eigið sameiginlegar.
+\n
+\nÞú getur afturkallað þessa aðgerð hvenær sem er í almennu stillingunum.
+ Þú getur ekki afturkallað þessa aðgerð, þar sem þú ert að lækka sjálfa/n þig í tign, og ef þú ert síðasti notandinn með nógu mikil völd á þessari spjallrás, verður ómögulegt að ná aftur stjórn á henni.
+ Ræstu ${app_name} á öðru tæki sem getur afkóðað skilaboðin og síðan sent dulritunarlyklana yfir í þessa setu.
+ Biðja aftur um dulritunarlykla frá hinum setunum þínum.
+ Þetta er þar sem nýjar beiðnir og boðsgestir birtast.
+ Svæði eru ný leið til að hópa fólk og spjallrásir. Útbúðu svæði til að komast í gang.
+ Öryggisafritun dulritunarlykla ætti að vera virk í öllum setunum þínum til að koma í veg fyrir að þú getir tapað aðgangi að dulrituðu skilaboðunum þínum.
+ - Sumir tengiliðir hafa verið afhunsaðir
+ ${app_name} þarf að hreinsa skyndiminnið til að haldast uppfært, af eftirfarandi ástæðu:
+\n%s\?
+\n
+\nAthugaðu að þessi aðgerð mun endurræsa forritið og það getur tekið nokkurn tíma.
+ Fella saman undirsvæði %s
+ Fella út undirsvæði %s
+ Farðu eftir leiðbeiningunum sem sendar voru á %s
+ Fékkstu ekki tölvupóst\?
+ Farðu eftir leiðbeiningunum sem sendar voru á %s
+ Hafðu það að minnsta kosti 8 stafa langt.
+ %s mun senda þér staðfestingartengil
+ %s þarf að sannreyna notandaaðganginn þinn
+ %s þarf að sannreyna notandaaðganginn þinn
+ Endilega lestu í gegnum stefnur og skilmála fyrir %s
+ Stefnur netþjónsins
+ Element Matrix Services (EMS) er afkastamikil og áreiðanleg hýsingarþjónusta fyrir hraðvirk og örugg samskipti í rauntíma. Skoðaðu hvernig við förum að því á element.io/ems
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml
index b2f9fa9238..cea69030bc 100644
--- a/library/ui-strings/src/main/res/values-it/strings.xml
+++ b/library/ui-strings/src/main/res/values-it/strings.xml
@@ -155,7 +155,7 @@
Hai permesso l\'accesso alla stanza per gli ospiti.Hai impedito l\'accesso alla stanza per gli ospiti.Hai attivato la crittografia end-to-end.
- Hai attivato la crittografia E2E (algoritmo %1$s sconosciuto).
+ Hai attivato la crittografia E2E (algoritmo %1$s non riconosciuto).Hai impedito l\'accesso alla stanza agli ospiti.%1$s ha impedito l\'accesso alla stanza agli ospiti.Hai permesso l\'accesso agli ospiti.
@@ -889,10 +889,10 @@
Regole di pushNessuna regola di push definitaNessun gateway di push registrato
- id_app:
- chiave_push:
- nome_visualizzato_app:
- nome_sessione:
+ ID app:
+ Chiave push:
+ Nome mostrato app:
+ Nome mostrato sessione:Url:Formato:Audio e Video
@@ -955,11 +955,11 @@
Attualmente stai usando %1$s per trovare altri utenti ed essere a tua volta rintracciabile da loro.Attualmente non stai usando alcun server d\'identità. Per trovare e farti rintracciare dagli altri utenti, configurane uno qua sotto.Indirizzi email visibili pubblicamente
- Le opzioni su come farsi trovare appariranno dopo che avrai aggiunto un\'email.
+ Le opzioni su come farsi trovare appariranno dopo che avrai aggiunto un indirizzo email.Le opzioni su come farsi trovare appariranno dopo che avrai aggiunto un numero di telefono.Se ti disconnetti dal server d\'identità gli altri utenti non potranno trovarti e tu non potrai invitarli tramite le loro email o numeri di telefono.Numeri di telefono visibili pubblicamente
- Abbiamo inviato un\'email di conferma a %s, controlla la tua posta e clicca sul link di conferma
+ Abbiamo inviato un\'email a %s, controlla la tua posta e clicca sul link di confermaInserisci un URL di un server d\'identitàImpossibile connettersi al server d\'identitàInserisci l\'URL del server d\'identità
@@ -1087,7 +1087,7 @@
L\'applicazione non riesce a creare un account su questo Home Server.
\n
\nVuoi registrarti usando un client web\?
- Questa email non è associata ad alcun account.
+ Questo indirizzo email non è associato ad alcun account.Reimposta la password su %1$sPer confermare la nuova password ti verrà inviata un\'email di verifica.Avanti
@@ -1096,7 +1096,7 @@
Attenzione!Cambiando la password verranno reimpostate le chiavi crittografiche E2E di tutte le tue sessioni rendendo illeggibile la cronologia delle chat criptate. Prima di reimpostare la password imposta il Backup delle Chiavi o esporta le chiavi della tua stanza da un\'altra sessione.Continua
- Questa email non è collegata ad alcun account
+ Questo indirizzo email non è collegato ad alcun accountControlla la tua postaUn\'email di verifica è stata inviata a %1$s.Clicca sul link per confermare la tua nuova password. Una volta fatto, clicca sotto.
@@ -1110,7 +1110,7 @@
\n
\nFermare il processo di cambio password\?
Imposta indirizzo email
- Imposta un\'email per recuperare il tuo account. Più tardi potrai decidere se permettere alle persone che conosci di trovarti tramite questa email.
+ Imposta un indirizzo email per recuperare il tuo account. Più tardi potrai decidere se permettere alle persone che conosci di trovarti tramite questo indirizzo.EmailEmail (facoltativa)Avanti
@@ -1448,7 +1448,7 @@
Messaggio rimossoMostra messaggi rimossiMostra un segnaposto per i messaggi rimossi
- Ti abbiamo inviato un\'email di conferma a %s, controlla la tua posta e clicca il link di conferma
+ Abbiamo inviato un\'email a %s, controlla la tua posta e clicca il link di confermaIl codice di verifica non è corretto.MEDIAIn questa stanza non ci sono file multimediali
@@ -1471,7 +1471,7 @@
Questa operazione non è possibile. L\'Home Server è obsoleto.Prima configura un server d\'identità.Prima accetta le condizioni del server d\'identità nelle impostazioni.
- Per la tua privacy, ${app_name} supporta solo l\'invio di email e numeri di telefono degli utenti in modalità oscurata (hash).
+ Per la tua privacy, ${app_name} supporta solo l\'invio di hash degli indirizzi email e dei numeri di telefono degli utenti.L\'associazione è fallita.Non c\'è alcuna associazione con questo identificativo.Il tuo home server (%1$s) propone di usare %2$s come tuo server d\'identità
@@ -1645,12 +1645,12 @@
Questo numero di telefono è già definito.Nessun numero di telefono aggiunto al tuo accountIndirizzi email
- Nessuna email aggiunta al tuo account
+ Nessun indirizzo email aggiunto al tuo accountNumeri di telefonoRimuovere %s\?Assicurati di avere cliccato il link nell\'email che ti abbiamo inviato.Email e numeri di telefono
- Gestisci le email e i numeri di telefono collegati al tuo account Matrix
+ Gestisci gli indirizzi email e i numeri di telefono collegati al tuo account MatrixCodiceSi prega di usare il formato internazionale (il numero deve iniziare con \'+\')Conferma la tua identità verificando questo accesso, dandogli l\'accesso ai messaggi cifrati.
@@ -1769,7 +1769,7 @@
%1$d di %2$dDai il consensoRevoca il mio consenso
- Hai acconsentito ad inviare email e numeri di telefono a questo server d\'identità per poter rintracciare altri utenti tra i tuoi contatti.
+ Hai acconsentito ad inviare indirizzi email e numeri di telefono a questo server d\'identità per poter rintracciare altri utenti tra i tuoi contatti.Invia email e numeri di telefonoSuggerimentiUtenti conosciuti
@@ -1978,7 +1978,7 @@
PubblicoUno Spazio privato per te e i tuoi compagniIo e i miei compagni
- Uno Spazio privato per organizzare le tue stanze
+ Uno spazio privato per organizzare le tue stanzeSolo ioAssicurati che le persone giuste abbiano accesso a %s.Con chi stai lavorando\?
@@ -2133,7 +2133,7 @@
Menzioni e parole chiaveNotifiche predefinite%s nella impostazioni per ricevere inviti direttamente in ${app_name}.
- Collega questa email con il tuo account
+ Collega questo indirizzo email con il tuo accountQuesto invito per questo spazio è stato inviato a %s, la quale non è associata al tuo accountQuesto invito per questa stanza è stato inviato a %s, la quale non è associata al tuo accountTutte le stanze in cui sei appariranno nella pagina principale.
@@ -2249,12 +2249,12 @@
Domanda o argomento del sondaggioCrea sondaggioSondaggio
- Invia email e numeri di telefono a %s
+ Invia indirizzi 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!La stanza è stata lasciata!Sei d\'accordo con l\'invio di queste informazioni\?
- Per trovare i contatti esistenti, devi inviare le informazioni dei contatti (email e numeri di telefono) al tuo server d\'identità. Facciamo un hash dei dati prima di inviarli per privacy.
+ Per trovare i contatti esistenti, devi inviare le informazioni dei contatti (indirizzi email e numeri di telefono) al tuo server d\'identità. Facciamo un hash dei dati prima di inviarli per privacy.Non oraVuoi davvero rimuovere questo sondaggio\? Non potrai recuperarlo una volta rimosso.Rimuovi sondaggio
@@ -2591,7 +2591,6 @@
\nQuesto homeserver potrebbe non essere configurato per mostrare mappe.
Apri le impostazioniTutte le chat
- Mostra tutte le sessioni (V2, WIP)Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.Altre sessioniSessioni
@@ -2701,4 +2700,100 @@
Crea messaggio diretto solo al primo messaggioUn Element semplificato con schede opzionaliAttiva nuova disposizione
+ Gli altri utenti nei messaggi diretti e nelle stanze in cui entri, possono vedere una lista completa delle tue sessioni.
+\n
+\nIn questo modo hanno la certezza che stanno parlando davvero con te, ma significa anche che possono vedere il nome della sessione che inserisci qui.
+ Rinominare le sessioni
+ Le sessioni verificate hanno effettuato l\'accesso con le tue credenziali e sono state verificate, usando la frase di sicurezza o la verifica incrociata.
+\n
+\nCiò significa che hanno le tue chiavi di crittografia per i messaggi passati, e confermano agli altri utenti con cui comunichi che queste sessioni sono usate da te.
+ Sessioni verificate
+ Le sessioni non verificate sono quelle in cui è stato fatto l\'accesso con le tue credenziali, ma che non sono state verificate.
+\n
+\nDovresti essere particolarmente sicuro di riconoscere queste sessioni dato che potrebbero rappresentare un uso non autorizzato del tuo account.
+ Sessioni non verificate
+ Le sessioni inattive sono quelle che non usi da un po\' di tempo, ma che continuano a ricevere chiavi di crittografia.
+\n
+\nLa rimozione di sessioni inattive migliora la sicurezza e le prestazioni, e ti rende più facile capire se una sessione nuova è sospetta.
+ Sessioni inattive
+ Ricorda che i nomi di sessione sono anche visibili alle persone con cui comunichi.
+ I nomi di sessione personalizzati possono aiutarti a riconoscere i tuoi dispositivi più facilmente.
+ Nome sessione
+ Rinomina sessione
+ Disconnetti questa sessione
+ Non verificata · La sessione attuale
+ Inizia un broadcast vocale
+ L\'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo.
+ Richiedi che la tastiera non debba aggiornare dati personalizzati come la cronologia di digitazione e il dizionario in base a cosa digiti nelle conversazioni. Nota che alcune tastiere potrebbero non rispettare questa impostazione.
+ Tastiera incognito
+ Antepone (╯°□°)╯︵ ┻━┻ ad un messaggio di testo
+ Broadcast voce
+ Apri la schermata degli strumenti per sviluppatori
+ 🔒 Hai attivato la crittografia solo per sessioni verificate in tutte le stanze nelle impostazioni di sicurezza.
+ ⚠ Ci sono dispositivi non verificati in questa stanza, non potranno decifrare i messaggi che invii.
+ Non inviare mai messaggi cifrati a sessioni non verificate in questa stanza.
+ Capito
+ Applica formato sottolineato
+ Applica formato sbarrato
+ Applica formato corsivo
+ Applica formato grassetto
+ Registra il nome, la versione e l\'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni.
+ Attiva registrazione info client
+ Maggiore visibilità e controllo su tutte le tue sessioni.
+ Attiva il nuovo gestore di sessioni
+ Sistema operativo
+ Modello
+ Browser
+ URL
+ Versione
+ Nome
+ Applicazione
+ Ricevi notifiche push in questa sessione.
+ Notifiche push
+ Verifica l\'attuale sessione per rivelare lo stato di verifica di questa sessione.
+ Stato di verifica sconosciuto
+ Attivato:
+ ID sessione:
+ Qualcosa è andato storto. Controlla la tua connessione di rete e riprova.
+ Concedi l\'autorizzazione
+ ${app_name} chiede l\'autorizzazione per mostrare notifiche.
+\nConcedi l\'autorizzazione.
+ ${app_name} chiede l\'autorizzazione per mostrare notifiche. Le notifiche possono mostrare i messaggi, gli inviti, ecc.
+\n
+\nConsenti l\'accesso nelle prossime schermate per potere vedere la notifica.
+ Prova l\'editor in rich text (il testo semplice è in arrivo)
+ Attiva editor in rich text
+ Assicurati di conoscere l\'origine di questo codice. Collegando i dispositivi, fornirai a qualcuno l\'accesso totale al tuo account.
+ Conferma
+ Riprova
+ Non corrisponde\?
+ Accesso in corso
+ Connessione al dispositivo
+ Scansiona codice QR
+ Effettuare l\'accesso in un dispositivo mobile\?
+ Mostra codice QR in questo dispositivo
+ Seleziona \'Scansiona codice QR\'
+ Inizia nella schermata di accesso
+ Seleziona ‘Accedi con codice QR’
+ Inizia nella schermata di accesso
+ Seleziona ‘Mostra codice QR in questo dispositivo’
+ Vai in Impostazioni -> Sicurezza e privacy -> Mostra tutte le sessioni
+ Apri ${app_name} sull\'altro dispositivo
+ La richiesta è stata negata sull\'altro dispositivo.
+ Il collegamento non è stato completato nel tempo previsto.
+ Il collegamento con questo dispositivo non è supportato.
+ Connessione non riuscita
+ Controlla il dispositivo che ha l\'accesso, dovresti vedere il codice sotto. Conferma che il codice corrisponda con quel dispositivo:
+ Connessione sicura stabilita
+ Scansiona il codice QR sottostante con il dispositivo che è disconnesso.
+ Usa il dispositivo che ha l\'accesso per scansionare il codice QR sotto:
+ Accedi con codice QR
+ Usa la fotocamera di questo dispositivo per scansionare il codice QR mostrato nell\'altro dispositivo:
+ Scansiona codice QR
+ 3
+ 2
+ 1
+ Puoi usare questo dispositivo per accedere in un dispositivo mobile o web con un codice QR. Ci sono due modi:
+ Accedi con codice QR
+ Scansiona codice QR
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml
index ff19310c8e..b9f81ae446 100644
--- a/library/ui-strings/src/main/res/values-iw/strings.xml
+++ b/library/ui-strings/src/main/res/values-iw/strings.xml
@@ -861,9 +861,7 @@
בחר שרת בית מותאם אישיתבחר שירותי מטריקס אלמנטבחר matrix.org
- חשבונך טרם נוצר.
-\n
-\nלהפסיק את תהליך ההרשמה\?
+ חשבונך טרם נוצר. להפסיק את תהליך ההרשמה\?אזהרהשם המשתמש הזה תפוסהבא
@@ -2304,7 +2302,7 @@
קהילותצוותיםחברים ומשפחה
- נעזור לך להתחבר.
+ נעזור לך להתחברעם מי תדברו הכי הרבה\?מוצפן מקצה לקצה ואין צורך במספר טלפון. ללא פרסומות או עיבוד נתונים.בחר היכן השיחות שלך נשמרות, נותן לך שליטה ועצמאות. מחובר דרך Matrix.
@@ -2508,4 +2506,4 @@
\nזה יהיה מעבר חד פעמי שכן שרשורים הם כעת חלק ממפרט Matrix.
שיתוף מסך של ${app_name}המסך משותף כרגע
-
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml
index 3e817e398c..37c0bca52f 100644
--- a/library/ui-strings/src/main/res/values-ja/strings.xml
+++ b/library/ui-strings/src/main/res/values-ja/strings.xml
@@ -987,7 +987,7 @@
プッシュ通知のテストFCMトークンのホームサーバーへの登録に失敗しました:
\n%1$s
- FCMトークンのホームサーバーへの登録が成功しました。
+ FCMトークンがホームサーバーに登録されました。トークンの登録アカウントを追加[%1$s]
@@ -1233,7 +1233,7 @@
続行するには利用規約を承認してくださいホームサーバーの利用規約を承認したら、再試行してください。次に
- 次に
+ 次へ次に次に次に
@@ -1282,9 +1282,9 @@
提案の送信に失敗しました(%s)ありがとうございます、提案は正常に送信されましたトークンの登録
- app_display_name:
- app_id:
- push_key:
+ アプリケーションの表示名:
+ App ID:
+ Push Key:登録されたプッシュゲートウェイはありませんプッシュ通知に関するルールが定義されていませんプッシュ通知に関するルール
@@ -1383,8 +1383,8 @@
同意を撤回あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意しています。メールと電話番号を送信
- %sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください
- %sに確認のためのメールを送りました。メールにて確認リンクをクリックしてください
+ %sにメールを送りました。メールを確認してリンクをクリックしてください
+ %sにメールを送りました。メールの確認リンクをクリックしてください発見可能な電話番号IDサーバーとの接続を解除すると、他のユーザーによって発見されなくなり、また、メールアドレスや電話で他のユーザーを招待することができなくなります。電話番号を追加すると、発見可能に設定する電話番号を選択できるようになります。
@@ -1412,7 +1412,7 @@
提案するフォーマット:URL:
- セッション名:
+ セッションの表示名:以下のうちいずれかが流出、あるいはハッキングされた恐れがあります。
\n
\n- あなたのパスワード
@@ -1696,7 +1696,7 @@
メッセージを送る…このファイルは大きすぎてアップロードできません。この情報の送信に同意しますか?
- 連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。
+ 連絡先を発見するには、連絡先のデータ(メールアドレスと電話番号)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。メールアドレスと電話番号を%sに送信このIDサーバーは運営方針を提供していませんIDサーバーの運営方針を隠す
@@ -2359,4 +2359,104 @@
ベータ版ベータ版試す
-
+ オフラインモード
+ 新着はありません。
+ - ユーザーの無視が解除されました
+ 試してみる
+ 右上をタップするとフィードバックを送信するオプションが表示されます。
+ フィードバックを送信
+ 右下からスペースにより早く簡単にアクセスできます。
+ スペースにアクセス
+ ${app_name}をシンプルにするために、タブはオプションになりました。右上のメニューから管理できます。
+ 新しいレイアウトにようこそ!
+ アニメーション画像を自動再生
+ エンドポイントのホームサーバーへの登録に失敗しました:
+\n%1$s
+ エンドポイントがホームサーバーに登録されました。
+ エンドポイントの登録
+ 権限を与える
+ ${app_name}は通知の表示に権限が必要です。
+\n権限を与えてください。
+
+ %1$sと他%2$d名
+
+ %1$sと%2$s
+ ホームサーバーがサポートしていないため、スレッド機能は不安定かもしれません。スレッドのメッセージは安定して表示されないおそれがあります。%sスレッド機能を有効にしてよろしいですか?
+ スレッド(ベータ版)
+ スレッドを用いると、会話のテーマを保ったり、会話を追跡したりするのが容易になります。%sスレッドを有効にするとアプリケーションが再起動します。再起動には時間がかかる可能性があります。
+ スレッド(ベータ版)
+ ${app_name}は通知を表示するために許可を必要としています。通知にはメッセージや招待などが表示されます。
+\n
+\n通知を表示するには、次のポップアップでアクセスを許可してください。
+ メールアドレスが認証されていません。メールボックスを確認してください
+ 画面共有を停止
+ 画面を共有
+ 招待
+ プッシュ通知
+ セッション名
+ セッションを改名
+ IPアドレス
+ オペレーティングシステム
+ 形式
+ ブラウザー
+ URL
+ バージョン
+ 名称
+ アプリケーション
+ このステップをスキップ
+ 問題ありません!
+ 進みましょう
+ ユーザー名 / メールアドレス / 電話番号
+ あなたは人間ですか?
+ %sに送信された手順に従ってください
+ パスワードを再設定
+ パスワードを忘れた場合
+ 電子メールを再送信
+ 電子メールが届いていませんか?
+ %sに送信された手順に従ってください
+ メールアドレスを認証
+ コードを再送信
+ コードが%sに送信されました
+ 電話番号を確認してください
+ 全ての端末からサインアウト
+ パスワードを再設定
+ パスワードは8文字以上に設定してください。
+ パスワードを選択
+ 新しいパスワード
+ 電子メールを確認してください。
+ %sは認証リンクを送信します
+ 確認コード
+ 電話番号
+ %sはアカウントの認証が必要です
+ 電話番号を入力してください
+ メールアドレス
+ %sはアカウントの認証が必要です
+ リッチテキストエディターを有効にする
+ 最初のメッセージを送信する際にダイレクトメッセージを作成
+ 遅延DMを有効にする
+ スペースがありません。
+ 新しいレイアウトを有効にする
+ アクティビティー順
+ アルファベット順
+ 並び替え
+ フィルターを表示
+ レイアウトの設定
+ 了解
+ 次へ
+ 詳しく知る
+ 秒
+ 分
+ 時
+ ${app_name}は以下の理由で、キャッシュを消去して最新の状態にする必要があります。
+\n%s
+\n
+\nアプリケーションが再起動します。再起動には時間がかかる可能性があります。
+ 初期同期のリクエスト
+ %sの子スペースを折りたたむ
+ %sの子スペースを展開
+ ルームを探索
+ スペースを変更
+ ルームを作成
+ チャットを開始
+ 全ての会話
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-ko/strings.xml b/library/ui-strings/src/main/res/values-ko/strings.xml
index 37e8849fa8..11e870f581 100644
--- a/library/ui-strings/src/main/res/values-ko/strings.xml
+++ b/library/ui-strings/src/main/res/values-ko/strings.xml
@@ -47,7 +47,8 @@
초기 동기화:
\n방 가져오는 중초기 동기화:
-\n들어간 방 가져오는 중
+\n대화 가져오는 중
+\n많은 방에 참여하신 경우, 오래 걸릴 수 있습니다초기 동기화:
\n초대받은 방 가져오는 중초기 동기화:
@@ -958,4 +959,39 @@
방 이름을 바꾸었습니다: %1$s방 사진을 바꾸었습니다%1$s님이 방 사진을 바꾸었습니다
-
+ 초기 동기화 요청
+ 초기 동기화:
+\n데이터 내려받는 중…
+ 초기 동기화:
+\n서버 응답을 기다리는 중…
+ 빈 방 (기존 %s)
+
+ %1$s님, %2$s님, %3$s님과 %4$d님 등
+
+ %1$s님, %2$s님, %3$s님과 %4$s님
+ %1$s님, %2$s님과 %3$s님
+ 이 방에 참여할 수 없습니다
+ 방 둘러보기
+ 방 만들기
+ 채팅 시작
+ 모든 채팅
+ 중재자
+ 관리자
+ %1$s위젯을 수정했습니다
+ %1$s님이 %2$s위젯을 수정했습니다
+ %1$s위젯을 삭제했습니다
+ %1$s님이 %2$s위젯을 삭제했습니다
+ %1$s위젯을 추가했습니다
+ %1$s님이 %2$s위젯을 추가했습니다
+ %1$s님의 초대를 수락했습니다
+ %1$s님 초대를 취소했습니다
+ %1$s님이 %2$s님 초대를 취소했습니다
+ %1$s님에게 방에 참가하라고 보낸 초대를 취소했습니다
+ %1$s님을 초대했습니다
+ %1$s님이 %2$s님을 초대했습니다
+ %1$s님에게 방 초대를 보냈습니다
+ 방 아바타를 삭제했습니다
+ %1$s님이 방 아바타를 삭제했습니다
+ 방 주제를 삭제했습니다
+ 방 이름을 삭제했습니다
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml
index ce122b0646..e8ede9b079 100644
--- a/library/ui-strings/src/main/res/values-nl/strings.xml
+++ b/library/ui-strings/src/main/res/values-nl/strings.xml
@@ -2596,7 +2596,6 @@
%1$s en %2$sE-mailadres niet geverifieerd, controleer je inbox
- Alle sessies weergeven (V2, WIP)Kan kaart niet laden
\nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven.Open instellingen
diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml
index c9bac8977b..ab9c367824 100644
--- a/library/ui-strings/src/main/res/values-pl/strings.xml
+++ b/library/ui-strings/src/main/res/values-pl/strings.xml
@@ -2688,7 +2688,6 @@
Email nie został zweryfikowany, sprawdź swoją skrzynkęNie udało się zarejestrować tokena punktu końcowego na serwerze domowym:
\n%1$s
- Wyświetl wszystkie sesje (V2, WIP)Bieżąca brama: %sWejścieNie można znaleźć punktu końcowego.
@@ -2744,4 +2743,4 @@
%s
\nwygląda nieco pusto.Brak przestrzeni.
-
\ No newline at end of file
+
diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
index 108ecc7e38..97141a9765 100644
--- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
+++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
@@ -1007,10 +1007,10 @@
Regras de PushNenhuma regra de push definidaNenhum gateway de push registrado
- app_id:
- push_key:
- app_display_name:
- session_name:
+ ID do App:
+ Chave Push:
+ Nome de Exibição do App:
+ Nome de Exibição da Sessão:Url:Formato:Voz & Vídeo
@@ -1053,12 +1053,12 @@
Você está atualmente usando %1$s para descobrir e ser descobertável por contatos existentes que você conhece.Você não está atualmente usando um servidor de identidade. Para descobrir e ser descobertável por contatos existentes que você conhece, configure um abaixo.Endereços de email descobertáveis
- Opções de descoberta vão aparecer uma vez que você tenha adicionado um email.
+ Opções de descoberta vão aparecer uma vez que você tenha adicionado um endereço de email.Opções de descoberta vão aparecer uma vez que você tenha adicionado um número de telefone.Desconectar-se de seu servidor de identidade vai significar que você não vai ser descobertável por outras(os) usuárias(os) e você não vai ser capaz de convidar outras(os) por email ou telefone.Números de telefone descobertáveis
- Nós enviamos a você um email de confirmar para %s, cheque seu email e clique no link de confirmação
- Nós enviamos a você um email de confirmar para %s, por favor primeiro cheque seu email e clique no link de confirmação
+ Nós enviamos um email para %s, cheque seu email e clique no link de confirmação
+ Nós enviamos um email para %s, por favor primeiro cheque seu email e clique no link de confirmaçãoEntre um URL de servidor de identidadeNão foi possível conectar-se a servidor de identidadePor favor entre o url de servidor de identidade
@@ -1171,7 +1171,7 @@
O aplicativo não é capaz de criar uma conta neste servidorcasa.
\n
\nVocê quer fazer signup usando um cliente web\?
- Este email não está associado com nenhuma conta.
+ Este endereço de email não está associado a nenhuma conta.Resettar senha em %1$sUm email de verificação vai ser enviado para sua inbox para confirmar definição de sua nova senha.Próximo
@@ -1180,7 +1180,7 @@
Aviso!Mudar sua senha vai resettar quaisquer chaves de encriptação ponta-a-ponta em todas as suas sessões, fazendo histórico de chat encriptado ilegível. Configure Backup de Chave ou exporte suas chaves de sala de uma outra sessão antes de resettar sua senha.Continuar
- Este email não está linkado a nenhuma conta
+ Este endereço de email não está linkado a nenhuma contaCheque sua inboxUm email de verificação foi enviado para %1$s.Toque no link para confirmar sua nova senha. Uma vez que você tenha seguido o link que ele contém, clique abaixo.
@@ -1194,7 +1194,7 @@
\n
\nPara o processo de mudança de senha\?
Definir endereço de email
- Defina um email para recuperar sua conta. Mais tarde, você pode opcionalmente permitir pessoas que você conhece descobrirem você por seu email.
+ Defina um endereço de email para recuperar sua conta. Mais tarde, você pode opcionalmente permitir pessoas que você conhece descobrirem você por este endereço.EmailEmail (opcional)Próximo
@@ -1560,7 +1560,7 @@
Esta operação não é possível. O servidorcasa está desatualizado.Por favor primeiro configure um servidor de identidade.Por favor primeiro aceite os termos do servidor de identidade nas configurações.
- Para sua privacidade, ${app_name} somente suporta enviar emails e números de telefone de usuária(o) hashados.
+ Para sua privacidade, ${app_name} somente suporta enviar endereços de email e números de telefone de usuária(o) hashados.A associação tem falhado.Não há nenhuma associação atual com este identificador.Seu servidorcasa (%1$s) propõe usar %2$s para seu servidor de identidade
@@ -1654,7 +1654,7 @@
Você não tem permissão para começar uma chamada nesta salaNenhum número de telefone tem sido adicionado a sua contaEndereços de email
- Nenhum email tem sido adicionado a sua conta
+ Nenhum endereço de email tem sido adicionado a sua contaNúmeros de telefoneRemover %s\?Assegure-se que você tem clicado no link no email que enviamos para você.
@@ -1663,7 +1663,7 @@
%d segundosEmails e números de telefone
- Gerenciar emails e números de telefone linkados a sua conta Matrix
+ Gerenciar endereços de email e números de telefone linkados a sua conta MatrixCódigoPor favor use o formato internacional (número de telefone deve começar com \'+\')Confirme sua identidade ao verificar este login, concedendo-lhe acesso a mensagens encriptadas.
@@ -1778,7 +1778,7 @@
%1$d de %2$dDar consentimentoRevogar meu consentimento
- Você tem dado seu consentimento para enviar emails e números de telefone para este servidor de identidade para descobrir outras(os) usuárias(os) de seus contatos.
+ Você tem dado seu consentimento para enviar endereços de email e números de telefone para este servidor de identidade para descobrir outras(os) usuárias(os) de seus contatos.Enviar emails e números de telefoneSugestõesUsuárias(os) Conhecidas(os)
@@ -2142,7 +2142,7 @@
Menções e PalavrachavesNotificações Default%s em Configurações para receber convites diretamente em ${app_name}.
- Linkar este email com sua conta
+ Linkar este endereço de email com sua contaEste convite para este espaço foi enviado para %s que não está associado com sua contaEste convite para esta sala foi enviado para %s que não está associado com sua contaTodas as salas em que você está vão ser mostradas em Home.
@@ -2211,7 +2211,7 @@
Acesso a espaçoQuem pode acessar\?Habilitar notificações de email para %s
- Para receber email com notificação, por favor associe um email a sua conta Matrix
+ Para receber email com notificação, por favor associe um endereço de email a sua conta MatrixNotificação de emailFazer upgrade do espaçoMudar nome de espaço
@@ -2258,12 +2258,12 @@
Sondar pergunta ou tópicoCriar SondagemSondagem
- Enviar emails e números de telefone para %s
+ Enviar endereços de email 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!Esta sala tem sido saída!Você concorda em enviar esta info\?
- Para descobrir contatos existentes, você precisa enviar info de contato (emails e números de telefone) para seu servidor de identidade. Nós hashamos seus dados antes de enviar por privacidade.
+ Para descobrir contatos existentes, você precisa enviar info de contato (endereços de email e números de telefone) para seu servidor de identidade. Nós hashamos seus dados antes de enviar por privacidade.Não agoraVocê tem certeza que você quer remover esta sondagem\? Você não vai ser capaz de recuperá-la uma vez removida.Remover sondagem
@@ -2600,7 +2600,6 @@
\nEste servidor casa pode não estar configurado para exibir mapas.
Abrir configuraçõesTodos os Chats
- Mostrar Todas Sessões (V2, WIP)Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.Outras sessõesSessões
@@ -2710,4 +2709,100 @@
Habilitar DMs diferidasUm Element simplificado com abas opcionaisHabilitar novo layout
-
+ Outras(os) usuárias(os) em mensagens diretas e salas a que você se junta são capazes de visualizar uma lista completa de suas sessões.
+\n
+\nIsto as/os provê com confiança que elas(es) são estão realmente falando com você, mas também significa que elas(es) veem o nome da sessão que você entrar aqui.
+ Renomeando sessões
+ Sessões verificadas têm feito login com suas credenciais e então têm sido verificadas, ou usando sua frasepasse segura ou por verificação cruzada.
+\n
+\nIsto significa que elas mantêm chaves de encriptação para suas mensagens anteriores, e confirmam a outras(os) usuárias(os) com quem você está comunicando que estas sessões são realmente você.
+ Sessões verificadas
+ Sessões não-verificadas são sessões que você tem feito login com suas credenciais mas não têm sido verificadas cruzado.
+\n
+\nVocê devia especialmente se certificar que você reconhece estas sessões já que elas podiam representar um uso não-autorizado de sua conta.
+ Sessões não-verificadas
+ Sessões inativas são sessões que você não tem usado em algum tempo, mas elas continuam a receber chaves de encriptação.
+\n
+\nRemover sessões inativas melhora segurança e performance, e torna-o mais fácil para você identificar se uma nova sessão é suspeita.
+ Sessões inativas
+ Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica.
+ Nomes de sessões personalizadas podem ajudar você a reconhecer seus dispositivos mais facilmente.
+ Nome da sessão
+ Renomear sessão
+ Fazer signout desta sessão
+ Não-verificada · Sua sessão atual
+ Começar um broadcast de voz
+ A autenticidade desta mensagem encriptada não pode ser garantida neste dispositivo.
+ Requisitar que o teclado não devia atualizar quaisquer dados personalizados tais como histórico de digitação e dicionário baseado no que você tem digitado em conversas. Note que alguns teclados podem não respeitar esta configuração.
+ Teclado incognito
+ Prepende (╯°□°)╯︵ ┻━┻ a uma mensagem de texto puro
+ Broadcast de Voz
+ Abrir tela de ferramentas de desenvolvedor(a)
+ 🔒 Você tem habilitado encriptar para sessões verificadas somente para todas as salas em Configurações de Segurança.
+ ⚠ Existem dispositivos não-verificados nesta sala, eles não vão ser capazes de decriptar mensagens que você enviar.
+ Nunca enviar mensagens encriptadas a sessões não-verificadas nesta sala.
+ Entendido
+ Aplicar formato tachar
+ Aplicar formato sublinhar
+ Aplicar formato itálico
+ Aplicar formato negrito
+ Gravar o nome de cliente, versão, e url para reconhecer sessões mais facilmente em gerenciador de sessão.
+ Habilitar gravação de info de cliente
+ Tenha visibilidade e controle maiores sobre todas suas sessões.
+ Habilitar novo gerenciador de sessão
+ Sistema operativo
+ Modelo
+ Browser
+ URL
+ Versão
+ Nome
+ Aplicativo
+ Receber notificações push nesta sessão.
+ Notificações push
+ Verifique sua sessão atual para revelar o status de verificação desta sessão.
+ Status de verificação desconhecido
+ Habilitado:
+ ID da Sessão:
+ Algo deu errado. Por favor cheque sua conexão de rede e tente de novo.
+ Conceder Permissão
+ ${app_name} precisa de permissão para mostrar notificações.
+\nPor favor conceda a permissão.
+ ${app_name} precisa de permissão para exibir notificações. Notificações podem exibir suas mensagens, seus convites, etc.
+\n
+\nPor favor permita acesso nos próximos pop-ups para ser capaz de visualizar notificação.
+ Experimente o editor de texto rico (modo de texto puro vindo em breve)
+ Habilitar editor de texto rico
+ Por favor assegure que você sabe a origem deste código. Ao linkar dispositivos, você vai prover alguém com acesso completo a sua conta.
+ Confirmar
+ Tentar de novo
+ Nenhuma correspondência\?
+ Fazendo-lhe signin
+ Conectando a dispositivo
+ Scannar QR code
+ Fazendo signin com um dispositivo móvel\?
+ Mostrar QR code neste dispositivo
+ Selecione \'Scannar QR code\'
+ Comece na tela de signin
+ Selecione \'Fazer signin com QR code\'
+ Comece na tela de signin
+ Selecione \'Mostrar QR code neste dispositivo\'
+ Vá para Configurações -> Segurança & Privacidade -> Mostrar Todas as Sessões
+ Obra ${app_name} em seu outro dispositivo
+ A requisição foi negada no outro dispositivo.
+ A linkagem não foi completada no tempo requerido.
+ Linkagem com este dispositivo não é suportado.
+ Conexão malsucedida
+ Cheque seu dispositivo feito signin, o código abaixo deveria ser exibido. Confirme que o código abaixo corresponde com esse dispositivo:
+ Conexão segura estabelecida
+ Scanne o QR code abaixo com seu dispositivo que está feito signout.
+ Use seu dispositivo feito signin para scannar o QR code abaixo:
+ Fazer signin com QR code
+ Use a câmera neste dispositivo para scannar o QR code mostrado em seu outro dispositivo:
+ Scannar QR code
+ 3
+ 2
+ 1
+ Você pode usar este dispositivo para fazer signin com um dispositivo móvel ou web com um QR code. Existem duas maneiras de fazer isto:
+ Fazer signin com QR Code
+ Scannar QR code
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml
index c8eee49d96..0349acedd1 100644
--- a/library/ui-strings/src/main/res/values-ru/strings.xml
+++ b/library/ui-strings/src/main/res/values-ru/strings.xml
@@ -398,8 +398,8 @@
Прикрепить комнаты с отключенными уведомлениямиПрикрепить комнаты с непрочитанными сообщениямиID
- Публичное имя
- Обновить публичное имя
+ Публичное название
+ Обновить публичное названиеНедавно%1$s @ %2$sАутентификация
@@ -431,23 +431,23 @@
Установить как основной адресСбросить основной адресОшибка дешифровки
- Публичное имя
+ Публичное названиеID сессииКлюч сессииЭкспорт E2E ключей комнатыЭкспорт ключей комнатыЭкспорт ключей в локальный файлЭкспорт
- Введите парольную фразу
- Подтвердите парольную фразу
+ Введите мнемоническую фразу
+ Подтвердите мнемоническую фразуИмпорт E2E ключей комнатыИмпорт ключей комнатыИмпортировать ключи из локального файлаИмпортШифровать только для проверенных сессийНе отправлять зашифрованные сообщения непроверенным сессиям с этой сессии.
- Не проверено
- Проверено
+ Не заверено
+ ЗавереноПодтвердитьЧтобы убедиться, что этой сессии можно доверять, обратитесь к ее владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии:Если они не совпадают, безопасность вашего общения может быть поставлена под угрозу.
@@ -617,7 +617,7 @@
Системные оповещенияОшибка
- Создать парольную фразу
+ Создать мнемоническую фразуПарольные фразы не совпадаютсвяжитесь с вашим администраторомПревышен один из ресурсных лимитов сервера, по этому некоторые пользователи не смогут авторизоваться.
@@ -637,7 +637,7 @@
Мелодия входящего вызоваВыберите мелодию звонка:Идёт видеозвонок …
- Удалить из чата
+ Удалить из комнатыПоиск проблем с уведомлениямиОправлять уведомления о наборе текстаMarkdown форматирование
@@ -725,23 +725,23 @@
Управление криптографическими ключамиУправление резервным копированием ключейБеззвучный
- Пожалуйста, введите парольную фразу
+ Пожалуйста, введите мнемоническую фразуПарольная фраза слишком простая
- Пожалуйста, удалите парольную фразу, если хотите, чтобы ${app_name} сгенерировал ключ восстановления.
+ Пожалуйста, удалите мнемоническую фразу, если хотите, чтобы ${app_name} сгенерировал бумажный ключ.Никогда не теряйте зашифрованных сообщенийСообщения в зашифрованных комнатах защищены сквозным шифрованием. Ключи для прочтения этих сообщений есть только у вас и получателя(ей).
\n
\nНадёжно сохраните резервную копию ключей, чтобы не потерять их.Установите парольную фразу
- Сохраните ключ восстановления
+ Сохранить бумажный ключГотовоСохранить как файлПожалуйста, сделайте копию
- Поделиться ключом восстановления с…
- Ключ для восстановления
+ Поделиться бумажным ключом с…
+ Бумажный ключНепредвиденная ошибкаУверены?
- Удалить резервную копию ключей шифрования с сервера? Вы больше не сможете использовать ключ восстановления для чтения истории зашифрованных сообщений.
+ Удалить резервную копию ключей шифрования с сервера\? Вы больше не сможете использовать бумажный ключ для чтения истории зашифрованных сообщений.Удалить резервную копиюУдаление резервной копии…Чтобы использовать резервную копию ключа в этой сессии, восстановите его с помощью своей парольной фразы или ключа восстановления.
@@ -755,17 +755,17 @@
Резервное копирование ключей успешно настроено для этой сессии.Удалить резервную копиюВосстановить из резервной копии
- Пожалуйста, введите ключ восстановления
+ Пожалуйста, введите бумажный ключРазблокировать историюВосстановление резервной копии:
- Введите ключ восстановления
- Используйте ключ восстановления для разблокировки истории зашифрованных сообщений
- Если вы не знаете вашу парольную фразу для восстановления, вы можете %s.
- используйте ключ восстановления
+ Введите бумажный ключ
+ Используйте бумажный ключ для разблокировки зашифрованных сообщений
+ Если забыли свою мнемоническую фразу, вы можете %s.
+ используйте бумажный ключВы можете потерять доступ к сообщениям, если выйдете из системы или потеряете это устройство.Получение версии резервной копии…
- Используйте парольную фразу для разблокировки истории зашифрованных сообщений
- Потеряли ключ восстановления? В настройках вы можете создать новый.
+ Используйте мнемоническую фразу для разблокировки зашифрованных сообщений
+ Потеряли бумажный ключ\? В настройках вы можете создать новый.Резервная копия восстановлена %s !Резервная копия имеет недействительную подпись из неподтвержденной сессии %sНе удалось получить последнюю версию ключей восстановления (%s).
@@ -780,9 +780,9 @@
Восстановлены резервные копии с %d ключами.Восстановлены резервные копии с %d ключами.
- Невозможно расшифровать резервную копию с помощью этого ключа восстановления: убедитесь, что вы ввели правильный ключ.
- Невозможно расшифровать резервную копию с помощью этого пароля: убедитесь, что вы ввели правильный пароль.
- Генерация ключей восстановления с использованием парольной фразы может занять несколько секунд.
+ Невозможно расшифровать резервную копию с помощью этого бумажного ключа: пожалуйста, убедитесь, что вы ввели правильный бумажный ключ.
+ Невозможно расшифровать резервную копию с помощью этой мнемонической фразы: пожалуйста, убедитесь, что вы ввели правильную мнемоническую фразу.
+ Генерация бумажного ключа с использованием мнемонической фразы может занять несколько секунд.[%1$s]
\nЭта ошибка вне контроля ${app_name}. На телефоне нет учетной записи Google. Пожалуйста, добавьте аккаунт Google.[%1$s]
@@ -815,9 +815,9 @@
Резервное копирование ключей…Никогда не теряйте зашифрованные сообщенияПоделиться
- Я сделал копию
- Храните ключ восстановления в надежном месте, например, в диспетчере паролей (или в сейфе)
- Защитите резервную копию парольной фразой.
+ Я сделал(а) копию
+ Храните бумажный ключ в очень надёжном месте, например, в менеджере паролей (или в сейфе)
+ Защитите резервную копию мнемонической фразой.Восстановление зашифрованных сообщенийНачать использовать резервное копирование ключейИспользовать резервное копирование ключей
@@ -825,16 +825,16 @@
Новые ключи зашифрованных сообщенийВаши ключи копируются.(Дополнительно) Настройка с ключом восстановления
- Или защитите резервную копию с помощью ключа восстановления, сохранив его в безопасном месте.
+ Или защитите резервную копию бумажным ключом, сохранив его в надёжном месте.Безопасная резервная копия ключей должна быть активирована на всех ваших сессиях, чтобы не потерять доступ к зашифрованным сообщениям.
- Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её парольной фразой.
+ Зашифрованная копия ключей будет храниться на вашем сервере. Для безопасности защитите её мнемонической фразой.
\n
-\nДля максимальной безопасности парольная фраза должна отличаться от пароля вашей учётной записи.
+\nДля максимальной безопасности мнемоническая фраза должна отличаться от пароля вашей учётной записи.Ключ восстановления — это страховка, вы можете использовать его для восстановления доступа к вашим зашифрованным сообщениям, если забудете вашу парольную фразу.
\nХраните ключ восстановления в надёжном месте, например, в диспетчере паролей (или в сейфе)Импортирование ключей…Скачивание ключей…
- Вычисление ключа восстановления…
+ Вычисление бумажного ключа…ИгнорироватьОтметить как прочитанноеВойти с помощью единого входа
@@ -929,10 +929,10 @@
ПредпочтенияБезопасностьПравила push-уведомлений
- app_id:
+ ID приложения:push_key:
- app_display_name:
- session_name:
+ Отображаемое название приложения:
+ Отображаемое название сессии:Url:Формат:Голос и видео
@@ -1219,10 +1219,10 @@
ЕщёQR-кодСоединение с сервером потеряно
- Используйте пароль восстановления или ключ
+ Используйте мнемоническую фразу или бумажный ключРазблокировать историю зашифрованных сообщенийПроверка была отменена. Вы можете начать проверку снова.
- Парольная фраза для восстановления
+ Мнемоническая фразаВведите %s, чтобы продолжить.Не переиспользуйте пароль учётной записи.Это может занять несколько секунд, пожалуйста, наберитесь терпения.
@@ -1314,7 +1314,7 @@
Сброс безопасного резервного копированияНастроить на этом устройствеЗащитите себя от потери доступа к зашифрованным сообщениям и данным, создав резервные копии ключей шифрования на вашем сервере.
- Создайте новый ключ безопасности или задайте новую секретную фразу для существующей резервной копии.
+ Создайте новый бумажный ключ или задайте новую мнемоническую фразу для существующей резервной копии.Это заменит ваш текущий ключ или фразу.Интеграции отключеныВключите «Управление интеграциями» в настройках, чтобы сделать это.
@@ -1326,7 +1326,7 @@
Ключи успешно экспортированыОБЗОРАктивные виджеты
- Ключ восстановления был сохранён.
+ Бумажный ключ сохранён.Безопасное резервное копированиеЗащита от потери доступа к зашифрованным сообщениям и даннымНастроить безопасное резервное копирование
@@ -1368,7 +1368,7 @@
Показываем только первые результаты, наберите больше букв…Раннее падение${app_name} может падать чаще, когда происходит непредвиденная ошибка
- Добавляет смайл ¯\\_(ツ)_/¯ в начало сообщения
+ Добавляет ¯\\_(ツ)_/¯ в начало сообщенияПосле включения шифрования его нельзя отключить.Ваш почтовый домен не имеет права регистрироваться на этом сервереНе безопасно
@@ -1429,9 +1429,9 @@
Сравните уникальные эмодзи, убедившись, что они появились в том же порядке.Сравните код с тем, который отображается на экране другого пользователя.Сообщения от этого пользователя зашифрованы сквозным шифрованием и не смогут быть прочитаны третьими лицами.
- Ваша новая сессия подтверждена. У нее есть доступ к вашим зашифрованным сообщениям, а другие пользователи увидят его как доверенное.
- Перекрестная подпись
- Перекрестная подпись включена
+ Ваша новая сессия подтверждена. Она имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут воспринимать её как заверенную.
+ Перекрёстная подпись
+ Перекрёстная подпись включена
\nЛичные ключи хранятся на устройстве.Перекрестная подпись включена
\nКлючи являются доверенными.
@@ -1553,12 +1553,12 @@
Эта учётная запись была деактивирована.Введите %s, чтобы продолжитьИспользовать файл
- Это недействительный ключ восстановления
- Пожалуйста, введите ключ восстановления
+ Этот бумажный ключ недействителен
+ Пожалуйста, введите бумажный ключПроверка ключа резервного копированияПроверка ключа резервного копирования (%s)Получение кривой ключа
- Генерация ключа SSSS из ключа восстановления
+ Генерация ключа SSSS из бумажного ключаСохранение резервной копии ключа в SSSSиспользуйте ваш ключ восстановления ключа резервной копииКлюч восстановления ключа резервной копии
@@ -1571,7 +1571,7 @@
\n${app_name} для Androidили другой клиент Matrix поддерживающий перекрестную подписьПринудительно отбрасывает текущую групповую сессию для отправки сообщений в зашифрованную комнату
- Чтобы продолжить, используйте ваш %1$s или используйте ваш %2$s.
+ Чтобы продолжить, используйте %1$s или %2$s.Используйте ключ восстановленияВыберите ключ восстановления или введите его вручную, введя или вставив из буфера обменаНе удалось получить доступ к защищенному хранилищу данных
@@ -1624,13 +1624,13 @@
НастроитьИспользуйте ключ безопасностиСоздайте ключ безопасности для хранения в надежном месте, например в менеджере паролей или сейфе.
- Использовать секретную фразу
+ Использовать мнемоническую фразуВведите секретную фразу, известную только вам, и создайте ключ для резервного копирования.Сохраните свой ключ безопасности
- Храните ключ безопасности в надежном месте, например в менеджере паролей или сейфе.
+ Храните бумажный ключ в надёжном месте, например, в менеджере паролей или в сейфе.Задайте секретную фразуВведите секретную фразу, известную только вам, для защиты данных на вашем сервере.
- Секретная фраза
+ Мнемоническая фразаДля подтверждения введите вашу секретную фразу ещё раз.Название комнатыТема
@@ -1646,7 +1646,7 @@
Мы рады сообщить, что сменили имя! Ваше приложение обновлено, и вы вошли в свою учетную запись.ПОНЯТНОУЗНАТЬ БОЛЬШЕ
- Сохранить ключ восстановления в
+ Сохранить бумажный ключ вПолучаем ваши контакты…Ваша контактная книга пустаКнига контактов
@@ -1701,12 +1701,12 @@
Этот номер телефона уже используется.В ваш аккаунт не добавлен номер телефонаАдрес электронной почты
- В ваш аккаунт не добавлен адрес электронной почты
+ В вашу учётную запись не добавлен адрес электронной почтыТелефонные номераУдалить %s\?Убедитесь, что вы перешли по ссылке в электронном письме, которое мы вам отправили.Электронная почта и номера телефонов
- Управляйте электронной почтой и номерами телефонов, привязанными к вашей учетной записи Matrix
+ Управляйте адресами электронной почты и номерами телефонов, привязанными к вашей учётной записи MatrixКодИспользуйте международный формат (номер телефона должен начинаться с \'+\')Подтвердите свою личность, проверив этот логин, предоставив ему доступ к зашифрованным сообщениям.
@@ -2275,7 +2275,7 @@
Доступ к пространствуКто имеет к этому доступ\?Включить уведомления по электронной почте для %s
- Чтобы получать уведомления по электронной почте, пожалуйста, привяжите электронную почту к вашей учетной записи Matrix
+ Чтобы получать уведомления по электронной почте, пожалуйста, привяжите адрес электронной почты к своей учётной записи MatrixУведомление по эл. почтеОбновить пространствоИзменить название пространства
@@ -2633,7 +2633,7 @@
Где хранятся ваши перепискиГде будут храниться ваши перепискиДолжно быть 8 или более символов
- Не удалось подтвердить это устройство
+ Не удалось подтвердить эту сессиюНевозможно открыть эту ссылку: сообщества были заменены пространствамиИмя пользователя / Почта / ТелефонСледуйте инструкциям, отправленным на %s
@@ -2664,7 +2664,6 @@
Другие сессииСессииСоздать беседу или комнату
- Показать все сессии (V2, в разработке)ЛСНастройки видаФильтры
@@ -2721,7 +2720,7 @@
%s
\nвыглядит слегка пустовато.Попробовать
- Сведения о приложении, устройстве и активности.
+ Информация о приложении, устройстве и активности.Подтвердите текущую сессию для более безопасного обмена сообщениями.Пока нет пространств.Подтвердите свои сессии для более безопасного обмена сообщениями или выйдите из тех, которые более не признаёте или не используете.
@@ -2742,4 +2741,55 @@
НезаверенныеФильтр
+ Незаверенная · Текущая сессия
+ Переименовать сессию
+ Название сессии
+ Заверенные
+ Выйти из этой сессии
+ Неактивные
+ Незаверенные
+ Пожалуйста, имейте в виду, что названия сессий также видны людям, с которыми вы общаетесь.
+ Заверенные сессии
+ Незаверенные сессии
+ Неактивные сессии
+ Добавляет (╯°□°)╯︵ ┻━┻ в начало сообщения
+ Приватная клавиатура
+ Запрещает клавиатуре обновлять персональные данные, такие как история набора текста и словарь, на основе того, что вы набрали при общении. Обратите внимание, что некоторые клавиатуры могут не соблюдать эту настройку.
+ Понятно
+ 🔒 В настройках безопасности вы включили шифрование только для заверенных сессий во всех комнатах.
+ Не отправлять зашифрованные сообщения незаверенным сессиям в этой комнате.
+ Неактивные сессии — это сессии, которыми вы не пользовались определённое время, но они продолжают получать ключи шифрования.
+\n
+\nУдаление неактивных сессий повышает безопасность и производительность, а также облегчает выявление подозрительных новых сессий.
+ Переименование сессий
+ Другие пользователи в личных сообщениях и комнатах, к которым вы присоединились, могут просматривать весь список ваших сессий.
+\n
+\nЭто даёт им уверенность в том, что они действительно общаются с вами, но это также означает, что они могут видеть название сессии, которое вы ввели здесь.
+ Визуальный редактор текста
+ ID сессии:
+ Уведомления
+ Получать push-уведомления в этой сессии.
+ URL-адрес
+ Приложение
+ Название
+ Версия
+ Веб-браузер
+ Модель
+ Операционная система
+ Новый менеджер сессий
+
+ Рассмотрите возможность выхода из старых сессий (%1$d день или дольше), которые вы более не используете.
+ Рассмотрите возможность выхода из старых сессий (%1$d дня или дольше), которые вы более не используете.
+ Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+ Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+
+ Результаты будут видны после завершения опроса
+ Доступ к пространствам (внизу справа) быстрее и проще, чем когда-либо прежде.
+ Доступ к пространствам
+
+ Рассмотрите возможность выхода из старых сессий (%1$d день или дольше), которые вы более не используете.
+ Рассмотрите возможность выхода из старых сессий (%1$d дня или дольше), которые вы более не используете.
+ Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+ Рассмотрите возможность выхода из старых сессий (%1$d дней или дольше), которые вы более не используете.
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml
index f37af1a654..43a8301d58 100644
--- a/library/ui-strings/src/main/res/values-sk/strings.xml
+++ b/library/ui-strings/src/main/res/values-sk/strings.xml
@@ -1040,8 +1040,8 @@
Možnosti objavovania sa zobrazia, až keď pridáte telefónne číslo.Odpojením sa od servera totožností znemožníte ostatným, aby vás našli a tiež nebudete môcť kontakty pozývať zadaním emailovej adresy alebo telefónneho čísla.Telefónne čísla, podľa ktorých je vás možné nájsť
- Odoslali sme vám potvrdzujúci e-mail na adresu %s, skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz
- Odoslali sme vám potvrdzujúci e-mail na adresu %s, najskôr si prosím skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz
+ Odoslali sme vám e-mail na adresu %s, skontrolujte svoj e-mail a kliknite na potvrdzujúci odkaz
+ Odoslali sme vám e-mail na adresu %s, najskôr si prosím skontrolujte svoj e-mail a kliknite na potvrdzujúci odkazZadajte URL adresu servera totožnostíNie je možné sa pripojiť k serveru totožnostíProsím, zadajte URL adresu servera totožností
@@ -1700,7 +1700,7 @@
Prosím, použite medzinárodný formát.Nastavte si telefónne číslo, aby ste voliteľne umožnili ľuďom, ktorých poznáte, aby vás objavili.Toto nevyzerá ako platná e-mailová adresa
- Nastavte si e-mail na obnovenie konta. Neskôr môžete voliteľne povoliť známym, aby vás objavili podľa vášho e-mailu.
+ Nastavte si e-mail na obnovenie konta. Neskôr môžete voliteľne povoliť svojim známym, aby vás objavili podľa tohto e-mailu.Nastaviť e-mailovú adresuSpäť na prihlásenieVaše heslo bolo obnovené.
@@ -1846,7 +1846,7 @@
Kľúčové slová nemôžu začínať na \".\"Pridať nové kľúčové slovoPovoliť e-mailové oznámenia pre %s
- Ak chcete dostávať e-mail s upozornením, priraďte e-mail k svojmu kontu Matrix
+ Ak chcete dostávať e-mail s upozornením, priraďte e-mail k svojmu Matrix účtuE-mailové oznámenieUpozorniť ma naUpozorniť ma na
@@ -1856,10 +1856,10 @@
Iba zmienky a kľúčové slováMedzinárodné telefónne čísla musia začínať znakom \"+\"Ak chcete zistiť existujúce kontakty, potrebujete odoslať kontaktné informácie (e-maily a telefónne čísla) na server totožností. Pred odoslaním vaše údaje zahašujeme kvôli ochrane osobných údajov.
- Odoslať e-maily a telefónne čísla na %s
+ Odoslať emailové adresy a telefónne čísla na %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.Odoslať e-maily a telefónne čísla
- Spravovať e-maily a telefónne čísla prepojené s vaším účtom Matrix
+ Spravujte e-maily a telefónne čísla prepojené s vaším účtom MatrixEmaily a telefónne číslaNastaviť nové heslo k účtu…Nepoužívajte heslo k svojmu účtu.
@@ -2650,8 +2650,7 @@
\nTento domovský server nemusí byť nakonfigurovaný na zobrazovanie máp.Otvoriť nastaveniaVšetky konverzácie
- Zobraziť všetky relácie (V2, WIP)
- V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.
+ V záujme čo najlepšieho zabezpečenia, overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.Iné relácieRelácieOtvoriť zoznam priestorov
@@ -2764,4 +2763,100 @@
Povoliť odložené priame správyZjednodušený Element s voliteľnými kartamiZapnúť nové usporiadanie
+ Ostatní používatelia v priamych správach a miestnostiach, do ktorých sa pripojíte, si môžu pozrieť úplný zoznam vašich relácií.
+\n
+\nTo im poskytuje istotu, že sa s vami naozaj rozprávajú, ale zároveň to znamená, že vidia názov relácie, ktorý sem zadáte.
+ Premenovanie relácií
+ Overené relácie, do ktorých ste sa prihlásili pomocou svojich prihlasovacích údajov a ktoré boli následne overené buď pomocou vašej bezpečnostnej prístupovej frázy, alebo krížovým overením.
+\n
+\nTo znamená, že majú šifrovacie kľúče pre vaše predchádzajúce správy a potvrdzujú ostatným používateľom, s ktorými komunikujete, že tieto relácie ste skutočne vy.
+ Overené relácie
+ Neoverené relácie sú relácie, do ktorých ste sa prihlásili pomocou svojich prístupových údajov, ale ktoré neboli krížovo overené.
+\n
+\nMali by ste si byť obzvlášť istí, že tieto relácie poznáte, pretože by mohli predstavovať neoprávnené použitie vášho konta.
+ Neoverené relácie
+ Neaktívne relácie sú relácie, ktoré ste určitý čas nepoužívali, ale naďalej dostávajú šifrovacie kľúče.
+\n
+\nOdstránenie neaktívnych relácií zvyšuje bezpečnosť a výkon a uľahčuje identifikáciu podozrivých nových relácií.
+ Neaktívne relácie
+ Uvedomte si, že názvy relácií sú viditeľné aj pre ľudí, s ktorými komunikujete.
+ Vlastné názvy relácií vám pomôžu ľahšie rozpoznať vaše zariadenia.
+ Názov relácie
+ Premenovať reláciu
+ Odhlásiť sa z tejto relácie
+ Neoverená - Vaša aktuálna relácia
+ Spustiť hlasové vysielanie
+ Vierohodnosť tejto zašifrovanej správy nie je možné zaručiť na tomto zariadení.
+ Požiadajte, aby klávesnica neaktualizovala žiadne personalizované údaje, napríklad históriu písania a slovník, na základe toho, čo ste napísali v konverzáciách. Upozorňujeme, že niektoré klávesnice nemusia toto nastavenie rešpektovať.
+ Inkognito klávesnica
+ Pridá znaky (╯°□°)╯︵ ┻━┻ pred správy vo formáte obyčajného textu
+ Hlasové vysielanie
+ Otvoriť obrazovku vývojárskych nástrojov
+ 🔒 V Nastaveniach zabezpečenia ste povolili šifrovanie len pre overené relácie pre všetky miestnosti.
+ ⚠ V tejto miestnosti sa nachádzajú neoverené zariadenia, ktoré nebudú schopné dešifrovať odoslané správy.
+ Nikdy neposielať šifrované správy do neoverených relácií v tejto miestnosti.
+ Rozumiem
+ Použiť formát podčiarknutia
+ Použiť formát prečiarknutia
+ Použiť formát kurzívou
+ Použiť tučný formát
+ Zaznamenať názov klienta, verziu a url, aby bolo možné ľahšie rozpoznať relácie v správcovi relácií.
+ Povoliť zaznamenanie informácií o klientovi
+ Majte lepší prehľad a kontrolu nad všetkými reláciami.
+ Použiť nového správcu relácií
+ Operačný systém
+ Model
+ Prehliadač
+ URL
+ Verzia
+ Názov
+ Aplikácia
+ Prijímať push oznámenia v tejto relácii.
+ Push oznámenia
+ Overením aktuálnej relácie zistíte stav overenia tejto relácie.
+ Neznámy stav overenia
+ Zapnuté:
+ ID relácie:
+ Niečo sa pokazilo. Skontrolujte, prosím, svoje sieťové pripojenie a skúste to znova.
+ Udeliť oprávnenie
+ ${app_name} potrebuje povolenie na zobrazovanie oznámení.
+\nProsím, udeľte toto povolenie.
+ ${app_name} potrebuje povolenie na zobrazovanie oznámení. Oznámenia môžu zobrazovať vaše správy, pozvánky atď.
+\n
+\nPovoľte prístup na ďalších vyskakovacích oknách, aby ste mohli zobrazovať oznámenia.
+ Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro)
+ Povoliť rozšírený textový editor
+ Uistite sa prosím, že poznáte pôvod tohto kódu. Prepojením zariadení poskytnete niekomu plný prístup k svojmu účtu.
+ Potvrdiť
+ Skúste to znova
+ Nezhoduje sa\?
+ Prebieha prihlasovanie
+ Pripájanie k zariadeniu
+ Skenovať QR kód
+ Prihlasovanie do mobilného zariadenia\?
+ Zobraziť QR kód na tomto zariadení
+ Vyberte možnosť \"Skenovať QR kód\"
+ Začnite na prihlasovacej obrazovke
+ Vyberte možnosť \"Prihlásiť sa pomocou QR kódu\"
+ Začnite na prihlasovacej obrazovke
+ Vyberte možnosť \"Zobraziť QR kód na tomto zariadení\"
+ Prejdite do Nastavenia -> Zabezpečenie a súkromie -> Zobraziť všetky relácie
+ Otvorte ${app_name} na vašom druhom zariadení
+ Žiadosť bola na druhom zariadení zamietnutá.
+ Prepojenie nebolo dokončené v požadovanom čase.
+ Prepojenie s týmto zariadením nie je podporované.
+ Neúspešné pripojenie
+ Skontrolujte svoje prihlásené zariadenie, mal by sa zobraziť nasledujúci kód. Skontrolujte, či sa nižšie uvedený kód zhoduje s daným zariadením:
+ Zabezpečené pripojenie bolo vytvorené
+ Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené.
+ Pomocou prihláseného zariadenia naskenujte nižšie uvedený QR kód:
+ Prihlásiť sa pomocou QR kódu
+ Pomocou fotoaparátu na tomto zariadení naskenujte QR kód zobrazený na vašom druhom zariadení:
+ Skenovať QR kód
+ 3
+ 2
+ 1
+ Pomocou tohto zariadenia sa môžete prihlásiť do mobilného alebo webového zariadenia pomocou QR kódu. Môžete to urobiť dvoma spôsobmi:
+ Prihlásiť sa pomocou QR kódu
+ Skenovať QR kód
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml
index 30b63c213c..1f38d2069c 100644
--- a/library/ui-strings/src/main/res/values-sv/strings.xml
+++ b/library/ui-strings/src/main/res/values-sv/strings.xml
@@ -570,8 +570,8 @@
Upptäckbarhetsalternativ kommer att synas när du har lagt till ett telefonnummer.Om du kopplar bort från din identitetsserver så kommer du inte att vara upptäckbar av andra användare och du kommer inte kunna bjuda in folk med hjälp av deras e-postadresser eller telefonnummer.Upptäckbara telefonnummer
- Vi skickade ett bekräftelse-e-brev till %s, kolla din e-post och klicka på bekräftelselänken
- Vi skickade ett bekräftelse-e-brev till %s, vänligen kolla din e-post och klicka på bekräftelselänken
+ Vi skickade ett e-brev till %s, kolla din e-post och klicka på bekräftelselänken
+ Vi skickade ett e-brev till %s, vänligen kolla din e-post och klicka på bekräftelselänkenSkriv in en identitetsserver-URLKunde inte ansluta till identitetsserverVänligen ange en identitetsserver-URL
@@ -615,7 +615,7 @@
Jag har verifierat min e-postadressDu har blivit utloggad ur alla sessioner och kommer inte längre motta pushnotiser. För att återaktivera pushnotiser, logga in igen på varje enhet.Sätt e-postadress
- Sätt en e-postadress för att kunna återförva ditt konto. Senare kan du valfritt låta personer du känner upptäcka dig med din e-postadress.
+ Sätt en e-postadress för att kunna återförvärva ditt konto. Senare kan du valfritt låta personer du känner upptäcka dig med den här e-postadressen.E-postE-post (valfritt)Sätt ett telefonnummer som valfritt kan användas för att vara upptäckbar av folk som känner dig.
@@ -1312,10 +1312,10 @@
Ett fel inträffade vid hämtning av nyckelsäkerhetskopiaDu tittar redan på det här rummet!Inga registrerade pushgateways
- app_id:
- push_key:
- app_display_name:
- session_name:
+ App-ID:
+ Pushnyckel:
+ Appens visningsnamn:
+ Sessionens visningsnamn:Url:Format:Registrera token
@@ -2581,4 +2581,141 @@
Välj manuelltStäll in automatisktVälj teckenstorlek
-
+ Öppna utvecklingsverktygsskärmen
+ Tyvärr hittades inte det här rummet.
+\nVänligen pröva igen senare.%s
+ 🔒 Du har aktiverat kryptering endast till verifierade sessioner för alla rum i säkerhetsinställningarna.
+ ⚠ Det finns overifierade enheter i det här rummet, de kommer inte kunna avkryptera meddelanden du skickar.
+ Skicka aldrig krypterade meddelanden till overifierade sessioner i det här rummet.
+
+ %1$s och %2$d till
+ %1$s och %2$d till
+
+ %1$s och %2$s
+ E-post inte verifierad, kolla din inkorg
+ Det här är var dina nya förfrågningar och inbjudningar kommer att vara.
+ Inget nytt.
+ Inbjudningar
+ Utrymmen är ett nytt sätt att gruppera rum och personer. Skapa ett utrymme för att komma igång.
+ Inga utrymmen än.
+ Skapa DM först när du skickar första meddelandet
+ Aktivera avvaktade DM:er
+ En förenklad Element med valfria flikar
+ Aktivera ny layout
+ A - Ö
+ Aktivitet
+ Sortera efter
+ Visa nyliga
+ Visa filter
+ Gränssnittsinställningar
+ Förstått
+ Kollapsa underutrymmen för %s
+ Expandera underutrymmen för %s
+ Utforska rum
+ Byt utrymme
+ Skapa rum
+ Starta chatt
+ Alla chattar
+ Den här sessionen är redo för säkra meddelanden.
+ Din nuvarande session är redo för säkra meddelanden.
+ Overifierad session
+ Verifierad session
+ Okänd enhetstyp
+ Skrivbord
+ Webb
+ Mobil
+ För bäst säkerhet, verifiera dina sessioner och logga ut från alla sessioner du inte känner igen eller använder längre.
+ Andra sessioner
+ Godkänn automatiskt Element Call-widgets och ge kamera-/mikrofonåtkomst
+ Aktivera Element Call-behörighetsgenvägar
+ Starta en röstsändning
+ Realtidsplats
+ Kunde inte ladda kartan.
+\nDen här hemservern kanske inte är konfigurerad för at visa kartor.
+ Öppna inställningar
+ Autenticiteten för det krypterade meddelanden kan inte garanteras på den här enheten.
+ Begär att tangentbordet inte ska uppdatera någon personanpassad data som skrivhistorik och ordlista baserat på vad du har skrivit i konversationer. Observera att vissa tangentbord inte respekterar den här inställningen.
+ Inkognitotangentbord
+ Det här QR-koden ser ogiltig ut. Vänligen pröva igen för att verifiera med en annan metod.
+ Du kommer inte komma åt krypterad meddelandehistorik. Återställ din säkra meddelandesäkerhetskopia och verifieringsnycklar för att börja om.
+ Kunde inte verifiera den här enheten
+ Sessioner
+ Lägger till (╯°□°)╯︵ ┻━┻ till början av ett textmeddelande
+ Vad är din servers adress\?
+ Där dina konversationer bor
+ Röstsändning
+ Öppna utrymmeslista
+ Skapa den ny konversation eller ett nytt rum
+ Uppdaterar din data…
+ Personer
+ Favoriter
+ Olästa
+ Alla
+ Pushnotiser
+ Applikations-, enhets- och aktivitetsinformation.
+ Sessionsdetaljer
+ Logga ut ur den här sessionen
+ Rensa filter
+ Inga inaktiva sessioner hittade.
+ Inga overifierade sessioner hittade.
+ Inga verifierade sessioner hittade.
+
+ Överväg att logga ut ur gamla sessioner (%1$d dag eller längre) du inte använder längre.
+ Överväg att logga ut ur gamla sessioner (%1$d dagar eller längre) du inte använder längre.
+
+ Inaktiv
+ Verifiera dina sessioner för förbättrad säker meddelandehantering eller logga ut ur de du inte känner igen eller använder längre.
+ Overifierad
+ För bäst säkerhet, logga ut från sessioner du inte känner igen eller använder längre.
+ Verifierad
+ Filter
+
+ Överväg att logga ut ur gamla sessioner (%1$d dag eller längre) som du inte använder längre.
+ Överväg att logga ut ur gamla sessioner (%1$d dagar eller längre) som du inte använder längre.
+
+
+ Inaktiv %1$d dag eller längre
+ Inaktiv %1$d dagar eller längre
+
+ Inaktiv
+ Inte redo för säkra meddelanden
+ Overifierad
+ Redo för säkra meddelanden
+ Verifierade
+ Alla sessioner
+ Filter
+ Senast aktiv %1$s
+ Enhet
+ Session
+ Nuvarande session
+ Inaktiva sessioner
+ Verifiera eller logga ut ur overifierade sessioner.
+ Overifierade sessioner
+ Förbättra din kontosäkerhet genom att följa dessa rekommendationer.
+ Säkerhetsrekommendationer
+
+ Inaktiv %1$d+ dag (%2$s)
+ Inaktiv %1$d+ dagar (%2$s)
+
+ Overifierad · Din nuvarande session
+ Overifierad · Senast aktiv %1$s
+ Verifierad · Senast aktiv %1$s
+ Visa alla (%1$d)
+ Visa detaljer
+ Verifiera session
+ Verifiera din nuvarande session för att visa den här sessionens verifieringsstatus.
+ Verifiera eller logga ut från den här sessionen för bäst säkerhet och pålitlighet.
+ Verifiera din nuvarande session för förbättrad säker meddelandehantering.
+ Okänd verifieringsstatus
+ Aktiverad:
+ Sessions-ID:
+ Nåt gick fel. Kolla din nätverksanslutning och pröva igen.
+ Ge åtkomst
+ ${app_name} behöver behörighet att visa aviseringar.
+\nVänligen ge åtkomst.
+ ${app_name} behöver behörighet att visa aviseringar. Aviseringar kan visa dina meddelanden, dina inbjudningar, o.s.v.
+\n
+\nVänligen ge åtkomst på nästa pop-uper för att kunna se aviseringar.
+ Aktivera rik-text-redigerare
+ Testa den nya rik-text-redigeraren
+
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml
index c4f1658f6b..2a04e58f41 100644
--- a/library/ui-strings/src/main/res/values-uk/strings.xml
+++ b/library/ui-strings/src/main/res/values-uk/strings.xml
@@ -675,8 +675,8 @@
У цій кімнаті немає файлів%1$s о %2$sЖодного номера телефону не додано до вашого облікового запису
- У ваш обліковий запис не додано жодної електронної адреси
- Для вашої приватності ${app_name} підтримує лише надсилання хешованих електронних адрес користувачів та номера телефону.
+ У ваш обліковий запис не додано жодної адреси електронної адреси
+ Для вашої приватності ${app_name} підтримує лише надсилання хешованих електронних адрес користувачів та номерів телефону.Ви погодилися надіслати електронні адреси та телефонні номери на цей сервер ідентифікації для виявлення інших користувачів із ваших контактів.Надіслати електронні адреси та номери телефонівКерування електронними адресами та номерами телефонів, пов’язаними з вашим обліковим записом Matrix
@@ -1740,10 +1740,10 @@
ВідгукФормат:Url:
- session_name:
- app_display_name:
- push_key:
- app_id:
+ Показувана назва сеансу:
+ Показувана назва застосунку:
+ Ключ Push:
+ ID застосунку:Версія Matrix SDKКімнату створено, але деякі запрошення не надіслано з такої причини:
\n
@@ -1814,8 +1814,8 @@
Схоже, що відповідь сервера надто тривала, це може бути спричинено або поганим з’єднанням, або помилкою сервера. Повторіть спробу через деякий час.Повторіть спробу, коли погодитесь з умовами свого домашнього сервера.Текстове повідомлення надіслано на %s. Введіть код підтвердження, який воно містить.
- Ми надіслали вам електронний лист для підтвердження на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження
- Ми надіслали вам електронний лист для підтвердження на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження
+ Ми надіслали вам електронний лист на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердження
+ Ми надіслали вам електронний лист на %s, відкрийте свою е-пошту та клацніть на посилання для підтвердженняОпитуванняФайлГолосове
@@ -1877,7 +1877,7 @@
ДаліДаліСкинути пароль на %1$s
- Ця е-пошта не пов\'язана з жодним обліковим записом.
+ Ця адреса е-пошти не пов\'язана з жодним обліковим записом.Перепрошуємо, сервер не приймає нових облікових записів.Сталася помилка під час завантаження сторінки: %1$s (%2$d)Введіть адресу сервера, який ви хочете використовувати
@@ -1895,7 +1895,7 @@
Під\'єднатися до Element Matrix ServicesПід\'єднатися до %1$sПеревірти вхідні
- Ця е-пошта не пов\'язана з жодним обліковим записом
+ Ця адреса е-пошти не пов\'язана з жодним обліковим записомПродовжитиПродовжитиєдиний вхід
@@ -1948,7 +1948,7 @@
Завершити налаштування виявності.Виявні номери телефонуОпції виявності з\'являться після додавання номера телефону.
- Опції виявності з\'являться після додавання е-пошти.
+ Опції виявності з\'являться після додавання адреси е-пошти.Відкрити налаштування виявностіПошук за іменем, ID або е-поштоюСтворити новий простір
@@ -1956,7 +1956,7 @@
Доступ до просторуХто може мати доступ\?Увімкнути сповіщення е-поштою для %s
- Щоб отримувати сповіщення е-поштою, пов’яжіть її зі своїм обліковим записом Matrix
+ Щоб отримувати сповіщення е-поштою, пов’яжіть її адресу зі своїм обліковим записом MatrixСповіщення е-поштоюОновити простірЗмінити назву простору
@@ -2197,7 +2197,7 @@
Над чим ви працюєте\?Хто учасники вашої команди\?Назвіть його, щоб продовжити.
- Приватний простір для організації ваших кімнат
+ Приватний простір для впорядкування ваших кімнатНе вдалося знайти таку кімнату. Переконайтеся, що вона існує.Показувати вміст у сповіщеннях
@@ -2233,7 +2233,7 @@
Надіслано забагато запитів. Спробуйте знову за %1$d секунд…Надіслано забагато запитів. Спробуйте знову за %1$d секунд…
- Вкажіть е-пошту для відновлення облікового запису. Згодом ви зможете дозволити знайомим знаходити вас за е-поштою.
+ Вкажіть адресу е-пошти для відновлення облікового запису. Згодом ви зможете дозволити знайомим знаходити вас за цією адресою.Ви вийшли з усіх сеансів і більше не отримуватимете сповіщень. Щоб отримувати сповіщення знову, ввійдіть на кожному пристрої заново.Зміна пароля скине всі ключі наскрізного шифрування всіх ваших сеансів, унеможливлюючи читання історії шифрованих чатів. Налаштуйте резервне копіювання ключів чи експортуйте ключі кімнат з іншого сеансу, перш ніж скинути пароль.Застосунку не вдалося створити обліковий запис на цьому домашньому сервері.
@@ -2242,7 +2242,7 @@
Застосунку не вдається зайти до вашого домашнього сервера. Домашній сервер підтримує такі типи входу: %1$s.
\n
\nБажаєте зайти через вебклієнт\?
- Щоб знайти наявні контакти, надішліть дані контактів (е-пошти й номери телефонів) серверу ідентифікації. Ми хешуємо ваші дані перед надсиланням для приватності.
+ Щоб знайти наявні контакти, надішліть дані контактів (адреси е-пошти й номери телефонів) серверу ідентифікації. Ми хешуємо ваші дані перед надсиланням для приватності.Ваші контакти приватні. Щоб дізнаватись про користувачів, відповідних вашим контактам, дозвольте нам надсилати дані ваших контактів серверу ідентифікації.Надіслати електронні адреси та номери телефонів %sСеанс завершено!
@@ -2340,7 +2340,7 @@
\nВвімкнути захищене резервне копіювання й керувати своїми ключами можна в налаштуваннях.Скасування залишить %1$s (%2$s) без звірки. У їхньому користувацькому профілі можна почати заново.Звірте цим сеансом свій новий. Це надасть йому доступ до зашифрованих повідомлень.
- Надіслані цьому сеансу й цим сеансом повідомлення позначатимуться застереженнями, поки цей користувач йому не довірить. Або ви можете власноруч звірити сеанс.
+ Поки цей користувач не довіряє цьому сеансу, повідомлення, що надсилаються до нього і від нього, позначаються попередженнями. Крім того, ви можете звірити його вручну.Якщо ви увімкнете шифрування для кімнати, його неможливо буде вимкнути. Надіслані у зашифровану кімнату повідомлення будуть прочитними тільки для учасників кімнати, натомість для сервера вони будуть непрочитними. Увімкнення шифрування може унеможливити роботу ботів та мостів.Не вдалося поширити звірку цього сеансу з вашими іншими.
\nЗвірка збережеться локально, її поширить майбутня версія застосунку.
@@ -2700,7 +2700,6 @@
\nМожливо, цей домашній сервер не налаштовано для показу карт.Відкрити налаштуванняУсі бесіди
- Показати всі сеанси (V2, WIP)Звірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте для кращої безпеки.Інші сеансиСеанси
@@ -2756,7 +2755,7 @@
Неактивні сеансиЗвірити або вийти з не звірених сеансів.
- Не звірений сеанс
+ Не звірені сеансиУдоскональте безпеку свого облікового запису, дотримуючись цих порад.Поради щодо безпеки
@@ -2818,4 +2817,100 @@
Увімкнути відкладені приватні повідомленняСпрощений Element з опціональними вкладкамиУвімкнути новий вигляд
+ Інші користувачі в особистих повідомленнях і кімнатах, до яких ви приєдналися, можуть переглядати повний список ваших сеансів.
+\n
+\nЦе дає їм впевненість у тому, що вони дійсно розмовляють з вами, а також означає, що вони можуть бачити назву сеансу, яку ви ввели тут.
+ Перейменування сеансів
+ Звірені сеанси — ті, до яких ви ввійшли за допомогою своїх облікових даних, а потім пройшли перевірку, використовуючи вашу захищену парольну фразу або шляхом перехресної перевірки.
+\n
+\nЦе означає, що вони мають ключі шифрування для ваших попередніх повідомлень і підтверджують іншим користувачам, з якими ви спілкуєтеся, що ці сеанси — це дійсно ви.
+ Звірені сеанси
+ Не звірені сеанси — це сеанси, до яких ви ввійшли в за допомогою своїх облікових даних, але не пройшли перехресну перевірку.
+\n
+\nВи повинні бути особливо впевнені, що розпізнаєте ці сеанси, оскільки вони можуть означати несанкціоноване використання вашого облікового запису.
+ Не звірені сеанси
+ Неактивні сеанси — це сеанси, які ви не використовували протягом певного часу, але вони продовжують отримувати ключі шифрування.
+\n
+\nВилучення неактивних сеансів поліпшує безпеку і швидкодію, а також полегшує визначення підозрілих нових сеансів.
+ Неактивні сеанси
+ Зауважте, що назви сеансів також видно людям, з якими ви спілкуєтесь.
+ Власні назви сеансів допоможуть вам легше розпізнавати ваші пристрої.
+ Назва сеансу
+ Перейменувати сеанс
+ Вийти з цього сеансу
+ Не звірений - Ваш поточний сеанс
+ Розпочати голосове мовлення
+ Справжність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої.
+ Заборонити клавіатурі оновлювати будь-які персоналізовані дані, як-от історію набору тексту та словник, на основі того, що ви набрали в розмовах. Зверніть увагу, що деякі клавіатури можуть не дотримуватися цього налаштування.
+ Клавіатура інкогніто
+ Надсилає (╯°□°)╯︵ ┻━┻ на початку текстового повідомлення
+ Голосове мовлення
+ Відкрийте інструменти розробника
+ 🔒 Ви увімкнули шифрування лише для перевірених сеансів для всіх кімнат у налаштуваннях безпеки.
+ ⚠ У цій кімнаті є неперевірені пристрої, вони не зможуть розшифрувати повідомлення, які ви надсилаєте.
+ Ніколи не надсилати зашифровані повідомлення на неперевірені сеанси в цій кімнаті.
+ Зрозуміло
+ Застосувати форматування підкресленим
+ Застосувати форматування перекресленим
+ Застосувати форматування курсивом
+ Застосувати форматування жирним
+ Записуйте назву клієнта, версію та URL-адресу, щоб легше розпізнавати сеанси в менеджері сеансів.
+ Увімкнути запис відомостей про клієнт
+ Отримайте кращу видимість і контроль над усіма вашими сеансами.
+ Увімкнути новий менеджер сеансів
+ Операційна система
+ Модель
+ Браузер
+ URL
+ Версія
+ Назва
+ Застосунок
+ Отримувати push-сповіщення про цей сеанс.
+ Push-сповіщення
+ Звірте свій поточний сеанс, щоб побачити стан перевірки цього сеансу.
+ Невідомий стан перевірки
+ Увімкнено:
+ ID сеансу:
+ Щось пішло не так. Будь ласка, перевірте мережеве з\'єднання та спробуйте ще раз.
+ Надати дозвіл
+ ${app_name} потребує дозволу на показ сповіщень.
+\nНадайте дозвіл.
+ Для показу сповіщень ${app_name} потрібен дозвіл. Сповіщення можуть показувати ваші повідомлення, запрошення тощо.
+\n
+\nДозвольте доступ до наступних спливних вікон, щоб мати змогу переглядати сповіщення.
+ Спробуйте розширений текстовий редактор (незабаром з\'явиться режим звичайного тексту)
+ Увімкнути розширений текстовий редактор
+ Переконайтеся, що ви знаєте походження цього коду. Пов\'язавши пристрої, ви надасте будь-кому повний доступ до свого облікового запису.
+ Підтвердити
+ Повторити спробу
+ Не збігається\?
+ Вхід
+ Під\'єднання до пристрою
+ Входите на мобільному пристрої\?
+ Показати QR-код на цьому пристрої
+ Виберіть «Сканувати QR-код»
+ Виберіть «Увійти за допомогою QR-коду»
+ Почніть з екрана входу
+ Почніть з екрана входу
+ Виберіть «Показати QR-код на цьому пристрої»
+ Перейдіть до Налаштування -> Безпека й приватність -> Показати всі сеанси
+ Відкрийте ${app_name} на іншому своєму пристрої
+ Запит на іншому пристрої було відхилено.
+ Пов\'язування не було завершено у встановлені терміни.
+ Пов\'язування з цим пристроєм не підтримується.
+ Невдале з\'єднання
+ Перевірте свій пристрій, на якому ви ввійшли. На екрані повинен з\'явитися код, наведений нижче. Переконайтеся, що наведений код збігається з кодом на вашому пристрої:
+ Безпечне з\'єднання встановлено
+ Зіскануйте QR-код нижче своїм пристроєм, з якого ви вийшли.
+ Скануйте QR-код нижче за допомогою свого пристрою для входу:
+ Увійти за допомогою QR-коду
+ Використовуйте камеру цього пристрою, щоб зісканувати QR-код, показаний на іншому пристрої:
+ 3
+ 2
+ 1
+ За допомогою цього пристрою ви можете ввійти на мобільному або вебпристрої за допомогою QR-коду. Зробити це можна двома способами:
+ Увійти за допомогою QR-коду
+ Сканувати QR-код
+ Сканувати QR-код
+ Сканувати QR-код
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
index eba96e82c3..ae29132c91 100644
--- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
+++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
@@ -1583,12 +1583,12 @@
此电话号码已定义。你的账户尚未添加电话号码电子邮件地址
- 你的账户尚未添加电子邮件
+ 你的账户尚未添加电子邮件地址电话号码移除 %s?请确认你已点击我们向你发送的电子邮件中的链接。电子邮件和电话号码
- 管理链接到你的 Matrix 账户的电子邮件和电话号码
+ 管理链接到你的Matrix账户的电子邮件地址和电话号码代码请使用国际格式(电话号码必须以“+”开始)验证此登录来确认你的身份,授权其访问加密消息。
@@ -2171,7 +2171,7 @@
空间访问谁可以访问?启用 %s 的电邮通知
- 要接收通知邮件,请将一个电子邮箱关联到你的 Matrix 账户
+ 要接收通知邮件,请将一个电子邮件地址关联到你的Matrix账户电子邮件通知升级空间更改空间名称
@@ -2550,7 +2550,6 @@
\n此主服务器可能没有设置好显示地图。打开设置全部聊天
- 显示全部会话(V2, WIP)为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。其他会话会话
@@ -2624,4 +2623,6 @@
仅在首条消息创建私聊消息启用延迟的私聊消息简化的Element,带有可选的标签
+ 无痕键盘
+ 要求键盘不要基于你在对话中的输入更新任何个性化数据,如输入历史和字典。请注意,某些键盘可能不会遵守此设置。
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
index 876084d566..c9057cd289 100644
--- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
+++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
@@ -873,10 +873,10 @@
推送規則未定義通送規則沒有已註冊的推送閘道
- app_id:
- push_key:
- app_display_name:
- session_name:
+ App ID:
+ 推送金鑰:
+ 應用程式顯示名稱:
+ 工作階段顯示名稱:Url:格式:音訊與視訊
@@ -938,11 +938,11 @@
您正在使用 %1$s 來探索與被您認識的既有聯絡人探索。您目前並未使用身份識別伺服器。要探索與被您認識的聯絡人探索,請在下方設定一個。可探索的電子郵件地址
- 在您新增電子郵件後,探索選項將會出現。
+ 在您新增電子郵件地址後,探索選項將會出現。在您新增電話號碼後,探索選項將會出現。與您的身份識別伺服器斷線代表您無法被其他使用者探索,且您將無法透過電子郵件或電話邀請其他人。可探索的電話號碼
- 我們將會傳送確認電子郵件到 %s 給您,請檢查您的電子郵件並在確認連結上點選
+ 我們將會傳送電子郵件到 %s,請檢查您的電子郵件並在確認連結上點選輸入身份識別伺服器 URL無法連線到身份識別伺服器請輸入身份識別伺服器 URL
@@ -1069,7 +1069,7 @@
應用程式無法在此家伺服器上建立帳號。
\n
\n您想要使用網路客戶端註冊嗎?
- 此電子郵件未關聯到任何帳號。
+ 此電子郵件地址未關聯到任何帳號。在 %1$s 上重設密碼驗證郵件已傳送到您的收件匣以確認您要設定新密碼。下一步
@@ -1078,7 +1078,7 @@
警告!變更您的密碼將會重設在您所有工作階段中任何的端到端加密金鑰,讓已加密的聊天歷史無法讀取。請在重設您的密碼前從其他工作階段設定金鑰備份或匯出您的聊天室金鑰。繼續
- 此電子郵件未被連結到任何帳號
+ 此電子郵件地址未被連結到任何帳號檢查您的收件匣驗證電子郵件已傳送至 %1$s。輕點連結以確認您的新密碼。在您使用了其中包含的連結後,請點擊下方。
@@ -1092,7 +1092,7 @@
\n
\n停止密碼變更流程?
設定電子郵件地址
- 設定電子郵件以復原您的帳號。之後您也可以選擇性地讓您認識的人透過您的電子郵件找到您。
+ 設定電子郵件地址以復原您的帳號。之後您也可以選擇性地讓您認識的人透過此地址找到您。電子郵件電子郵件(選擇性)下一個
@@ -1430,7 +1430,7 @@
訊息已移除顯示已移除的訊息為已移除的訊息顯示佔位符
- 我們已傳送確認電子郵件給 %s,請先檢查您的電子郵件並點擊確認連結
+ 我們已傳送電子郵件到 %s,請先檢查您的電子郵件並點擊確認連結驗證代碼不正確。媒體此聊天室中沒有媒體
@@ -1453,7 +1453,7 @@
此動作是不可能的。家伺服器太舊了。請先設定身份識別伺服器。請先在設定中同意身份識別伺服器的條款。
- 為了保護您的隱私,${app_name} 僅支援傳送雜湊過的使用者電子郵件與電話號碼。
+ 為了保護您的隱私,${app_name} 僅支援傳送雜湊過的使用者電子郵件地址與電話號碼。關聯失敗。目前沒有此識別符的關聯。您的家伺服器 (%1$s) 建議將 %2$s 用於您的身份識別伺服器
@@ -1623,12 +1623,12 @@
此電話號碼已被定義。未新增電話號碼到您的帳號電子郵件地址
- 未傳送電子郵件到您的帳號
+ 未新增電子郵件地址至您的帳號電話號碼移除 %s?確保您已經點擊我們傳送給您的電子郵件中的連結。電子郵件與電話號碼
- 管理連結到您 Matrix 帳號的電子郵件與電話號碼
+ 管理連結到您 Matrix 帳號的電子郵件地址與電話號碼代碼請使用國際格式(電話號碼必須以 \'+\' 開頭)透過確認此登入來驗證您的身份,以及授予存取加密訊息的權限。
@@ -1744,7 +1744,7 @@
%2$d 中的 %1$d給予同意撤銷我的同意
- 您已同意傳送電子郵件與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。
+ 您已同意傳送電子郵件地址與電話號碼到此身份提供者以從您的聯絡人中探索其他使用者。傳送電子郵件或電話號碼建議已知的使用者
@@ -2103,7 +2103,7 @@
提及與關鍵字預設通知設定中的 %s 可直接在 ${app_name} 中接收邀請。
- 將此電子郵件與您的帳號連結
+ 將此電子郵件地址與您的帳號連結此空間的邀請已傳送給與您的帳號無關的 %s此聊天室的邀請已傳送給與您的帳號無關的 %s您所在的所有聊天室都會顯示在 Home 中。
@@ -2171,7 +2171,7 @@
空間存取誰可以存取?啟用 %s 的電子郵件通知
- 要收到通知用的電子郵件,請將電子郵件關聯至您的 Matrix 帳號
+ 要收到通知用的電子郵件,請將電子郵件地址關聯至您的 Matrix 帳號電子郵件通知升級空間變更空間名稱
@@ -2217,12 +2217,12 @@
投票問題或主題建立投票投票
- 向 %s 傳送電子郵件與電話號碼
+ 向 %s 傳送電子郵件地址與電話號碼您的通訊錄是私人的。要從您的通訊錄中探索使用者,我們需要您的權限來傳送聯絡人資訊到您的身份識別伺服器。已登出工作階段!已離開聊天室!您同意傳送此資訊嗎?
- 要探索現有聯絡人,您必須傳送聯絡人資訊(電子郵件與電話號碼)到您的身份識別伺服器。我們會在傳送前對您的資料進行雜湊處理以保護隱私。
+ 要探索現有聯絡人,您必須傳送聯絡人資訊(電子郵件地址與電話號碼)到您的身份識別伺服器。我們會在傳送前對您的資料進行雜湊處理以保護隱私。現在不要您確定要移除此投票?移除後將無法復原。移除投票
@@ -2550,7 +2550,6 @@
\n此家伺服器可能未設定好顯示地圖。
開啟設定所有聊天
- 顯示所有工作階段 (V2, WIP)為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。其他工作階段工作階段
@@ -2656,4 +2655,100 @@
啟用延期直接訊息包含選擇性分頁的簡潔 Element啟用新佈局
+ 您加入的直接消息與聊天室中的其他使用者可以檢視您的工作階段的完整清單。
+\n
+\n這讓他們確信他們真的在與您交談,但這也意味著他們可以看到您在此處輸入的工作階段名稱。
+ 正在重新命名工作階段
+ 已驗證的工作階段代表使用您的憑證登入,然後使用您的安全通關密語或透過交叉驗證進行驗證。
+\n
+\n這代表了它們持有您先前訊息的加密金鑰,並向您正在與之通訊的其他使用者確認這些工作階段確實是您。
+ 已驗證的工作階段
+ 未驗證的工作階段是使用您的憑證登入但未交叉驗證的工作階段。
+\n
+\n您應特別確定您可以識別這些工作階段,因為它們可能代表未經授權使用您的帳號。
+ 未驗證的工作階段
+ 不活躍的工作階段是您有一段時間未使用的工作階段,但它們會繼續接收加密金鑰。
+\n
+\n移除不活躍的工作階段可以改善安全性與效能,並讓您可以更容易地識別新的工作階段是否可疑。
+ 不活躍的工作階段
+ 請注意,與您交流的人也可以看到工作階段名稱。
+ 自訂工作階段名稱可以協助您更輕鬆地識別您的裝置。
+ 工作階段名稱
+ 重新命名工作階段
+ 登出此工作階段
+ 未驗證 · 您目前的工作階段
+ 開始語音廣播
+ 此裝置無法保證此加密訊息的真實性。
+ 要求鍵盤不要根據您在對話中輸入的內容更新任何個人化資料(如輸入歷史紀錄與字典等)。請注意,某些鍵盤可能不會遵守此設定。
+ 無痕式鍵盤
+ 將 (╯°□°)╯︵ ┻━┻ 放到純文字訊息之前
+ 語音廣播
+ 開啟開發者工具畫面
+ 🔒 您已在「安全」設定中為所有聊天室啟用加密驗證工作階段。
+ ⚠ 此聊天室中有未驗證的裝置,它們將無法解密您傳送的訊息。
+ 切莫向此聊天室中未經驗證的工作階段傳送加密訊息。
+ 知道了
+ 套用底線格式
+ 套用刪除線格式
+ 套用義式斜體格式
+ 套用粗體格式
+ 記錄客戶端名稱、版本與 URL,以便在工作階段管理程式中可以更簡單地辨認工作階段。
+ 啟用客戶端資訊記錄
+ 對所有工作階段有更大的能見度與控制。
+ 啟用新的工作階段管理程式
+ 作業系統
+ 模型
+ 瀏覽器
+ URL
+ 版本
+ 名稱
+ 應用程式
+ 接收關於此工作階段的推播通知。
+ 推播通知
+ 驗證您目前的工作階段以顯示此工作階段的驗證狀態。
+ 未知的驗證狀態
+ 已啟用:
+ 工作階段 ID:
+ 發生了一些問題。請檢查您的網路連線並再試一次。
+ 授予權限
+ ${app_name} 需要權限以顯示通知。
+\n請授予權限。
+ ${app_name} 需要權限才能顯示通知。通知可以顯示您的訊息、您的邀請等等。
+\n
+\n請在下一個彈出式視窗允許存取以檢視通知。
+ 試用格式化文字編輯器(純文字模式即將推出)
+ 啟用格式化文字編輯器
+ 請確保您知道此驗證碼的來源。透過連結裝置,您將為某人提供對您帳號的完整存取權限。
+ 確認
+ 再試一次
+ 不相符?
+ 登入
+ 連線至裝置
+ 掃描 QR code
+ 正在使用行動裝置登入?
+ 在此裝置顯示 QR code
+ 選取「掃描 QR code」
+ 從登入畫面開始
+ 選取「使用 QR code 登入」
+ 從登入畫面開始
+ 選取「在此裝置上顯示 QR code」
+ 到「設定」→「安全與隱私」→「顯示所有工作階段」
+ 在您的其他裝置上開啟 ${app_name}
+ 請求在另一台裝置上被拒絕。
+ 連結未在規定時間內完成。
+ 不支援與其裝置連結。
+ 連線不成功
+ 請檢查您已登入的裝置,應該會顯示以下驗證碼。請確認以下驗證碼與該裝置相符:
+ 已建立安全連線
+ 使用您已登出的裝置掃描以下 QR code。
+ 使用您已登入的裝置來掃描下方的 QR code:
+ 使用 QR code 登入
+ 使用此裝置的相機掃描您其他裝置上顯示的 QR code:
+ 掃描 QR code
+ 3
+ 2
+ 1
+ 您可以使用此裝置透過 QR code 登入移動裝置或網路裝置。有兩種方法可以作到:
+ 使用 QR code 登入
+ 掃描 QR code
\ No newline at end of file
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index c997fb5639..ea9b4b5999 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -447,7 +447,7 @@
Create DM only on first messageEnable rich text editor
- Use a rich text editor to send formatted messages
+ Try out the rich text editor (plain text mode coming soon)Invites
@@ -638,6 +638,8 @@
${app_name} needs permission to access your microphone to perform audio calls.${app_name} needs permission to access your camera and your microphone to perform video calls.\n\nPlease allow access on the next pop-ups to be able to make the call.
+
+ ${app_name} needs permission to display notifications. Notifications can display your messages, your invitations, etc.\n\nPlease allow access on the next pop-ups to be able to view notification.To scan a QR code, you need to allow camera access.Allow permission to access your contacts.
@@ -857,7 +859,9 @@
System Settings.Notifications are enabled in the system settings.Notifications are disabled in the system settings.\nPlease check system settings.
+ ${app_name} needs the permission to show notifications.\nPlease grant the permission.Open Settings
+ Grant PermissionAccount Settings.Notifications are enabled for your account.
@@ -2179,6 +2183,7 @@
If you don’t know your password, go back to reset it.This is not a valid user identifier. Expected format: \'@user:homeserver.org\'Unable to find a valid homeserver. Please check your identifier
+ Scan QR codeSeen by
@@ -3073,6 +3078,14 @@
%1$s (%2$s)(%1$s)
+ Live
+ Resume voice broadcast record
+ Pause voice broadcast record
+ Stop voice broadcast record
+ Play or resume voice broadcast
+ Pause voice broadcast
+ Buffering
+
Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.
@@ -3240,8 +3253,6 @@
Auto-approve Element Call widgets and grant camera / mic access
-
- Show All Sessions (V2, WIP)Other sessionsFor best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.Mobile
@@ -3250,10 +3261,12 @@
Unknown device typeVerified sessionUnverified session
+ Unknown verification statusYour current session is ready for secure messaging.This session is ready for secure messaging.Verify your current session for enhanced secure messaging.Verify or sign out from this session for best security and reliability.
+ Verify your current session to reveal this session\'s verification status.Verify SessionView DetailsView All (%1$d)
@@ -3326,6 +3339,9 @@
Session nameCustom session names can help you recognize your devices more easily.Please be aware that session names are also visible to people you communicate with.
+ Sign in with QR Code
+ You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this:
+
Inactive sessionsInactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.Unverified sessions
@@ -3336,6 +3352,10 @@
Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.Enable new session managerHave greater visibility and control over all your sessions.
+ Enable client info recording
+ Record the client name, version, and url to recognise sessions more easily in session manager.
+ Enable voice broadcast (under active development)
+ Be able to record and send voice broadcast in room timeline.%s\nis looking a little empty.
@@ -3358,4 +3378,50 @@
Tap top right to see the option to feedback.Try it out
+ 1
+ 2
+ 3
+
+
+ Scan QR code
+ Use the camera on this device to scan the QR code shown on your other device:
+ Sign in with QR code
+ Use your signed in device to scan the QR code below:
+ Scan the QR code below with your device that’s signed out.
+ Secure connection established
+ Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device:
+ Unsuccessful connection
+ Linking with this device is not supported.
+ The linking wasn’t completed in the required time.
+ The request was denied on the other device.
+ The request failed.
+ A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s);
+ The other device is already signed in.
+ The other device must be signed in.
+ That QR code is invalid.
+ The sign in was cancelled on the other device.
+ The homeserver doesn\'t support sign in with QR code.
+ Open the app on your other device
+ Go to Settings -> Security & Privacy
+ Select \'Show QR code\'
+ Start at the sign in screen
+ Select \'Sign in with QR code\'
+ Start at the sign in screen
+ Select \'Scan QR code\'
+ Show QR code in this device
+ Signing in a mobile device?
+ Scan QR code
+ Connecting to device
+ Signing you in
+ No match?
+ Try again
+ Confirm
+ Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.
+
+
+ Apply bold format
+ Apply italic format
+ Apply strikethrough format
+ Apply underline format
+
diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle
index ee5771d995..c805153e1d 100644
--- a/library/ui-styles/build.gradle
+++ b/library/ui-styles/build.gradle
@@ -21,6 +21,8 @@ plugins {
android {
+ namespace "im.vector.lib.ui.styles"
+
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
diff --git a/library/ui-styles/src/debug/AndroidManifest.xml b/library/ui-styles/src/debug/AndroidManifest.xml
index e32676136d..be7aeafb07 100644
--- a/library/ui-styles/src/debug/AndroidManifest.xml
+++ b/library/ui-styles/src/debug/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
-
+
-
\ 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 f4384adb40..9d8645a707 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -146,10 +146,16 @@
#91A1C0#FF4B55#0FFF4B55
+ @color/palette_gray_200@color/palette_white@color/palette_black_950
+
+
+ #EEF8F4
+ #1D292A
+
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 0fb03f0ea3..50d5aaf014 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -47,7 +47,8 @@
56dp52dp1dp
-
+ 28dp
+ 14dp28dp6dp
@@ -72,6 +73,9 @@
12dp22dp
+
+ 48dp
+
112dp
diff --git a/library/ui-styles/src/main/res/values/stylable_qr_code_instructions_view.xml b/library/ui-styles/src/main/res/values/stylable_qr_code_instructions_view.xml
new file mode 100644
index 0000000000..c9a4bb9d05
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_qr_code_instructions_view.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/ui-styles/src/main/res/values/stylable_qr_code_login_header_view.xml b/library/ui-styles/src/main/res/values/stylable_qr_code_login_header_view.xml
new file mode 100644
index 0000000000..99f56084d9
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_qr_code_login_header_view.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml
index 8de548dd03..b640fc49d9 100644
--- a/library/ui-styles/src/main/res/values/styles_edit_text.xml
+++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml
@@ -11,4 +11,14 @@
?vctr_message_text_color
-
\ No newline at end of file
+
+
+
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index 9f4e5c1e28..d5aaa88ab8 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -152,6 +152,9 @@
@dimen/collapsing_toolbar_layout_medium_size
+
+
+ @color/vctr_rich_text_editor_menu_button_background_dark
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index c8182abecc..1978db9139 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -153,6 +153,9 @@
@dimen/collapsing_toolbar_layout_medium_size
+
+
+ @color/vctr_rich_text_editor_menu_button_background_light
diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle
index fb69af2d82..0a29334ea8 100644
--- a/matrix-sdk-android-flow/build.gradle
+++ b/matrix-sdk-android-flow/build.gradle
@@ -5,6 +5,8 @@ plugins {
}
android {
+ namespace "org.matrix.android.sdk.flow"
+
compileSdk versions.compileSdk
defaultConfig {
diff --git a/matrix-sdk-android-flow/src/main/AndroidManifest.xml b/matrix-sdk-android-flow/src/main/AndroidManifest.xml
index 2392c0bfcb..b2d3ea1235 100644
--- a/matrix-sdk-android-flow/src/main/AndroidManifest.xml
+++ b/matrix-sdk-android-flow/src/main/AndroidManifest.xml
@@ -1,5 +1,2 @@
-
-
-
\ No newline at end of file
+
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index ea2b5d6c47..968d8515ac 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -43,6 +43,8 @@ dokkaHtml {
}
android {
+ namespace "org.matrix.android.sdk"
+
testOptions.unitTests.includeAndroidResources = true
compileSdk versions.compileSdk
@@ -60,7 +62,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.5.4\""
+ buildConfigField "String", "SDK_VERSION", "\"1.5.6\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml
index de0731422c..7f940d4e1c 100644
--- a/matrix-sdk-android/src/main/AndroidManifest.xml
+++ b/matrix-sdk-android/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
+ xmlns:tools="http://schemas.android.com/tools">
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
index 5ae70e1978..252c33a8c4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
@@ -124,4 +124,24 @@ interface AuthenticationService {
initialDeviceName: String,
deviceId: String? = null
): Session
+
+ /**
+ * @param homeServerConnectionConfig the information about the homeserver and other configuration
+ * Return true if qr code login is supported by the server, false otherwise.
+ */
+ suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean
+
+ /**
+ * Authenticate using m.login.token method during sign in with QR code.
+ * @param homeServerConnectionConfig the information about the homeserver and other configuration
+ * @param loginToken the m.login.token
+ * @param initialDeviceName the initial device name
+ * @param deviceId the device id, optional. If not provided or null, the server will generate one.
+ */
+ suspend fun loginUsingQrLoginToken(
+ homeServerConnectionConfig: HomeServerConnectionConfig,
+ loginToken: String,
+ initialDeviceName: String? = null,
+ deviceId: String? = null
+ ): Session
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt
index 627a825679..991b7b654d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/LoginType.kt
@@ -22,7 +22,8 @@ enum class LoginType {
UNSUPPORTED,
CUSTOM,
DIRECT,
- UNKNOWN;
+ UNKNOWN,
+ QR;
companion object {
@@ -32,6 +33,7 @@ enum class LoginType {
UNSUPPORTED.name -> UNSUPPORTED
CUSTOM.name -> CUSTOM
DIRECT.name -> DIRECT
+ QR.name -> QR
else -> UNKNOWN
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
index ae65963f37..22af8cebbd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
@@ -27,6 +27,7 @@ open class LoggerTag(name: String, parentTag: LoggerTag? = null) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP")
object CRYPTO : LoggerTag("CRYPTO")
+ object RENDEZVOUS : LoggerTag("RZ")
val value: String = if (parentTag == null) {
name
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt
new file mode 100644
index 0000000000..f724ac4b62
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/Rendezvous.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous
+
+import android.net.Uri
+import org.matrix.android.sdk.api.auth.AuthenticationService
+import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel
+import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode
+import org.matrix.android.sdk.api.rendezvous.model.Outcome
+import org.matrix.android.sdk.api.rendezvous.model.Payload
+import org.matrix.android.sdk.api.rendezvous.model.PayloadType
+import org.matrix.android.sdk.api.rendezvous.model.Protocol
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent
+import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
+import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
+import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
+import org.matrix.android.sdk.api.util.MatrixJsonParser
+import timber.log.Timber
+
+/**
+ * Implementation of MSC3906 to sign in + E2EE set up using a QR code.
+ */
+class Rendezvous(
+ val channel: RendezvousChannel,
+ val theirIntent: RendezvousIntent,
+) {
+ companion object {
+ private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value
+
+ @Throws(RendezvousError::class)
+ fun buildChannelFromCode(code: String): Rendezvous {
+ val parsed = try {
+ // we rely on moshi validating the code and throwing exception if invalid JSON or doesn't
+ MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code)
+ } catch (a: Throwable) {
+ throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode)
+ } ?: throw RendezvousError("Invalid code", RendezvousFailureReason.InvalidCode)
+
+ val transport = SimpleHttpRendezvousTransport(parsed.rendezvous.transport.uri)
+
+ return Rendezvous(
+ ECDHRendezvousChannel(transport, parsed.rendezvous.key),
+ parsed.intent
+ )
+ }
+ }
+
+ private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java)
+
+ // not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
+ val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE
+
+ @Throws(RendezvousError::class)
+ private suspend fun checkCompatibility() {
+ val incompatible = theirIntent == ourIntent
+
+ Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible")
+
+ if (incompatible) {
+ // inform the other side
+ send(Payload(PayloadType.FINISH, intent = ourIntent))
+ if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) {
+ throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn)
+ } else {
+ throw RendezvousError("The other device is already signed in", RendezvousFailureReason.OtherDeviceAlreadySignedIn)
+ }
+ }
+ }
+
+ @Throws(RendezvousError::class)
+ suspend fun startAfterScanningCode(): String {
+ val checksum = channel.connect()
+
+ Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum")
+
+ checkCompatibility()
+
+ // get protocols
+ Timber.tag(TAG).i("Waiting for protocols")
+ val protocolsResponse = receive()
+
+ if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) {
+ send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED))
+ throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver)
+ }
+
+ send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN))
+
+ return checksum
+ }
+
+ @Throws(RendezvousError::class)
+ suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session {
+ Timber.tag(TAG).i("Waiting for login_token")
+
+ val loginToken = receive()
+
+ if (loginToken?.type == PayloadType.FINISH) {
+ when (loginToken.outcome) {
+ Outcome.DECLINED -> {
+ throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined)
+ }
+ Outcome.UNSUPPORTED -> {
+ throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver)
+ }
+ else -> {
+ throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown)
+ }
+ }
+ }
+
+ val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError)
+ val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError)
+
+ Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver")
+
+ val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver))
+ return authenticationService.loginUsingQrLoginToken(hsConfig, token)
+ }
+
+ @Throws(RendezvousError::class)
+ suspend fun completeVerificationOnNewDevice(session: Session) {
+ val userId = session.myUserId
+ val crypto = session.cryptoService()
+ val deviceId = crypto.getMyDevice().deviceId
+ val deviceKey = crypto.getMyDevice().fingerprint()
+ send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey))
+
+ // await confirmation of verification
+ val verificationResponse = receive()
+ if (verificationResponse?.outcome == Outcome.VERIFIED) {
+ val verifyingDeviceId = verificationResponse.verifyingDeviceId
+ ?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError)
+ val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId)
+ if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) {
+ Timber.tag(TAG).w(
+ "Verifying device $verifyingDeviceId key doesn't match: ${
+ verifyingDeviceFromServer?.fingerprint()
+ } vs ${verificationResponse.verifyingDeviceKey})"
+ )
+ // inform the other side
+ send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
+ throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
+ }
+
+ verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice ->
+ // verifying device provided us with a master key, so use it to check integrity
+
+ // see what the homeserver told us
+ val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()
+
+ // n.b. if no local master key this is a problem, as well as it not matching
+ if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) {
+ Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey")
+ // inform the other side
+ send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
+ throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
+ }
+
+ // set other device as verified
+ Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
+ crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
+
+ Timber.tag(TAG).i("Setting master key as trusted")
+ crypto.crossSigningService().markMyMasterKeyAsTrusted()
+ } ?: run {
+ // set other device as verified anyway
+ Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
+ crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
+
+ Timber.tag(TAG).i("No master key given by verifying device")
+ }
+
+ // request secrets from the verifying device
+ Timber.tag(TAG).i("Requesting secrets from $verifyingDeviceId")
+
+ session.sharedSecretStorageService().let {
+ it.requestSecret(MASTER_KEY_SSSS_NAME, verifyingDeviceId)
+ it.requestSecret(SELF_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
+ it.requestSecret(USER_SIGNING_KEY_SSSS_NAME, verifyingDeviceId)
+ it.requestSecret(KEYBACKUP_SECRET_SSSS_NAME, verifyingDeviceId)
+ }
+ } else {
+ Timber.tag(TAG).i("Not doing verification")
+ }
+ }
+
+ @Throws(RendezvousError::class)
+ private suspend fun receive(): Payload? {
+ val data = channel.receive() ?: return null
+ val payload = try {
+ adapter.fromJson(data.toString(Charsets.UTF_8))
+ } catch (e: Exception) {
+ Timber.tag(TAG).w(e, "Failed to parse payload")
+ throw RendezvousError("Invalid payload received", RendezvousFailureReason.Unknown)
+ }
+
+ return payload
+ }
+
+ private suspend fun send(payload: Payload) {
+ channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8))
+ }
+
+ suspend fun close() {
+ channel.close()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt
new file mode 100644
index 0000000000..0956a5b0a0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousChannel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous
+
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+
+/**
+ * Representation of a rendezvous channel such as that described by MSC3903.
+ */
+interface RendezvousChannel {
+ val transport: RendezvousTransport
+
+ /**
+ * @returns the checksum/confirmation digits to be shown to the user
+ */
+ @Throws(RendezvousError::class)
+ suspend fun connect(): String
+
+ /**
+ * Send a payload via the channel.
+ * @param data payload to send
+ */
+ @Throws(RendezvousError::class)
+ suspend fun send(data: ByteArray)
+
+ /**
+ * Receive a payload from the channel.
+ * @returns the received payload
+ */
+ @Throws(RendezvousError::class)
+ suspend fun receive(): ByteArray?
+
+ /**
+ * Closes the channel and cleans up.
+ */
+ suspend fun close()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt
new file mode 100644
index 0000000000..18e625d825
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousFailureReason.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous
+
+enum class RendezvousFailureReason(val canRetry: Boolean = true) {
+ UserDeclined,
+ OtherDeviceNotSignedIn,
+ OtherDeviceAlreadySignedIn,
+ Unknown,
+ Expired,
+ UserCancelled,
+ InvalidCode,
+ UnsupportedAlgorithm(false),
+ UnsupportedTransport(false),
+ UnsupportedHomeserver(false),
+ ProtocolError,
+ E2EESecurityIssue(false)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt
new file mode 100644
index 0000000000..81632e951a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/RendezvousTransport.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous
+
+import okhttp3.MediaType
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
+
+interface RendezvousTransport {
+ var ready: Boolean
+
+ @Throws(RendezvousError::class)
+ suspend fun details(): RendezvousTransportDetails
+
+ @Throws(RendezvousError::class)
+ suspend fun send(contentType: MediaType, data: ByteArray)
+
+ @Throws(RendezvousError::class)
+ suspend fun receive(): ByteArray?
+
+ suspend fun close()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt
new file mode 100644
index 0000000000..c1d6b1b70e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/channels/ECDHRendezvousChannel.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.channels
+
+import android.util.Base64
+import com.squareup.moshi.JsonClass
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import okhttp3.MediaType.Companion.toMediaType
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.rendezvous.RendezvousChannel
+import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
+import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm
+import org.matrix.android.sdk.api.util.MatrixJsonParser
+import org.matrix.android.sdk.internal.crypto.verification.SASDefaultVerificationTransaction
+import org.matrix.olm.OlmSAS
+import timber.log.Timber
+import java.security.SecureRandom
+import java.util.LinkedList
+import javax.crypto.Cipher
+import javax.crypto.spec.IvParameterSpec
+import javax.crypto.spec.SecretKeySpec
+
+/**
+ * Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903:
+ * https://github.com/matrix-org/matrix-spec-proposals/pull/3903
+ */
+class ECDHRendezvousChannel(override var transport: RendezvousTransport, theirPublicKeyBase64: String?) : RendezvousChannel {
+ companion object {
+ private const val ALGORITHM_SPEC = "AES/GCM/NoPadding"
+ private const val KEY_SPEC = "AES"
+ private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value
+ }
+
+ @JsonClass(generateAdapter = true)
+ internal data class ECDHPayload(
+ val algorithm: SecureRendezvousChannelAlgorithm? = null,
+ val key: String? = null,
+ val ciphertext: String? = null,
+ val iv: String? = null
+ )
+
+ private val olmSASMutex = Mutex()
+ private var olmSAS: OlmSAS?
+ private val ourPublicKey: ByteArray
+ private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java)
+ private var theirPublicKey: ByteArray? = null
+ private var aesKey: ByteArray? = null
+
+ init {
+ theirPublicKeyBase64?.let {
+ theirPublicKey = Base64.decode(it, Base64.NO_WRAP)
+ }
+ olmSAS = OlmSAS()
+ ourPublicKey = Base64.decode(olmSAS!!.publicKey, Base64.NO_WRAP)
+ }
+
+ @Throws(RendezvousError::class)
+ override suspend fun connect(): String {
+ val sas = olmSAS ?: throw RendezvousError("Channel closed", RendezvousFailureReason.Unknown)
+ val isInitiator = theirPublicKey == null
+
+ if (isInitiator) {
+ Timber.tag(TAG).i("Waiting for other device to send their public key")
+ val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError)
+
+ if (res.key == null) {
+ throw RendezvousError(
+ "Unsupported algorithm: ${res.algorithm}",
+ RendezvousFailureReason.UnsupportedAlgorithm,
+ )
+ }
+ theirPublicKey = Base64.decode(res.key, Base64.NO_WRAP)
+ } else {
+ // send our public key unencrypted
+ Timber.tag(TAG).i("Sending public key")
+ send(
+ ECDHPayload(
+ algorithm = SecureRendezvousChannelAlgorithm.ECDH_V1,
+ key = Base64.encodeToString(ourPublicKey, Base64.NO_WRAP)
+ )
+ )
+ }
+
+ olmSASMutex.withLock {
+ sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP))
+ sas.setTheirPublicKey(Base64.encodeToString(theirPublicKey, Base64.NO_WRAP))
+
+ val initiatorKey = Base64.encodeToString(if (isInitiator) ourPublicKey else theirPublicKey, Base64.NO_WRAP)
+ val recipientKey = Base64.encodeToString(if (isInitiator) theirPublicKey else ourPublicKey, Base64.NO_WRAP)
+ val aesInfo = "${SecureRendezvousChannelAlgorithm.ECDH_V1.value}|$initiatorKey|$recipientKey"
+
+ aesKey = sas.generateShortCode(aesInfo, 32)
+
+ val rawChecksum = sas.generateShortCode(aesInfo, 5)
+ return SASDefaultVerificationTransaction.getDecimalCodeRepresentation(rawChecksum, separator = "-")
+ }
+ }
+
+ private suspend fun send(payload: ECDHPayload) {
+ transport.send("application/json".toMediaType(), ecdhAdapter.toJson(payload).toByteArray(Charsets.UTF_8))
+ }
+
+ override suspend fun send(data: ByteArray) {
+ if (aesKey == null) {
+ throw IllegalStateException("Shared secret not established")
+ }
+ send(encrypt(data))
+ }
+
+ private suspend fun receiveAsPayload(): ECDHPayload? {
+ transport.receive()?.toString(Charsets.UTF_8)?.let {
+ return ecdhAdapter.fromJson(it)
+ } ?: return null
+ }
+
+ override suspend fun receive(): ByteArray? {
+ if (aesKey == null) {
+ throw IllegalStateException("Shared secret not established")
+ }
+ val payload = receiveAsPayload() ?: return null
+ return decrypt(payload)
+ }
+
+ override suspend fun close() {
+ val sas = olmSAS ?: throw IllegalStateException("Channel already closed")
+ olmSASMutex.withLock {
+ // this does a double release check already so we don't re-check ourselves
+ sas.releaseSas()
+ olmSAS = null
+ }
+ transport.close()
+ }
+
+ private fun encrypt(plainText: ByteArray): ECDHPayload {
+ val iv = ByteArray(16)
+ SecureRandom().nextBytes(iv)
+
+ val cipherText = LinkedList()
+
+ val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
+ val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
+ val ivParameterSpec = IvParameterSpec(iv)
+ encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
+ cipherText.addAll(encryptCipher.update(plainText).toList())
+ cipherText.addAll(encryptCipher.doFinal().toList())
+
+ return ECDHPayload(
+ ciphertext = Base64.encodeToString(cipherText.toByteArray(), Base64.NO_WRAP),
+ iv = Base64.encodeToString(iv, Base64.NO_WRAP)
+ )
+ }
+
+ private fun decrypt(payload: ECDHPayload): ByteArray {
+ val iv = Base64.decode(payload.iv, Base64.NO_WRAP)
+ val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
+ val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
+ val ivParameterSpec = IvParameterSpec(iv)
+ encryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
+
+ val plainText = LinkedList()
+ plainText.addAll(encryptCipher.update(Base64.decode(payload.ciphertext, Base64.NO_WRAP)).toList())
+ plainText.addAll(encryptCipher.doFinal().toList())
+
+ return plainText.toByteArray()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt
new file mode 100644
index 0000000000..55bac6397e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvous.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class ECDHRendezvous(
+ val transport: SimpleHttpRendezvousTransportDetails,
+ val algorithm: SecureRendezvousChannelAlgorithm,
+ val key: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt
new file mode 100644
index 0000000000..575b5d4bfd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/ECDHRendezvousCode.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class ECDHRendezvousCode(
+ val intent: RendezvousIntent,
+ val rendezvous: ECDHRendezvous
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt
new file mode 100644
index 0000000000..0ebd1f88b3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Outcome.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class Outcome(val value: String) {
+ @Json(name = "success")
+ SUCCESS("success"),
+
+ @Json(name = "declined")
+ DECLINED("declined"),
+
+ @Json(name = "unsupported")
+ UNSUPPORTED("unsupported"),
+
+ @Json(name = "verified")
+ VERIFIED("verified"),
+
+ @Json(name = "e2ee_security_error")
+ E2EE_SECURITY_ERROR("e2ee_security_error")
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt
new file mode 100644
index 0000000000..04631ce959
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Payload.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class Payload(
+ val type: PayloadType,
+ val intent: RendezvousIntent? = null,
+ val outcome: Outcome? = null,
+ val protocols: List? = null,
+ val protocol: Protocol? = null,
+ val homeserver: String? = null,
+ @Json(name = "login_token") val loginToken: String? = null,
+ @Json(name = "device_id") val deviceId: String? = null,
+ @Json(name = "device_key") val deviceKey: String? = null,
+ @Json(name = "verifying_device_id") val verifyingDeviceId: String? = null,
+ @Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null,
+ @Json(name = "master_key") val masterKey: String? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt
new file mode 100644
index 0000000000..33beb1f525
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/PayloadType.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+internal enum class PayloadType(val value: String) {
+ @Json(name = "m.login.start")
+ START("m.login.start"),
+
+ @Json(name = "m.login.finish")
+ FINISH("m.login.finish"),
+
+ @Json(name = "m.login.progress")
+ PROGRESS("m.login.progress")
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt
new file mode 100644
index 0000000000..6fce2fa11c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/Protocol.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class Protocol(val value: String) {
+ @Json(name = "org.matrix.msc3906.login_token")
+ LOGIN_TOKEN("org.matrix.msc3906.login_token")
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt
new file mode 100644
index 0000000000..c52b11a322
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousError.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
+
+class RendezvousError(val description: String, val reason: RendezvousFailureReason) : Exception(description)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt
new file mode 100644
index 0000000000..65037e1252
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousIntent.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class RendezvousIntent {
+ @Json(name = "login.start") LOGIN_ON_NEW_DEVICE,
+ @Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt
new file mode 100644
index 0000000000..1bde43ab7e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportDetails.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+open class RendezvousTransportDetails(
+ val type: RendezvousTransportType
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt
new file mode 100644
index 0000000000..6fca7efa71
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/RendezvousTransportType.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class RendezvousTransportType(val value: String) {
+ @Json(name = "org.matrix.msc3886.http.v1")
+ MSC3886_SIMPLE_HTTP_V1("org.matrix.msc3886.http.v1")
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt
new file mode 100644
index 0000000000..75f0024fda
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SecureRendezvousChannelAlgorithm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class SecureRendezvousChannelAlgorithm(val value: String) {
+ @Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256")
+ ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256")
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt
new file mode 100644
index 0000000000..049aa8b756
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/model/SimpleHttpRendezvousTransportDetails.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class SimpleHttpRendezvousTransportDetails(
+ val uri: String
+) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt
new file mode 100644
index 0000000000..620b599e3d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/rendezvous/transports/SimpleHttpRendezvousTransport.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.rendezvous.transports
+
+import kotlinx.coroutines.delay
+import okhttp3.MediaType
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
+import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
+import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails
+import timber.log.Timber
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886
+ */
+class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport {
+ companion object {
+ private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value
+ }
+
+ override var ready = false
+ private var cancelled = false
+ private var uri: String?
+ private var etag: String? = null
+ private var expiresAt: Date? = null
+
+ init {
+ uri = rendezvousUri
+ }
+
+ override suspend fun details(): RendezvousTransportDetails {
+ val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
+
+ return SimpleHttpRendezvousTransportDetails(uri)
+ }
+
+ @Throws(RendezvousError::class)
+ override suspend fun send(contentType: MediaType, data: ByteArray) {
+ if (cancelled) {
+ throw IllegalStateException("Rendezvous cancelled")
+ }
+
+ val method = if (uri != null) "PUT" else "POST"
+ val uri = this.uri ?: throw RuntimeException("No rendezvous URI")
+
+ val httpClient = okhttp3.OkHttpClient.Builder().build()
+
+ val request = Request.Builder()
+ .url(uri)
+ .method(method, data.toRequestBody())
+ .header("content-type", contentType.toString())
+
+ etag?.let {
+ request.header("if-match", it)
+ }
+
+ val response = httpClient.newCall(request.build()).execute()
+
+ if (response.code == 404) {
+ throw get404Error()
+ }
+ etag = response.header("etag")
+
+ Timber.tag(TAG).i("Sent data to $uri new etag $etag")
+
+ if (method == "POST") {
+ val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response")
+
+ response.header("expires")?.let {
+ val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
+ expiresAt = format.parse(it)
+ }
+
+ // resolve location header which could be relative or absolute
+ this.uri = response.request.url.toUri().resolve(location).toString()
+ ready = true
+ }
+ }
+
+ @Throws(RendezvousError::class)
+ override suspend fun receive(): ByteArray? {
+ if (cancelled) {
+ throw IllegalStateException("Rendezvous cancelled")
+ }
+ val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
+ val httpClient = okhttp3.OkHttpClient.Builder().build()
+ while (true) {
+ Timber.tag(TAG).i("Polling: $uri after etag $etag")
+ val request = Request.Builder()
+ .url(uri)
+ .get()
+
+ etag?.let {
+ request.header("if-none-match", it)
+ }
+
+ val response = httpClient.newCall(request.build()).execute()
+
+ try {
+ // expired
+ if (response.code == 404) {
+ throw get404Error()
+ }
+
+ // rely on server expiring the channel rather than checking ourselves
+
+ if (response.header("content-type") != "application/json") {
+ response.header("etag")?.let {
+ etag = it
+ }
+ } else if (response.code == 200) {
+ response.header("etag")?.let {
+ etag = it
+ }
+ return response.body?.bytes()
+ }
+
+ // sleep for a second before polling again
+ // we rely on the server expiring the channel rather than checking it ourselves
+ delay(1000)
+ } finally {
+ response.close()
+ }
+ }
+ }
+
+ private fun get404Error(): RendezvousError {
+ if (expiresAt != null && Date() > expiresAt) {
+ return RendezvousError("Expired", RendezvousFailureReason.Expired)
+ }
+
+ return RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown)
+ }
+
+ override suspend fun close() {
+ cancelled = true
+ ready = false
+
+ uri?.let {
+ try {
+ val httpClient = okhttp3.OkHttpClient.Builder().build()
+ val request = Request.Builder()
+ .url(it)
+ .delete()
+ .build()
+ httpClient.newCall(request).execute()
+ } catch (e: Throwable) {
+ Timber.tag(TAG).w(e, "Failed to delete channel")
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index f5d2c0d9a0..1f16041b54 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
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
+import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
import org.matrix.android.sdk.api.session.room.send.SendState
@@ -357,6 +358,10 @@ fun Event.isAudioMessage(): Boolean {
}
}
+fun Event.isVoiceMessage(): Boolean {
+ return this.asMessageAudioEvent()?.content?.voiceMessageIndicator != null
+}
+
fun Event.isFileMessage(): Boolean {
return when (getMsgType()) {
MessageType.MSGTYPE_FILE -> true
@@ -396,7 +401,7 @@ fun Event.getRelationContent(): RelationDefaultContent? {
when (getClearType()) {
EventType.STICKER -> getClearContent().toModel()?.relatesTo
in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel()?.relatesTo
- else -> null
+ else -> getClearContent()?.get("m.relates_to")?.toContent().toModel()
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index b5d6d891e4..773e870ffd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -59,7 +59,17 @@ data class HomeServerCapabilities(
/**
* True if the home server supports controlling the logout of all devices when changing password.
*/
- val canControlLogoutDevices: Boolean = false
+ val canControlLogoutDevices: Boolean = false,
+
+ /**
+ * True if the home server supports login via qr code, false otherwise.
+ */
+ val canLoginWithQrCode: Boolean = false,
+
+ /**
+ * True if the home server supports threaded read receipts and unread notifications.
+ */
+ val canUseThreadReadReceiptsAndNotifications: Boolean = false,
) {
enum class RoomCapabilitySupport {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt
new file mode 100644
index 0000000000..38ced8f385
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioEvent.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.message
+
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+
+/**
+ * [Event] wrapper for [EventType.MESSAGE] event type.
+ * Provides additional fields and functions related to this event type.
+ */
+@JvmInline
+value class MessageAudioEvent(val root: Event) {
+
+ /**
+ * The mapped [MessageAudioContent] model of the event content.
+ */
+ val content: MessageAudioContent
+ get() = root.getClearContent().toModel() as MessageAudioContent
+
+ init {
+ require(tryOrNull { content } != null)
+ }
+}
+
+/**
+ * Map a [EventType.MESSAGE] event to a [MessageAudioEvent].
+ */
+fun Event.asMessageAudioEvent() = if (getClearType() == EventType.MESSAGE) {
+ tryOrNull { MessageAudioEvent(this) }
+} else null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
index de9bcfbf0d..6a6fadc95a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.PollType
+import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
@@ -44,18 +45,30 @@ interface SendService {
* @param text the text message to send
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
- fun sendTextMessage(text: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable
+ fun sendTextMessage(
+ text: CharSequence,
+ msgType: String = MessageType.MSGTYPE_TEXT,
+ autoMarkdown: Boolean = false,
+ additionalContent: Content? = null,
+ ): Cancelable
/**
* Method to send a text message with a formatted body.
* @param text the text message to send
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
- fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
+ fun sendFormattedTextMessage(
+ text: String,
+ formattedText: String,
+ msgType: String = MessageType.MSGTYPE_TEXT,
+ additionalContent: Content? = null,
+ ): Cancelable
/**
* Method to quote an events content.
@@ -64,6 +77,7 @@ interface SendService {
* @param formattedText the formatted text message to send
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
* @param rootThreadEventId when this param is not null, the message will be sent in this specific thread
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendQuotedTextMessage(
@@ -71,7 +85,8 @@ interface SendService {
text: String,
formattedText: String? = null,
autoMarkdown: Boolean,
- rootThreadEventId: String? = null
+ rootThreadEventId: String? = null,
+ additionalContent: Content? = null,
): Cancelable
/**
@@ -81,13 +96,17 @@ interface SendService {
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread
+ * @param relatesTo add a relation content to the media event
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendMedia(
attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set,
- rootThreadEventId: String? = null
+ rootThreadEventId: String? = null,
+ relatesTo: RelationDefaultContent? = null,
+ additionalContent: Content? = null,
): Cancelable
/**
@@ -97,13 +116,15 @@ interface SendService {
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @param rootThreadEventId when this param is not null, all the Media will be sent in this specific thread
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendMedias(
attachments: List,
compressBeforeSending: Boolean,
roomIds: Set,
- rootThreadEventId: String? = null
+ rootThreadEventId: String? = null,
+ additionalContent: Content? = null,
): Cancelable
/**
@@ -111,31 +132,35 @@ interface SendService {
* @param pollType indicates open or closed polls
* @param question the question
* @param options list of options
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
- fun sendPoll(pollType: PollType, question: String, options: List): Cancelable
+ fun sendPoll(pollType: PollType, question: String, options: List, additionalContent: Content? = null): Cancelable
/**
* Method to send a poll response.
* @param pollEventId the poll currently replied to
* @param answerId The id of the answer
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
- fun voteToPoll(pollEventId: String, answerId: String): Cancelable
+ fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content? = null): Cancelable
/**
* End a poll in the room.
* @param pollEventId event id of the poll
+ * @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
- fun endPoll(pollEventId: String): Cancelable
+ fun endPoll(pollEventId: String, additionalContent: Content? = null): Cancelable
/**
* Redact (delete) the given event.
* @param event The event to redact
* @param reason Optional reason string
+ * @param additionalContent additional content to put in the event content
*/
- fun redactEvent(event: Event, reason: String?): Cancelable
+ fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable
/**
* Schedule this message to be resent.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
index 1824d5dc6c..9ac33c0545 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
@@ -106,6 +106,8 @@ interface Timeline {
/**
* Called when new events come through the sync.
+ * Note that the corresponding events may not be available yet in the database.
+ * [onTimelineUpdated] will be called with the event content.
*/
fun onNewTimelineEvents(eventIds: List) = Unit
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 7341fd922e..223acd1b9c 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
@@ -181,9 +181,13 @@ fun TimelineEvent.isRootThread(): Boolean {
/**
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary.
*/
-fun TimelineEvent.getTextEditableContent(): String {
+fun TimelineEvent.getTextEditableContent(formatted: Boolean): String {
val lastMessageContent = getLastMessageContent()
- val lastContentBody = lastMessageContent.getFormattedBody() ?: return ""
+ val lastContentBody = if (formatted && lastMessageContent is MessageContentWithFormattedBody) {
+ lastMessageContent.formattedBody
+ } else {
+ lastMessageContent?.body
+ } ?: return ""
return if (isReply()) {
extractUsefulTextFromReply(lastContentBody)
} else {
@@ -201,11 +205,3 @@ fun MessageContent.getTextDisplayableContent(): String {
?: (this as MessageTextContent?)?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
?: body
}
-
-fun MessageContent?.getFormattedBody(): String? {
- return if (this is MessageContentWithFormattedBody) {
- formattedBody
- } else {
- this?.body
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
index 46433f387d..aa9afd5c8c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
@@ -55,4 +55,9 @@ interface TimelineService {
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.
*/
fun getAttachmentMessages(): List
+
+ /**
+ * Returns a snapshot list of TimelineEvent with a content relation of the given type to the given eventId.
+ */
+ fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Compat.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Compat.kt
new file mode 100644
index 0000000000..b685c49713
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Compat.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.util
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Build
+
+fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getApplicationInfo(
+ packageName,
+ PackageManager.ApplicationInfoFlags.of(flags.toLong())
+ )
+ else -> @Suppress("DEPRECATION") getApplicationInfo(packageName, flags)
+ }
+}
+
+fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int): PackageInfo {
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getPackageInfo(
+ packageName,
+ PackageManager.PackageInfoFlags.of(flags.toLong())
+ )
+ else -> @Suppress("DEPRECATION") getPackageInfo(packageName, flags)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt
index 463692e574..b1f65194f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt
@@ -29,7 +29,9 @@ import org.matrix.android.sdk.internal.auth.db.AuthRealmModule
import org.matrix.android.sdk.internal.auth.db.RealmPendingSessionStore
import org.matrix.android.sdk.internal.auth.db.RealmSessionParamsStore
import org.matrix.android.sdk.internal.auth.login.DefaultDirectLoginTask
+import org.matrix.android.sdk.internal.auth.login.DefaultQrLoginTokenTask
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
+import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.AuthDatabase
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
@@ -94,4 +96,7 @@ internal abstract class AuthModule {
@Binds
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
+
+ @Binds
+ abstract fun bindQrLoginTokenTask(task: DefaultQrLoginTokenTask): QrLoginTokenTask
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index 446f931847..5449c0a735 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
+import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session
@@ -39,9 +40,11 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
+import org.matrix.android.sdk.internal.auth.login.QrLoginTokenTask
import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard
import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
+import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk
import org.matrix.android.sdk.internal.di.Unauthenticated
@@ -62,7 +65,8 @@ internal class DefaultAuthenticationService @Inject constructor(
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val getWellknownTask: GetWellknownTask,
- private val directLoginTask: DirectLoginTask
+ private val directLoginTask: DirectLoginTask,
+ private val qrLoginTokenTask: QrLoginTokenTask
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
@@ -404,6 +408,36 @@ internal class DefaultAuthenticationService @Inject constructor(
)
}
+ override suspend fun isQrLoginSupported(homeServerConnectionConfig: HomeServerConnectionConfig): Boolean {
+ val authAPI = buildAuthAPI(homeServerConnectionConfig)
+ val versions = runCatching {
+ executeRequest(null) {
+ authAPI.versions()
+ }
+ }
+ return if (versions.isSuccess) {
+ versions.getOrNull()?.doesServerSupportQrCodeLogin().orFalse()
+ } else {
+ false
+ }
+ }
+
+ override suspend fun loginUsingQrLoginToken(
+ homeServerConnectionConfig: HomeServerConnectionConfig,
+ loginToken: String,
+ initialDeviceName: String?,
+ deviceId: String?,
+ ): Session {
+ return qrLoginTokenTask.execute(
+ QrLoginTokenTask.Params(
+ homeServerConnectionConfig = homeServerConnectionConfig,
+ loginToken = loginToken,
+ deviceName = initialDeviceName,
+ deviceId = deviceId
+ )
+ )
+ }
+
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString())
return retrofit.create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt
index ea8578ed8c..8646752083 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginParams.kt
@@ -18,4 +18,6 @@ package org.matrix.android.sdk.internal.auth.data
internal interface LoginParams {
val type: String
+ val deviceDisplayName: String?
+ val deviceId: String?
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt
index 5f0a2298cb..062b2466e5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt
@@ -30,8 +30,8 @@ internal data class PasswordLoginParams(
@Json(name = "identifier") val identifier: Map,
@Json(name = "password") val password: String,
@Json(name = "type") override val type: String,
- @Json(name = "initial_device_display_name") val deviceDisplayName: String?,
- @Json(name = "device_id") val deviceId: String?
+ @Json(name = "initial_device_display_name") override val deviceDisplayName: String?,
+ @Json(name = "device_id") override val deviceId: String?
) : LoginParams {
companion object {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt
index 0c6fb45e78..52045a1d7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/TokenLoginParams.kt
@@ -23,5 +23,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
@JsonClass(generateAdapter = true)
internal data class TokenLoginParams(
@Json(name = "type") override val type: String = LoginFlowTypes.TOKEN,
- @Json(name = "token") val token: String
+ @Json(name = "token") val token: String,
+ @Json(name = "initial_device_display_name") override val deviceDisplayName: String? = null,
+ @Json(name = "device_id") override val deviceId: String? = null
) : LoginParams
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt
new file mode 100644
index 0000000000..477719f607
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/QrLoginTokenTask.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.login
+
+import dagger.Lazy
+import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.auth.LoginType
+import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.internal.auth.AuthAPI
+import org.matrix.android.sdk.internal.auth.SessionCreator
+import org.matrix.android.sdk.internal.auth.data.TokenLoginParams
+import org.matrix.android.sdk.internal.di.Unauthenticated
+import org.matrix.android.sdk.internal.network.RetrofitFactory
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
+import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface QrLoginTokenTask : Task {
+ data class Params(
+ val homeServerConnectionConfig: HomeServerConnectionConfig,
+ val loginToken: String,
+ val deviceName: String?,
+ val deviceId: String?
+ )
+}
+
+internal class DefaultQrLoginTokenTask @Inject constructor(
+ @Unauthenticated
+ private val okHttpClient: Lazy,
+ private val retrofitFactory: RetrofitFactory,
+ private val sessionCreator: SessionCreator,
+) : QrLoginTokenTask {
+
+ override suspend fun execute(params: QrLoginTokenTask.Params): Session {
+ val client = buildClient(params.homeServerConnectionConfig)
+ val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
+
+ val authAPI = retrofitFactory.create(client, homeServerUrl)
+ .create(AuthAPI::class.java)
+
+ val loginParams = TokenLoginParams(
+ token = params.loginToken,
+ deviceDisplayName = params.deviceName,
+ deviceId = params.deviceId
+ )
+
+ val credentials = try {
+ executeRequest(null) {
+ authAPI.login(loginParams)
+ }
+ } catch (throwable: Throwable) {
+ throw when (throwable) {
+ is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure(
+ homeServerUrl,
+ throwable.fingerprint
+ )
+ else -> throwable
+ }
+ }
+
+ return sessionCreator.createSession(credentials, params.homeServerConnectionConfig, LoginType.QR)
+ }
+
+ private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
+ return okHttpClient.get()
+ .newBuilder()
+ .addSocketFactory(homeServerConnectionConfig)
+ .build()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt
index 75639c6a21..d443d6e3c8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt
@@ -60,5 +60,6 @@ internal data class HomeServerVersion(
val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0)
val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1)
val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0)
+ val v1_4_0 = HomeServerVersion(major = 1, minor = 4, patch = 0)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt
index 915b25134b..1245d8df4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt
@@ -53,6 +53,9 @@ private const val FEATURE_ID_ACCESS_TOKEN = "m.id_access_token"
private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
+private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882"
+private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771"
+private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773"
/**
* Return true if the SDK supports this homeserver version.
@@ -78,6 +81,19 @@ internal fun Versions.doesServerSupportThreads(): Boolean {
return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
}
+/**
+ * Indicate if the homeserver support MSC3771 and MSC3773 for threaded read receipts and unread notifications.
+ */
+internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean {
+ val msc3771 = unstableFeatures?.get(FEATURE_THREADS_MSC3771) ?: false
+ val msc3773 = unstableFeatures?.get(FEATURE_THREADS_MSC3773) ?: false
+ return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773)
+}
+
+internal fun Versions.doesServerSupportQrCodeLogin(): Boolean {
+ return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false
+}
+
/**
* Return true if the server support the lazy loading of room members.
*
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt
index 1cbaff059a..29b416bb82 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt
@@ -82,6 +82,33 @@ internal abstract class SASDefaultVerificationTransaction(
// older devices have limited support of emoji but SDK offers images for the 64 verification emojis
// so always send that we support EMOJI
val KNOWN_SHORT_CODES = listOf(SasMode.EMOJI, SasMode.DECIMAL)
+
+ /**
+ * decimal: generate five bytes by using HKDF.
+ * Take the first 13 bits and convert it to a decimal number (which will be a number between 0 and 8191 inclusive),
+ * and add 1000 (resulting in a number between 1000 and 9191 inclusive).
+ * Do the same with the second 13 bits, and the third 13 bits, giving three 4-digit numbers.
+ * In other words, if the five bytes are B0, B1, B2, B3, and B4, then the first number is (B0 << 5 | B1 >> 3) + 1000,
+ * the second number is ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000, and the third number is ((B3 & 0x3f) << 7 | B4 >> 1) + 1000.
+ * (This method of converting 13 bits at a time is used to avoid requiring 32-bit clients to do big-number arithmetic,
+ * and adding 1000 to the number avoids having clients to worry about properly zero-padding the number when displaying to the user.)
+ * The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers,
+ * or with the three numbers on separate lines.
+ */
+ fun getDecimalCodeRepresentation(byteArray: ByteArray, separator: String = " "): String {
+ val b0 = byteArray[0].toUnsignedInt() // need unsigned byte
+ val b1 = byteArray[1].toUnsignedInt() // need unsigned byte
+ val b2 = byteArray[2].toUnsignedInt() // need unsigned byte
+ val b3 = byteArray[3].toUnsignedInt() // need unsigned byte
+ val b4 = byteArray[4].toUnsignedInt() // need unsigned byte
+ // (B0 << 5 | B1 >> 3) + 1000
+ val first = (b0.shl(5) or b1.shr(3)) + 1000
+ // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000
+ val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000
+ // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000
+ val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000
+ return "$first$separator$second$separator$third"
+ }
}
override var state: VerificationTxState = VerificationTxState.None
@@ -371,33 +398,6 @@ internal abstract class SASDefaultVerificationTransaction(
return getDecimalCodeRepresentation(shortCodeBytes!!)
}
- /**
- * decimal: generate five bytes by using HKDF.
- * Take the first 13 bits and convert it to a decimal number (which will be a number between 0 and 8191 inclusive),
- * and add 1000 (resulting in a number between 1000 and 9191 inclusive).
- * Do the same with the second 13 bits, and the third 13 bits, giving three 4-digit numbers.
- * In other words, if the five bytes are B0, B1, B2, B3, and B4, then the first number is (B0 << 5 | B1 >> 3) + 1000,
- * the second number is ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000, and the third number is ((B3 & 0x3f) << 7 | B4 >> 1) + 1000.
- * (This method of converting 13 bits at a time is used to avoid requiring 32-bit clients to do big-number arithmetic,
- * and adding 1000 to the number avoids having clients to worry about properly zero-padding the number when displaying to the user.)
- * The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers,
- * or with the three numbers on separate lines.
- */
- fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
- val b0 = byteArray[0].toUnsignedInt() // need unsigned byte
- val b1 = byteArray[1].toUnsignedInt() // need unsigned byte
- val b2 = byteArray[2].toUnsignedInt() // need unsigned byte
- val b3 = byteArray[3].toUnsignedInt() // need unsigned byte
- val b4 = byteArray[4].toUnsignedInt() // need unsigned byte
- // (B0 << 5 | B1 >> 3) + 1000
- val first = (b0.shl(5) or b1.shr(3)) + 1000
- // ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000
- val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000
- // ((B3 & 0x3f) << 7 | B4 >> 1) + 1000
- val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000
- return "$first $second $third"
- }
-
override fun getEmojiCodeRepresentation(): List {
return getEmojiCodeRepresentation(shortCodeBytes!!)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index aef482ae2e..def0f6de7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -55,6 +55,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@@ -63,7 +65,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
- schemaVersion = 38L,
+ schemaVersion = 40L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@@ -111,5 +113,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
if (oldVersion < 37) MigrateSessionTo037(realm).perform()
if (oldVersion < 38) MigrateSessionTo038(realm).perform()
+ if (oldVersion < 39) MigrateSessionTo039(realm).perform()
+ if (oldVersion < 40) MigrateSessionTo040(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index 184a0108b9..3528ca0051 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -43,7 +43,9 @@ internal object HomeServerCapabilitiesMapper {
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
roomVersions = mapRoomVersion(entity.roomVersionsJson),
canUseThreading = entity.canUseThreading,
- canControlLogoutDevices = entity.canControlLogoutDevices
+ canControlLogoutDevices = entity.canControlLogoutDevices,
+ canLoginWithQrCode = entity.canLoginWithQrCode,
+ canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo039.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo039.kt
new file mode 100644
index 0000000000..190a71c9be
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo039.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo039(realm: DynamicRealm) : RealmMigrator(realm, 39) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, Boolean::class.java)
+ ?.transform { obj ->
+ obj.set(HomeServerCapabilitiesEntityFields.CAN_LOGIN_WITH_QR_CODE, false)
+ }
+ ?.forceRefreshOfHomeServerCapabilities()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt
new file mode 100644
index 0000000000..b3e02342dd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo040.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo040(realm: DynamicRealm) : RealmMigrator(realm, 40) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, Boolean::class.java)
+ ?.transform { obj ->
+ obj.set(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, false)
+ }
+ ?.forceRefreshOfHomeServerCapabilities()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 9d90973f8a..89f1e50b30 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -30,7 +30,9 @@ internal open class HomeServerCapabilitiesEntity(
var defaultIdentityServerUrl: String? = null,
var lastUpdatedTimestamp: Long = 0L,
var canUseThreading: Boolean = false,
- var canControlLogoutDevices: Boolean = false
+ var canControlLogoutDevices: Boolean = false,
+ var canLoginWithQrCode: Boolean = false,
+ var canUseThreadReadReceiptsAndNotifications: Boolean = false,
) : RealmObject() {
companion object
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt
index 6eb4d5b104..04e8cf8a29 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCase.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.os.Build
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.util.getApplicationInfoCompat
+import org.matrix.android.sdk.api.util.getPackageInfoCompat
import javax.inject.Inject
class ComputeUserAgentUseCase @Inject constructor(
@@ -36,7 +38,7 @@ class ComputeUserAgentUseCase @Inject constructor(
val appPackageName = context.applicationContext.packageName
val pm = context.packageManager
- val appName = tryOrNull { pm.getApplicationLabel(pm.getApplicationInfo(appPackageName, 0)).toString() }
+ val appName = tryOrNull { pm.getApplicationLabel(pm.getApplicationInfoCompat(appPackageName, 0)).toString() }
?.takeIf {
it.matches("\\A\\p{ASCII}*\\z".toRegex())
}
@@ -44,7 +46,7 @@ class ComputeUserAgentUseCase @Inject constructor(
// Use appPackageName instead of appName if appName is null or contains any non-ASCII character
appPackageName
}
- val appVersion = tryOrNull { pm.getPackageInfo(context.applicationContext.packageName, 0).versionName } ?: FALLBACK_APP_VERSION
+ val appVersion = tryOrNull { pm.getPackageInfoCompat(context.applicationContext.packageName, 0).versionName } ?: FALLBACK_APP_VERSION
val deviceManufacturer = Build.MANUFACTURER
val deviceModel = Build.MODEL
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index c023646c7f..eee55735e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -130,6 +130,7 @@ internal class FileUploader @Inject constructor(
workingFile.outputStream().use {
inputStream.copyTo(it)
}
+ inputStream.close()
workingFile
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 1e62b5d7f5..db1cd1b33b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
+import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
@@ -407,7 +408,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
newAttachmentAttributes: NewAttachmentAttributes
) {
localEchoRepository.updateEcho(eventId) { _, event ->
- val messageContent: MessageContent? = event.asDomain().content.toModel()
+ val content: Content? = event.asDomain().content
+ val messageContent: MessageContent? = content.toModel()
+ // Retrieve potential additional content from the original event
+ val additionalContent = content.orEmpty() - messageContent?.toContent().orEmpty().keys
val updatedContent = when (messageContent) {
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes)
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes)
@@ -415,7 +419,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize)
else -> messageContent
}
- event.content = ContentMapper.map(updatedContent.toContent())
+ event.content = ContentMapper.map(updatedContent.toContent().plus(additionalContent))
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index add69dd8c7..a5953d870c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -20,11 +20,12 @@ import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
-import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
+import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
+import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
@@ -132,8 +133,6 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
}
- homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */
- getVersionResult?.doesServerSupportThreads().orFalse()
}
if (getMediaConfigResult != null) {
@@ -144,6 +143,11 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getVersionResult != null) {
homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk()
homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices()
+ homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */
+ getVersionResult.doesServerSupportThreads()
+ homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications =
+ getVersionResult.doesServerSupportThreadUnreadNotifications()
+ homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin()
}
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt
index 40444edcab..22bb3d37b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt
@@ -17,26 +17,40 @@
package org.matrix.android.sdk.internal.session.profile
+import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.user.UserEntityFactory
import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.awaitTransaction
import javax.inject.Inject
internal abstract class GetProfileInfoTask : Task {
data class Params(
- val userId: String
+ val userId: String,
+ val storeInDatabase: Boolean = true,
)
}
internal class DefaultGetProfileInfoTask @Inject constructor(
private val profileAPI: ProfileAPI,
- private val globalErrorReceiver: GlobalErrorReceiver
+ private val globalErrorReceiver: GlobalErrorReceiver,
+ @SessionDatabase private val monarchy: Monarchy,
) : GetProfileInfoTask() {
override suspend fun execute(params: Params): JsonDict {
return executeRequest(globalErrorReceiver) {
profileAPI.getProfile(params.userId)
+ }.also { user ->
+ if (params.storeInDatabase) {
+ // Insert into DB
+ monarchy.awaitTransaction {
+ it.insertOrUpdate(UserEntityFactory.create(User.fromJson(params.userId, user)))
+ }
+ }
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
index 5f5c000171..93c7f143fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDataSource
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
@@ -35,7 +36,8 @@ internal interface FetchEditHistoryTask : Task {
@@ -50,10 +52,14 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
}
// Filter out edition form other users, and redacted editions
- val originalSenderId = response.originalEvent?.senderId
+ val originalEvent = eventDataSource.getTimelineEvent(
+ roomId = params.roomId,
+ eventId = params.eventId,
+ )
+ val originalSenderId = originalEvent?.senderInfo?.userId
val events = response.chunks
.filter { it.senderId == originalSenderId }
.filter { !it.isRedacted() }
- return events + listOfNotNull(response.originalEvent)
+ return events + listOfNotNull(originalEvent?.root)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt
index a65165d457..f2b0651e01 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/RelationsResponse.kt
@@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
@JsonClass(generateAdapter = true)
internal data class RelationsResponse(
@Json(name = "chunk") val chunks: List,
- @Json(name = "original_event") val originalEvent: Event?,
@Json(name = "next_batch") val nextBatch: String?,
@Json(name = "prev_batch") val prevBatch: String?
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index edd74c2ce0..4cf6445920 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.relation.threads
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
@@ -24,6 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
+import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.mapper.toEntity
@@ -46,6 +48,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
+import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
@@ -87,6 +90,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val cryptoService: DefaultCryptoService,
private val clock: Clock,
+ private val realmSessionProvider: RealmSessionProvider,
+ private val getEventTask: GetEventTask,
) : FetchThreadTimelineTask {
enum class Result {
@@ -114,11 +119,26 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
params: FetchThreadTimelineTask.Params
): Result {
val threadList = response.chunks
- val threadRootEvent = response.originalEvent
val hasReachEnd = response.nextBatch == null
- monarchy.awaitTransaction { realm ->
+ val isRootThreadTimelineEventEntityKnown: Boolean
+ var threadRootEvent: Event? = null
+ if (hasReachEnd) {
+ isRootThreadTimelineEventEntityKnown = realmSessionProvider.withRealm { realm ->
+ TimelineEventEntity
+ .where(realm, roomId = params.roomId, eventId = params.rootThreadEventId)
+ .findFirst()
+ } != null
+ if (!isRootThreadTimelineEventEntityKnown) {
+ // Fetch the root event from the server
+ threadRootEvent = tryOrNull {
+ getEventTask.execute(GetEventTask.Params(roomId = params.roomId, eventId = params.rootThreadEventId))
+ }
+ }
+ }
+
+ monarchy.awaitTransaction { realm ->
val threadChunk = ChunkEntity.findLastForwardChunkOfThread(realm, params.roomId, params.rootThreadEventId)
?: run {
return@awaitTransaction
@@ -173,7 +193,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
// Case when thread event is not in the device
Timber.i("###THREADS FetchThreadTimelineTask root thread event: ${params.rootThreadEventId} NOT FOUND! Lets create a temp one")
val eventEntity = createEventEntity(params.roomId, threadRootEvent, realm)
- roomMemberContentsByUser.addSenderState(realm, params.roomId, threadRootEvent.senderId)
+ roomMemberContentsByUser.addSenderState(realm, params.roomId, threadRootEvent.senderId!!)
threadChunk.addTimelineEvent(
roomId = params.roomId,
eventEntity = eventEntity,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
index a3f2825a0c..9cdbc7ff46 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
@@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
+import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isTextMessage
@@ -39,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
+import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -87,14 +89,14 @@ internal class DefaultSendService @AssistedInject constructor(
.let { sendEvent(it) }
}
- override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
- return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
+ override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean, additionalContent: Content?): Cancelable {
+ return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
- override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable {
- return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType)
+ override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String, additionalContent: Content?): Cancelable {
+ return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
@@ -104,7 +106,8 @@ internal class DefaultSendService @AssistedInject constructor(
text: String,
formattedText: String?,
autoMarkdown: Boolean,
- rootThreadEventId: String?
+ rootThreadEventId: String?,
+ additionalContent: Content?,
): Cancelable {
return localEchoEventFactory.createQuotedTextEvent(
roomId = roomId,
@@ -112,33 +115,34 @@ internal class DefaultSendService @AssistedInject constructor(
text = text,
formattedText = formattedText,
autoMarkdown = autoMarkdown,
- rootThreadEventId = rootThreadEventId
+ rootThreadEventId = rootThreadEventId,
+ additionalContent = additionalContent,
)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
- override fun sendPoll(pollType: PollType, question: String, options: List): Cancelable {
- return localEchoEventFactory.createPollEvent(roomId, pollType, question, options)
+ override fun sendPoll(pollType: PollType, question: String, options: List, additionalContent: Content?): Cancelable {
+ return localEchoEventFactory.createPollEvent(roomId, pollType, question, options, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
- override fun voteToPoll(pollEventId: String, answerId: String): Cancelable {
- return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId)
+ override fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content?): Cancelable {
+ return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
- override fun endPoll(pollEventId: String): Cancelable {
- return localEchoEventFactory.createEndPollEvent(roomId, pollEventId)
+ override fun endPoll(pollEventId: String, additionalContent: Content?): Cancelable {
+ return localEchoEventFactory.createEndPollEvent(roomId, pollEventId, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
- override fun redactEvent(event: Event, reason: String?): Cancelable {
+ override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable {
// TODO manage media/attachements?
- val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason)
+ val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent)
.also { createLocalEcho(it) }
return eventSenderProcessor.postRedaction(redactionEcho, reason)
}
@@ -264,7 +268,8 @@ internal class DefaultSendService @AssistedInject constructor(
attachments: List,
compressBeforeSending: Boolean,
roomIds: Set,
- rootThreadEventId: String?
+ rootThreadEventId: String?,
+ additionalContent: Content?,
): Cancelable {
return attachments.mapTo(CancelableBag()) {
sendMedia(
@@ -280,7 +285,9 @@ internal class DefaultSendService @AssistedInject constructor(
attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set,
- rootThreadEventId: String?
+ rootThreadEventId: String?,
+ relatesTo: RelationDefaultContent?,
+ additionalContent: Content?,
): Cancelable {
// Ensure that the event will not be send in a thread if we are a different flow.
// Like sending files to multiple rooms
@@ -295,7 +302,9 @@ internal class DefaultSendService @AssistedInject constructor(
localEchoEventFactory.createMediaEvent(
roomId = it,
attachment = attachment,
- rootThreadEventId = rootThreadId
+ rootThreadEventId = rootThreadId,
+ relatesTo,
+ additionalContent,
).also { event ->
createLocalEcho(event)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index 4d5e574592..7d8605c2bd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -95,12 +95,12 @@ internal class LocalEchoEventFactory @Inject constructor(
private val permalinkFactory: PermalinkFactory,
private val clock: Clock,
) {
- fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
+ fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean, additionalContent: Content? = null): Event {
if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
- return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
+ return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType, additionalContent)
}
val content = MessageTextContent(msgType = msgType, body = text.toString())
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
@@ -116,8 +116,8 @@ internal class LocalEchoEventFactory @Inject constructor(
return TextContent(text.toString())
}
- fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event {
- return createMessageEvent(roomId, textContent.toMessageTextContent(msgType))
+ fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String, additionalContent: Content? = null): Event {
+ return createMessageEvent(roomId, textContent.toMessageTextContent(msgType), additionalContent)
}
fun createReplaceTextEvent(
@@ -127,7 +127,8 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyFormattedText: CharSequence?,
newBodyAutoMarkdown: Boolean,
msgType: String,
- compatibilityText: String
+ compatibilityText: String,
+ additionalContent: Content? = null,
): Event {
val content = if (newBodyFormattedText != null) {
TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType)
@@ -141,14 +142,15 @@ internal class LocalEchoEventFactory @Inject constructor(
body = compatibilityText,
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
newContent = content,
- )
+ ),
+ additionalContent,
)
}
private fun createPollContent(
question: String,
options: List,
- pollType: PollType
+ pollType: PollType,
): MessagePollContent {
return MessagePollContent(
unstablePollCreationInfo = PollCreationInfo(
@@ -166,7 +168,8 @@ internal class LocalEchoEventFactory @Inject constructor(
pollType: PollType,
targetEventId: String,
question: String,
- options: List
+ options: List,
+ additionalContent: Content? = null,
): Event {
val newContent = MessagePollContent(
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
@@ -179,14 +182,15 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_START.first(),
- content = newContent.toContent()
+ content = newContent.toContent().plus(additionalContent.orEmpty())
)
}
fun createPollReplyEvent(
roomId: String,
pollEventId: String,
- answerId: String
+ answerId: String,
+ additionalContent: Content? = null,
): Event {
val content = MessagePollResponseContent(
body = answerId,
@@ -203,7 +207,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_RESPONSE.first(),
- content = content.toContent(),
+ content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@@ -212,7 +216,8 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
pollType: PollType,
question: String,
- options: List
+ options: List,
+ additionalContent: Content? = null,
): Event {
val content = createPollContent(question, options, pollType)
val localId = LocalEcho.createLocalEchoId()
@@ -222,14 +227,15 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_START.first(),
- content = content.toContent(),
+ content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
fun createEndPollEvent(
roomId: String,
- eventId: String
+ eventId: String,
+ additionalContent: Content? = null,
): Event {
val content = MessageEndPollContent(
relatesTo = RelationDefaultContent(
@@ -244,7 +250,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_END.first(),
- content = content.toContent(),
+ content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@@ -254,7 +260,8 @@ internal class LocalEchoEventFactory @Inject constructor(
latitude: Double,
longitude: Double,
uncertainty: Double?,
- isUserLocation: Boolean
+ isUserLocation: Boolean,
+ additionalContent: Content? = null,
): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN
@@ -266,7 +273,7 @@ internal class LocalEchoEventFactory @Inject constructor(
unstableTimestampMillis = clock.epochMillis(),
unstableText = geoUri
)
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
fun createLiveLocationEvent(
@@ -274,7 +281,8 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
latitude: Double,
longitude: Double,
- uncertainty: Double?
+ uncertainty: Double?,
+ additionalContent: Content? = null,
): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val content = MessageBeaconLocationDataContent(
@@ -293,7 +301,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.BEACON_LOCATION_DATA.first(),
- content = content.toContent(),
+ content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@@ -305,7 +313,8 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyText: String,
autoMarkdown: Boolean,
msgType: String,
- compatibilityText: String
+ compatibilityText: String,
+ additionalContent: Content? = null,
): Event {
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false)
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
@@ -340,25 +349,42 @@ internal class LocalEchoEventFactory @Inject constructor(
formattedBody = replyFormatted
)
.toContent()
- )
+ ),
+ additionalContent,
)
}
fun createMediaEvent(
roomId: String,
attachment: ContentAttachmentData,
- rootThreadEventId: String?
+ rootThreadEventId: String?,
+ relatesTo: RelationDefaultContent?,
+ additionalContent: Content? = null,
): Event {
return when (attachment.type) {
- ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId)
- ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId)
- ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId)
- ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId)
- ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId)
+ ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent)
+ ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent)
+ ContentAttachmentData.Type.AUDIO -> createAudioEvent(
+ roomId,
+ attachment,
+ isVoiceMessage = false,
+ rootThreadEventId = rootThreadEventId,
+ relatesTo,
+ additionalContent
+ )
+ ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(
+ roomId,
+ attachment,
+ isVoiceMessage = true,
+ rootThreadEventId = rootThreadEventId,
+ relatesTo,
+ additionalContent,
+ )
+ ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent)
}
}
- fun createReactionEvent(roomId: String, targetEventId: String, reaction: String): Event {
+ fun createReactionEvent(roomId: String, targetEventId: String, reaction: String, additionalContent: Content? = null): Event {
val content = ReactionContent(
ReactionInfo(
RelationType.ANNOTATION,
@@ -373,12 +399,18 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.REACTION,
- content = content.toContent(),
+ content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
- private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
+ private fun createImageEvent(
+ roomId: String,
+ attachment: ContentAttachmentData,
+ rootThreadEventId: String?,
+ relatesTo: RelationDefaultContent?,
+ additionalContent: Content?,
+ ): Event {
var width = attachment.width
var height = attachment.height
@@ -403,19 +435,18 @@ internal class LocalEchoEventFactory @Inject constructor(
size = attachment.size
),
url = attachment.queryUri.toString(),
- relatesTo = rootThreadEventId?.let {
- RelationDefaultContent(
- type = RelationType.THREAD,
- eventId = it,
- isFallingBack = true,
- inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
- )
- }
+ relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
- private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
+ private fun createVideoEvent(
+ roomId: String,
+ attachment: ContentAttachmentData,
+ rootThreadEventId: String?,
+ relatesTo: RelationDefaultContent?,
+ additionalContent: Content?,
+ ): Event {
val mediaDataRetriever = MediaMetadataRetriever()
mediaDataRetriever.setDataSource(context, attachment.queryUri)
@@ -447,23 +478,18 @@ internal class LocalEchoEventFactory @Inject constructor(
thumbnailInfo = thumbnailInfo
),
url = attachment.queryUri.toString(),
- relatesTo = rootThreadEventId?.let {
- RelationDefaultContent(
- type = RelationType.THREAD,
- eventId = it,
- isFallingBack = true,
- inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
- )
- }
+ relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
private fun createAudioEvent(
roomId: String,
attachment: ContentAttachmentData,
isVoiceMessage: Boolean,
- rootThreadEventId: String?
+ rootThreadEventId: String?,
+ relatesTo: RelationDefaultContent?,
+ additionalContent: Content?
): Event {
val content = MessageAudioContent(
msgType = MessageType.MSGTYPE_AUDIO,
@@ -479,19 +505,18 @@ internal class LocalEchoEventFactory @Inject constructor(
waveform = waveformSanitizer.sanitize(attachment.waveform)
),
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
- relatesTo = rootThreadEventId?.let {
- RelationDefaultContent(
- type = RelationType.THREAD,
- eventId = it,
- isFallingBack = true,
- inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
- )
- }
+ relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
- private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
+ private fun createFileEvent(
+ roomId: String,
+ attachment: ContentAttachmentData,
+ rootThreadEventId: String?,
+ relatesTo: RelationDefaultContent?,
+ additionalContent: Content?
+ ): Event {
val content = MessageFileContent(
msgType = MessageType.MSGTYPE_FILE,
body = attachment.name ?: "file",
@@ -500,24 +525,18 @@ internal class LocalEchoEventFactory @Inject constructor(
size = attachment.size
),
url = attachment.queryUri.toString(),
- relatesTo = rootThreadEventId?.let {
- RelationDefaultContent(
- type = RelationType.THREAD,
- eventId = it,
- isFallingBack = true,
- inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
- )
- }
+ relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
- private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event {
- return createEvent(roomId, EventType.MESSAGE, content.toContent())
+ private fun createMessageEvent(roomId: String, content: MessageContent, additionalContent: Content?): Event {
+ return createEvent(roomId, EventType.MESSAGE, content.toContent(), additionalContent)
}
- fun createEvent(roomId: String, type: String, content: Content?): Event {
+ fun createEvent(roomId: String, type: String, content: Content?, additionalContent: Content? = null): Event {
val newContent = enhanceStickerIfNeeded(type, content) ?: content
+ val updatedNewContent = newContent?.plus(additionalContent.orEmpty()) ?: additionalContent
val localId = LocalEcho.createLocalEchoId()
return Event(
roomId = roomId,
@@ -525,7 +544,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = type,
- content = newContent,
+ content = updatedNewContent,
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@@ -559,7 +578,8 @@ internal class LocalEchoEventFactory @Inject constructor(
text: CharSequence,
msgType: String,
autoMarkdown: Boolean,
- formattedText: String?
+ formattedText: String?,
+ additionalContent: Content? = null,
): Event {
val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown)
return createEvent(
@@ -569,8 +589,7 @@ internal class LocalEchoEventFactory @Inject constructor(
rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = msgType
- )
- .toContent()
+ ).toContent().plus(additionalContent.orEmpty())
)
}
@@ -588,7 +607,8 @@ internal class LocalEchoEventFactory @Inject constructor(
replyTextFormatted: CharSequence?,
autoMarkdown: Boolean,
rootThreadEventId: String? = null,
- showInThread: Boolean
+ showInThread: Boolean,
+ additionalContent: Content? = null
): Event? {
// Fallbacks and event representation
// TODO Add error/warning logs when any of this is null
@@ -626,9 +646,17 @@ internal class LocalEchoEventFactory @Inject constructor(
showInThread = showInThread
)
)
- return createMessageEvent(roomId, content)
+ return createMessageEvent(roomId, content, additionalContent)
}
+ private fun generateThreadRelationContent(rootThreadEventId: String) =
+ RelationDefaultContent(
+ type = RelationType.THREAD,
+ eventId = rootThreadEventId,
+ isFallingBack = true,
+ inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)),
+ )
+
/**
* Generates the appropriate relatesTo object for a reply event.
* It can either be a regular reply or a reply within a thread
@@ -747,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor(
}
}
*/
- fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event {
+ fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event {
val localId = LocalEcho.createLocalEchoId()
return Event(
roomId = roomId,
@@ -756,7 +784,7 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId,
type = EventType.REDACTION,
redacts = eventId,
- content = reason?.let { mapOf("reason" to it).toContent() },
+ content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent,
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@@ -772,10 +800,15 @@ internal class LocalEchoEventFactory @Inject constructor(
text: String,
formattedText: String?,
autoMarkdown: Boolean,
- rootThreadEventId: String?
+ rootThreadEventId: String?,
+ additionalContent: Content? = null,
): Event {
val messageContent = quotedEvent.getLastMessageContent()
- val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body }
+ val textMsg = if (messageContent is MessageContentWithFormattedBody) {
+ messageContent.formattedBody
+ } else {
+ messageContent?.body
+ }
val quoteText = legacyRiotQuoteText(textMsg, text)
val quoteFormattedText = "
$textMsg
$formattedText"
@@ -788,13 +821,15 @@ internal class LocalEchoEventFactory @Inject constructor(
rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = MessageType.MSGTYPE_TEXT
- )
+ ),
+ additionalContent,
)
} else {
createFormattedTextEvent(
roomId,
markdownParser.parse(quoteText, force = true, advanced = autoMarkdown).copy(formattedText = quoteFormattedText),
- MessageType.MSGTYPE_TEXT
+ MessageType.MSGTYPE_TEXT,
+ additionalContent,
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 53c0253876..b1a3d51b36 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -96,4 +96,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
override fun getAttachmentMessages(): List {
return timelineEventDataSource.getAttachmentMessages(roomId)
}
+
+ override fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List {
+ return timelineEventDataSource.getTimelineEventsRelatedTo(roomId, relationType, eventId)
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
index b1b9e4bb22..2d6082f9b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
@@ -19,8 +19,11 @@ package org.matrix.android.sdk.internal.session.room.timeline
import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy
import io.realm.Sort
+import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
@@ -63,4 +66,24 @@ internal class TimelineEventDataSource @Inject constructor(
.orEmpty()
}
}
+
+ fun getTimelineEventsRelatedTo(roomId: String, eventType: String, eventId: String): List {
+ // TODO Remove this trick and call relations API
+ // see https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1roomsroomidrelationseventidreltypeeventtype
+ return realmSessionProvider.withRealm { realm ->
+ TimelineEventEntity.whereRoomId(realm, roomId)
+ .sort(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, Sort.ASCENDING)
+ .distinct(TimelineEventEntityFields.EVENT_ID)
+ .findAll()
+ .mapNotNull {
+ timelineEventMapper.map(it)
+ .takeIf {
+ val isEventRelatedTo = it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null
+ val isContentRelatedTo = it.root.getClearContent()?.toModel()
+ ?.relatesTo?.takeIf { it.type == eventType && it.eventId == eventId } != null
+ isEventRelatedTo || isContentRelatedTo
+ }
+ }
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt
index 1f840a82d5..1ee2fc4802 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UpdateUserWorker.kt
@@ -71,10 +71,16 @@ internal class UpdateUserWorker(context: Context, params: WorkerParameters, sess
?.saveLocally()
}
- private suspend fun fetchUsers(userIdsToFetch: Collection) = userIdsToFetch.mapNotNull {
- tryOrNull {
- val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it))
- User.fromJson(it, profileJson)
+ private suspend fun fetchUsers(userIdsToFetch: Collection): List {
+ return userIdsToFetch.mapNotNull { userId ->
+ tryOrNull {
+ val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(
+ userId = userId,
+ // Bulk insert later, so tell the task not to store the User.
+ storeInDatabase = false,
+ ))
+ User.fromJson(userId, profileJson)
+ }
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt
index f9feb04e97..98108008fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt
@@ -66,6 +66,8 @@ internal class UserDataSource @Inject constructor(
}
}
+ fun getUserOrDefault(userId: String): User = getUser(userId) ?: User(userId)
+
fun getUserLive(userId: String): LiveData> {
val liveData = monarchy.findAllMappedWithChanges(
{ UserEntity.where(it, userId) },
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt
index 8bd61a7bdf..a43c59a83b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
-import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
@@ -74,7 +73,7 @@ internal class WidgetFactory @Inject constructor(
// Ref: https://github.com/matrix-org/matrix-widget-api/blob/master/src/templating/url-template.ts#L29-L33
fun computeURL(widget: Widget, isLightTheme: Boolean): String? {
var computedUrl = widget.widgetContent.url ?: return null
- val myUser = userDataSource.getUser(userId) ?: User(userId)
+ val myUser = userDataSource.getUserOrDefault(userId)
val keyValue = widget.widgetContent.data.mapKeys { "\$${it.key}" }.toMutableMap()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt
index 9ed6f28d7e..2170371cde 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/network/ComputeUserAgentUseCaseTest.kt
@@ -27,6 +27,8 @@ import org.amshove.kluent.shouldBeEqualTo
import org.junit.Before
import org.junit.Test
import org.matrix.android.sdk.BuildConfig
+import org.matrix.android.sdk.api.util.getApplicationInfoCompat
+import org.matrix.android.sdk.api.util.getPackageInfoCompat
import java.lang.Exception
private const val A_PACKAGE_NAME = "org.matrix.sdk"
@@ -49,8 +51,8 @@ class ComputeUserAgentUseCaseTest {
every { context.applicationContext } returns context
every { context.packageName } returns A_PACKAGE_NAME
every { context.packageManager } returns packageManager
- every { packageManager.getApplicationInfo(any(), any()) } returns applicationInfo
- every { packageManager.getPackageInfo(any(), any()) } returns packageInfo
+ every { packageManager.getApplicationInfoCompat(any(), any()) } returns applicationInfo
+ every { packageManager.getPackageInfoCompat(any(), any()) } returns packageInfo
}
@Test
diff --git a/tools/adb/notification.sh b/tools/adb/notification.sh
new file mode 100755
index 0000000000..3c3c76c787
--- /dev/null
+++ b/tools/adb/notification.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+## From https://developer.android.com/develop/ui/views/notifications/notification-permission#test
+
+PACKAGE_NAME=im.vector.app.debug
+
+# App is newly installed on a device that runs Android 13 or higher:
+
+adb shell pm revoke ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
+adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
+adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
+
+# The user keeps notifications enabled when the app is installed on a device that runs 12L or lower,
+# then the device upgrades to Android 13 or higher:
+
+# adb shell pm grant ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
+# adb shell pm set-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
+# adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
+
+# The user manually disables notifications when the app is installed on a device that runs 12L or lower,
+# then the device upgrades to Android 13 or higher:
+
+# adb shell pm revoke ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS
+# adb shell pm set-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-set
+# adb shell pm clear-permission-flags ${PACKAGE_NAME} android.permission.POST_NOTIFICATIONS user-fixed
diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh
index fc46fca758..b9cbfa4cac 100755
--- a/tools/check/check_code_quality.sh
+++ b/tools/check/check_code_quality.sh
@@ -68,15 +68,14 @@ ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code.txt \
./matrix-sdk-android/src/main/java \
./matrix-sdk-android-flow/src/main/java \
./library/core-utils/src/main/java \
- ./library/jsonviewer/src/main/java \
+ ./library/external/jsonviewer/src/main/java \
./library/ui-styles/src/main/java \
./vector/src/main/java \
- ./vector/src/debug/java \
- ./vector/src/release/java \
- ./vector/src/fdroid/java \
- ./vector/src/gplay/java \
+ ./vector-app/src/debug/java \
+ ./vector-app/src/fdroid/java \
./vector-app/src/gplay/java \
- ./vector-app/src/main/java
+ ./vector-app/src/main/java \
+ ./vector-app/src/release/java
resultForbiddenStringInCode=$?
@@ -93,13 +92,15 @@ echo
echo "Search for forbidden patterns specific for App code..."
${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_app.txt \
+ ./library/core-utils/src/main/java \
+ ./library/external/jsonviewer/src/main/java \
+ ./library/ui-styles/src/main/java \
./vector/src/main/java \
- ./vector/src/debug/java \
- ./vector/src/release/java \
- ./vector/src/fdroid/java \
- ./vector/src/gplay/java \
+ ./vector-app/src/debug/java \
+ ./vector-app/src/fdroid/java \
./vector-app/src/gplay/java \
- ./vector-app/src/main/java
+ ./vector-app/src/main/java \
+ ./vector-app/src/release/java
resultForbiddenStringInCodeApp=$?
@@ -120,8 +121,7 @@ echo
echo "Search for forbidden patterns in layouts..."
${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_layout.txt \
- ./vector/src/main/res/layout \
- ./vector-app/src/main/res/layout
+ ./vector/src/main/res/layout
resultForbiddenStringInLayout=$?
@@ -154,17 +154,19 @@ echo "Search for kotlin files with more than ${maxLines} lines..."
${checkLongFilesScript} ${maxLines} \
./matrix-sdk-android/src/main/java \
./matrix-sdk-android-flow/src/main/java \
+ ./library/core-utils/src/main/java \
+ ./library/external/jsonviewer/src/main/java \
+ ./library/ui-styles/src/main/java \
./vector/src/androidTest/java \
- ./vector/src/debug/java \
- ./vector/src/fdroid/java \
- ./vector/src/gplay/java \
./vector/src/main/java \
- ./vector/src/release/java \
./vector/src/sharedTest/java \
./vector/src/test/java \
- ./vector/src/androidTest/java \
- ./vector/src/gplay/java \
- ./vector/src/main/java
+ ./vector-app/src/androidTest/java \
+ ./vector-app/src/debug/java \
+ ./vector-app/src/fdroid/java \
+ ./vector-app/src/gplay/java \
+ ./vector-app/src/main/java \
+ ./vector-app/src/release/java
resultLongFiles=$?
@@ -179,8 +181,11 @@ echo "Search for png files in /drawable..."
ls -1U ./vector/src/main/res/drawable/*.png
resultTmp=$?
+ls -1U ./vector-app/src/main/res/drawable/*.png
+resultTmp2=$?
+
# Inverse the result, cause no file found is an error for ls but this is what we want!
-if [[ ${resultTmp} -eq 0 ]]; then
+if [[ ${resultTmp} -eq 0 ]] || [[ ${resultTmp2} -eq 0 ]]; then
echo "ERROR, png files detected in /drawable"
resultPngInDrawable=1
else
diff --git a/tools/danger/dangerfile.js b/tools/danger/dangerfile.js
index 1a36474470..2fc55bad0c 100644
--- a/tools/danger/dangerfile.js
+++ b/tools/danger/dangerfile.js
@@ -81,6 +81,7 @@ const allowList = [
"Florian14",
"ganfra",
"jmartinesp",
+ "jonnyandrew",
"langleyd",
"MadLittleMods",
"manuroe",
diff --git a/tools/gradle/doctor.gradle b/tools/gradle/doctor.gradle
index c77d2eb338..7a7adad062 100644
--- a/tools/gradle/doctor.gradle
+++ b/tools/gradle/doctor.gradle
@@ -54,7 +54,8 @@ doctor {
/**
* Warn when not using parallel GC. Parallel GC is faster for build type tasks and is no longer the default in Java 9+.
*/
- warnWhenNotUsingParallelGC = !isCiBuild
+ // Note: Actually, if set to true, it fails the build. See https://lightrun.com/answers/runningcode-gradle-doctor-warnwhennotusingparallelgc-fails-the-build-warn-is-a-confusing-keyword-here
+ warnWhenNotUsingParallelGC = false
/**
* Throws an error when the `Delete` or `clean` task has dependencies.
* If a clean task depends on other tasks, clean can be reordered and made to run after the tasks that would produce
@@ -82,7 +83,7 @@ doctor {
/**
* Fail on any `JAVA_HOME` issues.
*/
- failOnError.set(!isCiBuild)
+ failOnError.set(false)
/**
* Extra message text, if any, to show with the Gradle Doctor message. This is useful if you have a wiki page or
* other instructions that you want to link for developers on your team if they encounter an issue.
diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index eb19027880..9a0bfdfa63 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -37,7 +37,7 @@ ext.versionMinor = 5
// 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'
@@ -125,6 +125,7 @@ ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
android {
+ namespace "im.vector.application"
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
// Ref: https://issuetracker.google.com/issues/144111441
ndkVersion "21.3.6528147"
@@ -371,9 +372,9 @@ dependencies {
debugImplementation 'com.facebook.soloader:soloader:0.10.4'
debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
- gplayImplementation "com.google.android.gms:play-services-location:20.0.0"
+ gplayImplementation "com.google.android.gms:play-services-location:21.0.0"
// UnifiedPush gplay flavor only
- gplayImplementation('com.google.firebase:firebase-messaging:23.0.8') {
+ gplayImplementation('com.google.firebase:firebase-messaging:23.1.0') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
diff --git a/vector-app/proguard-rules.pro b/vector-app/proguard-rules.pro
index 7fd72ba895..57559bf7b4 100644
--- a/vector-app/proguard-rules.pro
+++ b/vector-app/proguard-rules.pro
@@ -75,4 +75,8 @@
-keep class org.bouncycastle.** { *; }
-keepnames class org.bouncycastle.** { *; }
--dontwarn org.bouncycastle.**
\ No newline at end of file
+-dontwarn org.bouncycastle.**
+
+# JNA
+-keep class com.sun.jna.** { *; }
+-keep class * implements com.sun.jna.** { *; }
diff --git a/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ViewActionsExt.kt b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ViewActionsExt.kt
new file mode 100644
index 0000000000..c1e5c0164b
--- /dev/null
+++ b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ViewActionsExt.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.espresso.tools
+
+import android.view.View
+import androidx.test.espresso.PerformException
+import androidx.test.espresso.UiController
+import androidx.test.espresso.ViewAction
+import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import com.google.android.material.tabs.TabLayout
+import org.hamcrest.Matchers.allOf
+
+fun selectTabAtPosition(tabIndex: Int): ViewAction {
+ return object : ViewAction {
+ override fun getDescription() = "with tab at index $tabIndex"
+
+ override fun getConstraints() = allOf(isDisplayed(), isAssignableFrom(TabLayout::class.java))
+
+ override fun perform(uiController: UiController, view: View) {
+ val tabLayout = view as TabLayout
+ val tabAtIndex: TabLayout.Tab = tabLayout.getTabAt(tabIndex)
+ ?: throw PerformException.Builder()
+ .withCause(Throwable("No tab at index $tabIndex"))
+ .build()
+
+ tabAtIndex.select()
+ }
+ }
+}
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index d4878b8dcc..52607bd9a1 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -135,6 +135,14 @@ class UiAllScreensSanityTest {
elementRobot.space { selectSpace(spaceName) }
+ elementRobot.layoutPreferences {
+ crawl()
+ }
+
+ elementRobot.roomList {
+ crawlTabs()
+ }
+
elementRobot.withDeveloperMode {
settings {
advancedSettings { crawlDeveloperOptions() }
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index d9dfb0facf..8f1df52863 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -17,8 +17,10 @@
package im.vector.app.ui.robot
import android.view.View
+import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.closeSoftKeyboard
import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
@@ -94,6 +96,18 @@ class ElementRobot(
waitUntilViewVisible(withId(R.id.roomListContainer))
}
+ fun layoutPreferences(block: LayoutPreferencesRobot.() -> Unit) {
+ openActionBarOverflowOrOptionsMenu(
+ ApplicationProvider.getApplicationContext()
+ )
+ clickOn(R.string.home_layout_preferences)
+ waitUntilDialogVisible(withId(R.id.home_layout_settings_recents))
+
+ block(LayoutPreferencesRobot())
+
+ pressBack()
+ }
+
fun newDirectMessage(block: NewDirectMessageRobot.() -> Unit) {
if (labsPreferences.isNewAppLayoutEnabled) {
clickOn(R.id.newLayoutCreateChatButton)
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/LayoutPreferencesRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/LayoutPreferencesRobot.kt
new file mode 100644
index 0000000000..429511e10f
--- /dev/null
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/LayoutPreferencesRobot.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.ui.robot
+
+import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
+import im.vector.app.R
+
+class LayoutPreferencesRobot {
+
+ fun crawl() {
+ toggleRecents()
+ toggleFilters()
+ useAZOrderd()
+ useActivityOrder()
+ }
+
+ fun toggleRecents() {
+ clickOn(R.id.home_layout_settings_recents)
+ }
+
+ fun toggleFilters() {
+ clickOn(R.id.home_layout_settings_filters)
+ }
+
+ fun useAZOrderd() {
+ clickOn(R.id.home_layout_settings_sort_name)
+ }
+
+ fun useActivityOrder() {
+ clickOn(R.id.home_layout_settings_sort_activity)
+ }
+}
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt
index e4984aeed0..cbc46f15e7 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomListRobot.kt
@@ -21,29 +21,41 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.contrib.RecyclerViewActions
-import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
+import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import im.vector.app.R
+import im.vector.app.espresso.tools.selectTabAtPosition
import im.vector.app.espresso.tools.waitUntilActivityVisible
import im.vector.app.espresso.tools.waitUntilDialogVisible
+import im.vector.app.espresso.tools.waitUntilViewVisible
+import im.vector.app.features.home.HomeActivity
+import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences
+import im.vector.app.waitForView
class RoomListRobot(private val labsPreferences: LabFeaturesPreferences) {
fun openRoom(roomName: String, block: RoomDetailRobot.() -> Unit) {
- clickOn(roomName)
+ onView(withId(R.id.roomListView))
+ .perform(
+ RecyclerViewActions.actionOnItem(
+ hasDescendant(withText(roomName)),
+ ViewActions.click()
+ )
+ )
block(RoomDetailRobot())
pressBack()
}
fun verifyCreatedRoom() {
- onView(ViewMatchers.withId(R.id.roomListView))
+ onView(withId(R.id.roomListView))
.perform(
RecyclerViewActions.actionOnItem(
- ViewMatchers.hasDescendant(withText(R.string.room_displayname_empty_room)),
+ hasDescendant(withText(R.string.room_displayname_empty_room)),
ViewActions.longClick()
)
)
@@ -53,7 +65,7 @@ class RoomListRobot(private val labsPreferences: LabFeaturesPreferences) {
fun newRoom(block: NewRoomRobot.() -> Unit) {
if (labsPreferences.isNewAppLayoutEnabled) {
clickOn(R.id.newLayoutCreateChatButton)
- waitUntilDialogVisible(ViewMatchers.withId(R.id.create_room))
+ waitUntilDialogVisible(withId(R.id.create_room))
clickOn(R.id.create_room)
} else {
clickOn(R.id.createGroupRoomButton)
@@ -67,4 +79,19 @@ class RoomListRobot(private val labsPreferences: LabFeaturesPreferences) {
pressBack()
}
}
+
+ fun crawlTabs() {
+ waitUntilActivityVisible {
+ waitUntilViewVisible(withId(R.id.roomListContainer))
+ }
+
+ selectFilterTab(HomeRoomFilter.UNREADS)
+ waitForView(withId(R.id.emptyTitleView))
+ selectFilterTab(HomeRoomFilter.ALL)
+ waitForView(withId(R.id.roomNameView))
+ }
+
+ fun selectFilterTab(filter: HomeRoomFilter) {
+ onView(withId(R.id.home_filter_tabs_tabs)).perform(selectTabAtPosition(filter.ordinal))
+ }
}
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index 005e9c499b..f431192efd 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -26,7 +26,6 @@ import androidx.core.app.Person
import androidx.core.content.getSystemService
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.time.Clock
@@ -59,11 +58,7 @@ class DebugMenuActivity : VectorBaseActivity() {
override fun getBinding() = ActivityDebugMenuBinding.inflate(layoutInflater)
- @Inject
- lateinit var activeSessionHolder: ActiveSessionHolder
-
- @Inject
- lateinit var clock: Clock
+ @Inject lateinit var clock: Clock
private lateinit var buffer: ByteArray
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
index a9be5512e4..2b1b66e672 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
@@ -22,6 +22,7 @@ import android.os.Build
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.checkPermissions
@@ -46,7 +47,15 @@ class DebugPermissionActivity : VectorBaseActivity {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ listOf(Manifest.permission.POST_NOTIFICATIONS)
+ } else {
+ emptyList()
+ }
+ }
private var lastPermissions = emptyList()
@@ -77,6 +86,14 @@ class DebugPermissionActivity : VectorBaseActivity= Build.VERSION_CODES.TIRAMISU) {
+ views.notification.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.POST_NOTIFICATIONS)
+ checkPerm()
+ }
+ } else {
+ views.notification.isVisible = false
+ }
}
private fun checkPerm() {
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
index 5f34a349d6..16e26ff3b5 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
@@ -85,6 +85,21 @@ class DebugFeaturesStateFactory @Inject constructor(
key = DebugFeatureKeys.newAppLayoutEnabled,
factory = VectorFeatures::isNewAppLayoutFeatureEnabled
),
+ createBooleanFeature(
+ label = "Enable QR Code Login",
+ key = DebugFeatureKeys.qrCodeLoginEnabled,
+ factory = VectorFeatures::isQrCodeLoginEnabled
+ ),
+ createBooleanFeature(
+ label = "Allow QR Code Login for all servers",
+ key = DebugFeatureKeys.qrCodeLoginForAllServers,
+ factory = VectorFeatures::isQrCodeLoginForAllServers
+ ),
+ createBooleanFeature(
+ label = "Show QR Code Login in Device Manager",
+ key = DebugFeatureKeys.reciprocateQrCodeLogin,
+ factory = VectorFeatures::isReciprocateQrCodeLogin
+ ),
createBooleanFeature(
label = "Enable Voice Broadcast",
key = DebugFeatureKeys.voiceBroadcastEnabled,
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index 6062a1f999..5c497c24ec 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -76,6 +76,15 @@ class DebugVectorFeatures(
override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
?: vectorFeatures.isNewAppLayoutFeatureEnabled()
+ override fun isQrCodeLoginEnabled() = read(DebugFeatureKeys.qrCodeLoginEnabled)
+ ?: vectorFeatures.isQrCodeLoginEnabled()
+
+ override fun isQrCodeLoginForAllServers() = read(DebugFeatureKeys.qrCodeLoginForAllServers)
+ ?: vectorFeatures.isQrCodeLoginForAllServers()
+
+ override fun isReciprocateQrCodeLogin() = read(DebugFeatureKeys.reciprocateQrCodeLogin)
+ ?: vectorFeatures.isReciprocateQrCodeLogin()
+
override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
?: vectorFeatures.isVoiceBroadcastEnabled()
@@ -138,5 +147,8 @@ object DebugFeatureKeys {
val screenSharing = booleanPreferencesKey("screen-sharing")
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
+ val qrCodeLoginEnabled = booleanPreferencesKey("qr-code-login-enabled")
+ val qrCodeLoginForAllServers = booleanPreferencesKey("qr-code-login-for-all-servers")
+ val reciprocateQrCodeLogin = booleanPreferencesKey("reciprocate-qr-code-login")
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
}
diff --git a/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt b/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt
index 2e4336c942..cbf9e4f11f 100644
--- a/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt
+++ b/vector-app/src/debug/java/im/vector/app/flipper/VectorFlipperProxy.kt
@@ -17,6 +17,7 @@
package im.vector.app.flipper
import android.content.Context
+import android.os.Build
import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.android.utils.FlipperUtils
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin
@@ -31,6 +32,7 @@ import com.kgurgul.flipper.RealmDatabaseDriver
import com.kgurgul.flipper.RealmDatabaseProvider
import im.vector.app.core.debug.FlipperProxy
import io.realm.RealmConfiguration
+import okhttp3.Interceptor
import org.matrix.android.sdk.api.Matrix
import javax.inject.Inject
import javax.inject.Singleton
@@ -41,29 +43,43 @@ class VectorFlipperProxy @Inject constructor(
) : FlipperProxy {
private val networkFlipperPlugin = NetworkFlipperPlugin()
+ private val isEnabled: Boolean
+ get() {
+ // https://github.com/facebook/flipper/issues/3572
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ return false
+ }
+
+ return FlipperUtils.shouldEnableFlipper(context)
+ }
+
override fun init(matrix: Matrix) {
+ if (!isEnabled) return
+
SoLoader.init(context, false)
- if (FlipperUtils.shouldEnableFlipper(context)) {
- val client = AndroidFlipperClient.getInstance(context)
- client.addPlugin(CrashReporterPlugin.getInstance())
- client.addPlugin(SharedPreferencesFlipperPlugin(context))
- client.addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()))
- client.addPlugin(networkFlipperPlugin)
- client.addPlugin(
- DatabasesFlipperPlugin(
- RealmDatabaseDriver(
- context = context,
- realmDatabaseProvider = object : RealmDatabaseProvider {
- override fun getRealmConfigurations(): List {
- return matrix.debugService().getAllRealmConfigurations()
- }
- })
- )
- )
- client.start()
- }
+ val client = AndroidFlipperClient.getInstance(context)
+ client.addPlugin(CrashReporterPlugin.getInstance())
+ client.addPlugin(SharedPreferencesFlipperPlugin(context))
+ client.addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()))
+ client.addPlugin(networkFlipperPlugin)
+ client.addPlugin(
+ DatabasesFlipperPlugin(
+ RealmDatabaseDriver(
+ context = context,
+ realmDatabaseProvider = object : RealmDatabaseProvider {
+ override fun getRealmConfigurations(): List {
+ return matrix.debugService().getAllRealmConfigurations()
+ }
+ })
+ )
+ )
+ client.start()
}
- override fun networkInterceptor() = FlipperOkhttpInterceptor(networkFlipperPlugin)
+ override fun networkInterceptor(): Interceptor? {
+ if (!isEnabled) return null
+
+ return FlipperOkhttpInterceptor(networkFlipperPlugin)
+ }
}
diff --git a/vector-app/src/debug/res/layout/activity_debug_permission.xml b/vector-app/src/debug/res/layout/activity_debug_permission.xml
index 6340d8faa7..0f1fef0b9b 100644
--- a/vector-app/src/debug/res/layout/activity_debug_permission.xml
+++ b/vector-app/src/debug/res/layout/activity_debug_permission.xml
@@ -30,43 +30,43 @@
android:id="@+id/camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="CAMERA"
- android:textAllCaps="false" />
+ android:text="CAMERA" />
+ android:text="RECORD_AUDIO" />
+ android:text="CAMERA + RECORD_AUDIO" />
+ android:text="WRITE_EXTERNAL_STORAGE" />
+ android:text="READ_EXTERNAL_STORAGE" />
+ android:text="READ_CONTACTS" />
+
+
diff --git a/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt b/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
index e028ed0988..ce1263c67b 100644
--- a/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
+++ b/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
@@ -15,8 +15,6 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
-import android.content.Intent
-import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences
@@ -32,7 +30,7 @@ class TestAutoStartBoot @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
- override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ override fun perform(testParameters: TestParameters) {
if (vectorPreferences.autoStartOnBoot()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.SUCCESS
@@ -42,7 +40,7 @@ class TestAutoStartBoot @Inject constructor(
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true)
- manager?.retry(activityResultLauncher)
+ manager?.retry(testParameters)
}
}
status = TestStatus.FAILED
diff --git a/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt b/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
index ebd8d07540..a14d3b5c8a 100644
--- a/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
+++ b/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
@@ -15,9 +15,7 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
-import android.content.Intent
import android.net.ConnectivityManager
-import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.getSystemService
import androidx.core.net.ConnectivityManagerCompat
import androidx.fragment.app.FragmentActivity
@@ -32,7 +30,7 @@ class TestBackgroundRestrictions @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
- override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ override fun perform(testParameters: TestParameters) {
context.getSystemService()!!.apply {
// Checks if the device is on a metered network
if (isActiveNetworkMetered) {
diff --git a/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt b/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt
index 57bdf721a2..075432c9d2 100644
--- a/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt
+++ b/vector-app/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt
@@ -15,8 +15,6 @@
*/
package im.vector.app.fdroid.features.settings.troubleshoot
-import android.content.Intent
-import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
@@ -30,7 +28,7 @@ class TestBatteryOptimization @Inject constructor(
private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
- override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ override fun perform(testParameters: TestParameters) {
if (context.isIgnoringBatteryOptimizations()) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success)
status = TestStatus.SUCCESS
@@ -39,7 +37,7 @@ class TestBatteryOptimization @Inject constructor(
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) {
override fun doFix() {
- requestDisablingBatteryOptimization(context, activityResultLauncher)
+ requestDisablingBatteryOptimization(context, testParameters.activityResultLauncher)
}
}
status = TestStatus.FAILED
diff --git a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt
index d6180a9fe8..8e7e4f43cc 100644
--- a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt
+++ b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt
@@ -15,8 +15,6 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
-import android.content.Intent
-import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R
@@ -36,7 +34,7 @@ class TestFirebaseToken @Inject constructor(
private val fcmHelper: FcmHelper,
) : TroubleshootTest(R.string.settings_troubleshoot_test_fcm_title) {
- override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ override fun perform(testParameters: TestParameters) {
status = TestStatus.RUNNING
try {
FirebaseMessaging.getInstance().token
@@ -53,7 +51,7 @@ class TestFirebaseToken @Inject constructor(
"ACCOUNT_MISSING" -> {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
- startAddGoogleAccountIntent(context, activityResultLauncher)
+ startAddGoogleAccountIntent(context, testParameters.activityResultLauncher)
}
}
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
diff --git a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
index e78132908d..3ebcceb3fb 100644
--- a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
+++ b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
@@ -15,8 +15,6 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
-import android.content.Intent
-import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
@@ -35,7 +33,7 @@ class TestPlayServices @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
- override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ override fun perform(testParameters: TestParameters) {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode == ConnectionResult.SUCCESS) {
diff --git a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
index 840bde77b1..cafc2d65e6 100644
--- a/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
+++ b/vector-app/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
@@ -15,8 +15,6 @@
*/
package im.vector.app.gplay.features.settings.troubleshoot
-import android.content.Intent
-import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
@@ -42,7 +40,7 @@ class TestTokenRegistration @Inject constructor(
) :
TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
- override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ override fun perform(testParameters: TestParameters) {
// Check if we have a registered pusher for this token
val fcmToken = fcmHelper.getFcmToken() ?: run {
status = TestStatus.FAILED
@@ -66,9 +64,9 @@ class TestTokenRegistration @Inject constructor(
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
if (workInfo != null) {
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
- manager?.retry(activityResultLauncher)
+ manager?.retry(testParameters)
} else if (workInfo.state == WorkInfo.State.FAILED) {
- manager?.retry(activityResultLauncher)
+ manager?.retry(testParameters)
}
}
})
diff --git a/vector-app/src/main/AndroidManifest.xml b/vector-app/src/main/AndroidManifest.xml
index 7a515449b4..661bd3f934 100644
--- a/vector-app/src/main/AndroidManifest.xml
+++ b/vector-app/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:tools="http://schemas.android.com/tools">
-
+
diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 9c7eee5c0c..504c587b8d 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -42,10 +42,15 @@
falsetruefalse
+ true
+ false
+ truetruefalsetruefalse
+ true
+ false
diff --git a/vector/build.gradle b/vector/build.gradle
index c59e1a3028..0b884f4d99 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -28,6 +28,7 @@ project.android.buildTypes.all { buildType ->
initScreenshotTests(project)
android {
+ namespace "im.vector.app"
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
// Ref: https://issuetracker.google.com/issues/144111441
ndkVersion "21.3.6528147"
@@ -53,6 +54,8 @@ android {
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
+
+ vectorDrawables.useSupportLibrary = true
}
testOptions {
@@ -236,7 +239,7 @@ dependencies {
implementation libs.sentry.sentryAndroid
// UnifiedPush
- implementation 'com.github.UnifiedPush:android-connector:2.1.0'
+ implementation 'com.github.UnifiedPush:android-connector:2.1.1'
implementation "androidx.emoji2:emoji2:1.2.0"
diff --git a/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt b/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt
index 73174e4b34..527751aae2 100644
--- a/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt
@@ -25,6 +25,7 @@ import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Ignore
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel
import org.matrix.android.sdk.api.session.room.model.Membership
@@ -36,6 +37,7 @@ import kotlin.coroutines.suspendCoroutine
class RoomMemberListControllerTest {
@Test
+ @Ignore("Too flaky")
fun testControllerUserVerificationLevel() = runTest {
val roomListController = RoomMemberListController(
avatarRenderer = mockk {
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index f079d3429e..b0cd202d12 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:tools="http://schemas.android.com/tools">
+
+
+
@@ -321,6 +323,7 @@
+
diff --git a/vector/src/main/java/im/vector/app/core/animations/SimpleAnimatorListener.kt b/vector/src/main/java/im/vector/app/core/animations/SimpleAnimatorListener.kt
index d8ffde20ef..ec20de5914 100644
--- a/vector/src/main/java/im/vector/app/core/animations/SimpleAnimatorListener.kt
+++ b/vector/src/main/java/im/vector/app/core/animations/SimpleAnimatorListener.kt
@@ -19,19 +19,19 @@ package im.vector.app.core.animations
import android.animation.Animator
open class SimpleAnimatorListener : Animator.AnimatorListener {
- override fun onAnimationRepeat(animation: Animator?) {
+ override fun onAnimationRepeat(animation: Animator) {
// No op
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
// No op
}
- override fun onAnimationCancel(animation: Animator?) {
+ override fun onAnimationCancel(animation: Animator) {
// No op
}
- override fun onAnimationStart(animation: Animator?) {
+ override fun onAnimationStart(animation: Animator) {
// No op
}
}
diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
index 38b62e1511..97590028d8 100644
--- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
@@ -60,6 +60,7 @@ import im.vector.app.features.location.LocationSharingViewModel
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
import im.vector.app.features.location.preview.LocationPreviewViewModel
import im.vector.app.features.login.LoginViewModel
+import im.vector.app.features.login.qr.QrCodeLoginViewModel
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
import im.vector.app.features.media.VectorAttachmentViewerViewModel
import im.vector.app.features.onboarding.OnboardingViewModel
@@ -100,6 +101,7 @@ import im.vector.app.features.settings.devtools.KeyRequestViewModel
import im.vector.app.features.settings.font.FontScaleSettingViewModel
import im.vector.app.features.settings.homeserver.HomeserverSettingsViewModel
import im.vector.app.features.settings.ignored.IgnoredUsersViewModel
+import im.vector.app.features.settings.labs.VectorSettingsLabsViewModel
import im.vector.app.features.settings.legals.LegalsViewModel
import im.vector.app.features.settings.locale.LocalePickerViewModel
import im.vector.app.features.settings.push.PushGatewaysViewModel
@@ -661,8 +663,18 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(RenameSessionViewModel::class)
fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(QrCodeLoginViewModel::class)
+ fun qrCodeLoginViewModelFactory(factory: QrCodeLoginViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
@Binds
@IntoMap
@MavericksViewModelKey(SessionLearnMoreViewModel::class)
fun sessionLearnMoreViewModelFactory(factory: SessionLearnMoreViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(VectorSettingsLabsViewModel::class)
+ fun vectorSettingsLabsViewModelFactory(factory: VectorSettingsLabsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
}
diff --git a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt
new file mode 100644
index 0000000000..54d556ea91
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.di
+
+import android.content.Context
+import android.os.Build
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
+import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object VoiceModule {
+ @Provides
+ @Singleton
+ fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ VoiceBroadcastRecorderQ(context)
+ } else {
+ null
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/LayoutManagerStateRestorer.kt b/vector/src/main/java/im/vector/app/core/epoxy/LayoutManagerStateRestorer.kt
index dfbdfff2b8..b1d641826e 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/LayoutManagerStateRestorer.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/LayoutManagerStateRestorer.kt
@@ -21,6 +21,7 @@ import android.os.Parcelable
import androidx.recyclerview.widget.RecyclerView
import im.vector.app.core.platform.DefaultListUpdateCallback
import im.vector.app.core.platform.Restorable
+import im.vector.lib.core.utils.compat.getParcelableCompat
import java.util.concurrent.atomic.AtomicReference
private const val LAYOUT_MANAGER_STATE = "LAYOUT_MANAGER_STATE"
@@ -44,7 +45,7 @@ class LayoutManagerStateRestorer(layoutManager: RecyclerView.LayoutManager) : Re
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
- val parcelable = savedInstanceState?.getParcelable(LAYOUT_MANAGER_STATE)
+ val parcelable = savedInstanceState?.getParcelableCompat(LAYOUT_MANAGER_STATE)
layoutManagerState.set(parcelable)
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt b/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt
index 6120a84d7c..8201a1b5e8 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt
@@ -25,6 +25,7 @@ import com.airbnb.mvrx.MavericksViewModelProvider
inline fun , reified S : MavericksState> ComponentActivity.lazyViewModel(): Lazy {
return lazy(mode = LazyThreadSafetyMode.NONE) {
+ @Suppress("DEPRECATION")
MavericksViewModelProvider.get(
viewModelClass = VM::class.java,
stateClass = S::class.java,
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 cdb84387ce..5c3393416b 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
@@ -16,7 +16,7 @@
package im.vector.app.core.extensions
-import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
+import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -39,7 +39,9 @@ fun TimelineEvent.canReact(): Boolean {
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
return when (root.getClearType()) {
- STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
+ VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
+ (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
+ }
else -> getLastMessageContent()
}
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewPager2.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewPager2.kt
index 7d3a678af1..c94352b998 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/ViewPager2.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/ViewPager2.kt
@@ -22,6 +22,7 @@ import android.animation.ValueAnimator
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.viewpager2.widget.ViewPager2
+import im.vector.app.core.animations.SimpleAnimatorListener
fun ViewPager2.setCurrentItem(
item: Int,
@@ -45,19 +46,16 @@ fun ViewPager2.setCurrentItem(
previousValue = currentValue
}.onFailure { animator.cancel() }
}
- animator.addListener(object : Animator.AnimatorListener {
- override fun onAnimationStart(animation: Animator?) {
+ animator.addListener(object : SimpleAnimatorListener() {
+ override fun onAnimationStart(animation: Animator) {
isUserInputEnabled = false
beginFakeDrag()
}
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
isUserInputEnabled = true
endFakeDrag()
}
-
- override fun onAnimationCancel(animation: Animator?) = Unit
- override fun onAnimationRepeat(animation: Animator?) = Unit
})
animator.interpolator = interpolator
animator.duration = duration
diff --git a/vector/src/main/java/im/vector/app/core/files/FileSaver.kt b/vector/src/main/java/im/vector/app/core/files/FileSaver.kt
index c595bb2693..8afcdac73b 100644
--- a/vector/src/main/java/im/vector/app/core/files/FileSaver.kt
+++ b/vector/src/main/java/im/vector/app/core/files/FileSaver.kt
@@ -16,6 +16,7 @@
package im.vector.app.core.files
+import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.ContentValues
import android.content.Context
@@ -52,6 +53,7 @@ fun writeToFile(data: ByteArray, file: File) {
}
}
+@SuppressLint("Recycle")
fun addEntryToDownloadManager(
context: Context,
file: File,
diff --git a/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt b/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt
index 4cd7da2a4f..794d5c7886 100644
--- a/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt
@@ -83,6 +83,7 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
// ignore
return
}
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
}
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 0b8d6698d2..4e5116eda9 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -56,6 +56,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.dialogs.DialogLocker
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
+import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.error.fatalError
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.observeNotNull
@@ -105,7 +106,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
protected var analyticsScreenName: MobileScreen.ScreenName? = null
- protected lateinit var analyticsTracker: AnalyticsTracker
+ @Inject lateinit var analyticsTracker: AnalyticsTracker
/* ==========================================================================================
* View
@@ -149,27 +150,23 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
* ========================================================================================== */
private lateinit var configurationViewModel: ConfigurationViewModel
- private lateinit var sessionListener: SessionListener
- protected lateinit var bugReporter: BugReporter
- private lateinit var pinLocker: PinLocker
+ @Inject lateinit var sessionListener: SessionListener
+ @Inject lateinit var bugReporter: BugReporter
+ @Inject lateinit var pinLocker: PinLocker
@Inject lateinit var rageShake: RageShake
@Inject lateinit var buildMeta: BuildMeta
@Inject lateinit var fontScalePreferences: FontScalePreferences
@Inject lateinit var vectorLocale: VectorLocaleProvider
+ @Inject lateinit var vectorFeatures: VectorFeatures
+ @Inject lateinit var navigator: Navigator
+ @Inject lateinit var activeSessionHolder: ActiveSessionHolder
+ @Inject lateinit var vectorPreferences: VectorPreferences
+ @Inject lateinit var errorFormatter: ErrorFormatter
// For debug only
@Inject lateinit var debugReceiver: DebugReceiver
- @Inject
- lateinit var vectorFeatures: VectorFeatures
-
- lateinit var navigator: Navigator
- private set
-
- private lateinit var activeSessionHolder: ActiveSessionHolder
- private lateinit var vectorPreferences: VectorPreferences
-
// Filter for multiple invalid token error
private var mainActivityStarted = false
@@ -205,7 +202,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("onCreate Activity ${javaClass.simpleName}")
- val singletonEntryPoint = singletonEntryPoint()
val activityEntryPoint = EntryPointAccessors.fromActivity(this, ActivityEntryPoint::class.java)
ThemeUtils.setActivityTheme(this, getOtherThemes())
viewModelFactory = activityEntryPoint.viewModelFactory()
@@ -213,12 +209,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
addOnMultiWindowModeChangedListener(onMultiWindowModeChangedListener)
setupMenu()
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
- bugReporter = singletonEntryPoint.bugReporter()
- pinLocker = singletonEntryPoint.pinLocker()
- analyticsTracker = singletonEntryPoint.analyticsTracker()
- navigator = singletonEntryPoint.navigator()
- activeSessionHolder = singletonEntryPoint.activeSessionHolder()
- vectorPreferences = singletonEntryPoint.vectorPreferences()
configurationViewModel.activityRestarter.observe(this) {
if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed
@@ -230,7 +220,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
navigator.openPinCode(this, pinStartForActivityResult, PinMode.AUTH)
}
}
- sessionListener = singletonEntryPoint.sessionListener()
sessionListener.globalErrorLiveData.observeEvent(this) {
handleGlobalError(it)
}
@@ -505,6 +494,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
private fun onBackPressed(fromToolbar: Boolean) {
val handled = recursivelyDispatchOnBackPressed(supportFragmentManager, fromToolbar)
if (!handled) {
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
}
diff --git a/vector/src/main/java/im/vector/app/core/resources/VersionCodeProvider.kt b/vector/src/main/java/im/vector/app/core/resources/VersionCodeProvider.kt
index 1e136bbcc2..edb99d472f 100644
--- a/vector/src/main/java/im/vector/app/core/resources/VersionCodeProvider.kt
+++ b/vector/src/main/java/im/vector/app/core/resources/VersionCodeProvider.kt
@@ -19,6 +19,7 @@ package im.vector.app.core.resources
import android.content.Context
import android.os.Build
import androidx.annotation.NonNull
+import org.matrix.android.sdk.api.util.getPackageInfoCompat
import javax.inject.Inject
class VersionCodeProvider @Inject constructor(private val context: Context) {
@@ -28,7 +29,7 @@ class VersionCodeProvider @Inject constructor(private val context: Context) {
*/
@NonNull
fun getVersionCode(): Long {
- val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode
diff --git a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt
index 829fec1652..4925adf69d 100644
--- a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt
+++ b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt
@@ -23,6 +23,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import java.lang.ref.WeakReference
class BluetoothHeadsetReceiver : BroadcastReceiver() {
@@ -59,7 +60,7 @@ class BluetoothHeadsetReceiver : BroadcastReceiver() {
else -> return // ignore intermediate states
}
- val device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
+ val device = intent.getParcelableExtraCompat(BluetoothDevice.EXTRA_DEVICE)
val deviceName = device?.name
when (device?.bluetoothClass?.deviceClass) {
BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE,
diff --git a/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt b/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt
index 7a078ce1c8..85ea7f1a1b 100644
--- a/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt
+++ b/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt
@@ -39,6 +39,8 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.popup.IncomingCallAlert
import im.vector.app.features.popup.PopupAlertManager
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
+import im.vector.lib.core.utils.compat.getSerializableExtraCompat
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.matrix.android.sdk.api.util.MatrixItem
@@ -71,7 +73,7 @@ class CallAndroidService : VectorAndroidService() {
private var mediaSession: MediaSessionCompat? = null
private val mediaSessionButtonCallback = object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
- val keyEvent = mediaButtonEvent?.getParcelableExtra(Intent.EXTRA_KEY_EVENT) ?: return false
+ val keyEvent = mediaButtonEvent?.getParcelableExtraCompat(Intent.EXTRA_KEY_EVENT) ?: return false
if (keyEvent.keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
callManager.headSetButtonTapped()
return true
@@ -158,7 +160,7 @@ class CallAndroidService : VectorAndroidService() {
val incomingCallAlert = IncomingCallAlert(callId,
shouldBeDisplayedIn = { activity ->
if (activity is VectorCallActivity) {
- activity.intent.getParcelableExtra(Mavericks.KEY_ARG)?.callId != call.callId
+ activity.intent.getParcelableExtraCompat(Mavericks.KEY_ARG)?.callId != call.callId
} else true
}
).apply {
@@ -188,7 +190,7 @@ class CallAndroidService : VectorAndroidService() {
private fun handleCallTerminated(intent: Intent) {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
- val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason
+ val endCallReason = intent.getSerializableExtraCompat(EXTRA_END_CALL_REASON)
val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
alertManager.cancelAlert(callId)
val terminatedCall = knownCalls.remove(callId)
@@ -202,7 +204,7 @@ class CallAndroidService : VectorAndroidService() {
startForeground(notificationId, notification)
if (knownCalls.isEmpty()) {
Timber.tag(loggerTag.value).v("No more call, stop the service")
- stopForeground(true)
+ stopForegroundCompat()
mediaSession?.isActive = false
myStopSelf()
}
diff --git a/vector/src/main/java/im/vector/app/core/services/VectorAndroidService.kt b/vector/src/main/java/im/vector/app/core/services/VectorAndroidService.kt
index f30a74e9de..04fc746b14 100644
--- a/vector/src/main/java/im/vector/app/core/services/VectorAndroidService.kt
+++ b/vector/src/main/java/im/vector/app/core/services/VectorAndroidService.kt
@@ -18,6 +18,7 @@ package im.vector.app.core.services
import android.app.Service
import android.content.Intent
+import android.os.Build
import android.os.IBinder
import timber.log.Timber
@@ -55,4 +56,13 @@ abstract class VectorAndroidService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
+
+ protected fun stopForegroundCompat() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ } else {
+ @Suppress("DEPRECATION")
+ stopForeground(true)
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt b/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
index dfcb92af24..a5e1fe68bd 100644
--- a/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
+++ b/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
@@ -21,6 +21,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
import im.vector.app.features.call.webrtc.WebRtcCallManager
+import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.sync.FilterService
import timber.log.Timber
@@ -30,6 +31,7 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
@ApplicationContext private val context: Context,
private val webRtcCallManager: WebRtcCallManager,
private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
+ private val vectorPreferences: VectorPreferences,
) {
suspend fun execute(session: Session, startSyncing: Boolean = true) {
@@ -41,6 +43,8 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
}
session.pushersService().refreshPushers()
webRtcCallManager.checkForProtocolsSupportIfNeeded()
- updateMatrixClientInfoUseCase.execute(session)
+ if (vectorPreferences.isClientInfoRecordingEnabled()) {
+ updateMatrixClientInfoUseCase.execute(session)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteMatrixClientInfoUseCase.kt b/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteMatrixClientInfoUseCase.kt
new file mode 100644
index 0000000000..bab3af9af5
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/session/clientinfo/DeleteMatrixClientInfoUseCase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.session.clientinfo
+
+import im.vector.app.core.di.ActiveSessionHolder
+import timber.log.Timber
+import javax.inject.Inject
+
+/**
+ * This use case delete the account data event containing extended client info.
+ */
+class DeleteMatrixClientInfoUseCase @Inject constructor(
+ private val activeSessionHolder: ActiveSessionHolder,
+ private val setMatrixClientInfoUseCase: SetMatrixClientInfoUseCase,
+) {
+
+ suspend fun execute(): Result = runCatching {
+ Timber.d("deleting recorded client info")
+ val session = activeSessionHolder.getActiveSession()
+ val emptyClientInfo = MatrixClientInfoContent(
+ name = "",
+ version = "",
+ url = "",
+ )
+ return setMatrixClientInfoUseCase.execute(session, emptyClientInfo)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
index 1990859668..6327daec86 100644
--- a/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
@@ -45,8 +45,8 @@ class ShieldImageView @JvmOverloads constructor(
RoomEncryptionTrustLevel.Default -> {
contentDescription = context.getString(R.string.a11y_trust_level_default)
setImageResource(
- if (borderLess) R.drawable.ic_shield_black_no_border
- else R.drawable.ic_shield_black
+ if (borderLess) R.drawable.ic_shield_unknown_no_border
+ else R.drawable.ic_shield_unknown
)
}
RoomEncryptionTrustLevel.Warning -> {
@@ -137,7 +137,7 @@ class ShieldImageView @JvmOverloads constructor(
@DrawableRes
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
return when (this) {
- RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
+ RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_unknown
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
index 915a8d4d0d..23641bd170 100644
--- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
@@ -16,6 +16,7 @@
package im.vector.app.core.utils
+import android.annotation.SuppressLint
import android.app.Activity
import android.app.DownloadManager
import android.content.ActivityNotFoundException
@@ -256,6 +257,7 @@ private fun appendTimeToFilename(name: String): String {
return """${filename}_$dateExtension.$fileExtension"""
}
+@SuppressLint("Recycle")
suspend fun saveMedia(
context: Context,
file: File,
diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
index cde4fe2a35..20eda102e2 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
@@ -34,6 +34,7 @@ import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils
+import org.matrix.android.sdk.api.util.getApplicationInfoCompat
/**
* Tells if the application ignores battery optimizations.
@@ -63,7 +64,7 @@ fun Context.isAnimationEnabled(): Boolean {
*/
fun Context.getApplicationLabel(packageName: String): String {
return try {
- val ai = packageManager.getApplicationInfo(packageName, 0)
+ val ai = packageManager.getApplicationInfoCompat(packageName, 0)
packageManager.getApplicationLabel(ai).toString()
} catch (e: PackageManager.NameNotFoundException) {
packageName
diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
index 1b3a9eb142..c197cfccf3 100644
--- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
@@ -30,8 +30,6 @@ import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.extensions.vectorStore
import im.vector.app.core.platform.VectorBaseActivity
@@ -42,13 +40,11 @@ import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.ShortcutsHandler
import im.vector.app.features.notifications.NotificationDrawerManager
-import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.UnlockedActivity
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.session.VectorSessionStore
-import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.start.StartAppAction
import im.vector.app.features.start.StartAppAndroidService
@@ -57,6 +53,7 @@ import im.vector.app.features.start.StartAppViewModel
import im.vector.app.features.start.StartAppViewState
import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.ui.UiStateRepository
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -130,13 +127,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
private lateinit var args: MainActivityArgs
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
- @Inject lateinit var sessionHolder: ActiveSessionHolder
- @Inject lateinit var errorFormatter: ErrorFormatter
- @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var uiStateRepository: UiStateRepository
@Inject lateinit var shortcutsHandler: ShortcutsHandler
@Inject lateinit var pinCodeHelper: PinCodeHelper
- @Inject lateinit var pinLocker: PinLocker
@Inject lateinit var popupAlertManager: PopupAlertManager
@Inject lateinit var vectorAnalytics: VectorAnalytics
@Inject lateinit var lockScreenKeyRepository: LockScreenKeyRepository
@@ -181,7 +174,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
private fun handleAppStarted() {
if (intent.hasExtra(EXTRA_NEXT_INTENT)) {
// Start the next Activity
- val nextIntent = intent.getParcelableExtra(EXTRA_NEXT_INTENT)
+ val nextIntent = intent.getParcelableExtraCompat(EXTRA_NEXT_INTENT)
startIntentAndFinish(nextIntent)
} else if (intent.hasExtra(EXTRA_INIT_SESSION)) {
setResult(RESULT_OK)
@@ -218,7 +211,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
}
private fun parseArgs(): MainActivityArgs {
- val argsFromIntent: MainActivityArgs? = intent.getParcelableExtra(EXTRA_ARGS)
+ val argsFromIntent: MainActivityArgs? = intent.getParcelableExtraCompat(EXTRA_ARGS)
Timber.w("Starting MainActivity with $argsFromIntent")
return MainActivityArgs(
@@ -231,7 +224,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
}
private fun doCleanUp() {
- val session = sessionHolder.getSafeActiveSession()
+ val session = activeSessionHolder.getSafeActiveSession()
if (session == null) {
startNextActivityAndFinish()
return
@@ -243,7 +236,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
lifecycleScope.launch {
// Just do the local cleanup
Timber.w("Account deactivated, start app")
- sessionHolder.clearActiveSession()
+ activeSessionHolder.clearActiveSession()
doLocalCleanup(clearPreferences = true, onboardingStore)
startNextActivityAndFinish()
}
@@ -257,7 +250,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
return@launch
}
Timber.w("SIGN_OUT: success, start app")
- sessionHolder.clearActiveSession()
+ activeSessionHolder.clearActiveSession()
doLocalCleanup(clearPreferences = true, onboardingStore)
startNextActivityAndFinish()
}
@@ -329,10 +322,10 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
args.isUserLoggedOut ->
// the homeserver has invalidated the token (password changed, device deleted, other security reasons)
SignedOutActivity.newIntent(this)
- sessionHolder.hasActiveSession() ->
+ activeSessionHolder.hasActiveSession() ->
// We have a session.
// Check it can be opened
- if (sessionHolder.getActiveSession().isOpenable) {
+ if (activeSessionHolder.getActiveSession().isOpenable) {
HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true)
} else {
// The token is still invalid
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index f59f5afdea..95cf272abd 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -40,6 +40,9 @@ interface VectorFeatures {
* use [VectorPreferences.isNewAppLayoutEnabled] instead.
*/
fun isNewAppLayoutFeatureEnabled(): Boolean
+ fun isQrCodeLoginEnabled(): Boolean
+ fun isQrCodeLoginForAllServers(): Boolean
+ fun isReciprocateQrCodeLogin(): Boolean
fun isVoiceBroadcastEnabled(): Boolean
}
@@ -56,5 +59,8 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
override fun forceUsageOfOpusEncoder(): Boolean = false
override fun isNewAppLayoutFeatureEnabled(): Boolean = true
- override fun isVoiceBroadcastEnabled(): Boolean = false
+ override fun isQrCodeLoginEnabled(): Boolean = true
+ override fun isQrCodeLoginForAllServers(): Boolean = false
+ override fun isReciprocateQrCodeLogin(): Boolean = false
+ override fun isVoiceBroadcastEnabled(): Boolean = true
}
diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt
index 646758654f..1a8e10d102 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsHelper.kt
@@ -26,6 +26,8 @@ import im.vector.app.core.dialogs.PhotoOrVideoDialog
import im.vector.app.core.platform.Restorable
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.settings.VectorPreferences
+import im.vector.lib.core.utils.compat.getParcelableCompat
+import im.vector.lib.core.utils.compat.getSerializableCompat
import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import timber.log.Timber
@@ -66,8 +68,8 @@ class AttachmentsHelper(
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
- captureUri = savedInstanceState?.getParcelable(CAPTURE_PATH_KEY) as? Uri
- pendingType = savedInstanceState?.getSerializable(PENDING_TYPE_KEY) as? AttachmentTypeSelectorView.Type
+ captureUri = savedInstanceState?.getParcelableCompat(CAPTURE_PATH_KEY)
+ pendingType = savedInstanceState?.getSerializableCompat(PENDING_TYPE_KEY)
}
// Public Methods
diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt
index 7ddba0d229..4a965022b8 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt
@@ -24,6 +24,8 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.themes.ActivityOtherThemes
+import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat
+import im.vector.lib.core.utils.compat.getParcelableCompat
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
@AndroidEntryPoint
@@ -41,7 +43,7 @@ class AttachmentsPreviewActivity : VectorBaseActivity() {
}
fun getOutput(intent: Intent): List {
- return intent.getParcelableArrayListExtra(ATTACHMENTS_PREVIEW_RESULT).orEmpty()
+ return intent.getParcelableArrayListExtraCompat(ATTACHMENTS_PREVIEW_RESULT).orEmpty()
}
fun getKeepOriginalSize(intent: Intent): Boolean {
@@ -57,7 +59,7 @@ class AttachmentsPreviewActivity : VectorBaseActivity() {
override fun initUiAndData() {
if (isFirstCreation()) {
- val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelable(EXTRA_FRAGMENT_ARGS) ?: return
+ val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelableCompat(EXTRA_FRAGMENT_ARGS) ?: return
addFragment(views.simpleFragmentContainer, AttachmentsPreviewFragment::class.java, fragmentArgs)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
index aa46feeea6..a96097a830 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
@@ -69,6 +69,7 @@ import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import io.github.hyuwah.draggableviewlib.DraggableView
import io.github.hyuwah.draggableviewlib.setupDraggable
import kotlinx.parcelize.Parcelize
@@ -178,7 +179,7 @@ class VectorCallActivity :
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) }
- ?.let { intent.getParcelableExtra(Mavericks.KEY_ARG) }
+ ?.let { intent.getParcelableExtraCompat(Mavericks.KEY_ARG) }
?.let {
callViewModel.handle(VectorCallViewActions.SwitchCall(it))
}
@@ -193,6 +194,7 @@ class VectorCallActivity :
override fun onBackPressed() {
if (!enterPictureInPictureIfRequired()) {
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
}
@@ -230,6 +232,7 @@ class VectorCallActivity :
}
android.R.id.home -> {
// We check here as we want PiP in some cases
+ @Suppress("DEPRECATION")
onBackPressed()
true
}
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
index 5bf05d353c..4d107ac414 100644
--- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
@@ -38,6 +38,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityJitsiBinding
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.parcelize.Parcelize
import org.jitsi.meet.sdk.JitsiMeet
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
@@ -200,7 +201,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee
// Is it a switch to another conf?
intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) }
- ?.let { intent.getParcelableExtra(Mavericks.KEY_ARG) }
+ ?.let { intent.getParcelableExtraCompat(Mavericks.KEY_ARG) }
?.let {
jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(it, true))
}
diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt
index 251195ddc4..1a7571598e 100644
--- a/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/dialpad/PstnDialActivity.kt
@@ -23,7 +23,6 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.features.call.webrtc.WebRtcCallManager
@@ -39,7 +38,6 @@ class PstnDialActivity : SimpleFragmentActivity() {
@Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var directRoomHelper: DirectRoomHelper
@Inject lateinit var session: Session
- @Inject lateinit var errorFormatter: ErrorFormatter
private var progress: AppCompatDialog? = null
diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
index ccda4871ef..ae168c5f92 100644
--- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
@@ -25,11 +25,10 @@ import com.airbnb.mvrx.viewModel
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityCallTransferBinding
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize
-import javax.inject.Inject
@Parcelize
data class CallTransferArgs(val callId: String) : Parcelable
@@ -39,8 +38,6 @@ private const val USER_LIST_FRAGMENT_TAG = "USER_LIST_FRAGMENT_TAG"
@AndroidEntryPoint
class CallTransferActivity : VectorBaseActivity() {
- @Inject lateinit var errorFormatter: ErrorFormatter
-
private lateinit var sectionsPagerAdapter: CallTransferPagerAdapter
private val callTransferViewModel: CallTransferViewModel by viewModel()
@@ -112,7 +109,7 @@ class CallTransferActivity : VectorBaseActivity() {
}
fun getCallTransferResult(intent: Intent?): CallTransferResult? {
- return intent?.extras?.getParcelable(EXTRA_TRANSFER_RESULT)
+ return intent?.extras?.getParcelableCompat(EXTRA_TRANSFER_RESULT)
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 707b78d328..acaf24dca7 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -32,7 +32,6 @@ import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.platform.SimpleFragmentActivity
@@ -58,7 +57,6 @@ import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection
-import javax.inject.Inject
@AndroidEntryPoint
class CreateDirectRoomActivity : SimpleFragmentActivity() {
@@ -67,7 +65,6 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
private val qrViewModel: QrCodeScannerViewModel by viewModel()
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
- @Inject lateinit var errorFormatter: ErrorFormatter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -78,6 +75,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
sharedActionViewModel
.stream()
.onEach { action ->
+ @Suppress("DEPRECATION")
when (action) {
UserListSharedAction.Close -> finish()
UserListSharedAction.GoBack -> onBackPressed()
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
index 4b3a657740..556d9bcec3 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
@@ -47,10 +47,12 @@ class KeysExporter @Inject constructor(
when {
output == null -> throw IllegalStateException("Exported file not found")
output.statSize != expectedSize -> {
- throw UnexpectedExportKeysFileSizeException(
+ val exception = UnexpectedExportKeysFileSizeException(
expectedFileSize = expectedSize,
actualFileSize = output.statSize
)
+ output.close()
+ throw exception
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
index c6e86f6f6b..4adccf3953 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
@@ -22,7 +22,6 @@ import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.registerStartForActivityResult
@@ -32,7 +31,6 @@ import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
import im.vector.app.features.workers.signout.ServerBackupStatusAction
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
-import javax.inject.Inject
@AndroidEntryPoint
class KeysBackupRestoreActivity : SimpleFragmentActivity() {
@@ -52,11 +50,10 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
override fun onBackPressed() {
hideWaitingView()
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
- @Inject lateinit var activeSessionHolder: ActiveSessionHolder
-
override fun initUiAndData() {
super.initUiAndData()
viewModel = viewModelProvider.get(KeysBackupRestoreSharedViewModel::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt
index aabd05d913..32826d013f 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt
@@ -114,6 +114,7 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
finish()
return
}
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
index 077bcc2cf3..4473d54765 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt
@@ -25,7 +25,6 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.queryExportKeys
@@ -45,7 +44,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
private lateinit var viewModel: KeysBackupSetupSharedViewModel
@Inject lateinit var keysExporter: KeysExporter
- @Inject lateinit var activeSessionHolder: ActiveSessionHolder
private val session by lazy {
activeSessionHolder.getActiveSession()
@@ -183,6 +181,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
}
.show()
} else {
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
index b39992256d..d393636a8e 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
@@ -30,13 +30,11 @@ import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.features.crypto.recover.SetupMode
import kotlinx.parcelize.Parcelize
-import javax.inject.Inject
import kotlin.reflect.KClass
@AndroidEntryPoint
@@ -54,7 +52,6 @@ class SharedSecureStorageActivity :
) : Parcelable
private val viewModel: SharedSecureStorageViewModel by viewModel()
- @Inject lateinit var errorFormatter: ErrorFormatter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt
index 3406a86d1e..3a5c7e7eb8 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt
@@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
+import im.vector.lib.core.utils.compat.getParcelableCompat
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@@ -147,7 +148,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
R.drawable.ic_shield_black,
shouldBeDisplayedIn = { activity ->
if (activity is RoomDetailActivity) {
- activity.intent?.extras?.getParcelable(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let {
+ activity.intent?.extras?.getParcelableCompat(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let {
it.roomId != pr.roomId
} ?: true
} else true
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt
index e5402424d0..dc538597db 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt
@@ -27,6 +27,7 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@@ -49,7 +50,7 @@ class VerificationQRWaitingFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
- (arguments?.getParcelable(Mavericks.KEY_ARG) as? Args)?.let {
+ (arguments?.getParcelableCompat(Mavericks.KEY_ARG))?.let {
controller.update(it)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 4402b5a053..2df94fecad 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -37,7 +37,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.SpaceStateHandler
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.replaceFragment
@@ -48,6 +47,7 @@ import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity
@@ -76,7 +76,6 @@ import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
-import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.spaces.SpacePreviewActivity
@@ -86,6 +85,7 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.usercode.UserCodeActivity
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -127,11 +127,9 @@ class HomeActivity :
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
- @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var pushersManager: PushersManager
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
- @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var popupAlertManager: PopupAlertManager
@Inject lateinit var shortcutsHandler: ShortcutsHandler
@Inject lateinit var permalinkHandler: PermalinkHandler
@@ -142,6 +140,7 @@ class HomeActivity :
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var nightlyProxy: NightlyProxy
@Inject lateinit var disclaimerDialog: DisclaimerDialog
+ @Inject lateinit var notificationPermissionManager: NotificationPermissionManager
private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed
@@ -171,6 +170,10 @@ class HomeActivity :
}
}
+ private val postPermissionLauncher = registerForPermissionsResult { _, _ ->
+ // Nothing to do with the result.
+ }
+
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
if (f is MatrixToBottomSheet) {
@@ -211,7 +214,7 @@ class HomeActivity :
fcmHelper.ensureFcmTokenIsRetrieved(
this,
pushersManager,
- vectorPreferences.areNotificationEnabledForDevice()
+ homeActivityViewModel.shouldAddHttpPusher()
)
}
}
@@ -246,7 +249,7 @@ class HomeActivity :
}
.launchIn(lifecycleScope)
- val args = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ val args = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
if (args?.clearNotification == true) {
notificationDrawerManager.clearAllEvents()
@@ -273,6 +276,7 @@ class HomeActivity :
}
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn()
+ HomeActivityViewEvents.ShowNotificationDialog -> handleShowNotificationDialog()
HomeActivityViewEvents.ShowReleaseNotes -> handleShowReleaseNotes()
HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
@@ -288,6 +292,10 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
}
+ private fun handleShowNotificationDialog() {
+ notificationPermissionManager.eventuallyRequestPermission(this, postPermissionLauncher)
+ }
+
private fun handleShowReleaseNotes() {
startActivity(Intent(this, ReleaseNotesActivity::class.java))
}
@@ -328,7 +336,7 @@ class HomeActivity :
private fun migrateThreadsIfNeeded(checkSession: Boolean) {
if (checkSession) {
// We should check session to ensure we will only clear cache if needed
- val args = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ val args = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
if (args?.hasExistingSession == true) {
// existingSession --> Will be true only if we came from an existing active session
Timber.i("----> Migrating threads from an existing session..")
@@ -547,7 +555,7 @@ class HomeActivity :
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
- val parcelableExtra = intent?.getParcelableExtra(Mavericks.KEY_ARG)
+ val parcelableExtra = intent?.getParcelableExtraCompat(Mavericks.KEY_ARG)
if (parcelableExtra?.clearNotification == true) {
notificationDrawerManager.clearAllEvents()
}
@@ -676,7 +684,10 @@ class HomeActivity :
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
views.drawerLayout.closeDrawer(GravityCompat.START)
} else {
- validateBackPressed { super.onBackPressed() }
+ validateBackPressed {
+ @Suppress("DEPRECATION")
+ super.onBackPressed()
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
index 4147cf7186..e548fdb2f3 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
@@ -31,6 +31,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents
+ object ShowNotificationDialog : HomeActivityViewEvents
object ShowReleaseNotes : HomeActivityViewEvents
object NotifyUserForThreadsMigration : HomeActivityViewEvents
data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index 85a8fb0c7f..61a8e5b79e 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -42,6 +42,7 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired
import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
@@ -101,7 +102,7 @@ class HomeActivityViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
val activity: HomeActivity = viewModelContext.activity()
- val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG)
+ val args: HomeActivityArgs? = activity.intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) }
?: super.initialState(viewModelContext)
}
@@ -142,6 +143,14 @@ class HomeActivityViewModel @AssistedInject constructor(
}
}
+ fun shouldAddHttpPusher() = if (vectorPreferences.areNotificationEnabledForDevice()) {
+ val currentSession = activeSessionHolder.getActiveSession()
+ val currentPushers = currentSession.pushersService().getPushers()
+ currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId }
+ } else {
+ false
+ }
+
fun observeLocalNotificationsSilenced() {
val currentSession = activeSessionHolder.getActiveSession()
val deviceId = currentSession.cryptoService().getMyDevice().deviceId
@@ -160,6 +169,8 @@ class HomeActivityViewModel @AssistedInject constructor(
.onEach { didAskUser ->
if (!didAskUser) {
_viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn)
+ } else {
+ _viewEvents.post(HomeActivityViewEvents.ShowNotificationDialog)
}
}
.launchIn(viewModelScope)
@@ -179,6 +190,8 @@ class HomeActivityViewModel @AssistedInject constructor(
// do nothing
}
}
+ } else {
+ _viewEvents.post(HomeActivityViewEvents.ShowNotificationDialog)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
index 106fbc7281..22d9709229 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
@@ -32,6 +32,7 @@ import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentHomeDrawerBinding
import im.vector.app.features.analytics.plan.MobileScreen
+import im.vector.app.features.permalink.PermalinkFactory
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.spaces.SpaceListFragment
@@ -49,6 +50,7 @@ class HomeDrawerFragment :
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var buildMeta: BuildMeta
+ @Inject lateinit var permalinkFactory: PermalinkFactory
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
@@ -101,7 +103,7 @@ class HomeDrawerFragment :
}
views.homeDrawerInviteFriendButton.debouncedClicks {
- session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
+ permalinkFactory.createPermalinkOfCurrentUser()?.let { permalink ->
analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
val text = getString(R.string.invite_friends_text, permalink)
diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
index 66bb9ef876..5956646eab 100644
--- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
@@ -61,7 +61,6 @@ import im.vector.app.features.workers.signout.ServerBackupStatusAction
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
@@ -80,7 +79,6 @@ class NewHomeDetailFragment :
@Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var spaceStateHandler: SpaceStateHandler
- @Inject lateinit var session: Session
@Inject lateinit var buildMeta: BuildMeta
private val viewModel: HomeDetailViewModel by fragmentViewModel()
diff --git a/vector/src/main/java/im/vector/app/features/home/NotificationPermissionManager.kt b/vector/src/main/java/im/vector/app/features/home/NotificationPermissionManager.kt
new file mode 100644
index 0000000000..33ba1be02b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/NotificationPermissionManager.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home
+
+import android.Manifest
+import android.app.Activity
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.activity.result.ActivityResultLauncher
+import androidx.annotation.ChecksSdkIntAtLeast
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import im.vector.app.R
+import im.vector.app.core.utils.checkPermissions
+import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
+import javax.inject.Inject
+
+class NotificationPermissionManager @Inject constructor(
+ private val sdkIntProvider: BuildVersionSdkIntProvider,
+ private val vectorPreferences: VectorPreferences,
+) {
+
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
+ fun isPermissionGranted(activity: Activity): Boolean {
+ return if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+ ContextCompat.checkSelfPermission(
+ activity,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ } else {
+ // No notification permission management before API 33.
+ true
+ }
+ }
+
+ fun eventuallyRequestPermission(
+ activity: Activity,
+ requestPermissionLauncher: ActivityResultLauncher>,
+ showRationale: Boolean = true,
+ ignorePreference: Boolean = false,
+ ) {
+ if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return
+ if (!vectorPreferences.areNotificationEnabledForDevice() && !ignorePreference) return
+ checkPermissions(
+ listOf(Manifest.permission.POST_NOTIFICATIONS),
+ activity,
+ activityResultLauncher = requestPermissionLauncher,
+ if (showRationale) R.string.permissions_rationale_msg_notification else 0
+ )
+ }
+
+ @RequiresApi(33)
+ fun askPermission(requestPermissionLauncher: ActivityResultLauncher>) {
+ requestPermissionLauncher.launch(
+ arrayOf(Manifest.permission.POST_NOTIFICATIONS)
+ )
+ }
+
+ fun eventuallyRevokePermission(
+ activity: Activity,
+ ) {
+ if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return
+ activity.revokeSelfPermissionOnKill(Manifest.permission.POST_NOTIFICATIONS)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
index 3e828f62b7..f773671694 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
@@ -121,9 +121,17 @@ sealed class RoomDetailAction : VectorViewModelAction {
object OpenElementCallWidget : RoomDetailAction()
sealed class VoiceBroadcastAction : RoomDetailAction() {
- object Start : VoiceBroadcastAction()
- object Pause : VoiceBroadcastAction()
- object Resume : VoiceBroadcastAction()
- object Stop : VoiceBroadcastAction()
+ sealed class Recording : VoiceBroadcastAction() {
+ object Start : Recording()
+ object Pause : Recording()
+ object Resume : Recording()
+ object Stop : Recording()
+ }
+
+ sealed class Listening : VoiceBroadcastAction() {
+ data class PlayOrResume(val eventId: String) : Listening()
+ object Pause : Listening()
+ object Stop : Listening()
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
index 75cda67ce6..ecbea133df 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
@@ -46,6 +46,7 @@ import im.vector.app.features.navigation.Navigator
import im.vector.app.features.room.RequireActiveMembershipAction
import im.vector.app.features.room.RequireActiveMembershipViewEvents
import im.vector.app.features.room.RequireActiveMembershipViewModel
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@@ -99,7 +100,7 @@ class RoomDetailActivity :
super.onCreate(savedInstanceState)
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
waitingView = views.waitingView.waitingView
- val timelineArgs: TimelineArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) ?: return
+ val timelineArgs: TimelineArgs = intent?.extras?.getParcelableCompat(EXTRA_ROOM_DETAIL_ARGS) ?: return
intent.putExtra(Mavericks.KEY_ARG, timelineArgs)
currentRoomId = timelineArgs.roomId
@@ -177,6 +178,7 @@ class RoomDetailActivity :
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
views.drawerLayout.closeDrawer(GravityCompat.START)
} else {
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
}
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 c34d60de71..9d50cdb070 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
@@ -168,6 +168,7 @@ import im.vector.app.features.media.VideoContentRenderer
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.permalink.NavigationInterceptor
+import im.vector.app.features.permalink.PermalinkFactory
import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.poll.PollMode
import im.vector.app.features.reactions.EmojiReactionPickerActivity
@@ -247,6 +248,7 @@ class TimelineFragment :
@Inject lateinit var clock: Clock
@Inject lateinit var vectorFeatures: VectorFeatures
@Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory
+ @Inject lateinit var permalinkFactory: PermalinkFactory
companion object {
const val MAX_TYPING_MESSAGE_USERS_COUNT = 4
@@ -772,7 +774,7 @@ class TimelineFragment :
}
// We use a custom layout for this menu item, so we need to set a ClickListener
menu.findItem(R.id.open_matrix_apps)?.let { menuItem ->
- menuItem.actionView.debouncedClicks {
+ menuItem.actionView?.debouncedClicks {
handleMenuItemSelected(menuItem)
}
}
@@ -783,7 +785,7 @@ class TimelineFragment :
// Custom thread notification menu item
menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem ->
- menuItem.actionView.debouncedClicks {
+ menuItem.actionView?.debouncedClicks {
handleMenuItemSelected(menuItem)
}
}
@@ -812,16 +814,16 @@ class TimelineFragment :
// icon should be default color no badge
val actionView = matrixAppsMenuItem.actionView
actionView
- .findViewById(R.id.action_view_icon_image)
- .setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary))
- actionView.findViewById(R.id.cart_badge).isVisible = false
+ ?.findViewById(R.id.action_view_icon_image)
+ ?.setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary))
+ actionView?.findViewById(R.id.cart_badge)?.isVisible = false
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
} else {
val actionView = matrixAppsMenuItem.actionView
actionView
- .findViewById(R.id.action_view_icon_image)
- .setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary))
- actionView.findViewById(R.id.cart_badge).setTextOrHide("$widgetsCount")
+ ?.findViewById(R.id.action_view_icon_image)
+ ?.setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary))
+ actionView?.findViewById(R.id.cart_badge)?.setTextOrHide("$widgetsCount")
@Suppress("AlwaysShowAction")
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
}
@@ -867,7 +869,7 @@ class TimelineFragment :
}
R.id.menu_thread_timeline_copy_link -> {
getRootThreadEventId()?.let {
- val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it)
+ val permalink = permalinkFactory.createPermalink(timelineArgs.roomId, it)
copyToClipboard(requireContext(), permalink, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard))
}
@@ -879,7 +881,7 @@ class TimelineFragment :
}
R.id.menu_thread_timeline_share -> {
getRootThreadEventId()?.let {
- val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it)
+ val permalink = permalinkFactory.createPermalink(timelineArgs.roomId, it)
shareText(requireContext(), permalink)
}
true
@@ -893,7 +895,7 @@ class TimelineFragment :
*/
private fun updateMenuThreadNotificationBadge(menu: Menu, state: RoomDetailViewState) {
val menuThreadList = menu.findItem(R.id.menu_timeline_thread_list).actionView
- val badgeFrameLayout = menuThreadList.findViewById(R.id.threadNotificationBadgeFrameLayout)
+ val badgeFrameLayout = menuThreadList?.findViewById(R.id.threadNotificationBadgeFrameLayout) ?: return
val badgeTextView = menuThreadList.findViewById(R.id.threadNotificationBadgeTextView)
val unreadThreadMessages = state.threadNotificationBadgeState.numberOfLocalUnreadThreads
@@ -1788,7 +1790,7 @@ class TimelineFragment :
}
}
is EventSharedAction.CopyPermalink -> {
- val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, action.eventId)
+ val permalink = permalinkFactory.createPermalink(timelineArgs.roomId, action.eventId)
copyToClipboard(requireContext(), permalink, false)
showSnackWithMessage(getString(R.string.copied_to_clipboard))
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 511fd597fe..82ad96d645 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
@@ -604,10 +604,13 @@ class TimelineViewModel @AssistedInject constructor(
if (room == null) return
viewModelScope.launch {
when (action) {
- RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId)
- RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
- RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
- RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
+ RoomDetailAction.VoiceBroadcastAction.Recording.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId)
+ RoomDetailAction.VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
+ RoomDetailAction.VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
+ RoomDetailAction.VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
+ is RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.eventId)
+ RoomDetailAction.VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
+ RoomDetailAction.VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
index a5e899c672..bede02c17f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
@@ -55,8 +55,8 @@ class AudioMessageHelper @Inject constructor(
private var amplitudeTicker: CountUpTimer? = null
private var playbackTicker: CountUpTimer? = null
- fun initializeRecorder(attachmentData: ContentAttachmentData) {
- voiceRecorder.initializeRecord(attachmentData)
+ fun initializeRecorder(roomId: String, attachmentData: ContentAttachmentData) {
+ voiceRecorder.initializeRecord(roomId, attachmentData)
amplitudeList.clear()
attachmentData.waveform?.let {
amplitudeList.addAll(it)
@@ -114,6 +114,7 @@ class AudioMessageHelper @Inject constructor(
* When entering in playback mode actually.
*/
fun pauseRecording() {
+ // TODO should we pause instead of stop?
voiceRecorder.stopRecord()
stopRecordingAmplitudes()
}
@@ -221,6 +222,10 @@ class AudioMessageHelper @Inject constructor(
}
}
+ private fun resumeRecordingAmplitudes() {
+ amplitudeTicker?.resume()
+ }
+
private fun stopRecordingAmplitudes() {
amplitudeTicker?.stop()
amplitudeTicker = null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
index b3abfa480e..55ec922a57 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
@@ -227,10 +227,19 @@ class MessageComposerFragment : VectorBaseFragment(), A
override fun onPause() {
super.onPause()
- if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) {
- // we're rotating, maintain any active recordings
- } else {
- messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString()))
+ withState(messageComposerViewModel) {
+ when {
+ it.isVoiceRecording && requireActivity().isChangingConfigurations -> {
+ // we're rotating, maintain any active recordings
+ }
+ // TODO remove this when there will be a recording indicator outside of the timeline
+ // Pause voice broadcast if the timeline is not shown anymore
+ it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause)
+ else -> {
+ timelineViewModel.handle(VoiceBroadcastAction.Listening.Pause)
+ messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString()))
+ }
+ }
}
}
@@ -301,7 +310,7 @@ class MessageComposerFragment : VectorBaseFragment(), A
)
attachmentTypeSelector.setAttachmentVisibility(
AttachmentTypeSelectorView.Type.VOICE_BROADCAST,
- vectorFeatures.isVoiceBroadcastEnabled(), // TODO check user permission
+ vectorPreferences.isVoiceBroadcastEnabled(), // TODO check user permission
)
}
attachmentTypeSelector.show(composer.attachmentButton)
@@ -676,7 +685,7 @@ class MessageComposerFragment : VectorBaseFragment(), A
locationOwnerId = session.myUserId
)
}
- AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Start)
+ AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
index b877c2979b..1a9f9e6291 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.composer
+import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -40,8 +41,12 @@ import im.vector.app.features.home.room.detail.toMessageType
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
+import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
+import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
+import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
@@ -80,6 +85,7 @@ class MessageComposerViewModel @AssistedInject constructor(
private val rainbowGenerator: RainbowGenerator,
private val audioMessageHelper: AudioMessageHelper,
private val analyticsTracker: AnalyticsTracker,
+ private val voiceBroadcastHelper: VoiceBroadcastHelper,
) : VectorViewModel(initialState) {
private val room = session.getRoom(initialState.roomId)!!
@@ -90,6 +96,7 @@ class MessageComposerViewModel @AssistedInject constructor(
init {
loadDraftIfAny()
observePowerLevelAndEncryption()
+ observeVoiceBroadcast()
subscribeToStateInternal()
}
@@ -151,7 +158,8 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
- setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent())) }
+ val formatted = vectorPreferences.isRichTextEditorEnabled()
+ setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) }
}
}
@@ -181,6 +189,16 @@ class MessageComposerViewModel @AssistedInject constructor(
}
}
+ private fun observeVoiceBroadcast() {
+ room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
+ .asFlow()
+ .unwrap()
+ .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState }
+ .setOnEach {
+ copy(voiceBroadcastState = it)
+ }
+ }
+
private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
@@ -552,7 +570,7 @@ class MessageComposerViewModel @AssistedInject constructor(
state.sendMode.timelineEvent,
messageContent?.msgType ?: MessageType.MSGTYPE_TEXT,
action.text,
- (messageContent as? MessageContentWithFormattedBody)?.formattedBody,
+ action.formattedText,
action.autoMarkdown
)
} else {
@@ -942,7 +960,7 @@ class MessageComposerViewModel @AssistedInject constructor(
}
private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
- audioMessageHelper.initializeRecorder(attachmentData)
+ audioMessageHelper.initializeRecorder(room.roomId, attachmentData)
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
}
@@ -965,6 +983,8 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleEntersBackground(composerText: String) {
// Always stop all voice actions. It may be playing in timeline or active recording
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
+ // TODO remove this when there will be a listening indicator outside of the timeline
+ voiceBroadcastHelper.pausePlayback()
val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
if (isVoiceRecording) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt
index 47a7122584..0df1dbebd8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer
import com.airbnb.mvrx.MavericksState
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
+import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import kotlin.random.Random
@@ -67,6 +68,7 @@ data class MessageComposerViewState(
val startsThread: Boolean = false,
val sendMode: SendMode = SendMode.Regular("", false),
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle,
+ val voiceBroadcastState: VoiceBroadcastState? = null,
val text: CharSequence? = null,
) : MavericksState {
@@ -77,6 +79,13 @@ data class MessageComposerViewState(
is VoiceMessageRecorderView.RecordingUiState.Recording -> true
}
+ val isVoiceBroadcasting = when (voiceBroadcastState) {
+ VoiceBroadcastState.STARTED,
+ VoiceBroadcastState.PAUSED,
+ VoiceBroadcastState.RESUMED -> true
+ else -> false
+ }
+
val isVoiceMessageIdle = !isVoiceRecording
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
index 76bdcfc9a8..07b7d151ad 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
@@ -27,6 +27,7 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.toSpannable
@@ -42,7 +43,10 @@ import im.vector.app.core.animations.SimpleTransitionListener
import im.vector.app.core.extensions.setTextIfDifferent
import im.vector.app.databinding.ComposerRichTextLayoutBinding
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
+import io.element.android.wysiwyg.EditorEditText
import io.element.android.wysiwyg.InlineFormat
+import uniffi.wysiwyg_composer.ComposerAction
+import uniffi.wysiwyg_composer.MenuState
class RichTextComposerLayout @JvmOverloads constructor(
context: Context,
@@ -91,10 +95,18 @@ class RichTextComposerLayout @JvmOverloads constructor(
collapse(false)
views.composerEditText.addTextChangedListener(object : TextWatcher {
+ private var previousTextWasExpanded = false
+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
callback?.onTextChanged(s)
+
+ val isExpanded = s.lines().count() > 1
+ if (previousTextWasExpanded != isExpanded) {
+ updateTextFieldBorder(isExpanded)
+ }
+ previousTextWasExpanded = isExpanded
}
})
@@ -116,32 +128,57 @@ class RichTextComposerLayout @JvmOverloads constructor(
}
private fun setupRichTextMenu() {
- addRichTextMenuItem(R.drawable.ic_composer_bold, "Bold") {
+ addRichTextMenuItem(R.drawable.ic_composer_bold, R.string.rich_text_editor_format_bold, ComposerAction.Bold) {
views.composerEditText.toggleInlineFormat(InlineFormat.Bold)
}
- addRichTextMenuItem(R.drawable.ic_composer_italic, "Italic") {
+ addRichTextMenuItem(R.drawable.ic_composer_italic, R.string.rich_text_editor_format_italic, ComposerAction.Italic) {
views.composerEditText.toggleInlineFormat(InlineFormat.Italic)
}
- addRichTextMenuItem(R.drawable.ic_composer_underlined, "Underline") {
+ addRichTextMenuItem(R.drawable.ic_composer_underlined, R.string.rich_text_editor_format_underline, ComposerAction.Underline) {
views.composerEditText.toggleInlineFormat(InlineFormat.Underline)
}
- addRichTextMenuItem(R.drawable.ic_composer_strikethrough, "Strikethrough") {
+ addRichTextMenuItem(R.drawable.ic_composer_strikethrough, R.string.rich_text_editor_format_strikethrough, ComposerAction.StrikeThrough) {
views.composerEditText.toggleInlineFormat(InlineFormat.StrikeThrough)
}
+
+ views.composerEditText.menuStateChangedListener = EditorEditText.OnMenuStateChangedListener { state ->
+ if (state is MenuState.Update) {
+ updateMenuStateFor(ComposerAction.Bold, state)
+ updateMenuStateFor(ComposerAction.Italic, state)
+ updateMenuStateFor(ComposerAction.Underline, state)
+ updateMenuStateFor(ComposerAction.StrikeThrough, state)
+ }
+ }
}
- private fun addRichTextMenuItem(@DrawableRes iconId: Int, description: String, action: () -> Unit) {
+ private fun addRichTextMenuItem(@DrawableRes iconId: Int, @StringRes description: Int, action: ComposerAction, onClick: () -> Unit) {
val inflater = LayoutInflater.from(context)
val button = ViewRichTextMenuButtonBinding.inflate(inflater, views.richTextMenu, true)
+ button.root.tag = action
with(button.root) {
- contentDescription = description
+ contentDescription = resources.getString(description)
setImageResource(iconId)
setOnClickListener {
- action()
+ onClick()
}
}
}
+ private fun updateMenuStateFor(action: ComposerAction, menuState: MenuState.Update) {
+ val button = findViewWithTag(action) ?: return
+ button.isEnabled = !menuState.disabledActions.contains(action)
+ button.isSelected = menuState.reversedActions.contains(action)
+ }
+
+ private fun updateTextFieldBorder(isExpanded: Boolean) {
+ val borderResource = if (isExpanded) {
+ R.drawable.bg_composer_rich_edit_text_expanded
+ } else {
+ R.drawable.bg_composer_rich_edit_text_single_line
+ }
+ views.composerEditTextOuterBorder.setBackgroundResource(borderResource)
+ }
+
override fun replaceFormattedContent(text: CharSequence) {
views.composerEditText.setHtml(text.toString())
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/DraggableStateProcessor.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/DraggableStateProcessor.kt
index 1355c89006..1ff937add5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/DraggableStateProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/DraggableStateProcessor.kt
@@ -21,6 +21,7 @@ import android.view.MotionEvent
import im.vector.app.R
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState
+import kotlin.math.absoluteValue
class DraggableStateProcessor(
resources: Resources,
@@ -46,7 +47,7 @@ class DraggableStateProcessor(
fun process(event: MotionEvent, draggingState: DraggingState): DraggingState {
val currentX = event.rawX
val currentY = event.rawY
- val distanceX = firstX - currentX
+ val distanceX = (firstX - currentX).absoluteValue
val distanceY = firstY - currentY
return draggingState.nextDragState(currentX, currentY, distanceX, distanceY).also {
lastDistanceX = distanceX
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
index 1eac3b440a..cd41219371 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
@@ -260,7 +260,7 @@ class VoiceMessageViews(
fun resetMicButtonUi() {
views.voiceMessageMicButton.isVisible = true
- views.voiceMessageMicButton.setImageResource(R.drawable.ic_voice_mic)
+ views.voiceMessageMicButton.setImageResource(R.drawable.ic_microphone)
views.voiceMessageMicButton.setAttributeBackground(android.R.attr.selectableItemBackgroundBorderless)
views.voiceMessageMicButton.updateLayoutParams {
if (rtlXMultiplier == -1) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt
index eed596cda0..06f438d78e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt
@@ -25,6 +25,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySearchBinding
+import im.vector.lib.core.utils.compat.getParcelableCompat
@AndroidEntryPoint
class SearchActivity : VectorBaseActivity() {
@@ -46,7 +47,7 @@ class SearchActivity : VectorBaseActivity() {
override fun initUiAndData() {
if (isFirstCreation()) {
- val fragmentArgs: SearchArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
+ val fragmentArgs: SearchArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
addFragment(views.searchFragmentContainer, SearchFragment::class.java, fragmentArgs, FRAGMENT_TAG)
}
views.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt
index eda1929133..8cb82691d9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt
@@ -17,7 +17,7 @@
package im.vector.app.features.home.room.detail.timeline.action
import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
+import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import javax.inject.Inject
@@ -31,7 +31,7 @@ class CheckIfCanRedactEventUseCase @Inject constructor(
val canRedactEventTypes: List = listOf(
EventType.MESSAGE,
EventType.STICKER,
- STATE_ROOM_VOICE_BROADCAST_INFO,
+ VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
) +
EventType.POLL_START +
EventType.STATE_ROOM_BEACON_INFO
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 06da69fc1a..245d92f95b 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
@@ -43,9 +43,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
-import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
-import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem
import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_
@@ -58,8 +56,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
-import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem
-import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
import im.vector.app.features.home.room.detail.timeline.item.PollItem
@@ -82,8 +78,8 @@ import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voice.AudioWaveformView
+import im.vector.app.features.voicebroadcast.isVoiceBroadcast
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
-import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
@@ -107,6 +103,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
+import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
@@ -141,6 +138,7 @@ class MessageItemFactory @Inject constructor(
private val urlMapProvider: UrlMapProvider,
private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory,
private val pollItemViewStateFactory: PollItemViewStateFactory,
+ private val voiceBroadcastItemFactory: VoiceBroadcastItemFactory,
) {
// TODO inject this properly?
@@ -203,7 +201,7 @@ class MessageItemFactory @Inject constructor(
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
- is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, params.eventsGroup, highlight, callback, attributes)
+ is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, callback, attributes)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
}
return messageItem?.apply {
@@ -323,7 +321,10 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData,
highlight: Boolean,
attributes: AbsMessageItem.Attributes
- ): MessageVoiceItem {
+ ): MessageVoiceItem? {
+ // Do not display voice broadcast messages
+ if (params.event.root.asMessageAudioEvent().isVoiceBroadcast()) return null
+
val fileUrl = getAudioFileUrl(messageContent, informationData)
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
@@ -713,25 +714,6 @@ class MessageItemFactory @Inject constructor(
.highlighted(highlight)
}
- private fun buildVoiceBroadcastItem(
- messageContent: MessageVoiceBroadcastInfoContent,
- eventsGroup: TimelineEventsGroup?,
- highlight: Boolean,
- callback: TimelineEventController.Callback?,
- attributes: AbsMessageItem.Attributes,
- ): MessageVoiceBroadcastItem? {
- if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
- val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null
- val mostRecentEvent = voiceBroadcastEventsGroup.getLastEvent()
- val mostRecentMessageContent = (mostRecentEvent.getVectorLastMessageContent() as? MessageVoiceBroadcastInfoContent) ?: return null
- return MessageVoiceBroadcastItem_()
- .attributes(attributes)
- .highlighted(highlight)
- .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState)
- .leftGuideline(avatarSizeProvider.leftGuideline)
- .callback(callback)
- }
-
private fun List?.toFft(): List? {
return this
?.filterNotNull()
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 0b8f95b4a1..31ff257214 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
@@ -21,7 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
-import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
+import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import timber.log.Timber
@@ -89,7 +89,7 @@ class TimelineItemFactory @Inject constructor(
// State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params)
- STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params)
+ VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params)
// Unhandled state event types
else -> {
// Should only happen when shouldShowHiddenEvents() settings is ON
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt
new file mode 100644
index 0000000000..5dc601a91a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.app.features.home.room.detail.timeline.factory
+
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
+import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
+import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup
+import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
+import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem
+import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_
+import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem
+import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem_
+import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
+import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
+import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
+import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
+import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.getUser
+import org.matrix.android.sdk.api.util.toMatrixItem
+import javax.inject.Inject
+
+class VoiceBroadcastItemFactory @Inject constructor(
+ private val session: Session,
+ private val avatarSizeProvider: AvatarSizeProvider,
+ private val colorProvider: ColorProvider,
+ private val drawableProvider: DrawableProvider,
+ private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
+ private val voiceBroadcastPlayer: VoiceBroadcastPlayer,
+) {
+
+ fun create(
+ params: TimelineItemFactoryParams,
+ messageContent: MessageVoiceBroadcastInfoContent,
+ highlight: Boolean,
+ callback: TimelineEventController.Callback?,
+ attributes: AbsMessageItem.Attributes,
+ ): VectorEpoxyModel? {
+ // Only display item of the initial event with updated data
+ if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
+ val eventsGroup = params.eventsGroup ?: return null
+ val voiceBroadcastEventsGroup = VoiceBroadcastEventsGroup(eventsGroup)
+ val mostRecentTimelineEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent()
+ val mostRecentEvent = mostRecentTimelineEvent.root.asVoiceBroadcastEvent()
+ val mostRecentMessageContent = mostRecentEvent?.content ?: return null
+ val isRecording = mostRecentMessageContent.voiceBroadcastState != VoiceBroadcastState.STOPPED && mostRecentEvent.root.stateKey == session.myUserId
+ val recorderName = mostRecentTimelineEvent.root.stateKey?.let { session.getUser(it) }?.displayName ?: mostRecentTimelineEvent.root.stateKey
+ return if (isRecording) {
+ createRecordingItem(
+ params.event.roomId,
+ eventsGroup.groupId,
+ highlight,
+ callback,
+ attributes
+ )
+ } else {
+ createListeningItem(
+ params.event.roomId,
+ eventsGroup.groupId,
+ mostRecentMessageContent.voiceBroadcastState,
+ recorderName,
+ highlight,
+ callback,
+ attributes
+ )
+ }
+ }
+
+ private fun createRecordingItem(
+ roomId: String,
+ voiceBroadcastId: String,
+ highlight: Boolean,
+ callback: TimelineEventController.Callback?,
+ attributes: AbsMessageItem.Attributes,
+ ): MessageVoiceBroadcastRecordingItem {
+ val roomSummary = session.getRoom(roomId)?.roomSummary()
+ return MessageVoiceBroadcastRecordingItem_()
+ .id("voice_broadcast_$voiceBroadcastId")
+ .attributes(attributes)
+ .highlighted(highlight)
+ .roomItem(roomSummary?.toMatrixItem())
+ .colorProvider(colorProvider)
+ .drawableProvider(drawableProvider)
+ .voiceBroadcastRecorder(voiceBroadcastRecorder)
+ .leftGuideline(avatarSizeProvider.leftGuideline)
+ .callback(callback)
+ }
+
+ private fun createListeningItem(
+ roomId: String,
+ voiceBroadcastId: String,
+ voiceBroadcastState: VoiceBroadcastState?,
+ broadcasterName: String?,
+ highlight: Boolean,
+ callback: TimelineEventController.Callback?,
+ attributes: AbsMessageItem.Attributes,
+ ): MessageVoiceBroadcastListeningItem {
+ val roomSummary = session.getRoom(roomId)?.roomSummary()
+ return MessageVoiceBroadcastListeningItem_()
+ .id("voice_broadcast_$voiceBroadcastId")
+ .attributes(attributes)
+ .highlighted(highlight)
+ .roomItem(roomSummary?.toMatrixItem())
+ .colorProvider(colorProvider)
+ .drawableProvider(drawableProvider)
+ .voiceBroadcastPlayer(voiceBroadcastPlayer)
+ .voiceBroadcastId(voiceBroadcastId)
+ .voiceBroadcastState(voiceBroadcastState)
+ .broadcasterName(broadcasterName)
+ .leftGuideline(avatarSizeProvider.leftGuideline)
+ .callback(callback)
+ }
+}
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 8ef910c931..7f276f2f73 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
@@ -29,7 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.AvatarRenderer
-import org.matrix.android.sdk.api.session.getUser
+import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber
import javax.inject.Inject
@@ -67,9 +67,9 @@ class LocationPinProvider @Inject constructor(
activeSessionHolder
.getActiveSession()
- .getUser(userId)
- ?.toMatrixItem()
- ?.let { userItem ->
+ .getUserOrDefault(userId)
+ .toMatrixItem()
+ .let { userItem ->
val size = dimensionConverter.dpToPx(44)
val bgTintColor = matrixItemColorProvider.getColor(userItem)
avatarRenderer.render(glideRequests, userItem, object : CustomTarget(size, size) {
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 87844aba8e..2411cb3877 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
@@ -16,7 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.helper
-import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
+import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -52,7 +52,7 @@ object TimelineDisplayableEvents {
EventType.STATE_ROOM_JOIN_RULES,
EventType.KEY_VERIFICATION_DONE,
EventType.KEY_VERIFICATION_CANCEL,
- STATE_ROOM_VOICE_BROADCAST_INFO,
+ VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
) +
EventType.POLL_START +
EventType.STATE_ROOM_BEACON_INFO +
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
index bd211a4513..d8817c1f44 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
@@ -17,13 +17,16 @@
package im.vector.app.features.home.room.detail.timeline.helper
import im.vector.app.core.utils.TextUtils
-import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
+import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
+import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId
+import im.vector.app.features.voicebroadcast.isVoiceBroadcast
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import org.matrix.android.sdk.api.extensions.orFalse
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.model.call.CallInviteContent
+import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
import org.threeten.bp.Duration
@@ -59,8 +62,12 @@ class TimelineEventsGroups {
val content = root.getClearContent()
return when {
EventType.isCallEvent(type) -> (content?.get("call_id") as? String)
- type == STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId
+ type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId
type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey
+ type == EventType.MESSAGE && root.asMessageAudioEvent().isVoiceBroadcast() -> {
+ // Group voice messages with a reference to an eventId
+ root.asMessageAudioEvent()?.getVoiceBroadcastEventId()
+ }
else -> {
null
}
@@ -134,8 +141,8 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) {
}
class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) {
- fun getLastEvent(): TimelineEvent {
+ fun getLastDisplayableEvent(): TimelineEvent {
return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED }
- ?: group.events.maxBy { it.root.originServerTs ?: 0L }
+ ?: group.events.filter { it.root.type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO }.maxBy { it.root.originServerTs ?: 0L }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt
deleted file mode 100644
index 14a4fc6b07..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2022 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home.room.detail.timeline.item
-
-import android.annotation.SuppressLint
-import android.widget.ImageButton
-import android.widget.TextView
-import com.airbnb.epoxy.EpoxyAttribute
-import com.airbnb.epoxy.EpoxyModelClass
-import im.vector.app.R
-import im.vector.app.features.home.room.detail.RoomDetailAction
-import im.vector.app.features.home.room.detail.timeline.TimelineEventController
-import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
-
-@EpoxyModelClass
-abstract class MessageVoiceBroadcastItem : AbsMessageItem() {
-
- @EpoxyAttribute
- var callback: TimelineEventController.Callback? = null
-
- @EpoxyAttribute
- var voiceBroadcastState: VoiceBroadcastState? = null
-
- override fun isCacheable(): Boolean = false
-
- override fun bind(holder: Holder) {
- super.bind(holder)
- bindVoiceBroadcastItem(holder)
- }
-
- @SuppressLint("SetTextI18n") // Temporary text
- private fun bindVoiceBroadcastItem(holder: Holder) {
- with(holder) {
- currentStateText.text = "Voice Broadcast state: ${voiceBroadcastState?.value ?: "None"}"
- playButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.PAUSED
- pauseButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || voiceBroadcastState == VoiceBroadcastState.RESUMED
- stopButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED ||
- voiceBroadcastState == VoiceBroadcastState.RESUMED ||
- voiceBroadcastState == VoiceBroadcastState.PAUSED
- playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Resume) }
- pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Pause) }
- stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Stop) }
- }
- }
-
- override fun getViewStubId() = STUB_ID
-
- class Holder : AbsMessageLocationItem.Holder(STUB_ID) {
- val currentStateText by bind(R.id.currentStateText)
- val playButton by bind(R.id.playButton)
- val pauseButton by bind(R.id.pauseButton)
- val stopButton by bind(R.id.stopButton)
- }
-
- companion object {
- private val STUB_ID = R.id.messageVoiceBroadcastStub
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt
new file mode 100644
index 0000000000..5b58dda4e6
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.item
+
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.onClick
+import im.vector.app.core.extensions.tintBackground
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
+import im.vector.app.features.home.room.detail.RoomDetailAction
+import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.voicebroadcast.VoiceBroadcastPlayer
+import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
+import org.matrix.android.sdk.api.util.MatrixItem
+
+@EpoxyModelClass
+abstract class MessageVoiceBroadcastListeningItem : AbsMessageItem() {
+
+ @EpoxyAttribute
+ var callback: TimelineEventController.Callback? = null
+
+ @EpoxyAttribute
+ var voiceBroadcastPlayer: VoiceBroadcastPlayer? = null
+
+ @EpoxyAttribute
+ lateinit var voiceBroadcastId: String
+
+ @EpoxyAttribute
+ var voiceBroadcastState: VoiceBroadcastState? = null
+
+ @EpoxyAttribute
+ var broadcasterName: String? = null
+
+ @EpoxyAttribute
+ lateinit var colorProvider: ColorProvider
+
+ @EpoxyAttribute
+ lateinit var drawableProvider: DrawableProvider
+
+ @EpoxyAttribute
+ var roomItem: MatrixItem? = null
+
+ @EpoxyAttribute
+ var title: String? = null
+
+ private lateinit var playerListener: VoiceBroadcastPlayer.Listener
+
+ override fun isCacheable(): Boolean = false
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ bindVoiceBroadcastItem(holder)
+ }
+
+ private fun bindVoiceBroadcastItem(holder: Holder) {
+ playerListener = VoiceBroadcastPlayer.Listener { state ->
+ renderState(holder, state)
+ }
+ voiceBroadcastPlayer?.addListener(playerListener)
+ renderHeader(holder)
+ renderLiveIcon(holder)
+ }
+
+ private fun renderHeader(holder: Holder) {
+ with(holder) {
+ roomItem?.let {
+ attributes.avatarRenderer.render(it, roomAvatarImageView)
+ titleText.text = it.displayName
+ }
+ broadcasterNameText.text = broadcasterName
+ }
+ }
+
+ private fun renderLiveIcon(holder: Holder) {
+ with(holder) {
+ when (voiceBroadcastState) {
+ VoiceBroadcastState.STARTED,
+ VoiceBroadcastState.RESUMED -> {
+ liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError))
+ liveIndicator.isVisible = true
+ }
+ VoiceBroadcastState.PAUSED -> {
+ liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary))
+ liveIndicator.isVisible = true
+ }
+ VoiceBroadcastState.STOPPED, null -> {
+ liveIndicator.isVisible = false
+ }
+ }
+ }
+ }
+
+ private fun renderState(holder: Holder, state: VoiceBroadcastPlayer.State) {
+ if (isCurrentMediaActive()) {
+ renderActiveMedia(holder, state)
+ } else {
+ renderInactiveMedia(holder)
+ }
+ }
+
+ private fun renderActiveMedia(holder: Holder, state: VoiceBroadcastPlayer.State) {
+ with(holder) {
+ bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
+ playPauseButton.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING
+
+ when (state) {
+ VoiceBroadcastPlayer.State.PLAYING -> {
+ playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
+ playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
+ playPauseButton.onClick { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) }
+ }
+ VoiceBroadcastPlayer.State.IDLE,
+ VoiceBroadcastPlayer.State.PAUSED -> {
+ playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
+ playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
+ playPauseButton.onClick {
+ attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
+ }
+ }
+ VoiceBroadcastPlayer.State.BUFFERING -> Unit
+ }
+ }
+ }
+
+ private fun renderInactiveMedia(holder: Holder) {
+ with(holder) {
+ bufferingView.isVisible = false
+ playPauseButton.isVisible = true
+ playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
+ playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
+ playPauseButton.onClick {
+ attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
+ }
+ }
+ }
+
+ private fun isCurrentMediaActive() = voiceBroadcastPlayer?.currentVoiceBroadcastId == voiceBroadcastId
+
+ override fun unbind(holder: Holder) {
+ super.unbind(holder)
+ voiceBroadcastPlayer?.removeListener(playerListener)
+ }
+
+ override fun getViewStubId() = STUB_ID
+
+ class Holder : AbsMessageItem.Holder(STUB_ID) {
+ val liveIndicator by bind(R.id.liveIndicator)
+ val roomAvatarImageView by bind(R.id.roomAvatarImageView)
+ val titleText by bind(R.id.titleText)
+ val playPauseButton by bind(R.id.playPauseButton)
+ val bufferingView by bind(R.id.bufferingView)
+ val broadcasterNameText by bind(R.id.broadcasterNameText)
+ }
+
+ companion object {
+ private val STUB_ID = R.id.messageVoiceBroadcastListeningStub
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt
new file mode 100644
index 0000000000..c417053b2a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastRecordingItem.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.item
+
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.onClick
+import im.vector.app.core.extensions.tintBackground
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
+import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
+import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
+import org.matrix.android.sdk.api.util.MatrixItem
+
+@EpoxyModelClass
+abstract class MessageVoiceBroadcastRecordingItem : AbsMessageItem() {
+
+ @EpoxyAttribute
+ var callback: TimelineEventController.Callback? = null
+
+ @EpoxyAttribute
+ var voiceBroadcastRecorder: VoiceBroadcastRecorder? = null
+
+ @EpoxyAttribute
+ lateinit var colorProvider: ColorProvider
+
+ @EpoxyAttribute
+ lateinit var drawableProvider: DrawableProvider
+
+ @EpoxyAttribute
+ var roomItem: MatrixItem? = null
+
+ @EpoxyAttribute
+ var title: String? = null
+
+ private lateinit var recorderListener: VoiceBroadcastRecorder.Listener
+
+ override fun isCacheable(): Boolean = false
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ bindVoiceBroadcastItem(holder)
+ }
+
+ private fun bindVoiceBroadcastItem(holder: Holder) {
+ recorderListener = object : VoiceBroadcastRecorder.Listener {
+ override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
+ renderState(holder, state)
+ }
+ }
+ voiceBroadcastRecorder?.addListener(recorderListener)
+ renderHeader(holder)
+ }
+
+ private fun renderHeader(holder: Holder) {
+ with(holder) {
+ roomItem?.let {
+ attributes.avatarRenderer.render(it, roomAvatarImageView)
+ titleText.text = it.displayName
+ }
+ }
+ }
+
+ private fun renderState(holder: Holder, state: VoiceBroadcastRecorder.State) {
+ with(holder) {
+ when (state) {
+ VoiceBroadcastRecorder.State.Recording -> {
+ stopRecordButton.isEnabled = true
+ recordButton.isEnabled = true
+
+ liveIndicator.isVisible = true
+ liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.colorError))
+
+ val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
+ val drawable = drawableProvider.getDrawable(R.drawable.ic_play_pause_pause, drawableColor)
+ recordButton.setImageDrawable(drawable)
+ recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_pause_voice_broadcast_record)
+ recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Pause) }
+ stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) }
+ }
+ VoiceBroadcastRecorder.State.Paused -> {
+ stopRecordButton.isEnabled = true
+ recordButton.isEnabled = true
+
+ liveIndicator.isVisible = true
+ liveIndicator.tintBackground(colorProvider.getColorFromAttribute(R.attr.vctr_content_quaternary))
+
+ recordButton.setImageResource(R.drawable.ic_recording_dot)
+ recordButton.contentDescription = holder.view.resources.getString(R.string.a11y_resume_voice_broadcast_record)
+ recordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Resume) }
+ stopRecordButton.onClick { attributes.callback?.onTimelineItemAction(VoiceBroadcastAction.Recording.Stop) }
+ }
+ VoiceBroadcastRecorder.State.Idle -> {
+ recordButton.isEnabled = false
+ stopRecordButton.isEnabled = false
+ liveIndicator.isVisible = false
+ }
+ }
+ }
+ }
+
+ override fun unbind(holder: Holder) {
+ super.unbind(holder)
+ voiceBroadcastRecorder?.removeListener(recorderListener)
+ }
+
+ override fun getViewStubId() = STUB_ID
+
+ class Holder : AbsMessageItem.Holder(STUB_ID) {
+ val liveIndicator by bind(R.id.liveIndicator)
+ val roomAvatarImageView by bind(R.id.roomAvatarImageView)
+ val titleText by bind(R.id.titleText)
+ val recordButton by bind(R.id.recordButton)
+ val stopRecordButton by bind(R.id.stopRecordButton)
+ }
+
+ companion object {
+ private val STUB_ID = R.id.messageVoiceBroadcastRecordingStub
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
index 71a24da5ae..b3f2ef1f75 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
@@ -34,13 +34,13 @@ import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
+import im.vector.lib.core.utils.compat.getParcelableCompat
import javax.inject.Inject
@AndroidEntryPoint
class ThreadsActivity : VectorBaseActivity() {
- @Inject
- lateinit var avatarRenderer: AvatarRenderer
+ @Inject lateinit var avatarRenderer: AvatarRenderer
// private val roomThreadDetailFragment: RoomThreadDetailFragment?
// get() {
@@ -129,8 +129,8 @@ class ThreadsActivity : VectorBaseActivity() {
return DisplayFragment.ErrorFragment
}
- private fun getThreadTimelineArgs(): ThreadTimelineArgs? = intent?.extras?.getParcelable(THREAD_TIMELINE_ARGS)
- private fun getThreadListArgs(): ThreadListArgs? = intent?.extras?.getParcelable(THREAD_LIST_ARGS)
+ private fun getThreadTimelineArgs(): ThreadTimelineArgs? = intent?.extras?.getParcelableCompat(THREAD_TIMELINE_ARGS)
+ private fun getThreadListArgs(): ThreadListArgs? = intent?.extras?.getParcelableCompat(THREAD_LIST_ARGS)
private fun getEventIdToNavigate(): String? = intent?.extras?.getString(THREAD_EVENT_ID_TO_NAVIGATE)
companion object {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
index ef07067bac..f91fe9bd91 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
@@ -78,7 +78,7 @@ class ThreadListFragment :
override fun handlePostCreateMenu(menu: Menu) {
// We use a custom layout for this menu item, so we need to set a ClickListener
menu.findItem(R.id.menu_thread_list_filter)?.let { menuItem ->
- menuItem.actionView.debouncedClicks {
+ menuItem.actionView?.debouncedClicks {
handleMenuItemSelected(menuItem)
}
}
@@ -96,7 +96,7 @@ class ThreadListFragment :
override fun handlePrepareMenu(menu: Menu) {
withState(threadListViewModel) { state ->
- val filterIcon = menu.findItem(R.id.menu_thread_list_filter).actionView
+ val filterIcon = menu.findItem(R.id.menu_thread_list_filter).actionView ?: return@withState
val filterBadge = filterIcon.findViewById(R.id.threadListFilterBadge)
filterBadge.isVisible = state.shouldFilterThreads
when (threadListViewModel.canHomeserverUseThreading()) {
diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
index 5f20b7278e..85cfb76ff7 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
@@ -27,7 +27,7 @@ import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer
import io.noties.markwon.core.spans.LinkSpan
import org.matrix.android.sdk.api.session.getRoomSummary
-import org.matrix.android.sdk.api.session.getUser
+import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -101,7 +101,7 @@ class PillsPostProcessor @AssistedInject constructor(
private fun PermalinkData.UserLink.toMatrixItem(roomId: String?): MatrixItem? =
if (roomId == null) {
- sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem()
+ sessionHolder.getSafeActiveSession()?.getUserOrDefault(userId)?.toMatrixItem()
} else {
sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, roomId)?.toMatrixItem()
}
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
index 883c879e90..7f514d2ad2 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
@@ -27,7 +27,6 @@ import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.platform.SimpleFragmentActivity
@@ -47,7 +46,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.failure.Failure
import java.net.HttpURLConnection
-import javax.inject.Inject
@Parcelize
data class InviteUsersToRoomArgs(val roomId: String) : Parcelable
@@ -57,7 +55,6 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
private val viewModel: InviteUsersToRoomViewModel by viewModel()
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
- @Inject lateinit var errorFormatter: ErrorFormatter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -68,6 +65,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
sharedActionViewModel
.stream()
.onEach { sharedAction ->
+ @Suppress("DEPRECATION")
when (sharedAction) {
UserListSharedAction.Close -> finish()
UserListSharedAction.GoBack -> onBackPressed()
diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
index 5bdd92dcf4..26024148f4 100644
--- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
+++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
@@ -34,6 +34,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.util.getPackageInfoCompat
import timber.log.Timber
class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager: PopupAlertManager) : Application.ActivityLifecycleCallbacks {
@@ -64,16 +65,16 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager
val packageManager: PackageManager = context.packageManager
// Get all activities from element android
- activitiesInfo = packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES).activities
+ activitiesInfo = packageManager.getPackageInfoCompat(context.packageName, PackageManager.GET_ACTIVITIES).activities
// Get all activities from PermissionController module
// See https://source.android.com/docs/core/architecture/modular-system/permissioncontroller#package-format
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
activitiesInfo += tryOrNull {
- packageManager.getPackageInfo("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities
+ packageManager.getPackageInfoCompat("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities
} ?: tryOrNull {
packageManager.getModuleInfo("com.google.android.permission", 1).packageName?.let {
- packageManager.getPackageInfo(it, PackageManager.GET_ACTIVITIES or PackageManager.MATCH_APEX).activities
+ packageManager.getPackageInfoCompat(it, PackageManager.GET_ACTIVITIES or PackageManager.MATCH_APEX).activities
}
}.orEmpty()
}
diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt
index 0bdec53f60..1ca67e1fb7 100644
--- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt
@@ -24,8 +24,6 @@ import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
@@ -46,8 +44,6 @@ import javax.inject.Inject
@AndroidEntryPoint
class LinkHandlerActivity : VectorBaseActivity() {
- @Inject lateinit var sessionHolder: ActiveSessionHolder
- @Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var permalinkHandler: PermalinkHandler
private val startAppViewModel: StartAppViewModel by viewModel()
@@ -103,7 +99,7 @@ class LinkHandlerActivity : VectorBaseActivity() {
}
private fun handleConfigUrl(uri: Uri) {
- if (sessionHolder.hasActiveSession()) {
+ if (activeSessionHolder.hasActiveSession()) {
displayAlreadyLoginPopup(uri)
} else {
// user is not yet logged in, this is the nominal case
@@ -114,7 +110,7 @@ class LinkHandlerActivity : VectorBaseActivity() {
private fun handleSupportedHostUrl() {
// If we are not logged in, open login screen.
// In the future, we might want to relaunch the process after login.
- if (!sessionHolder.hasActiveSession()) {
+ if (!activeSessionHolder.hasActiveSession()) {
startLoginActivity()
return
}
@@ -152,7 +148,7 @@ class LinkHandlerActivity : VectorBaseActivity() {
}
private fun safeSignout(uri: Uri) {
- val session = sessionHolder.getSafeActiveSession()
+ val session = activeSessionHolder.getSafeActiveSession()
if (session == null) {
// Should not happen
startLoginActivity(uri)
@@ -161,7 +157,7 @@ class LinkHandlerActivity : VectorBaseActivity() {
try {
session.signOutService().signOut(true)
Timber.d("## displayAlreadyLoginPopup(): logout succeeded")
- sessionHolder.clearActiveSession()
+ activeSessionHolder.clearActiveSession()
startLoginActivity(uri)
} catch (failure: Throwable) {
displayError(failure)
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
index 9eddcad649..b4d2a1a565 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt
@@ -24,6 +24,7 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLocationSharingBinding
import im.vector.app.features.location.preview.LocationPreviewFragment
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.parcelize.Parcelize
@Parcelize
@@ -40,7 +41,7 @@ class LocationSharingActivity : VectorBaseActivity {
locationPinProvider.create(userId) { pinDrawable ->
val session = activeSessionHolder.getActiveSession()
- session.getUser(userId)?.toMatrixItem()?.let { matrixItem ->
- val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis()
- val viewState = UserLiveLocationViewState(
- matrixItem = matrixItem,
- pinDrawable = pinDrawable,
- locationData = locationData,
- endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis,
- locationTimestampMillis = locationTimestampMillis,
- showStopSharingButton = userId == session.myUserId
- )
- continuation.resume(viewState) {
- // do nothing on cancellation
- }
+ val locationTimestampMillis = liveLocationShareAggregatedSummary.lastLocationDataContent?.getBestTimestampMillis()
+ val viewState = UserLiveLocationViewState(
+ matrixItem = session.getUserOrDefault(userId).toMatrixItem(),
+ pinDrawable = pinDrawable,
+ locationData = locationData,
+ endOfLiveTimestampMillis = liveLocationShareAggregatedSummary.endOfLiveTimestampMillis,
+ locationTimestampMillis = locationTimestampMillis,
+ showStopSharingButton = userId == session.myUserId
+ )
+ continuation.resume(viewState) {
+ // do nothing on cancellation
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt
index 819ec8634e..545f98f01e 100644
--- a/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt
+++ b/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt
@@ -29,6 +29,7 @@ import im.vector.app.features.location.LocationTracker
import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase
import im.vector.app.features.session.coroutineScope
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -95,7 +96,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startInProgress = true
- val roomArgs = intent?.getParcelableExtra(EXTRA_ROOM_ARGS) as? RoomArgs
+ val roomArgs = intent?.getParcelableExtraCompat(EXTRA_ROOM_ARGS) as? RoomArgs
Timber.i("onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}")
@@ -191,7 +192,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
}
override fun onNoLocationProviderAvailable() {
- stopForeground(true)
+ stopForegroundCompat()
stopSelf()
}
diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
index 9512a518e8..6ee276342c 100644
--- a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt
@@ -133,6 +133,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(
.setMessage(R.string.login_signup_cancel_confirmation_content)
.setPositiveButton(R.string.yes) { _, _ ->
displayCancelDialog = false
+ @Suppress("DEPRECATION")
vectorBaseActivity.onBackPressed()
}
.setNegativeButton(R.string.no, null)
@@ -147,6 +148,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(
.setMessage(R.string.login_reset_password_cancel_confirmation_content)
.setPositiveButton(R.string.yes) { _, _ ->
displayCancelDialog = false
+ @Suppress("DEPRECATION")
vectorBaseActivity.onBackPressed()
}
.setNegativeButton(R.string.no, null)
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index 763d1eed38..4e4df5d1aa 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -45,6 +45,7 @@ import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.pin.UnlockedActivity
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
@@ -96,7 +97,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
// Get config extra
- val loginConfig = intent.getParcelableExtra(EXTRA_CONFIG)
+ val loginConfig = intent.getParcelableExtraCompat(EXTRA_CONFIG)
if (isFirstCreation()) {
loginViewModel.handle(LoginAction.InitWith(loginConfig))
}
@@ -316,7 +317,10 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
}
override fun onBackPressed() {
- validateBackPressed { super.onBackPressed() }
+ validateBackPressed {
+ @Suppress("DEPRECATION")
+ super.onBackPressed()
+ }
}
private fun onRegistrationStageNotSupported() {
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt
new file mode 100644
index 0000000000..5ea46d3dcd
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginAction.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class QrCodeLoginAction : VectorViewModelAction {
+ data class OnQrCodeScanned(val qrCode: String) : QrCodeLoginAction()
+ object GenerateQrCode : QrCodeLoginAction()
+ object ShowQrCode : QrCodeLoginAction()
+ object TryAgain : QrCodeLoginAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt
new file mode 100644
index 0000000000..a0c113224d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginActivity.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import com.airbnb.mvrx.Mavericks
+import com.airbnb.mvrx.viewModel
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.extensions.replaceFragment
+import im.vector.app.core.platform.SimpleFragmentActivity
+import im.vector.app.features.home.HomeActivity
+import im.vector.lib.core.utils.compat.getParcelableCompat
+import timber.log.Timber
+
+@AndroidEntryPoint
+class QrCodeLoginActivity : SimpleFragmentActivity() {
+
+ private val viewModel: QrCodeLoginViewModel by viewModel()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ views.toolbar.visibility = View.GONE
+
+ if (isFirstCreation()) {
+ navigateToInitialFragment()
+ }
+
+ observeViewEvents()
+ }
+
+ private fun navigateToInitialFragment() {
+ val qrCodeLoginArgs: QrCodeLoginArgs? = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG)
+ when (qrCodeLoginArgs?.loginType) {
+ QrCodeLoginType.LOGIN -> {
+ showInstructionsFragment(qrCodeLoginArgs)
+ }
+ QrCodeLoginType.LINK_A_DEVICE -> {
+ if (qrCodeLoginArgs.showQrCodeImmediately) {
+ handleNavigateToShowQrCodeScreen()
+ } else {
+ showInstructionsFragment(qrCodeLoginArgs)
+ }
+ }
+ null -> {
+ Timber.i("QrCodeLoginArgs is null. This is not expected.")
+ finish()
+ }
+ }
+ }
+
+ private fun showInstructionsFragment(qrCodeLoginArgs: QrCodeLoginArgs) {
+ replaceFragment(
+ views.container,
+ QrCodeLoginInstructionsFragment::class.java,
+ qrCodeLoginArgs,
+ tag = FRAGMENT_QR_CODE_INSTRUCTIONS_TAG
+ )
+ }
+
+ private fun observeViewEvents() {
+ viewModel.observeViewEvents {
+ when (it) {
+ QrCodeLoginViewEvents.NavigateToStatusScreen -> handleNavigateToStatusScreen()
+ QrCodeLoginViewEvents.NavigateToShowQrCodeScreen -> handleNavigateToShowQrCodeScreen()
+ QrCodeLoginViewEvents.NavigateToHomeScreen -> handleNavigateToHomeScreen()
+ QrCodeLoginViewEvents.NavigateToInitialScreen -> handleNavigateToInitialScreen()
+ }
+ }
+ }
+
+ private fun handleNavigateToInitialScreen() {
+ navigateToInitialFragment()
+ }
+
+ private fun handleNavigateToShowQrCodeScreen() {
+ addFragment(
+ views.container,
+ QrCodeLoginShowQrCodeFragment::class.java,
+ tag = FRAGMENT_SHOW_QR_CODE_TAG
+ )
+ }
+
+ private fun handleNavigateToStatusScreen() {
+ addFragment(
+ views.container,
+ QrCodeLoginStatusFragment::class.java,
+ tag = FRAGMENT_QR_CODE_STATUS_TAG
+ )
+ }
+
+ private fun handleNavigateToHomeScreen() {
+ val intent = HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true)
+ startActivity(intent)
+ }
+
+ companion object {
+
+ private const val FRAGMENT_QR_CODE_INSTRUCTIONS_TAG = "FRAGMENT_QR_CODE_INSTRUCTIONS_TAG"
+ private const val FRAGMENT_SHOW_QR_CODE_TAG = "FRAGMENT_SHOW_QR_CODE_TAG"
+ private const val FRAGMENT_QR_CODE_STATUS_TAG = "FRAGMENT_QR_CODE_STATUS_TAG"
+
+ fun getIntent(context: Context, qrCodeLoginArgs: QrCodeLoginArgs): Intent {
+ return Intent(context, QrCodeLoginActivity::class.java).apply {
+ putExtra(Mavericks.KEY_ARG, qrCodeLoginArgs)
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt
new file mode 100644
index 0000000000..6c23d07c0f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginArgs.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class QrCodeLoginArgs(
+ val loginType: QrCodeLoginType,
+ val showQrCodeImmediately: Boolean,
+) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt
new file mode 100644
index 0000000000..4bef41b6c1
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginConnectionStatus.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
+
+sealed class QrCodeLoginConnectionStatus {
+ object ConnectingToDevice : QrCodeLoginConnectionStatus()
+ data class Connected(val securityCode: String, val canConfirmSecurityCode: Boolean) : QrCodeLoginConnectionStatus()
+ object SigningIn : QrCodeLoginConnectionStatus()
+ data class Failed(val errorType: RendezvousFailureReason, val canTryAgain: Boolean) : QrCodeLoginConnectionStatus()
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt
new file mode 100644
index 0000000000..03478d2f50
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginHeaderView.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.use
+import im.vector.app.R
+import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.databinding.ViewQrCodeLoginHeaderBinding
+
+class QrCodeLoginHeaderView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private val binding = ViewQrCodeLoginHeaderBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ init {
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.QrCodeLoginHeaderView,
+ 0,
+ 0
+ ).use {
+ setTitle(it)
+ setDescription(it)
+ setImage(it)
+ }
+ }
+
+ private fun setTitle(typedArray: TypedArray) {
+ val title = typedArray.getString(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderTitle)
+ setTitle(title)
+ }
+
+ private fun setDescription(typedArray: TypedArray) {
+ val description = typedArray.getString(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderDescription)
+ setDescription(description)
+ }
+
+ private fun setImage(typedArray: TypedArray) {
+ val imageResource = typedArray.getResourceId(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderImageResource, 0)
+ val backgroundTint = typedArray.getColor(R.styleable.QrCodeLoginHeaderView_qrCodeLoginHeaderImageBackgroundTint, 0)
+ setImage(imageResource, backgroundTint)
+ }
+
+ fun setTitle(title: String?) {
+ binding.qrCodeLoginHeaderTitleTextView.setTextOrHide(title)
+ }
+
+ fun setDescription(description: String?) {
+ binding.qrCodeLoginHeaderDescriptionTextView.setTextOrHide(description)
+ }
+
+ fun setImage(imageResource: Int, backgroundTintColor: Int) {
+ binding.qrCodeLoginHeaderImageView.setImageResource(imageResource)
+ binding.qrCodeLoginHeaderImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt
new file mode 100644
index 0000000000..40fcbbbb85
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsFragment.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.withState
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.registerStartForActivityResult
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentQrCodeLoginInstructionsBinding
+import im.vector.app.features.qrcode.QrCodeScannerActivity
+import timber.log.Timber
+
+@AndroidEntryPoint
+class QrCodeLoginInstructionsFragment : VectorBaseFragment() {
+
+ private val viewModel: QrCodeLoginViewModel by activityViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginInstructionsBinding {
+ return FragmentQrCodeLoginInstructionsBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initScanQrCodeButton()
+ initShowQrCodeButton()
+ }
+
+ private fun initShowQrCodeButton() {
+ views.qrCodeLoginInstructionsShowQrCodeButton.debouncedClicks {
+ viewModel.handle(QrCodeLoginAction.ShowQrCode)
+ }
+ }
+
+ private fun initScanQrCodeButton() {
+ views.qrCodeLoginInstructionsScanQrCodeButton.debouncedClicks {
+ QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
+ }
+ }
+
+ private val scanActivityResultLauncher = registerStartForActivityResult { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK) {
+ val scannedQrCode = QrCodeScannerActivity.getResultText(activityResult.data)
+ val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(activityResult.data)
+
+ Timber.d("Scanned QR code: $scannedQrCode, was QR code: $wasQrCode")
+ if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
+ onQrCodeScanned(scannedQrCode)
+ } else {
+ onQrCodeScannerFailed()
+ }
+ }
+ }
+
+ private fun onQrCodeScanned(scannedQrCode: String) {
+ viewModel.handle(QrCodeLoginAction.OnQrCodeScanned(scannedQrCode))
+ }
+
+ private fun onQrCodeScannerFailed() {
+ // The user scanned something unexpected, so we try scanning again.
+ // This seems to happen particularly with the large QRs needed for rendezvous
+ // especially when the QR is partially off the screen
+ Timber.d("QrCodeLoginInstructionsFragment.onQrCodeScannerFailed - showing scanner again")
+ QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ if (state.loginType == QrCodeLoginType.LOGIN) {
+ views.qrCodeLoginInstructionsView.setInstructions(
+ listOf(
+ getString(R.string.qr_code_login_new_device_instruction_1),
+ getString(R.string.qr_code_login_new_device_instruction_2),
+ getString(R.string.qr_code_login_new_device_instruction_3),
+ )
+ )
+ } else {
+ views.qrCodeLoginInstructionsView.setInstructions(
+ listOf(
+ getString(R.string.qr_code_login_link_a_device_scan_qr_code_instruction_1),
+ getString(R.string.qr_code_login_link_a_device_scan_qr_code_instruction_2),
+ )
+ )
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt
new file mode 100644
index 0000000000..ed5c4de175
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginInstructionsView.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.use
+import androidx.core.view.isVisible
+import im.vector.app.R
+import im.vector.app.databinding.ViewQrCodeLoginInstructionsBinding
+
+class QrCodeLoginInstructionsView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private val binding = ViewQrCodeLoginInstructionsBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ init {
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.QrCodeLoginInstructionsView,
+ 0,
+ 0
+ ).use {
+ setInstructions(it)
+ }
+ }
+
+ private fun setInstructions(typedArray: TypedArray) {
+ val instruction1 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction1)
+ val instruction2 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction2)
+ val instruction3 = typedArray.getString(R.styleable.QrCodeLoginInstructionsView_qrCodeLoginInstruction3)
+ setInstructions(
+ listOf(
+ instruction1,
+ instruction2,
+ instruction3,
+ )
+ )
+ }
+
+ fun setInstructions(instructions: List?) {
+ setInstruction(binding.instructions1Layout, binding.instruction1TextView, instructions?.getOrNull(0))
+ setInstruction(binding.instructions2Layout, binding.instruction2TextView, instructions?.getOrNull(1))
+ setInstruction(binding.instructions3Layout, binding.instruction3TextView, instructions?.getOrNull(2))
+ }
+
+ private fun setInstruction(instructionLayout: LinearLayout, instructionTextView: TextView, instruction: String?) {
+ instruction?.let {
+ instructionLayout.isVisible = true
+ instructionTextView.text = instruction
+ } ?: run {
+ instructionLayout.isVisible = false
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt
new file mode 100644
index 0000000000..d31f531a49
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginShowQrCodeFragment.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.withState
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentQrCodeLoginShowQrCodeBinding
+
+@AndroidEntryPoint
+class QrCodeLoginShowQrCodeFragment : VectorBaseFragment() {
+
+ private val viewModel: QrCodeLoginViewModel by activityViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginShowQrCodeBinding {
+ return FragmentQrCodeLoginShowQrCodeBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initCancelButton()
+ viewModel.handle(QrCodeLoginAction.GenerateQrCode)
+ }
+
+ private fun initCancelButton() {
+ views.qrCodeLoginShowQrCodeCancelButton.debouncedClicks {
+ activity?.onBackPressedDispatcher?.onBackPressed()
+ }
+ }
+
+ private fun setInstructions(loginType: QrCodeLoginType) {
+ if (loginType == QrCodeLoginType.LOGIN) {
+ views.qrCodeLoginShowQrCodeHeaderView.setDescription(getString(R.string.qr_code_login_header_show_qr_code_new_device_description))
+ views.qrCodeLoginShowQrCodeInstructionsView.setInstructions(
+ listOf(
+ getString(R.string.qr_code_login_new_device_instruction_1),
+ getString(R.string.qr_code_login_new_device_instruction_2),
+ getString(R.string.qr_code_login_new_device_instruction_3),
+ )
+ )
+ } else {
+ views.qrCodeLoginShowQrCodeHeaderView.setDescription(getString(R.string.qr_code_login_header_show_qr_code_link_a_device_description))
+ views.qrCodeLoginShowQrCodeInstructionsView.setInstructions(
+ listOf(
+ getString(R.string.qr_code_login_link_a_device_show_qr_code_instruction_1),
+ getString(R.string.qr_code_login_link_a_device_show_qr_code_instruction_2),
+ )
+ )
+ }
+ }
+
+ private fun showQrCode(qrCodeData: String) {
+ views.qrCodeLoginSHowQrCodeImageView.setData(qrCodeData)
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ state.generatedQrCodeData?.let { qrCodeData ->
+ showQrCode(qrCodeData)
+ }
+ setInstructions(state.loginType)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt
new file mode 100644
index 0000000000..6ef261e6d9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginStatusFragment.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.withState
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentQrCodeLoginStatusBinding
+import im.vector.app.features.themes.ThemeUtils
+import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
+
+@AndroidEntryPoint
+class QrCodeLoginStatusFragment : VectorBaseFragment() {
+
+ private val viewModel: QrCodeLoginViewModel by activityViewModel()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeLoginStatusBinding {
+ return FragmentQrCodeLoginStatusBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ initCancelButton()
+ initTryAgainButton()
+ }
+
+ private fun initTryAgainButton() {
+ views.qrCodeLoginStatusTryAgainButton.debouncedClicks {
+ viewModel.handle(QrCodeLoginAction.TryAgain)
+ }
+ }
+
+ private fun initCancelButton() {
+ views.qrCodeLoginStatusCancelButton.debouncedClicks {
+ activity?.onBackPressedDispatcher?.onBackPressed()
+ }
+ }
+
+ private fun handleFailed(connectionStatus: QrCodeLoginConnectionStatus.Failed) {
+ views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
+ views.qrCodeLoginStatusLoadingLayout.isVisible = false
+ views.qrCodeLoginStatusHeaderView.isVisible = true
+ views.qrCodeLoginStatusSecurityCode.isVisible = false
+ views.qrCodeLoginStatusNoMatchLayout.isVisible = false
+ views.qrCodeLoginStatusCancelButton.isVisible = true
+ views.qrCodeLoginStatusTryAgainButton.isVisible = connectionStatus.canTryAgain
+ views.qrCodeLoginStatusHeaderView.setTitle(getString(R.string.qr_code_login_header_failed_title))
+ views.qrCodeLoginStatusHeaderView.setDescription(getErrorDescription(connectionStatus.errorType))
+ views.qrCodeLoginStatusHeaderView.setImage(
+ imageResource = R.drawable.ic_qr_code_login_failed,
+ backgroundTintColor = ThemeUtils.getColor(requireContext(), R.attr.colorError)
+ )
+ }
+
+ private fun getErrorDescription(reason: RendezvousFailureReason): String {
+ return when (reason) {
+ RendezvousFailureReason.UnsupportedAlgorithm,
+ RendezvousFailureReason.UnsupportedTransport -> getString(R.string.qr_code_login_header_failed_device_is_not_supported_description)
+ RendezvousFailureReason.UnsupportedHomeserver -> getString(R.string.qr_code_login_header_failed_homeserver_is_not_supported_description)
+ RendezvousFailureReason.Expired -> getString(R.string.qr_code_login_header_failed_timeout_description)
+ RendezvousFailureReason.UserDeclined -> getString(R.string.qr_code_login_header_failed_denied_description)
+ RendezvousFailureReason.E2EESecurityIssue -> getString(R.string.qr_code_login_header_failed_e2ee_security_issue_description)
+ RendezvousFailureReason.OtherDeviceAlreadySignedIn -> getString(R.string.qr_code_login_header_failed_other_device_already_signed_in_description)
+ RendezvousFailureReason.OtherDeviceNotSignedIn -> getString(R.string.qr_code_login_header_failed_other_device_not_signed_in_description)
+ RendezvousFailureReason.InvalidCode -> getString(R.string.qr_code_login_header_failed_invalid_qr_code_description)
+ RendezvousFailureReason.UserCancelled -> getString(R.string.qr_code_login_header_failed_user_cancelled_description)
+ else -> getString(R.string.qr_code_login_header_failed_other_description)
+ }
+ }
+
+ private fun handleConnectingToDevice() {
+ views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
+ views.qrCodeLoginStatusLoadingLayout.isVisible = true
+ views.qrCodeLoginStatusHeaderView.isVisible = false
+ views.qrCodeLoginStatusSecurityCode.isVisible = false
+ views.qrCodeLoginStatusNoMatchLayout.isVisible = false
+ views.qrCodeLoginStatusCancelButton.isVisible = true
+ views.qrCodeLoginStatusTryAgainButton.isVisible = false
+ views.qrCodeLoginStatusLoadingTextView.setText(R.string.qr_code_login_connecting_to_device)
+ }
+
+ private fun handleSigningIn() {
+ views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = false
+ views.qrCodeLoginStatusLoadingLayout.isVisible = true
+ views.qrCodeLoginStatusHeaderView.apply {
+ isVisible = true
+ setTitle(getString(R.string.dialog_title_success))
+ setDescription("")
+ setImage(R.drawable.ic_tick, ThemeUtils.getColor(requireContext(), R.attr.colorPrimary))
+ }
+ views.qrCodeLoginStatusSecurityCode.isVisible = false
+ views.qrCodeLoginStatusNoMatchLayout.isVisible = false
+ views.qrCodeLoginStatusCancelButton.isVisible = false
+ views.qrCodeLoginStatusTryAgainButton.isVisible = false
+ views.qrCodeLoginStatusLoadingTextView.setText(R.string.qr_code_login_signing_in)
+ }
+
+ private fun handleConnectionEstablished(connectionStatus: QrCodeLoginConnectionStatus.Connected, loginType: QrCodeLoginType) {
+ views.qrCodeLoginConfirmSecurityCodeLayout.isVisible = loginType == QrCodeLoginType.LINK_A_DEVICE
+ views.qrCodeLoginStatusLoadingLayout.isVisible = false
+ views.qrCodeLoginStatusHeaderView.isVisible = true
+ views.qrCodeLoginStatusSecurityCode.isVisible = true
+ views.qrCodeLoginStatusNoMatchLayout.isVisible = loginType == QrCodeLoginType.LOGIN
+ views.qrCodeLoginStatusCancelButton.isVisible = true
+ views.qrCodeLoginStatusTryAgainButton.isVisible = false
+ views.qrCodeLoginStatusSecurityCode.text = connectionStatus.securityCode
+ views.qrCodeLoginStatusHeaderView.setTitle(getString(R.string.qr_code_login_header_connected_title))
+ views.qrCodeLoginStatusHeaderView.setDescription(getString(R.string.qr_code_login_header_connected_description))
+ views.qrCodeLoginStatusHeaderView.setImage(
+ imageResource = R.drawable.ic_qr_code_login_connected,
+ backgroundTintColor = ThemeUtils.getColor(requireContext(), R.attr.colorPrimary)
+ )
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ when (state.connectionStatus) {
+ is QrCodeLoginConnectionStatus.Connected -> handleConnectionEstablished(state.connectionStatus, state.loginType)
+ QrCodeLoginConnectionStatus.ConnectingToDevice -> handleConnectingToDevice()
+ QrCodeLoginConnectionStatus.SigningIn -> handleSigningIn()
+ is QrCodeLoginConnectionStatus.Failed -> handleFailed(state.connectionStatus)
+ null -> { /* NOOP */ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginType.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginType.kt
new file mode 100644
index 0000000000..b4bb5b667f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginType.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.features.login.qr
+
+enum class QrCodeLoginType {
+ LOGIN,
+ LINK_A_DEVICE,
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt
new file mode 100644
index 0000000000..e20ea6b2e8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewEvents.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class QrCodeLoginViewEvents : VectorViewEvents {
+ object NavigateToStatusScreen : QrCodeLoginViewEvents()
+ object NavigateToShowQrCodeScreen : QrCodeLoginViewEvents()
+ object NavigateToHomeScreen : QrCodeLoginViewEvents()
+ object NavigateToInitialScreen : QrCodeLoginViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt
new file mode 100644
index 0000000000..97cca9d791
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewModel.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.login.qr
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.session.ConfigureAndStartSessionUseCase
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.auth.AuthenticationService
+import org.matrix.android.sdk.api.rendezvous.Rendezvous
+import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
+import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
+import timber.log.Timber
+
+class QrCodeLoginViewModel @AssistedInject constructor(
+ @Assisted private val initialState: QrCodeLoginViewState,
+ private val authenticationService: AuthenticationService,
+ private val activeSessionHolder: ActiveSessionHolder,
+ private val configureAndStartSessionUseCase: ConfigureAndStartSessionUseCase,
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: QrCodeLoginViewState): QrCodeLoginViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
+ val TAG: String = QrCodeLoginViewModel::class.java.simpleName
+ }
+
+ override fun handle(action: QrCodeLoginAction) {
+ when (action) {
+ is QrCodeLoginAction.OnQrCodeScanned -> handleOnQrCodeScanned(action)
+ QrCodeLoginAction.GenerateQrCode -> handleQrCodeViewStarted()
+ QrCodeLoginAction.ShowQrCode -> handleShowQrCode()
+ QrCodeLoginAction.TryAgain -> handleTryAgain()
+ }
+ }
+
+ private fun handleTryAgain() {
+ setState {
+ copy(
+ connectionStatus = null
+ )
+ }
+ _viewEvents.post(QrCodeLoginViewEvents.NavigateToInitialScreen)
+ }
+
+ private fun handleShowQrCode() {
+ _viewEvents.post(QrCodeLoginViewEvents.NavigateToShowQrCodeScreen)
+ }
+
+ private fun handleQrCodeViewStarted() {
+ val qrCodeData = generateQrCodeData()
+ setState {
+ copy(
+ generatedQrCodeData = qrCodeData
+ )
+ }
+ }
+
+ private fun handleOnQrCodeScanned(action: QrCodeLoginAction.OnQrCodeScanned) {
+ Timber.tag(TAG).d("Scanned code of length ${action.qrCode.length}")
+
+ val rendezvous = try { Rendezvous.buildChannelFromCode(action.qrCode) } catch (t: Throwable) {
+ Timber.tag(TAG).e(t, "Error occurred during sign in")
+ if (t is RendezvousError) {
+ onFailed(t.reason)
+ } else {
+ onFailed(RendezvousFailureReason.Unknown)
+ }
+ return
+ }
+
+ setState {
+ copy(
+ connectionStatus = QrCodeLoginConnectionStatus.ConnectingToDevice
+ )
+ }
+
+ _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
+
+ viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val confirmationCode = rendezvous.startAfterScanningCode()
+ Timber.tag(TAG).i("Established secure channel with checksum: $confirmationCode")
+
+ onConnectionEstablished(confirmationCode)
+
+ val session = rendezvous.waitForLoginOnNewDevice(authenticationService)
+ onSigningIn()
+
+ activeSessionHolder.setActiveSession(session)
+ authenticationService.reset()
+ configureAndStartSessionUseCase.execute(session)
+
+ rendezvous.completeVerificationOnNewDevice(session)
+
+ _viewEvents.post(QrCodeLoginViewEvents.NavigateToHomeScreen)
+ } catch (t: Throwable) {
+ Timber.tag(TAG).e(t, "Error occurred during sign in")
+ if (t is RendezvousError) {
+ onFailed(t.reason)
+ } else {
+ onFailed(RendezvousFailureReason.Unknown)
+ }
+ }
+ }
+ }
+
+ private fun onFailed(reason: RendezvousFailureReason) {
+ _viewEvents.post(QrCodeLoginViewEvents.NavigateToStatusScreen)
+
+ setState {
+ copy(
+ connectionStatus = QrCodeLoginConnectionStatus.Failed(reason, reason.canRetry)
+ )
+ }
+ }
+
+ private fun onConnectionEstablished(securityCode: String) {
+ val canConfirmSecurityCode = initialState.loginType == QrCodeLoginType.LINK_A_DEVICE
+ setState {
+ copy(
+ connectionStatus = QrCodeLoginConnectionStatus.Connected(securityCode, canConfirmSecurityCode)
+ )
+ }
+ }
+
+ private fun onSigningIn() {
+ setState {
+ copy(
+ connectionStatus = QrCodeLoginConnectionStatus.SigningIn
+ )
+ }
+ }
+
+ /**
+ * QR code generation is not currently supported and this is a placeholder for future
+ * functionality.
+ */
+ private fun generateQrCodeData(): String {
+ return "NOT SUPPORTED"
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewState.kt b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewState.kt
new file mode 100644
index 0000000000..0c4457c12f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/login/qr/QrCodeLoginViewState.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.features.login.qr
+
+import com.airbnb.mvrx.MavericksState
+
+data class QrCodeLoginViewState(
+ val loginType: QrCodeLoginType,
+ val connectionStatus: QrCodeLoginConnectionStatus? = null,
+ val generatedQrCodeData: String? = null,
+) : MavericksState {
+
+ constructor(args: QrCodeLoginArgs) : this(
+ loginType = args.loginType,
+ )
+}
diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt
index 08d87b528c..089fdcebd4 100644
--- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt
@@ -47,6 +47,8 @@ import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.attachmentviewer.AttachmentCommands
import im.vector.lib.attachmentviewer.AttachmentViewerActivity
+import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -67,14 +69,9 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
val sharedTransitionName: String?
) : Parcelable
- @Inject
- lateinit var sessionHolder: ActiveSessionHolder
-
- @Inject
- lateinit var dataSourceFactory: AttachmentProviderFactory
-
- @Inject
- lateinit var imageContentRenderer: ImageContentRenderer
+ @Inject lateinit var activeSessionHolder: ActiveSessionHolder
+ @Inject lateinit var dataSourceFactory: AttachmentProviderFactory
+ @Inject lateinit var imageContentRenderer: ImageContentRenderer
private val viewModel: VectorAttachmentViewerViewModel by viewModel()
private val errorFormatter by lazy(LazyThreadSafetyMode.NONE) { singletonEntryPoint().errorFormatter() }
@@ -105,7 +102,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
transitionImageContainer.isVisible = true
// Postpone transaction a bit until thumbnail is loaded
- val mediaData: Parcelable? = intent.getParcelableExtra(EXTRA_IMAGE_DATA)
+ val mediaData: Parcelable? = intent.getParcelableExtraCompat(EXTRA_IMAGE_DATA)
if (mediaData is ImageContentRenderer.Data) {
// will be shown at end of transition
pager2.isInvisible = true
@@ -126,11 +123,11 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
}
}
- val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() }
+ val session = activeSessionHolder.getSafeActiveSession() ?: return Unit.also { finish() }
val room = args.roomId?.let { session.getRoom(it) }
- val inMemoryData = intent.getParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA)
+ val inMemoryData = intent.getParcelableArrayListExtraCompat(EXTRA_IN_MEMORY_DATA)
val sourceProvider = if (inMemoryData != null) {
initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0)
dataSourceFactory.createProvider(inMemoryData, room, lifecycleScope)
@@ -173,6 +170,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
transitionImageContainer.isVisible = true
}
isAnimatingOut = true
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
@@ -227,7 +225,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
return false
}
- private fun args() = intent.getParcelableExtra(EXTRA_ARGS)
+ private fun args() = intent.getParcelableExtraCompat(EXTRA_ARGS)
private fun scheduleStartPostponedTransition(sharedElement: View) {
sharedElement.viewTreeObserver.addOnPreDrawListener(
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 53ed307da9..3970af385e 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -70,6 +70,8 @@ import im.vector.app.features.location.live.map.LiveLocationMapViewActivity
import im.vector.app.features.location.live.map.LiveLocationMapViewArgs
import im.vector.app.features.login.LoginActivity
import im.vector.app.features.login.LoginConfig
+import im.vector.app.features.login.qr.QrCodeLoginActivity
+import im.vector.app.features.login.qr.QrCodeLoginArgs
import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.media.AttachmentData
@@ -604,6 +606,14 @@ class DefaultNavigator @Inject constructor(
activityResultLauncher.launch(screenCaptureIntent)
}
+ override fun openLoginWithQrCode(context: Context, qrCodeLoginArgs: QrCodeLoginArgs) {
+ QrCodeLoginActivity
+ .getIntent(context, qrCodeLoginArgs)
+ .also {
+ context.startActivity(it)
+ }
+ }
+
private fun Intent.start(context: Context) {
context.startActivity(this)
}
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index 3521a02775..1d67f883a3 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -31,6 +31,7 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.location.LocationData
import im.vector.app.features.location.LocationSharingMode
import im.vector.app.features.login.LoginConfig
+import im.vector.app.features.login.qr.QrCodeLoginArgs
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.media.AttachmentData
import im.vector.app.features.pin.PinMode
@@ -201,4 +202,9 @@ interface Navigator {
screenCaptureIntent: Intent,
activityResultLauncher: ActivityResultLauncher
)
+
+ fun openLoginWithQrCode(
+ context: Context,
+ qrCodeLoginArgs: QrCodeLoginArgs,
+ )
}
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 4ee7da4b64..ba1d5c7f6f 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
@@ -38,7 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.supportsNotification
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
-import org.matrix.android.sdk.api.session.getUser
+import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
@@ -112,7 +112,7 @@ class NotifiableEventResolver @Inject constructor(
val notificationAction = actions.toNotificationAction()
return if (notificationAction.shouldNotify) {
- val user = session.getUser(event.senderId!!) ?: return null
+ val user = session.getUserOrDefault(event.senderId!!)
val timelineEvent = TimelineEvent(
root = event,
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
index 5f43ff6b90..2623045cf3 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
@@ -27,7 +27,7 @@ import im.vector.app.features.displayname.getBestName
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
-import org.matrix.android.sdk.api.session.getUser
+import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber
import javax.inject.Inject
@@ -186,11 +186,11 @@ class NotificationDrawerManager @Inject constructor(
}
private fun renderEvents(session: Session, eventsToRender: List>) {
- val user = session.getUser(session.myUserId)
+ val user = session.getUserOrDefault(session.myUserId)
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
- val myUserDisplayName = user?.toMatrixItem()?.getBestName() ?: session.myUserId
+ val myUserDisplayName = user.toMatrixItem().getBestName()
val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(
- contentUrl = user?.avatarUrl,
+ contentUrl = user.avatarUrl,
width = avatarSize,
height = avatarSize,
method = ContentUrlResolver.ThumbnailMethod.SCALE
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt
index 060472a2da..e79250d39d 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt
@@ -48,7 +48,10 @@ class OnboardingActivity : VectorBaseActivity(), UnlockedA
}
override fun onBackPressed() {
- validateBackPressed { super.onBackPressed() }
+ validateBackPressed {
+ @Suppress("DEPRECATION")
+ super.onBackPressed()
+ }
}
override fun initUiAndData() {
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 9bb52fb1a5..022fea5ed1 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
@@ -118,6 +118,35 @@ class OnboardingViewModel @AssistedInject constructor(
}
}
+ private fun checkQrCodeLoginCapability(homeServerUrl: String) {
+ if (!vectorFeatures.isQrCodeLoginEnabled()) {
+ setState {
+ copy(
+ canLoginWithQrCode = false
+ )
+ }
+ } else if (vectorFeatures.isQrCodeLoginForAllServers()) {
+ // allow for all servers
+ setState {
+ copy(
+ canLoginWithQrCode = true
+ )
+ }
+ } else {
+ viewModelScope.launch {
+ // check if selected server supports MSC3882 first
+ homeServerConnectionConfigFactory.create(homeServerUrl)?.let {
+ val canLoginWithQrCode = authenticationService.isQrLoginSupported(it)
+ setState {
+ copy(
+ canLoginWithQrCode = canLoginWithQrCode
+ )
+ }
+ }
+ }
+ }
+ }
+
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
private val defaultHomeserverUrl = matrixOrgUrl
@@ -641,6 +670,7 @@ class OnboardingViewModel @AssistedInject constructor(
val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities()
val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities?.firstOrNull()
state.personalizationState.copy(
+ userId = session.myUserId,
displayName = state.registrationState.selectedMatrixId?.let { MatrixPatterns.extractUserNameFromId(it) },
supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName,
supportsChangingProfilePicture = capabilityOverrides?.canChangeAvatar ?: homeServerCapabilities.canChangeAvatar
@@ -680,6 +710,7 @@ class OnboardingViewModel @AssistedInject constructor(
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
} else {
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
+ checkQrCodeLoginCapability(homeServerConnectionConfig.homeServerUri.toString())
}
}
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 99678ea5c1..6e7d58338e 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
@@ -58,7 +58,9 @@ data class OnboardingViewState(
val selectedAuthenticationState: SelectedAuthenticationState = SelectedAuthenticationState(),
@PersistState
- val personalizationState: PersonalizationState = PersonalizationState()
+ val personalizationState: PersonalizationState = PersonalizationState(),
+
+ val canLoginWithQrCode: Boolean = false,
) : MavericksState
enum class OnboardingFlow {
@@ -78,10 +80,11 @@ data class SelectedHomeserverState(
@Parcelize
data class PersonalizationState(
+ val userId: String = "",
val supportsChangingDisplayName: Boolean = false,
val supportsChangingProfilePicture: Boolean = false,
val displayName: String? = null,
- val selectedPictureUri: Uri? = null
+ val selectedPictureUri: Uri? = null,
) : Parcelable {
fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
index f3cb326221..9fd0bd08ed 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
@@ -123,6 +123,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment
displayCancelDialog = false
+ @Suppress("DEPRECATION")
vectorBaseActivity.onBackPressed()
}
.setNegativeButton(R.string.no, null)
@@ -137,6 +138,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment
displayCancelDialog = false
+ @Suppress("DEPRECATION")
vectorBaseActivity.onBackPressed()
}
.setNegativeButton(R.string.no, null)
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 a53ca52e85..2089dc5ad0 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
@@ -26,21 +26,17 @@ import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.animations.play
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.utils.isAnimationEnabled
import im.vector.app.core.utils.styleMatchingText
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
@AndroidEntryPoint
class FtueAuthAccountCreatedFragment :
AbstractFtueAuthFragment() {
- @Inject lateinit var activeSessionHolder: ActiveSessionHolder
-
private var hasPlayedConfetti = false
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAccountCreatedBinding {
@@ -53,15 +49,15 @@ class FtueAuthAccountCreatedFragment :
}
private fun setupViews() {
- val userId = activeSessionHolder.getActiveSession().myUserId
- val subtitle = getString(R.string.ftue_account_created_subtitle, userId).toSpannable().styleMatchingText(userId, Typeface.BOLD)
- views.accountCreatedSubtitle.text = subtitle
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 userId = state.personalizationState.userId
+ val subtitle = getString(R.string.ftue_account_created_subtitle, userId).toSpannable().styleMatchingText(userId, Typeface.BOLD)
+ views.accountCreatedSubtitle.text = subtitle
val canPersonalize = state.personalizationState.supportsPersonalization()
views.personalizeButtonGroup.isVisible = canPersonalize
views.takeMeHomeButtonGroup.isVisible = !canPersonalize
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 92d0aa2a0f..5450c74095 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
@@ -26,7 +26,6 @@ import androidx.core.view.isInvisible
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory
import im.vector.app.databinding.FragmentFtueProfilePictureBinding
@@ -42,7 +41,6 @@ class FtueAuthChooseProfilePictureFragment :
AbstractFtueAuthFragment(),
GalleryOrCameraDialogHelper.Listener {
- @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory
@Inject lateinit var avatarRenderer: AvatarRenderer
@@ -85,10 +83,9 @@ class FtueAuthChooseProfilePictureFragment :
views.profilePictureSubmit.isEnabled = hasSetPicture
views.changeProfilePictureIcon.setImageResource(if (hasSetPicture) R.drawable.ic_edit else R.drawable.ic_camera_plain)
- val session = activeSessionHolder.getActiveSession()
val matrixItem = MatrixItem.UserItem(
- id = session.myUserId,
- displayName = state.personalizationState.displayName ?: ""
+ id = state.personalizationState.userId,
+ displayName = state.personalizationState.displayName.orEmpty()
)
avatarRenderer.render(matrixItem, localUri = state.personalizationState.selectedPictureUri, imageView = views.profilePictureView)
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
index 6877810f0a..aad54877c9 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
@@ -36,10 +36,13 @@ import im.vector.app.core.extensions.setOnFocusLostListener
import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentFtueCombinedLoginBinding
+import im.vector.app.features.VectorFeatures
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.SsoState
+import im.vector.app.features.login.qr.QrCodeLoginArgs
+import im.vector.app.features.login.qr.QrCodeLoginType
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewEvents
@@ -55,6 +58,7 @@ class FtueAuthCombinedLoginFragment :
@Inject lateinit var loginFieldsValidation: LoginFieldsValidation
@Inject lateinit var loginErrorParser: LoginErrorParser
+ @Inject lateinit var vectorFeatures: VectorFeatures
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedLoginBinding {
return FragmentFtueCombinedLoginBinding.inflate(inflater, container, false)
@@ -70,6 +74,26 @@ class FtueAuthCombinedLoginFragment :
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(views.loginInput.content()))
}
views.loginForgotPassword.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnForgetPasswordClicked)) }
+
+ viewModel.onEach(OnboardingViewState::canLoginWithQrCode) {
+ configureQrCodeLoginButtonVisibility(it)
+ }
+ }
+
+ private fun configureQrCodeLoginButtonVisibility(canLoginWithQrCode: Boolean) {
+ views.loginWithQrCode.isVisible = canLoginWithQrCode
+ if (canLoginWithQrCode) {
+ views.loginWithQrCode.debouncedClicks {
+ navigator
+ .openLoginWithQrCode(
+ requireActivity(),
+ QrCodeLoginArgs(
+ loginType = QrCodeLoginType.LOGIN,
+ showQrCodeImmediately = false,
+ )
+ )
+ }
+ }
}
private fun setupSubmitButton() {
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 6fe1b5b7bb..7467104da1 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
@@ -54,6 +54,7 @@ import im.vector.app.features.onboarding.OnboardingViewState
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsLegacyStyleFragmentArgument
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -107,7 +108,7 @@ class FtueAuthVariant(
}
// Get config extra
- val loginConfig = activity.intent.getParcelableExtra(OnboardingActivity.EXTRA_CONFIG)
+ val loginConfig = activity.intent.getParcelableExtraCompat(OnboardingActivity.EXTRA_CONFIG)
if (isFirstCreation) {
onboardingViewModel.handle(OnboardingAction.InitWith(loginConfig))
}
diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkFactory.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkFactory.kt
new file mode 100644
index 0000000000..378ffc4569
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkFactory.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.permalink
+
+import org.matrix.android.sdk.api.session.Session
+import javax.inject.Inject
+
+/**
+ * Contains synchronous methods to create permalinks from the Session.
+ */
+class PermalinkFactory @Inject constructor(
+ private val session: Session,
+) {
+ fun createPermalinkOfCurrentUser(): String? {
+ return createPermalink(session.myUserId)
+ }
+
+ fun createPermalink(id: String): String? {
+ return session.permalinkService().createPermalink(id)
+ }
+
+ fun createPermalink(roomId: String, eventId: String): String {
+ return session.permalinkService().createPermalink(roomId, eventId)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt
index faf15d8006..cef0064fd0 100644
--- a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt
@@ -23,6 +23,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
+import im.vector.lib.core.utils.compat.getParcelableCompat
@AndroidEntryPoint
class PinActivity : VectorBaseActivity(), UnlockedActivity {
@@ -41,7 +42,7 @@ class PinActivity : VectorBaseActivity(), UnlockedActivit
override fun initUiAndData() {
if (isFirstCreation()) {
- val fragmentArgs: PinArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
+ val fragmentArgs: PinArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
addFragment(views.simpleFragmentContainer, PinFragment::class.java, fragmentArgs)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt
index eb72d86bf4..d9d6d56737 100644
--- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt
+++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt
@@ -142,7 +142,8 @@ class LockScreenCodeView @JvmOverloads constructor(
var codeLength: Int = 0
constructor(source: Parcel) : super(source) {
- source.readList(code, null)
+ val codeStr = source.readString().orEmpty()
+ code = codeStr.toMutableList()
codeLength = source.readInt()
}
@@ -150,7 +151,7 @@ class LockScreenCodeView @JvmOverloads constructor(
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
- out.writeList(code)
+ out.writeString(String(code.toCharArray()))
out.writeInt(codeLength)
}
diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt
index 5a93e19b1a..3dfec3c255 100644
--- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollActivity.kt
@@ -23,6 +23,7 @@ import android.view.View
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
+import im.vector.lib.core.utils.compat.getParcelableCompat
@AndroidEntryPoint
class CreatePollActivity : SimpleFragmentActivity() {
@@ -31,7 +32,7 @@ class CreatePollActivity : SimpleFragmentActivity() {
super.onCreate(savedInstanceState)
views.toolbar.visibility = View.GONE
- val createPollArgs: CreatePollArgs? = intent?.extras?.getParcelable(EXTRA_CREATE_POLL_ARGS)
+ val createPollArgs: CreatePollArgs? = intent?.extras?.getParcelableCompat(EXTRA_CREATE_POLL_ARGS)
if (isFirstCreation()) {
addFragment(
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt
index e24c57c6de..3167eebc9f 100644
--- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerViewModel.kt
@@ -24,11 +24,9 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorDummyViewState
import im.vector.app.core.platform.VectorViewModel
-import org.matrix.android.sdk.api.session.Session
class QrCodeScannerViewModel @AssistedInject constructor(
@Assisted initialState: VectorDummyViewState,
- val session: Session
) : VectorViewModel(initialState) {
@AssistedFactory
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
index c7f549c5cb..78d3347cbe 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt
@@ -128,7 +128,7 @@ class BugReportActivity :
val isValid = !views.bugReportMaskView.isVisible
it.isEnabled = isValid
- it.icon.alpha = if (isValid) 255 else 100
+ it.icon?.alpha = if (isValid) 255 else 100
}
}
@@ -267,6 +267,7 @@ class BugReportActivity :
// Ensure there is no crash status remaining, which will be sent later on by mistake
bugReporter.deleteCrashFile()
+ @Suppress("DEPRECATION")
super.onBackPressed()
}
diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt
index f9be57b13f..24a45872d5 100644
--- a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt
@@ -146,7 +146,7 @@ class EmojiReactionPickerActivity :
val searchItem = menu.findItem(R.id.search)
(searchItem.actionView as? SearchView)?.let { searchView ->
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
- override fun onMenuItemActionExpand(p0: MenuItem?): Boolean {
+ override fun onMenuItemActionExpand(p0: MenuItem): Boolean {
searchView.isIconified = false
searchView.requestFocusFromTouch()
// we want to force the tool bar as visible even if hidden with scroll flags
@@ -154,7 +154,7 @@ class EmojiReactionPickerActivity :
return true
}
- override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
+ override fun onMenuItemActionCollapse(p0: MenuItem): Boolean {
// when back, clear all search
views.emojiPickerToolbar.minimumHeight = 0
searchView.setQuery("", true)
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt
index 7b5cc20910..e541e7f879 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt
@@ -37,12 +37,12 @@ import im.vector.app.core.utils.toast
import im.vector.app.databinding.FragmentPublicRoomsBinding
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.permalink.NavigationInterceptor
+import im.vector.app.features.permalink.PermalinkFactory
import im.vector.app.features.permalink.PermalinkHandler
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.Session
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import reactivecircus.flowbinding.appcompat.queryTextChanges
import timber.log.Timber
@@ -60,7 +60,7 @@ class PublicRoomsFragment :
@Inject lateinit var publicRoomsController: PublicRoomsController
@Inject lateinit var permalinkHandler: PermalinkHandler
- @Inject lateinit var session: Session
+ @Inject lateinit var permalinkFactory: PermalinkFactory
private val viewModel: RoomDirectoryViewModel by activityViewModel()
private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel
@@ -128,7 +128,7 @@ class PublicRoomsFragment :
override fun onUnknownRoomClicked(roomIdOrAlias: String) {
viewLifecycleOwner.lifecycleScope.launch {
- val permalink = session.permalinkService().createPermalink(roomIdOrAlias)
+ val permalink = permalinkFactory.createPermalink(roomIdOrAlias)
val isHandled = permalinkHandler
.launch(requireActivity(), permalink, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean {
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt
index d2b9b16086..171d51e811 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt
@@ -29,6 +29,7 @@ import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -46,7 +47,7 @@ class CreateRoomActivity : VectorBaseActivity() {
override fun initUiAndData() {
if (isFirstCreation()) {
- val fragmentArgs: CreateRoomArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
+ val fragmentArgs: CreateRoomArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
addFragment(
views.simpleFragmentContainer,
CreateRoomFragment::class.java,
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt
index f4c3e515c5..59657aae35 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt
@@ -100,7 +100,10 @@ class CreateRoomFragment :
.allowBack(useCross = true)
viewModel.observeViewEvents {
when (it) {
- CreateRoomViewEvents.Quit -> vectorBaseActivity.onBackPressed()
+ CreateRoomViewEvents.Quit -> {
+ @Suppress("DEPRECATION")
+ vectorBaseActivity.onBackPressed()
+ }
is CreateRoomViewEvents.Failure -> showFailure(it.throwable)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt
index b69788b1ed..a70fd5ef23 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt
@@ -24,6 +24,7 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.RoomDirectoryData
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
@@ -83,7 +84,7 @@ class RoomPreviewActivity : VectorBaseActivity() {
override fun initUiAndData() {
if (isFirstCreation()) {
- val args = intent.getParcelableExtra(ARG)
+ val args = intent.getParcelableExtraCompat(ARG)
if (args?.worldReadable == true) {
// TODO Room preview: Note: M does not recommend to use /events anymore, so for now we just display the room preview
diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt
index 1b55207743..26a2a7de22 100644
--- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt
@@ -28,6 +28,7 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.room.RequireActiveMembershipViewEvents
import im.vector.app.features.room.RequireActiveMembershipViewModel
+import im.vector.lib.core.utils.compat.getParcelableCompat
@AndroidEntryPoint
class RoomMemberProfileActivity : VectorBaseActivity() {
@@ -48,7 +49,7 @@ class RoomMemberProfileActivity : VectorBaseActivity() {
override fun initUiAndData() {
if (isFirstCreation()) {
- val fragmentArgs: RoomMemberProfileArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
+ val fragmentArgs: RoomMemberProfileArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
addFragment(views.simpleFragmentContainer, RoomMemberProfileFragment::class.java, fragmentArgs)
}
diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt
index 92687e1a37..eb23c5654e 100644
--- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt
@@ -29,11 +29,13 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.SingletonEntryPoint
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
-import org.matrix.android.sdk.api.session.getUser
+import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow
@@ -42,7 +44,6 @@ data class DeviceListViewState(
val userId: String,
val allowDeviceAction: Boolean,
val userItem: MatrixItem? = null,
- val isMine: Boolean = false,
val memberCrossSigningKey: MXCrossSigningInfo? = null,
val cryptoDevices: Async> = Loading(),
val selectedDevice: CryptoDeviceInfo? = null
@@ -61,23 +62,19 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
- override fun initialState(viewModelContext: ViewModelContext): DeviceListViewState? {
+ override fun initialState(viewModelContext: ViewModelContext): DeviceListViewState {
val args = viewModelContext.args()
val userId = args.userId
val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession()
- return session.getUser(userId)?.toMatrixItem()?.let {
- DeviceListViewState(
- userId = userId,
- allowDeviceAction = args.allowDeviceAction,
- userItem = it,
- isMine = userId == session.myUserId
- )
- } ?: return super.initialState(viewModelContext)
+ return DeviceListViewState(
+ userId = userId,
+ allowDeviceAction = args.allowDeviceAction,
+ userItem = session.getUserOrDefault(userId).toMatrixItem(),
+ )
}
}
init {
-
session.flow().liveUserCryptoDevices(initialState.userId)
.execute {
copy(cryptoDevices = it).also {
@@ -89,6 +86,16 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(
.execute {
copy(memberCrossSigningKey = it.invoke()?.getOrNull())
}
+
+ updateMatrixItem()
+ }
+
+ private fun updateMatrixItem() {
+ viewModelScope.launch {
+ tryOrNull { session.userService().resolveUser(initialState.userId) }
+ ?.toMatrixItem()
+ ?.let { setState { copy(userItem = it) } }
+ }
}
override fun handle(action: DeviceListAction) {
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
index 12f1db1366..526d676dee 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt
@@ -38,6 +38,7 @@ import im.vector.app.features.roomprofile.notifications.RoomNotificationSettings
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
+import im.vector.lib.core.utils.compat.getParcelableCompat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@@ -68,8 +69,7 @@ class RoomProfileActivity :
private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel()
- @Inject
- lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore
+ @Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore
override fun getBinding(): ActivitySimpleBinding {
return ActivitySimpleBinding.inflate(layoutInflater)
@@ -77,7 +77,7 @@ class RoomProfileActivity :
override fun initUiAndData() {
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
- roomProfileArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
+ roomProfileArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
if (isFirstCreation()) {
when (intent?.extras?.getInt(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOM_ROOT)) {
EXTRA_DIRECT_ACCESS_ROOM_SETTINGS -> {
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt
index ba50890db3..26da1a45d2 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt
@@ -106,6 +106,7 @@ class RoomSettingsFragment :
RoomSettingsViewEvents.Success -> showSuccess()
RoomSettingsViewEvents.GoBack -> {
ignoreChanges = true
+ @Suppress("DEPRECATION")
vectorBaseActivity.onBackPressed()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt
index e7b0087755..818300ac72 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt
@@ -29,7 +29,6 @@ import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
@@ -43,7 +42,7 @@ import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRul
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
-import javax.inject.Inject
+import im.vector.lib.core.utils.compat.getParcelableCompat
@AndroidEntryPoint
class RoomJoinRuleActivity : VectorBaseActivity() {
@@ -52,13 +51,10 @@ class RoomJoinRuleActivity : VectorBaseActivity() {
private lateinit var roomProfileArgs: RoomProfileArgs
- @Inject
- lateinit var errorFormatter: ErrorFormatter
-
val viewModel: RoomJoinRuleChooseRestrictedViewModel by viewModel()
override fun initUiAndData() {
- roomProfileArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return
+ roomProfileArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return
if (isFirstCreation()) {
addFragment(
views.simpleFragmentContainer,
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index aaf328e9c7..2dc8b12160 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -73,6 +73,8 @@ class VectorPreferences @Inject constructor(
const val SETTINGS_LABS_DEFERRED_DM_KEY = "SETTINGS_LABS_DEFERRED_DM_KEY"
const val SETTINGS_LABS_RICH_TEXT_EDITOR_KEY = "SETTINGS_LABS_RICH_TEXT_EDITOR_KEY"
const val SETTINGS_LABS_NEW_SESSION_MANAGER_KEY = "SETTINGS_LABS_NEW_SESSION_MANAGER_KEY"
+ const val SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY = "SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY"
+ const val SETTINGS_LABS_VOICE_BROADCAST_KEY = "SETTINGS_LABS_VOICE_BROADCAST_KEY"
const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
@@ -1188,6 +1190,13 @@ class VectorPreferences @Inject constructor(
return defaultPrefs.getBoolean(SETTINGS_LABS_NEW_SESSION_MANAGER_KEY, getDefault(R.bool.settings_labs_new_session_manager_default))
}
+ /**
+ * Indicates whether or not client info recording is enabled.
+ */
+ fun isClientInfoRecordingEnabled(): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY, getDefault(R.bool.settings_labs_client_info_recording_default))
+ }
+
fun showLiveSenderInfo(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default))
}
@@ -1195,4 +1204,9 @@ class VectorPreferences @Inject constructor(
fun isRichTextEditorEnabled(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LABS_RICH_TEXT_EDITOR_KEY, getDefault(R.bool.settings_labs_rich_text_editor_default))
}
+
+ fun isVoiceBroadcastEnabled(): Boolean {
+ return vectorFeatures.isVoiceBroadcastEnabled() &&
+ defaultPrefs.getBoolean(SETTINGS_LABS_VOICE_BROADCAST_KEY, getDefault(R.bool.settings_labs_enable_voice_broadcast_default))
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt
index 261f30ebe3..4a9db49c67 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt
@@ -34,6 +34,7 @@ import im.vector.app.features.navigation.SettingsActivityPayload
import im.vector.app.features.settings.devices.VectorSettingsDevicesFragment
import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment
import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
@@ -76,13 +77,19 @@ class VectorSettingsActivity : VectorBaseActivity
replaceFragment(views.vectorSettingsPage, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG)
SettingsActivityPayload.SecurityPrivacy ->
replaceFragment(views.vectorSettingsPage, VectorSettingsSecurityPrivacyFragment::class.java, null, FRAGMENT_TAG)
- SettingsActivityPayload.SecurityPrivacyManageSessions ->
+ SettingsActivityPayload.SecurityPrivacyManageSessions -> {
+ val fragmentClass = if (vectorPreferences.isNewSessionManagerEnabled()) {
+ im.vector.app.features.settings.devices.v2.VectorSettingsDevicesFragment::class.java
+ } else {
+ VectorSettingsDevicesFragment::class.java
+ }
replaceFragment(
views.vectorSettingsPage,
- VectorSettingsDevicesFragment::class.java,
+ fragmentClass,
null,
FRAGMENT_TAG
)
+ }
SettingsActivityPayload.Notifications -> {
requestHighlightPreferenceKeyOnResume(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)
replaceFragment(views.vectorSettingsPage, VectorSettingsNotificationPreferenceFragment::class.java, null, FRAGMENT_TAG)
@@ -194,8 +201,8 @@ class VectorSettingsActivity : VectorBaseActivity
}
}
-private fun Activity.readPayload(default: T): T {
- return intent.getParcelableExtra(KEY_ACTIVITY_PAYLOAD) ?: default
+private inline fun Activity.readPayload(default: T): T {
+ return intent.getParcelableExtraCompat(KEY_ACTIVITY_PAYLOAD) ?: default
}
private fun Intent.applyPayload(payload: T): Intent {
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt
index 28e167779d..1cb7dcbf28 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.utils.RingtoneUtils
import im.vector.app.features.analytics.plan.MobileScreen
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import javax.inject.Inject
@AndroidEntryPoint
@@ -69,7 +70,7 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() {
private val ringtoneStartForActivityResult = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
- val callRingtoneUri: Uri? = activityResult.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
+ val callRingtoneUri: Uri? = activityResult.data?.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
if (callRingtoneUri != null) {
ringtoneUtils.setCallRingtoneUri(callRingtoneUri)
mCallRingtonePreference.summary = ringtoneUtils.getCallRingtoneName()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
index 42e4cebe4c..6adb33d5ab 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
@@ -57,7 +57,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
} else {
emptyList()
}
- filterDevicesUseCase.execute(deviceFullInfoList, filterType, excludedDeviceIds)
+ filterDevicesUseCase.execute(currentSessionCrossSigningInfo, deviceFullInfoList, filterType, excludedDeviceIds)
}
deviceFullInfoFlow.distinctUntilChanged()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 47ea96c09d..c507699e0b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -35,8 +35,11 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.FragmentSettingsDevicesBinding
+import im.vector.app.features.VectorFeatures
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.crypto.verification.VerificationBottomSheet
+import im.vector.app.features.login.qr.QrCodeLoginArgs
+import im.vector.app.features.login.qr.QrCodeLoginType
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
@@ -44,6 +47,7 @@ import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INAC
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import javax.inject.Inject
/**
@@ -62,6 +66,8 @@ class VectorSettingsDevicesFragment :
@Inject lateinit var colorProvider: ColorProvider
+ @Inject lateinit var vectorFeatures: VectorFeatures
+
@Inject lateinit var stringProvider: StringProvider
private val viewModel: DevicesViewModel by fragmentViewModel()
@@ -87,6 +93,7 @@ class VectorSettingsDevicesFragment :
initWaitingView()
initOtherSessionsView()
initSecurityRecommendationsView()
+ initQrLoginView()
observeViewEvents()
}
@@ -151,6 +158,38 @@ class VectorSettingsDevicesFragment :
}
}
+ private fun initQrLoginView() {
+ if (!vectorFeatures.isReciprocateQrCodeLogin()) {
+ views.deviceListHeaderSignInWithQrCode.isVisible = false
+ views.deviceListHeaderScanQrCodeButton.isVisible = false
+ views.deviceListHeaderShowQrCodeButton.isVisible = false
+ return
+ }
+
+ views.deviceListHeaderSignInWithQrCode.isVisible = true
+ views.deviceListHeaderScanQrCodeButton.isVisible = true
+ views.deviceListHeaderShowQrCodeButton.isVisible = true
+
+ views.deviceListHeaderScanQrCodeButton.debouncedClicks {
+ navigateToQrCodeScreen(showQrCodeImmediately = false)
+ }
+
+ views.deviceListHeaderShowQrCodeButton.debouncedClicks {
+ navigateToQrCodeScreen(showQrCodeImmediately = true)
+ }
+ }
+
+ private fun navigateToQrCodeScreen(showQrCodeImmediately: Boolean) {
+ navigator
+ .openLoginWithQrCode(
+ requireActivity(),
+ QrCodeLoginArgs(
+ loginType = QrCodeLoginType.LINK_A_DEVICE,
+ showQrCodeImmediately = showQrCodeImmediately,
+ )
+ )
+ }
+
override fun onDestroyView() {
cleanUpLearnMoreButtonsListeners()
super.onDestroyView()
@@ -164,12 +203,11 @@ class VectorSettingsDevicesFragment :
if (state.devices is Success) {
val devices = state.devices()
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
- val currentDeviceInfo = devices?.firstOrNull {
- it.deviceInfo.deviceId == currentDeviceId
- }
+ val currentDeviceInfo = devices?.firstOrNull { it.deviceInfo.deviceId == currentDeviceId }
+ val isCurrentSessionVerified = currentDeviceInfo?.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
- renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount)
+ renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
renderCurrentDevice(currentDeviceInfo)
renderOtherSessionsView(otherDevices)
} else {
@@ -181,14 +219,21 @@ class VectorSettingsDevicesFragment :
handleLoadingStatus(state.isLoading)
}
- private fun renderSecurityRecommendations(inactiveSessionsCount: Int, unverifiedSessionsCount: Int) {
- if (unverifiedSessionsCount == 0 && inactiveSessionsCount == 0) {
+ private fun renderSecurityRecommendations(
+ inactiveSessionsCount: Int,
+ unverifiedSessionsCount: Int,
+ isCurrentSessionVerified: Boolean,
+ ) {
+ val isUnverifiedSectionVisible = unverifiedSessionsCount > 0 && isCurrentSessionVerified
+ val isInactiveSectionVisible = inactiveSessionsCount > 0
+ if (isUnverifiedSectionVisible.not() && isInactiveSectionVisible.not()) {
hideSecurityRecommendations()
} else {
views.deviceListHeaderSectionSecurityRecommendations.isVisible = true
views.deviceListSecurityRecommendationsDivider.isVisible = true
- views.deviceListUnverifiedSessionsRecommendation.isVisible = unverifiedSessionsCount > 0
- views.deviceListInactiveSessionsRecommendation.isVisible = inactiveSessionsCount > 0
+
+ views.deviceListUnverifiedSessionsRecommendation.isVisible = isUnverifiedSectionVisible
+ views.deviceListInactiveSessionsRecommendation.isVisible = isInactiveSectionVisible
val unverifiedSessionsViewState = SecurityRecommendationViewState(
description = getString(R.string.device_manager_unverified_sessions_description),
sessionsCount = unverifiedSessionsCount,
@@ -206,11 +251,19 @@ class VectorSettingsDevicesFragment :
}
}
+ private fun hideUnverifiedSessionsRecommendation() {
+ views.deviceListUnverifiedSessionsRecommendation.isVisible = false
+ }
+
+ private fun hideInactiveSessionsRecommendation() {
+ views.deviceListInactiveSessionsRecommendation.isVisible = false
+ }
+
private fun hideSecurityRecommendations() {
views.deviceListHeaderSectionSecurityRecommendations.isVisible = false
- views.deviceListUnverifiedSessionsRecommendation.isVisible = false
- views.deviceListInactiveSessionsRecommendation.isVisible = false
views.deviceListSecurityRecommendationsDivider.isVisible = false
+ hideUnverifiedSessionsRecommendation()
+ hideInactiveSessionsRecommendation()
}
private fun renderOtherSessionsView(otherDevices: List?) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt
index 101bf1da2e..fb4e94b630 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsActivity.kt
@@ -23,6 +23,7 @@ import com.airbnb.mvrx.Mavericks
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
/**
* Display the details info about a Session.
@@ -37,7 +38,7 @@ class SessionDetailsActivity : SimpleFragmentActivity() {
addFragment(
container = views.container,
fragmentClass = SessionDetailsFragment::class.java,
- params = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ params = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt
index a23a7a7108..8f23fd06cc 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt
@@ -17,22 +17,27 @@
package im.vector.app.features.settings.devices.v2.filter
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject
class FilterDevicesUseCase @Inject constructor() {
fun execute(
+ currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo,
devices: List,
filterType: DeviceManagerFilterType,
excludedDeviceIds: List = emptyList(),
): List {
+ val isCurrentSessionVerified = currentSessionCrossSigningInfo.isCrossSigningVerified.orFalse()
return devices
.filter {
when (filterType) {
DeviceManagerFilterType.ALL_SESSIONS -> true
- DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
- DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
+ // when current session is not verified, other session status cannot be trusted
+ DeviceManagerFilterType.VERIFIED -> isCurrentSessionVerified && it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
+ // when current session is not verified, other session status cannot be trusted
+ DeviceManagerFilterType.UNVERIFIED -> isCurrentSessionVerified && !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
DeviceManagerFilterType.INACTIVE -> it.isInactive
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
index b0ba8baa1a..59e7e1888e 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
@@ -53,7 +53,7 @@ class OtherSessionsController @Inject constructor(
data.forEach { device ->
val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
- val description = calculateDescription(device, formattedLastActivityDate)
+ val description = buildDescription(device, formattedLastActivityDate)
val descriptionColor = if (device.isCurrentDevice) {
host.colorProvider.getColorFromAttribute(R.attr.colorError)
} else {
@@ -77,7 +77,7 @@ class OtherSessionsController @Inject constructor(
}
}
- private fun calculateDescription(device: DeviceFullInfo, formattedLastActivityDate: String): String {
+ private fun buildDescription(device: DeviceFullInfo, formattedLastActivityDate: String): String {
return when {
device.isInactive -> {
stringProvider.getQuantityString(
@@ -93,6 +93,9 @@ class OtherSessionsController @Inject constructor(
device.isCurrentDevice -> {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified_current_session)
}
+ device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Default -> {
+ stringProvider.getString(R.string.device_manager_session_last_activity, formattedLastActivityDate)
+ }
else -> {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index 6f6c5b24e2..3d9c3a8f37 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -90,10 +90,10 @@ class SessionInfoView @JvmOverloads constructor(
isVerifyButtonVisible: Boolean,
) {
views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
- if (encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
- renderCrossSigningVerified(isCurrentSession)
- } else {
- renderCrossSigningUnverified(isCurrentSession, isVerifyButtonVisible)
+ when {
+ encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted -> renderCrossSigningVerified(isCurrentSession)
+ encryptionTrustLevel == RoomEncryptionTrustLevel.Default && !isCurrentSession -> renderCrossSigningUnknown()
+ else -> renderCrossSigningUnverified(isCurrentSession, isVerifyButtonVisible)
}
if (hasLearnMoreLink) {
appendLearnMoreToVerificationStatus()
@@ -142,6 +142,12 @@ class SessionInfoView @JvmOverloads constructor(
views.sessionInfoVerifySessionButton.isVisible = isVerifyButtonVisible
}
+ private fun renderCrossSigningUnknown() {
+ views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unknown)
+ views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_other_session_unknown)
+ views.sessionInfoVerifySessionButton.isVisible = false
+ }
+
private fun renderDeviceInfo(sessionName: String, deviceType: DeviceType, stringProvider: StringProvider) {
setDeviceTypeIconUseCase.execute(deviceType, views.sessionInfoDeviceTypeImageView, stringProvider)
views.sessionInfoNameTextView.text = sessionName
@@ -155,34 +161,31 @@ class SessionInfoView @JvmOverloads constructor(
drawableProvider: DrawableProvider,
colorProvider: ColorProvider,
) {
- deviceInfo.lastSeenTs
- ?.takeIf { isLastSeenDetailsVisible }
- ?.let { timestamp ->
- views.sessionInfoLastActivityTextView.isVisible = true
- views.sessionInfoLastActivityTextView.text = if (isInactive) {
- val formattedTs = dateFormatter.format(timestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)
- context.resources.getQuantityString(
- R.plurals.device_manager_other_sessions_description_inactive,
- SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
- SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
- formattedTs
- )
- } else {
- val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
- context.getString(R.string.device_manager_session_last_activity, formattedTs)
- }
- val drawable = if (isInactive) {
- val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
- drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor)
- } else {
- null
- }
- views.sessionInfoLastActivityTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
- }
- ?: run {
- views.sessionInfoLastActivityTextView.isGone = true
- }
-
+ if (deviceInfo.lastSeenTs != null && isLastSeenDetailsVisible) {
+ val timestamp = deviceInfo.lastSeenTs
+ views.sessionInfoLastActivityTextView.isVisible = true
+ views.sessionInfoLastActivityTextView.text = if (isInactive) {
+ val formattedTs = dateFormatter.format(timestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)
+ context.resources.getQuantityString(
+ R.plurals.device_manager_other_sessions_description_inactive,
+ SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
+ SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
+ formattedTs
+ )
+ } else {
+ val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
+ context.getString(R.string.device_manager_session_last_activity, formattedTs)
+ }
+ val drawable = if (isInactive) {
+ val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
+ drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor)
+ } else {
+ null
+ }
+ views.sessionInfoLastActivityTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
+ } else {
+ views.sessionInfoLastActivityTextView.isGone = true
+ }
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible })
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
index f146f77690..81e38e0e9d 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
@@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
@AndroidEntryPoint
class OtherSessionsActivity : SimpleFragmentActivity() {
@@ -39,7 +40,7 @@ class OtherSessionsActivity : SimpleFragmentActivity() {
addFragment(
container = views.container,
fragmentClass = OtherSessionsFragment::class.java,
- params = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ params = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
index 015fcccf51..5695d02ae9 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
@@ -23,6 +23,7 @@ import com.airbnb.mvrx.Mavericks
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.SimpleFragmentActivity
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
/**
* Display the overview info about a Session.
@@ -37,7 +38,7 @@ class SessionOverviewActivity : SimpleFragmentActivity() {
addFragment(
container = views.container,
fragmentClass = SessionOverviewFragment::class.java,
- params = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ params = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index 7c133bc229..a1cd7ea586 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -24,7 +24,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
@@ -42,14 +41,12 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.FragmentSessionOverviewBinding
import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.crypto.recover.SetupMode
-import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
import im.vector.app.features.workers.signout.SignOutUiWorker
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
-import org.matrix.android.sdk.api.session.pushers.Pusher
import javax.inject.Inject
/**
@@ -180,12 +177,7 @@ class SessionOverviewFragment :
updateEntryDetails(state.deviceId)
updateSessionInfo(state)
updateLoading(state.isLoading)
- updatePushNotificationToggle(state.deviceId, state.pushers.invoke().orEmpty())
- if (state.deviceInfo is Success) {
- renderSessionInfo(state.isCurrentSessionTrusted, state.deviceInfo.invoke())
- } else {
- hideSessionInfo()
- }
+ updatePushNotificationToggle(state.deviceId, state.notificationsEnabled)
}
private fun updateToolbar(viewState: SessionOverviewViewState) {
@@ -214,7 +206,7 @@ class SessionOverviewFragment :
deviceFullInfo = deviceInfo,
isVerifyButtonVisible = isCurrentSession || viewState.isCurrentSessionTrusted,
isDetailsButtonVisible = false,
- isLearnMoreLinkVisible = true,
+ isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
isLastSeenDetailsVisible = !isCurrentSession,
)
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
@@ -226,35 +218,18 @@ class SessionOverviewFragment :
}
}
- private fun updatePushNotificationToggle(deviceId: String, pushers: List) {
+ private fun updatePushNotificationToggle(deviceId: String, enabled: Boolean) {
views.sessionOverviewPushNotifications.apply {
- if (pushers.isEmpty()) {
- isVisible = false
- } else {
- val allPushersAreEnabled = pushers.all { it.enabled }
- setOnCheckedChangeListener(null)
- setChecked(allPushersAreEnabled)
- post {
- setOnCheckedChangeListener { _, isChecked ->
- viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked))
- }
+ setOnCheckedChangeListener(null)
+ setChecked(enabled)
+ post {
+ setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked))
}
}
}
}
- private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) {
- views.sessionOverviewInfo.isVisible = true
- val viewState = SessionInfoViewState(
- isCurrentSession = isCurrentSession,
- deviceFullInfo = deviceFullInfo,
- isDetailsButtonVisible = false,
- isLearnMoreLinkVisible = true,
- isLastSeenDetailsVisible = true,
- )
- views.sessionOverviewInfo.render(viewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
- }
-
private fun updateLoading(isLoading: Boolean) {
if (isLoading) {
showLoading(null)
@@ -313,8 +288,4 @@ class SessionOverviewFragment :
)
SessionLearnMoreBottomSheet.show(childFragmentManager, args)
}
-
- private fun hideSessionInfo() {
- views.sessionOverviewInfo.isGone = true
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index a7b0435e29..21054270f8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -36,24 +36,27 @@ import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSes
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure
-import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
import org.matrix.android.sdk.flow.flow
+import org.matrix.android.sdk.flow.unwrap
import timber.log.Timber
import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation
class SessionOverviewViewModel @AssistedInject constructor(
@Assisted val initialState: SessionOverviewViewState,
- private val session: Session,
private val stringProvider: StringProvider,
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
@@ -61,6 +64,7 @@ class SessionOverviewViewModel @AssistedInject constructor(
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
private val pendingAuthHandler: PendingAuthHandler,
private val activeSessionHolder: ActiveSessionHolder,
+ private val togglePushNotificationUseCase: TogglePushNotificationUseCase,
refreshDevicesUseCase: RefreshDevicesUseCase,
) : VectorSessionsListViewModel(
initialState, activeSessionHolder, refreshDevicesUseCase
@@ -74,11 +78,16 @@ class SessionOverviewViewModel @AssistedInject constructor(
}
init {
+ refreshPushers()
observeSessionInfo(initialState.deviceId)
observeCurrentSessionInfo()
observePushers(initialState.deviceId)
}
+ private fun refreshPushers() {
+ activeSessionHolder.getSafeActiveSession()?.pushersService()?.refreshPushers()
+ }
+
private fun observeSessionInfo(deviceId: String) {
getDeviceFullInfoUseCase.execute(deviceId)
.onEach { setState { copy(deviceInfo = Success(it)) } }
@@ -99,10 +108,20 @@ class SessionOverviewViewModel @AssistedInject constructor(
}
private fun observePushers(deviceId: String) {
- session.flow()
+ val session = activeSessionHolder.getSafeActiveSession() ?: return
+ val pusherFlow = session.flow()
.livePushers()
.map { it.filter { pusher -> pusher.deviceId == deviceId } }
- .execute { copy(pushers = it) }
+ .map { it.takeIf { it.isNotEmpty() }?.any { pusher -> pusher.enabled } }
+
+ val accountDataFlow = session.flow()
+ .liveUserAccountData(TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
+ .unwrap()
+ .map { it.content.toModel()?.isSilenced?.not() }
+
+ merge(pusherFlow, accountDataFlow)
+ .onEach { it?.let { setState { copy(notificationsEnabled = it) } } }
+ .launchIn(viewModelScope)
}
override fun handle(action: SessionOverviewAction) {
@@ -213,10 +232,8 @@ class SessionOverviewViewModel @AssistedInject constructor(
private fun handleTogglePusherAction(action: SessionOverviewAction.TogglePushNotifications) {
viewModelScope.launch {
- val devicePushers = awaitState().pushers.invoke()?.filter { it.deviceId == action.deviceId }
- devicePushers?.forEach { pusher ->
- session.pushersService().togglePusher(pusher, action.enabled)
- }
+ togglePushNotificationUseCase.execute(action.deviceId, action.enabled)
+ setState { copy(notificationsEnabled = action.enabled) }
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index c2d4a858b3..440805bad6 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -20,14 +20,13 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
-import org.matrix.android.sdk.api.session.pushers.Pusher
data class SessionOverviewViewState(
val deviceId: String,
val isCurrentSessionTrusted: Boolean = false,
val deviceInfo: Async = Uninitialized,
val isLoading: Boolean = false,
- val pushers: Async> = Uninitialized,
+ val notificationsEnabled: Boolean = false,
) : MavericksState {
constructor(args: SessionOverviewArgs) : this(
deviceId = args.deviceId
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/TogglePushNotificationUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/TogglePushNotificationUseCase.kt
new file mode 100644
index 0000000000..45c234aaef
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/TogglePushNotificationUseCase.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import im.vector.app.core.di.ActiveSessionHolder
+import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
+import org.matrix.android.sdk.api.session.events.model.toContent
+import javax.inject.Inject
+
+class TogglePushNotificationUseCase @Inject constructor(
+ private val activeSessionHolder: ActiveSessionHolder,
+) {
+
+ suspend fun execute(deviceId: String, enabled: Boolean) {
+ val session = activeSessionHolder.getSafeActiveSession() ?: return
+ val devicePusher = session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
+ devicePusher?.let { pusher ->
+ session.pushersService().togglePusher(pusher, enabled)
+ }
+
+ val accountData = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
+ if (accountData != null) {
+ val newNotificationSettingsContent = LocalNotificationSettingsContent(isSilenced = !enabled)
+ session.accountDataService().updateUserAccountData(
+ UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId,
+ newNotificationSettingsContent.toContent(),
+ )
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt
index eb0d994ce3..44bc251382 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt
@@ -25,6 +25,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
+import im.vector.lib.core.utils.compat.getParcelableExtraCompat
/**
* Display the screen to rename a Session.
@@ -42,7 +43,7 @@ class RenameSessionActivity : VectorBaseActivity() {
addFragment(
container = views.simpleFragmentContainer,
fragmentClass = RenameSessionFragment::class.java,
- params = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ params = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsAction.kt b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsAction.kt
new file mode 100644
index 0000000000..db48c3400b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsAction.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.settings.labs
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class VectorSettingsLabsAction : VectorViewModelAction {
+ object UpdateClientInfo : VectorSettingsLabsAction()
+ object DeleteRecordedClientInfo : VectorSettingsLabsAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt
similarity index 81%
rename from vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
rename to vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt
index 18bc35f72a..c10411301f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsFragment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright (c) 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package im.vector.app.features.settings
+package im.vector.app.features.settings.labs
+import android.os.Build
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.preference.Preference
+import androidx.preference.Preference.OnPreferenceChangeListener
import androidx.preference.SwitchPreference
+import com.airbnb.mvrx.fragmentViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
@@ -30,6 +33,8 @@ import im.vector.app.features.MainActivityArgs
import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.room.threads.ThreadsManager
+import im.vector.app.features.settings.VectorPreferences
+import im.vector.app.features.settings.VectorSettingsBaseFragment
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import javax.inject.Inject
@@ -37,6 +42,8 @@ import javax.inject.Inject
class VectorSettingsLabsFragment :
VectorSettingsBaseFragment() {
+ private val viewModel: VectorSettingsLabsViewModel by fragmentViewModel()
+
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var lightweightSettingsStorage: LightweightSettingsStorage
@Inject lateinit var threadsManager: ThreadsManager
@@ -84,7 +91,13 @@ class VectorSettingsLabsFragment :
}
}
+ findPreference(VectorPreferences.SETTINGS_LABS_VOICE_BROADCAST_KEY)?.let { pref ->
+ // Voice Broadcast recording is not available on Android < 10
+ pref.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && vectorFeatures.isVoiceBroadcastEnabled()
+ }
+
configureUnreadNotificationsAsTabPreference()
+ configureEnableClientInfoRecordingPreference()
}
private fun configureUnreadNotificationsAsTabPreference() {
@@ -140,4 +153,16 @@ class VectorSettingsLabsFragment :
private fun onNewLayoutPreferenceClicked() {
configureUnreadNotificationsAsTabPreference()
}
+
+ private fun configureEnableClientInfoRecordingPreference() {
+ findPreference(VectorPreferences.SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY)?.onPreferenceChangeListener =
+ OnPreferenceChangeListener { _, newValue ->
+ when (newValue as? Boolean) {
+ false -> viewModel.handle(VectorSettingsLabsAction.DeleteRecordedClientInfo)
+ true -> viewModel.handle(VectorSettingsLabsAction.UpdateClientInfo)
+ else -> Unit
+ }
+ true
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsViewModel.kt
new file mode 100644
index 0000000000..38686b06be
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/labs/VectorSettingsLabsViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.settings.labs
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.session.clientinfo.DeleteMatrixClientInfoUseCase
+import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
+import kotlinx.coroutines.launch
+
+class VectorSettingsLabsViewModel @AssistedInject constructor(
+ @Assisted initialState: VectorSettingsLabsViewState,
+ private val activeSessionHolder: ActiveSessionHolder,
+ private val updateMatrixClientInfoUseCase: UpdateMatrixClientInfoUseCase,
+ private val deleteMatrixClientInfoUseCase: DeleteMatrixClientInfoUseCase,
+) : VectorViewModel